From ee3ecc00eb1d157bd444b941ebb772a2ffd1806c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E4=BC=8A=E5=B8=86?= Date: Fri, 9 Oct 2026 14:29:58 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E7=99=BB=E5=BD=95=E6=B3=A8=E5=86=8C?= =?UTF-8?q?=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .cursor/rules/src-directory-structure.mdc | 52 +++ .env.development | 2 + .env.example | 5 + package-lock.json | 382 ++++++++++++++++++ package.json | 3 +- src/App.vue | 4 +- src/api/auth.ts | 25 ++ src/api/request.ts | 49 +++ src/components/agreement-checkbox/index.vue | 88 ++++ src/components/app-navbar/index.vue | 72 ++++ src/components/app-tab-bar/index.vue | 129 ++++++ src/components/base-input/index.vue | 119 ++++++ src/components/brand-logo/index.vue | 35 ++ src/components/primary-button/index.vue | 51 +++ src/components/sms-code-btn/index.vue | 56 +++ src/hooks/use-countdown.ts | 37 ++ src/hooks/use-sms-code.ts | 26 ++ src/hooks/use-tab-page.ts | 14 + src/main.ts | 3 + src/pages.json | 105 ++++- src/pages/add-device/add-device.scss | 91 +++++ src/pages/add-device/add-device.types.ts | 11 + src/pages/add-device/complete/complete.scss | 219 ++++++++++ .../complete-success-hero/index.vue | 71 ++++ src/pages/add-device/complete/index.vue | 112 +++++ .../complete/use-complete-device.ts | 42 ++ .../components/bind-bluetooth-hero/index.vue | 127 ++++++ .../components/bind-feature-list/index.vue | 75 ++++ .../components/bind-steps/index.vue | 131 ++++++ src/pages/add-device/index.vue | 37 ++ .../pair/components/pair-check-list/index.vue | 132 ++++++ .../components/pair-device-summary/index.vue | 90 +++++ .../pair/components/pair-hero/index.vue | 123 ++++++ src/pages/add-device/pair/index.vue | 65 +++ src/pages/add-device/pair/pair.scss | 109 +++++ src/pages/add-device/pair/pair.types.ts | 13 + src/pages/add-device/pair/use-pair-device.ts | 81 ++++ .../components/scan-device-item/index.vue | 102 +++++ .../scan/components/scan-help-tip/index.vue | 76 ++++ .../scan/components/scan-hero/index.vue | 116 ++++++ src/pages/add-device/scan/index.vue | 70 ++++ src/pages/add-device/scan/scan.scss | 78 ++++ src/pages/add-device/scan/scan.types.ts | 7 + src/pages/add-device/scan/use-scan-device.ts | 69 ++++ src/pages/add-device/use-add-device.ts | 48 +++ src/pages/agreement/agreement.api.ts | 11 + src/pages/agreement/agreement.types.ts | 10 + src/pages/agreement/index.vue | 52 +++ src/pages/agreement/use-agreement.ts | 51 +++ .../change-password/change-password.api.ts | 17 + .../change-password/change-password.types.ts | 5 + src/pages/change-password/index.vue | 49 +++ .../change-password/use-change-password.ts | 55 +++ .../device/components/device-empty/index.vue | 118 ++++++ .../device/components/device-header/index.vue | 96 +++++ .../device/components/hot-devices/index.vue | 97 +++++ .../device/components/room-tabs/index.vue | 74 ++++ src/pages/device/device.scss | 6 + src/pages/device/device.types.ts | 10 + src/pages/device/index.vue | 47 +++ src/pages/device/use-device.ts | 66 +++ .../forgot-password/forgot-password.api.ts | 16 + .../forgot-password/forgot-password.types.ts | 4 + src/pages/forgot-password/index.vue | 48 +++ .../forgot-password/use-forgot-password.ts | 47 +++ src/pages/index/index.vue | 41 -- src/pages/login/index.vue | 69 ++++ src/pages/login/login.api.ts | 29 ++ src/pages/login/login.scss | 59 +++ src/pages/login/login.types.ts | 11 + src/pages/login/use-login.ts | 96 +++++ src/pages/mine/index.vue | 44 ++ src/pages/register/index.vue | 70 ++++ src/pages/register/register.api.ts | 18 + src/pages/register/register.types.ts | 6 + src/pages/register/use-register.ts | 73 ++++ src/plugins/register-global-components.ts | 24 ++ src/static/icon-checked@2x.png | Bin 0 -> 797 bytes src/static/icon-code@2x.png | Bin 0 -> 1277 bytes src/static/icon-phone@2x.png | Bin 0 -> 616 bytes src/static/logo-center@2x.png | Bin 0 -> 3818 bytes src/static/tab-device-active.png | Bin 0 -> 434 bytes src/static/tab-device.png | Bin 0 -> 382 bytes src/static/tab-mine-active.png | Bin 0 -> 559 bytes src/static/tab-mine.png | Bin 0 -> 461 bytes src/static/图标 4.png | Bin 0 -> 352 bytes src/static/图标 8.png | Bin 0 -> 629 bytes src/static/图标 9.png | Bin 0 -> 302 bytes src/static/组 27.png | Bin 0 -> 695 bytes src/static/组 281@2x.png | Bin 0 -> 1393 bytes src/static/组 286@2x.png | Bin 0 -> 1312 bytes src/static/组 291@2x.png | Bin 0 -> 1353 bytes src/static/组 41@2x.png | Bin 0 -> 29861 bytes src/static/组 468@2x.png | Bin 0 -> 2468 bytes src/static/组 476@2x.png | Bin 0 -> 1462 bytes src/static/组 600@2x.png | Bin 0 -> 57173 bytes src/static/组 9.png | Bin 0 -> 208 bytes src/static/蓝牙-F.png | Bin 0 -> 5637 bytes src/static/路径.png | Bin 0 -> 358 bytes src/styles/index.scss | 13 + src/styles/variables.scss | 14 + src/types/api.ts | 62 +++ src/utils/api-error.ts | 34 ++ src/utils/static-assets.ts | 66 +++ src/utils/validate.ts | 11 + vite.config.ts | 25 +- 106 files changed, 4937 insertions(+), 59 deletions(-) create mode 100644 .cursor/rules/src-directory-structure.mdc create mode 100644 .env.development create mode 100644 .env.example create mode 100644 src/api/auth.ts create mode 100644 src/api/request.ts create mode 100644 src/components/agreement-checkbox/index.vue create mode 100644 src/components/app-navbar/index.vue create mode 100644 src/components/app-tab-bar/index.vue create mode 100644 src/components/base-input/index.vue create mode 100644 src/components/brand-logo/index.vue create mode 100644 src/components/primary-button/index.vue create mode 100644 src/components/sms-code-btn/index.vue create mode 100644 src/hooks/use-countdown.ts create mode 100644 src/hooks/use-sms-code.ts create mode 100644 src/hooks/use-tab-page.ts create mode 100644 src/pages/add-device/add-device.scss create mode 100644 src/pages/add-device/add-device.types.ts create mode 100644 src/pages/add-device/complete/complete.scss create mode 100644 src/pages/add-device/complete/components/complete-success-hero/index.vue create mode 100644 src/pages/add-device/complete/index.vue create mode 100644 src/pages/add-device/complete/use-complete-device.ts create mode 100644 src/pages/add-device/components/bind-bluetooth-hero/index.vue create mode 100644 src/pages/add-device/components/bind-feature-list/index.vue create mode 100644 src/pages/add-device/components/bind-steps/index.vue create mode 100644 src/pages/add-device/index.vue create mode 100644 src/pages/add-device/pair/components/pair-check-list/index.vue create mode 100644 src/pages/add-device/pair/components/pair-device-summary/index.vue create mode 100644 src/pages/add-device/pair/components/pair-hero/index.vue create mode 100644 src/pages/add-device/pair/index.vue create mode 100644 src/pages/add-device/pair/pair.scss create mode 100644 src/pages/add-device/pair/pair.types.ts create mode 100644 src/pages/add-device/pair/use-pair-device.ts create mode 100644 src/pages/add-device/scan/components/scan-device-item/index.vue create mode 100644 src/pages/add-device/scan/components/scan-help-tip/index.vue create mode 100644 src/pages/add-device/scan/components/scan-hero/index.vue create mode 100644 src/pages/add-device/scan/index.vue create mode 100644 src/pages/add-device/scan/scan.scss create mode 100644 src/pages/add-device/scan/scan.types.ts create mode 100644 src/pages/add-device/scan/use-scan-device.ts create mode 100644 src/pages/add-device/use-add-device.ts create mode 100644 src/pages/agreement/agreement.api.ts create mode 100644 src/pages/agreement/agreement.types.ts create mode 100644 src/pages/agreement/index.vue create mode 100644 src/pages/agreement/use-agreement.ts create mode 100644 src/pages/change-password/change-password.api.ts create mode 100644 src/pages/change-password/change-password.types.ts create mode 100644 src/pages/change-password/index.vue create mode 100644 src/pages/change-password/use-change-password.ts create mode 100644 src/pages/device/components/device-empty/index.vue create mode 100644 src/pages/device/components/device-header/index.vue create mode 100644 src/pages/device/components/hot-devices/index.vue create mode 100644 src/pages/device/components/room-tabs/index.vue create mode 100644 src/pages/device/device.scss create mode 100644 src/pages/device/device.types.ts create mode 100644 src/pages/device/index.vue create mode 100644 src/pages/device/use-device.ts create mode 100644 src/pages/forgot-password/forgot-password.api.ts create mode 100644 src/pages/forgot-password/forgot-password.types.ts create mode 100644 src/pages/forgot-password/index.vue create mode 100644 src/pages/forgot-password/use-forgot-password.ts create mode 100644 src/pages/login/index.vue create mode 100644 src/pages/login/login.api.ts create mode 100644 src/pages/login/login.scss create mode 100644 src/pages/login/login.types.ts create mode 100644 src/pages/login/use-login.ts create mode 100644 src/pages/mine/index.vue create mode 100644 src/pages/register/index.vue create mode 100644 src/pages/register/register.api.ts create mode 100644 src/pages/register/register.types.ts create mode 100644 src/pages/register/use-register.ts create mode 100644 src/plugins/register-global-components.ts create mode 100644 src/static/icon-checked@2x.png create mode 100644 src/static/icon-code@2x.png create mode 100644 src/static/icon-phone@2x.png create mode 100644 src/static/logo-center@2x.png create mode 100644 src/static/tab-device-active.png create mode 100644 src/static/tab-device.png create mode 100644 src/static/tab-mine-active.png create mode 100644 src/static/tab-mine.png create mode 100644 src/static/图标 4.png create mode 100644 src/static/图标 8.png create mode 100644 src/static/图标 9.png create mode 100644 src/static/组 27.png create mode 100644 src/static/组 281@2x.png create mode 100644 src/static/组 286@2x.png create mode 100644 src/static/组 291@2x.png create mode 100644 src/static/组 41@2x.png create mode 100644 src/static/组 468@2x.png create mode 100644 src/static/组 476@2x.png create mode 100644 src/static/组 600@2x.png create mode 100644 src/static/组 9.png create mode 100644 src/static/蓝牙-F.png create mode 100644 src/static/路径.png create mode 100644 src/styles/index.scss create mode 100644 src/styles/variables.scss create mode 100644 src/types/api.ts create mode 100644 src/utils/api-error.ts create mode 100644 src/utils/static-assets.ts create mode 100644 src/utils/validate.ts diff --git a/.cursor/rules/src-directory-structure.mdc b/.cursor/rules/src-directory-structure.mdc new file mode 100644 index 0000000..1d452c5 --- /dev/null +++ b/.cursor/rules/src-directory-structure.mdc @@ -0,0 +1,52 @@ +--- +description: src 目录结构、就近聚合与全局/局部划分(uni-app / Vue3) +alwaysApply: true +--- + +# 目录与就近聚合 + +源码在 `src`。全局目录:`components`(kebab-case + `index.vue`)、`hooks`(`use-xxx.ts`)、`types`、`api`(含唯一 `request.ts`)、`static`、`store`、`utils`、`styles`。 + +## 页面文件夹(`pages//`) + +可就近存放该页专属内容: + +| 文件 | 说明 | +|------|------| +| `index.vue` | 页面入口 | +| `.types.ts` | 仅本页使用的类型 | +| `use-.ts` | 仅本页使用的 hooks | +| `.api.ts` | 仅本页的接口函数(调用 `src/api/request.ts`,不重复写拦截) | +| `.scss` | 本页独立样式(可选) | +| `components/` | 页面私有子组件;子组件目录内可有 `types.ts` | + +命名示例(页面 `home`):`home.types.ts`、`use-home.ts`、`home.api.ts`。 + +## 局部 vs 全局 + +- 仅单页使用 → 放在 `pages//` 下就近管理 +- 多页复用 → `src/types`、`src/hooks`、`src/api`(按业务模块拆分,非每页一份 request) + +## 禁止 + +- 页面 `.vue` 在 `pages` 根目录;静态资源在 `src` 外 +- 页面私有组件/类型/hooks/api 随意提到 `src` 顶层 +- 每页重复实现请求封装(拦截器只在 `src/api/request.ts`) +- 在 `.vue` 里堆应抽离的共享类型(简单局部类型除外) + +## 示例 + +``` +src/pages/home/ + index.vue + home.types.ts + use-home.ts + home.api.ts + components/feed-item/index.vue + +src/api/request.ts +src/api/user.ts +src/hooks/use-auth.ts +``` + +新建与重构时:先判断是否多页复用,再选页面内就近或顶层全局。 diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..45953e7 --- /dev/null +++ b/.env.development @@ -0,0 +1,2 @@ +VITE_API_BASE_URL=http://192.168.0.222:8082 +VITE_API_PROXY_TARGET=http://192.168.0.222:8082 diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..9264453 --- /dev/null +++ b/.env.example @@ -0,0 +1,5 @@ +# 接口根地址(App / 小程序 / H5 直连) +VITE_API_BASE_URL=http://192.168.0.222:8082 + +# H5 开发:BASE_URL 留空时可走 vite 代理 /app +VITE_API_PROXY_TARGET=http://192.168.0.222:8082 diff --git a/package-lock.json b/package-lock.json index a6a6b12..15c4913 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,7 @@ "@dcloudio/vite-plugin-uni": "3.0.0-5020420260813003", "@vue/runtime-core": "^3.4.21", "@vue/tsconfig": "^0.1.3", + "sass": "^1.105.1", "typescript": "^4.9.4", "vite": "5.2.8", "vue-tsc": "^1.0.24" @@ -4664,6 +4665,312 @@ "node": ">= 8" } }, + "node_modules/@parcel/watcher": { + "version": "2.6.0", + "resolved": "https://registry.npmmirror.com/@parcel/watcher/-/watcher-2.6.0.tgz", + "integrity": "sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.6.0", + "@parcel/watcher-darwin-arm64": "2.6.0", + "@parcel/watcher-darwin-x64": "2.6.0", + "@parcel/watcher-freebsd-x64": "2.6.0", + "@parcel/watcher-linux-arm-glibc": "2.6.0", + "@parcel/watcher-linux-arm-musl": "2.6.0", + "@parcel/watcher-linux-arm64-glibc": "2.6.0", + "@parcel/watcher-linux-arm64-musl": "2.6.0", + "@parcel/watcher-linux-x64-glibc": "2.6.0", + "@parcel/watcher-linux-x64-musl": "2.6.0", + "@parcel/watcher-win32-arm64": "2.6.0", + "@parcel/watcher-win32-x64": "2.6.0" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.6.0.tgz", + "integrity": "sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.6.0.tgz", + "integrity": "sha512-Y3QV0gl7Q1zbfueunkWIERICbEojQFCgpyG7YqOGNFLsckXyI1xu9mAIUpKY9QBYzBtSkN8dBPwd3yiAO9ovMw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.6.0.tgz", + "integrity": "sha512-Ohv6OpzhUfKYD7Beb8kDvG0jbIxORCYY1JRdZnaBtnjjkJxgD7ZVL0nw2sCYd0yTMKTvz3nnTnOF3cDifK+kvw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.6.0.tgz", + "integrity": "sha512-5HmXvDgs8VK+74jF9y9/2FE3/OnlcKmc56tjmSrEuZjpSZOGL+fvAu+HKJBdPs9uwoP2hE6TlSUpXZ/C5jUFmQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.6.0.tgz", + "integrity": "sha512-Ps/hui3A+vMbjdqlqAowK2ZL8+BO8dBjxeWXj6npTBs3jx4wWmbPpaLuqwrQrSqIVMCnpWo238bJ1U37GhQOYg==", + "cpu": [ + "arm" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.6.0.tgz", + "integrity": "sha512-9c6AUHgHoG+IY88MRIHupztQiQnrbqHYQjkM2btA+Bf/wQnQMuiD0Wfk1EVv3TlNT3x41uU71rn6E4xh/+zvkw==", + "cpu": [ + "arm" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.6.0.tgz", + "integrity": "sha512-yHRqS2owEXe6Hic9z6Mh1ECsCd+ODVOGvZDyciqRd21+v+o+DnXMOrw50DSpIG2sb8GPEaPPmfeCAWKPJdq46g==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.6.0.tgz", + "integrity": "sha512-WhB2e/V7rqdHHWZusBSPuy5Ei8S6lSz6FE5TKKQz5h3a0O+C+mhY7vxU9b/stqvMb8beLnPY82ZrFTLKs+SrKA==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.6.0.tgz", + "integrity": "sha512-ulGE6x6Oz6iAwg75T8YQSoguBWasniIbX+QWpaYPcCnDOpdWX3k+4xbEYPZVLxOuoJI+svJJPD3sEj8G7lrQ3A==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.6.0.tgz", + "integrity": "sha512-tkBYKt7YQrjIJWYDnto2YgO8MRkjlMTSNoRHzsXinBqbLdeOM3L32wPZJvIZxqaLMfSlS/4sUjH/6STVP/XDLw==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.6.0.tgz", + "integrity": "sha512-gIZAP23jaHjGWasY/TY6yL7NHFClf0Ga7FN+iINvk+KN94rhm94lYZhFsbYFNcA04/onvGD9kKmiJLJB2HbNwQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.6.0.tgz", + "integrity": "sha512-cA+/pXV2YkfxlIcXOQ5fSWqAzzPyD78/x5qbK/I0vUkrlYHA8TIz+MXjAbGouguKVSI4bOmkTSJ1/poVSsgt+A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@rollup/pluginutils": { "version": "5.1.0", "resolved": "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", @@ -7075,6 +7382,16 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmmirror.com/detect-newline/-/detect-newline-3.1.0.tgz", @@ -8123,6 +8440,13 @@ ], "license": "BSD-3-Clause" }, + "node_modules/immutable": { + "version": "5.1.9", + "resolved": "https://registry.npmmirror.com/immutable/-/immutable-5.1.9.tgz", + "integrity": "sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==", + "devOptional": true, + "license": "MIT" + }, "node_modules/import-local": { "version": "3.2.0", "resolved": "https://registry.npmmirror.com/import-local/-/import-local-3.2.0.tgz", @@ -11027,6 +11351,13 @@ "node": ">= 0.6" } }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT", + "optional": true + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmmirror.com/node-int64/-/node-int64-0.4.0.tgz", @@ -12134,6 +12465,57 @@ "dev": true, "license": "MIT" }, + "node_modules/sass": { + "version": "1.105.1", + "resolved": "https://registry.npmmirror.com/sass/-/sass-1.105.1.tgz", + "integrity": "sha512-1CnAWPyrrnVQzXGB/DUep1vYCKCm1CIBr+YxjjiW6wk9qtzemnGP4qicH56T2F9S4Hjtv/Ne3jDz8JqfdDU4oA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "chokidar": "^5.0.0", + "immutable": "^5.1.5", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=20.19.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/sass/node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/sass/node_modules/readdirp": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-5.1.1.tgz", + "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/sax": { "version": "1.6.1", "resolved": "https://registry.npmmirror.com/sax/-/sax-1.6.1.tgz", diff --git a/package.json b/package.json index 248b25d..2871fdc 100644 --- a/package.json +++ b/package.json @@ -62,8 +62,9 @@ "@dcloudio/uni-cli-shared": "3.0.0-5020420260813003", "@dcloudio/uni-stacktracey": "3.0.0-5020420260813003", "@dcloudio/vite-plugin-uni": "3.0.0-5020420260813003", - "@vue/tsconfig": "^0.1.3", "@vue/runtime-core": "^3.4.21", + "@vue/tsconfig": "^0.1.3", + "sass": "^1.105.1", "typescript": "^4.9.4", "vite": "5.2.8", "vue-tsc": "^1.0.24" diff --git a/src/App.vue b/src/App.vue index 6086575..c366687 100644 --- a/src/App.vue +++ b/src/App.vue @@ -10,4 +10,6 @@ onHide(() => { console.log("App Hide"); }); - + diff --git a/src/api/auth.ts b/src/api/auth.ts new file mode 100644 index 0000000..ad573a7 --- /dev/null +++ b/src/api/auth.ts @@ -0,0 +1,25 @@ +import { request } from './request' +import type { AuthTokenResult, SendSmsCodeBody, SmsCodeScene } from '@/types/api' +import { ApiError } from '@/utils/api-error' + +/** POST /app/auth/sendSmsCode */ +export function sendSmsCode(phone: string, scene: SmsCodeScene) { + const data: SendSmsCodeBody = { + phonenumber: phone.trim(), + scene, + } + return request({ + url: '/app/auth/sendSmsCode', + method: 'POST', + data, + }) +} + +export function saveAuthToken(data: AuthTokenResult) { + const token = data?.token ?? data?.accessToken + if (!token) { + throw new ApiError('登录失败,未获取到令牌') + } + uni.setStorageSync('token', token) + return token +} diff --git a/src/api/request.ts b/src/api/request.ts new file mode 100644 index 0000000..d80c520 --- /dev/null +++ b/src/api/request.ts @@ -0,0 +1,49 @@ +import type { ApiResponse } from '@/types/api' +import { ApiError, isApiSuccess, unwrapApiData } from '@/utils/api-error' + +const BASE_URL = import.meta.env.VITE_API_BASE_URL ?? '' + +export interface RequestOptions extends Omit { + url: string +} + +export function request(options: RequestOptions): Promise { + const token = uni.getStorageSync('token') as string + + return new Promise((resolve, reject) => { + uni.request({ + ...options, + url: `${BASE_URL}${options.url}`, + header: { + 'Content-Type': 'application/json', + ...(token ? { Authorization: `Bearer ${token}` } : {}), + ...options.header, + }, + success: (res) => { + if (res.statusCode < 200 || res.statusCode >= 300) { + const body = res.data as ApiResponse | undefined + reject( + new ApiError( + body?.msg || body?.message || `请求失败(${res.statusCode})`, + body?.code ?? res.statusCode, + ), + ) + return + } + + const body = (res.data ?? {}) as ApiResponse & Record + if (!isApiSuccess(body)) { + reject( + new ApiError(body.msg || body.message || '请求失败', body.code), + ) + return + } + + resolve(unwrapApiData(body)) + }, + fail: (err) => { + reject(new ApiError(err.errMsg || '网络异常')) + }, + }) + }) +} diff --git a/src/components/agreement-checkbox/index.vue b/src/components/agreement-checkbox/index.vue new file mode 100644 index 0000000..4189bf3 --- /dev/null +++ b/src/components/agreement-checkbox/index.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/src/components/app-navbar/index.vue b/src/components/app-navbar/index.vue new file mode 100644 index 0000000..75daa61 --- /dev/null +++ b/src/components/app-navbar/index.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/src/components/app-tab-bar/index.vue b/src/components/app-tab-bar/index.vue new file mode 100644 index 0000000..6401b3b --- /dev/null +++ b/src/components/app-tab-bar/index.vue @@ -0,0 +1,129 @@ + + + + + diff --git a/src/components/base-input/index.vue b/src/components/base-input/index.vue new file mode 100644 index 0000000..c7f860c --- /dev/null +++ b/src/components/base-input/index.vue @@ -0,0 +1,119 @@ + + + + + diff --git a/src/components/brand-logo/index.vue b/src/components/brand-logo/index.vue new file mode 100644 index 0000000..ff4632e --- /dev/null +++ b/src/components/brand-logo/index.vue @@ -0,0 +1,35 @@ + + + + + diff --git a/src/components/primary-button/index.vue b/src/components/primary-button/index.vue new file mode 100644 index 0000000..9478057 --- /dev/null +++ b/src/components/primary-button/index.vue @@ -0,0 +1,51 @@ + + + + + diff --git a/src/components/sms-code-btn/index.vue b/src/components/sms-code-btn/index.vue new file mode 100644 index 0000000..aa3572a --- /dev/null +++ b/src/components/sms-code-btn/index.vue @@ -0,0 +1,56 @@ + + + + + diff --git a/src/hooks/use-countdown.ts b/src/hooks/use-countdown.ts new file mode 100644 index 0000000..180a335 --- /dev/null +++ b/src/hooks/use-countdown.ts @@ -0,0 +1,37 @@ +import { onUnmounted, ref } from 'vue' + +export function useCountdown(duration = 60) { + const counting = ref(false) + const seconds = ref(0) + let timer: ReturnType | null = null + + function clearTimer() { + if (timer) { + clearInterval(timer) + timer = null + } + } + + function start() { + clearTimer() + seconds.value = duration + counting.value = true + timer = setInterval(() => { + if (seconds.value <= 1) { + stop() + } else { + seconds.value -= 1 + } + }, 1000) + } + + function stop() { + clearTimer() + counting.value = false + seconds.value = 0 + } + + onUnmounted(stop) + + return { counting, seconds, start, stop } +} diff --git a/src/hooks/use-sms-code.ts b/src/hooks/use-sms-code.ts new file mode 100644 index 0000000..6adc925 --- /dev/null +++ b/src/hooks/use-sms-code.ts @@ -0,0 +1,26 @@ +import { sendSmsCode } from '@/api/auth' +import type { SmsCodeScene } from '@/types/api' +import { getErrorMessage } from '@/utils/api-error' +import { isValidPhone } from '@/utils/validate' +import { useCountdown } from './use-countdown' + +export function useSmsCode(scene: SmsCodeScene) { + const { counting, seconds, start } = useCountdown(60) + + async function sendCode(phone: string) { + if (counting.value) return + if (!isValidPhone(phone)) { + uni.showToast({ title: '请输入正确手机号', icon: 'none' }) + return + } + try { + await sendSmsCode(phone, scene) + uni.showToast({ title: '验证码已发送', icon: 'none' }) + start() + } catch (err) { + uni.showToast({ title: getErrorMessage(err, '发送失败,请稍后重试'), icon: 'none' }) + } + } + + return { counting, seconds, sendCode } +} diff --git a/src/hooks/use-tab-page.ts b/src/hooks/use-tab-page.ts new file mode 100644 index 0000000..aef0ab3 --- /dev/null +++ b/src/hooks/use-tab-page.ts @@ -0,0 +1,14 @@ +import { onShow } from '@dcloudio/uni-app' +import { ref } from 'vue' +import type { TabKey } from '@/components/app-tab-bar/index.vue' + +/** Tab 页在 onShow 时同步底栏高亮 */ +export function useTabPage(tab: TabKey) { + const tabCurrent = ref(tab) + + onShow(() => { + tabCurrent.value = tab + }) + + return { tabCurrent } +} diff --git a/src/main.ts b/src/main.ts index b12c1ee..5c4de56 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,7 +1,10 @@ import { createSSRApp } from "vue"; import App from "./App.vue"; +import { registerGlobalComponents } from "./plugins/register-global-components"; + export function createApp() { const app = createSSRApp(App); + registerGlobalComponents(app); return { app, }; diff --git a/src/pages.json b/src/pages.json index 1aea851..ab1ff0e 100644 --- a/src/pages.json +++ b/src/pages.json @@ -1,16 +1,93 @@ { - "pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages - { - "path": "pages/index/index", - "style": { - "navigationBarTitleText": "uni-app" - } - } - ], - "globalStyle": { - "navigationBarTextStyle": "black", - "navigationBarTitleText": "uni-app", - "navigationBarBackgroundColor": "#F8F8F8", - "backgroundColor": "#F8F8F8" - } + "easycom": { + "autoscan": true, + "custom": { + "^([a-z][a-z0-9]*(?:-[a-z0-9]+)+)$": "@/components/$1/index.vue" + } + }, + "pages": [ + { + "path": "pages/login/index", + "style": { + "navigationStyle": "custom", + "navigationBarTitleText": "登录" + } + }, + { + "path": "pages/device/index", + "style": { + "navigationStyle": "custom", + "navigationBarTitleText": "设备" + } + }, + { + "path": "pages/add-device/index", + "style": { + "navigationStyle": "custom", + "navigationBarTitleText": "添加设备" + } + }, + { + "path": "pages/add-device/scan/index", + "style": { + "navigationStyle": "custom", + "navigationBarTitleText": "扫描设备" + } + }, + { + "path": "pages/add-device/pair/index", + "style": { + "navigationStyle": "custom", + "navigationBarTitleText": "设备配置" + } + }, + { + "path": "pages/add-device/complete/index", + "style": { + "navigationStyle": "custom", + "navigationBarTitleText": "设备配置" + } + }, + { + "path": "pages/mine/index", + "style": { + "navigationBarTitleText": "我的", + "navigationStyle": "custom" + } + }, + { + "path": "pages/register/index", + "style": { + "navigationStyle": "custom", + "navigationBarTitleText": "注册" + } + }, + { + "path": "pages/forgot-password/index", + "style": { + "navigationStyle": "custom", + "navigationBarTitleText": "忘记密码" + } + }, + { + "path": "pages/change-password/index", + "style": { + "navigationStyle": "custom", + "navigationBarTitleText": "修改密码" + } + }, + { + "path": "pages/agreement/index", + "style": { + "navigationStyle": "custom", + "navigationBarTitleText": "用户服务协议" + } + } + ], + "globalStyle": { + "navigationBarTextStyle": "black", + "navigationBarTitleText": "虫子智家", + "navigationBarBackgroundColor": "#F7F8FA", + "backgroundColor": "#F7F8FA" + } } diff --git a/src/pages/add-device/add-device.scss b/src/pages/add-device/add-device.scss new file mode 100644 index 0000000..4d638c0 --- /dev/null +++ b/src/pages/add-device/add-device.scss @@ -0,0 +1,91 @@ +.add-device-page { + min-height: 100vh; + background: $color-bg-page; + display: flex; + flex-direction: column; + box-sizing: border-box; +} + +.add-device-page__scroll { + flex: 1; + height: 0; +} + +.add-device-page__body { + padding-bottom: calc(32rpx + env(safe-area-inset-bottom)); +} + +.add-device-page__title { + display: block; + padding: 0 40rpx; + font-size: 40rpx; + font-weight: 700; + color: $color-text; + margin-bottom: 20rpx; + text-align: center; +} + +.add-device-page__desc { + display: block; + padding: 0 48rpx; + font-size: 26rpx; + color: #98A2B3; + line-height: 1.65; + margin-bottom: 32rpx; + text-align: center; +} + +.add-device-page__footer { + padding: 40rpx 16rpx 0; + margin-top: 8rpx; +} + +.add-device-page__btn { + width: 100%; + height: 96rpx; + border-radius: 24rpx; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 40rpx; + background: linear-gradient(98deg, #45d685 0%, #21c25e 100%); + box-shadow: 0 8rpx 28rpx rgba(33, 194, 94, 0.28); + margin-bottom: 28rpx; +} + +.add-device-page__btn-text { + font-size: 32rpx; + font-weight: 500; + color: #fff; +} + +.add-device-page__btn-badge { + min-width: 44rpx; + height: 44rpx; + padding: 0 12rpx; + background: #ffc940; + border-radius: 12rpx; + display: flex; + align-items: center; + justify-content: center; + border: 3rpx solid #fff; +} + +.add-device-page__btn-badge-num { + font-size: 26rpx; + font-weight: 700; + color: #fff; +} + +.add-device-page__later-wrap { + display: flex; + justify-content: center; + align-items: center; + padding: 16rpx 0 8rpx; +} + +.add-device-page__later { + font-size: 28rpx; + color: #999; + text-align: center; +} diff --git a/src/pages/add-device/add-device.types.ts b/src/pages/add-device/add-device.types.ts new file mode 100644 index 0000000..f967504 --- /dev/null +++ b/src/pages/add-device/add-device.types.ts @@ -0,0 +1,11 @@ +export interface BindStepItem { + id: number + label: string +} + +export interface BindFeatureItem { + id: string + title: string + desc: string + icon: string +} diff --git a/src/pages/add-device/complete/complete.scss b/src/pages/add-device/complete/complete.scss new file mode 100644 index 0000000..45837e1 --- /dev/null +++ b/src/pages/add-device/complete/complete.scss @@ -0,0 +1,219 @@ +.complete-page { + min-height: 100vh; + background: $color-bg-page; + display: flex; + flex-direction: column; + box-sizing: border-box; +} + +.complete-page__scroll { + flex: 1; + height: 0; +} + +.complete-page__body { + padding-bottom: 24rpx; +} + +.complete-page__status { + padding: 0 40rpx; + margin-bottom: 32rpx; + display: flex; + flex-direction: column; + align-items: center; +} + +.complete-page__title { + font-size: 40rpx; + font-weight: 700; + color: $color-text; + margin-bottom: 16rpx; +} + +.complete-page__desc { + font-size: 26rpx; + color: #98a2b3; + line-height: 1.65; + text-align: center; +} + +.complete-page__device-card { + margin: 0 32rpx 24rpx; + padding: 24rpx; + background: #fff; + border-radius: 20rpx; + box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.04); + display: flex; + align-items: center; + gap: 20rpx; +} + +.complete-page__thumb { + width: 88rpx; + height: 88rpx; + border-radius: 16rpx; + background: #f0f2f5; + flex-shrink: 0; +} + +.complete-page__device-info { + flex: 1; + min-width: 0; +} + +.complete-page__device-name { + display: block; + font-size: 30rpx; + font-weight: 600; + color: $color-text; + margin-bottom: 8rpx; +} + +.complete-page__device-serial { + display: block; + font-size: 24rpx; + color: $color-text-secondary; + margin-bottom: 8rpx; +} + +.complete-page__bt-status { + display: flex; + align-items: center; + gap: 8rpx; +} + +.complete-page__bt-dot { + width: 12rpx; + height: 12rpx; + border-radius: 50%; + background: $color-primary; +} + +.complete-page__bt-text { + font-size: 24rpx; + color: $color-primary; + font-weight: 500; +} + +.complete-page__form { + margin: 0 32rpx 24rpx; + padding: 8rpx 24rpx 24rpx; + background: #fff; + border-radius: 20rpx; + box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.04); +} + +.complete-page__field { + padding-top: 24rpx; +} + +.complete-page__label { + display: block; + font-size: 28rpx; + font-weight: 500; + color: $color-text; + margin-bottom: 16rpx; +} + +.complete-page__input-wrap { + display: flex; + align-items: center; + height: 88rpx; + padding: 0 24rpx; + background: #f7f8fa; + border-radius: 16rpx; + gap: 16rpx; +} + +.complete-page__input { + flex: 1; + font-size: 28rpx; + color: $color-text; + min-width: 0; +} + +.complete-page__edit-icon { + width: 32rpx; + height: 32rpx; + flex-shrink: 0; +} + +.complete-page__room-row { + display: flex; + align-items: center; + height: 88rpx; + padding: 0 24rpx; + background: #f7f8fa; + border-radius: 16rpx; + gap: 12rpx; +} + +.complete-page__room-icon { + width: 36rpx; + height: 36rpx; + flex-shrink: 0; +} + +.complete-page__room-text { + flex: 1; + font-size: 28rpx; + color: $color-text; +} + +.complete-page__room-arrow { + width: 24rpx; + height: 24rpx; + flex-shrink: 0; + opacity: 0.5; +} + +.complete-page__notice { + display: flex; + align-items: flex-start; + gap: 12rpx; + margin: 0 32rpx 24rpx; + padding: 0 8rpx; +} + +.complete-page__notice-icon { + width: 32rpx; + height: 32rpx; + border-radius: 50%; + background: #4a9eff; + color: #fff; + font-size: 22rpx; + font-weight: 700; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + margin-top: 2rpx; +} + +.complete-page__notice-text { + flex: 1; + font-size: 24rpx; + color: #98a2b3; + line-height: 1.6; +} + +.complete-page__footer { + padding: 16rpx 32rpx calc(24rpx + env(safe-area-inset-bottom)); + background: $color-bg-page; +} + +.complete-page__submit { + height: 96rpx; + border-radius: 24rpx; + background: linear-gradient(98deg, #45d685 0%, #21c25e 100%); + box-shadow: 0 8rpx 28rpx rgba(33, 194, 94, 0.28); + display: flex; + align-items: center; + justify-content: center; +} + +.complete-page__submit-text { + font-size: 32rpx; + font-weight: 500; + color: #fff; +} diff --git a/src/pages/add-device/complete/components/complete-success-hero/index.vue b/src/pages/add-device/complete/components/complete-success-hero/index.vue new file mode 100644 index 0000000..dbd92f2 --- /dev/null +++ b/src/pages/add-device/complete/components/complete-success-hero/index.vue @@ -0,0 +1,71 @@ + + + + + diff --git a/src/pages/add-device/complete/index.vue b/src/pages/add-device/complete/index.vue new file mode 100644 index 0000000..7b1a961 --- /dev/null +++ b/src/pages/add-device/complete/index.vue @@ -0,0 +1,112 @@ + + + + + diff --git a/src/pages/add-device/complete/use-complete-device.ts b/src/pages/add-device/complete/use-complete-device.ts new file mode 100644 index 0000000..83929ac --- /dev/null +++ b/src/pages/add-device/complete/use-complete-device.ts @@ -0,0 +1,42 @@ +import { ref } from 'vue' + +const ROOM_OPTIONS = ['客厅', '主卧', '卧室', '厨房', '卫生间'] + +export function useCompleteDevice() { + const currentStep = 4 + const deviceName = ref('Z2 空气净化器') + const serialMasked = ref('****A3B2') + const roomName = ref('客厅') + const roomOptions = ROOM_OPTIONS + + function onPickRoom() { + uni.showActionSheet({ + itemList: roomOptions, + success: (res) => { + const picked = roomOptions[res.tapIndex] + if (picked) roomName.value = picked + }, + }) + } + + function onFinishAdd() { + const name = deviceName.value.trim() + if (!name) { + uni.showToast({ title: '请输入设备名称', icon: 'none' }) + return + } + uni.showToast({ title: '添加成功', icon: 'success' }) + setTimeout(() => { + uni.reLaunch({ url: '/pages/device/index' }) + }, 400) + } + + return { + currentStep, + deviceName, + serialMasked, + roomName, + onPickRoom, + onFinishAdd, + } +} diff --git a/src/pages/add-device/components/bind-bluetooth-hero/index.vue b/src/pages/add-device/components/bind-bluetooth-hero/index.vue new file mode 100644 index 0000000..c37b81d --- /dev/null +++ b/src/pages/add-device/components/bind-bluetooth-hero/index.vue @@ -0,0 +1,127 @@ + + + + + diff --git a/src/pages/add-device/components/bind-feature-list/index.vue b/src/pages/add-device/components/bind-feature-list/index.vue new file mode 100644 index 0000000..1f686b1 --- /dev/null +++ b/src/pages/add-device/components/bind-feature-list/index.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/src/pages/add-device/components/bind-steps/index.vue b/src/pages/add-device/components/bind-steps/index.vue new file mode 100644 index 0000000..45d38d6 --- /dev/null +++ b/src/pages/add-device/components/bind-steps/index.vue @@ -0,0 +1,131 @@ + + + + + diff --git a/src/pages/add-device/index.vue b/src/pages/add-device/index.vue new file mode 100644 index 0000000..490080d --- /dev/null +++ b/src/pages/add-device/index.vue @@ -0,0 +1,37 @@ + + + + + diff --git a/src/pages/add-device/pair/components/pair-check-list/index.vue b/src/pages/add-device/pair/components/pair-check-list/index.vue new file mode 100644 index 0000000..0b5826a --- /dev/null +++ b/src/pages/add-device/pair/components/pair-check-list/index.vue @@ -0,0 +1,132 @@ + + + + + diff --git a/src/pages/add-device/pair/components/pair-device-summary/index.vue b/src/pages/add-device/pair/components/pair-device-summary/index.vue new file mode 100644 index 0000000..de8ebcf --- /dev/null +++ b/src/pages/add-device/pair/components/pair-device-summary/index.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/src/pages/add-device/pair/components/pair-hero/index.vue b/src/pages/add-device/pair/components/pair-hero/index.vue new file mode 100644 index 0000000..b6159b0 --- /dev/null +++ b/src/pages/add-device/pair/components/pair-hero/index.vue @@ -0,0 +1,123 @@ + + + + + diff --git a/src/pages/add-device/pair/index.vue b/src/pages/add-device/pair/index.vue new file mode 100644 index 0000000..d2edd43 --- /dev/null +++ b/src/pages/add-device/pair/index.vue @@ -0,0 +1,65 @@ + + + + + diff --git a/src/pages/add-device/pair/pair.scss b/src/pages/add-device/pair/pair.scss new file mode 100644 index 0000000..2088b85 --- /dev/null +++ b/src/pages/add-device/pair/pair.scss @@ -0,0 +1,109 @@ +.pair-page { + min-height: 100vh; + background: $color-bg-page; + display: flex; + flex-direction: column; + box-sizing: border-box; +} + +.pair-page__scroll { + flex: 1; + height: 0; +} + +.pair-page__body { + padding-bottom: 24rpx; +} + +.pair-page__status { + padding: 0 40rpx; + margin-bottom: 32rpx; + display: flex; + flex-direction: column; + align-items: center; +} + +.pair-page__title { + font-size: 40rpx; + font-weight: 700; + color: $color-text; + margin-bottom: 16rpx; + text-align: center; +} + +.pair-page__desc { + font-size: 26rpx; + color: #98a2b3; + line-height: 1.65; + text-align: center; + padding: 0 24rpx; +} + +.pair-page__progress-block { + margin: 0 32rpx 24rpx; + padding: 28rpx 24rpx; + background: #fff; + border-radius: 20rpx; + box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.04); +} + +.pair-page__progress-head { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 20rpx; +} + +.pair-page__progress-label { + font-size: 28rpx; + color: $color-text; + font-weight: 500; +} + +.pair-page__progress-percent { + font-size: 28rpx; + font-weight: 600; + color: $color-primary; +} + +.pair-page__progress-track { + height: 16rpx; + border-radius: 999rpx; + background: #eef1f4; + overflow: hidden; + margin-bottom: 16rpx; +} + +.pair-page__progress-fill { + height: 100%; + border-radius: 999rpx; + background: linear-gradient(90deg, #45d685 0%, #21c25e 100%); + transition: width 0.4s ease; +} + +.pair-page__progress-tip { + font-size: 24rpx; + color: #98a2b3; + line-height: 1.5; +} + +.pair-page__footer { + padding: 16rpx 32rpx calc(24rpx + env(safe-area-inset-bottom)); + background: $color-bg-page; +} + +.pair-page__cancel { + height: 96rpx; + border-radius: 24rpx; + background: #fff; + border: 2rpx solid #e8eaed; + display: flex; + align-items: center; + justify-content: center; +} + +.pair-page__cancel-text { + font-size: 32rpx; + color: #666; + font-weight: 500; +} diff --git a/src/pages/add-device/pair/pair.types.ts b/src/pages/add-device/pair/pair.types.ts new file mode 100644 index 0000000..2d1880e --- /dev/null +++ b/src/pages/add-device/pair/pair.types.ts @@ -0,0 +1,13 @@ +export type PairCheckStatus = 'done' | 'syncing' | 'pending' + +export interface PairCheckItem { + id: string + title: string + status: PairCheckStatus + tag: string +} + +export interface PairDeviceInfo { + name: string + serialMasked: string +} diff --git a/src/pages/add-device/pair/use-pair-device.ts b/src/pages/add-device/pair/use-pair-device.ts new file mode 100644 index 0000000..92a3728 --- /dev/null +++ b/src/pages/add-device/pair/use-pair-device.ts @@ -0,0 +1,81 @@ +import { onUnmounted, ref, watch } from 'vue' +import type { PairCheckItem, PairDeviceInfo } from './pair.types' + +const DEFAULT_DEVICE: PairDeviceInfo = { + name: 'Z2 空气净化器', + serialMasked: '****A3B2', +} + +export function usePairDevice(initial?: Partial) { + const currentStep = 3 + const device = ref({ + ...DEFAULT_DEVICE, + ...initial, + }) + + const progress = ref(65) + const etaSeconds = ref(15) + const checkItems = ref([ + { id: 'signal', title: '蓝牙信号强度', status: 'done', tag: '良好' }, + { id: 'credential', title: '设备凭证校验', status: 'done', tag: '通过' }, + { id: 'network', title: '网络配置下发', status: 'syncing', tag: '同步中' }, + ]) + + let etaTick = 0 + const tick = setInterval(() => { + if (progress.value < 100) { + progress.value = Math.min(100, progress.value + 1) + } + etaTick += 1 + if (etaTick % 2 === 0 && etaSeconds.value > 0 && progress.value < 100) { + etaSeconds.value = Math.max(0, etaSeconds.value - 1) + } + if (progress.value >= 100) { + checkItems.value = checkItems.value.map((item) => + item.id === 'network' + ? { ...item, status: 'done', tag: '完成' } + : item, + ) + } + }, 500) + + let navigated = false + + watch(progress, (value) => { + if (value < 100 || navigated) return + navigated = true + const d = device.value + const q = [ + `name=${encodeURIComponent(d.name)}`, + `serial=${encodeURIComponent(d.serialMasked)}`, + ].join('&') + setTimeout(() => { + uni.redirectTo({ url: `/pages/add-device/complete/index?${q}` }) + }, 600) + }) + + onUnmounted(() => { + clearInterval(tick) + }) + + function onCancelPair() { + uni.showModal({ + title: '取消配对', + content: '确定要取消当前设备的配对吗?', + success: (res) => { + if (res.confirm) { + uni.navigateBack() + } + }, + }) + } + + return { + currentStep, + device, + progress, + etaSeconds, + checkItems, + onCancelPair, + } +} diff --git a/src/pages/add-device/scan/components/scan-device-item/index.vue b/src/pages/add-device/scan/components/scan-device-item/index.vue new file mode 100644 index 0000000..7689a58 --- /dev/null +++ b/src/pages/add-device/scan/components/scan-device-item/index.vue @@ -0,0 +1,102 @@ + + + + + diff --git a/src/pages/add-device/scan/components/scan-help-tip/index.vue b/src/pages/add-device/scan/components/scan-help-tip/index.vue new file mode 100644 index 0000000..10032a3 --- /dev/null +++ b/src/pages/add-device/scan/components/scan-help-tip/index.vue @@ -0,0 +1,76 @@ + + + diff --git a/src/pages/add-device/scan/components/scan-hero/index.vue b/src/pages/add-device/scan/components/scan-hero/index.vue new file mode 100644 index 0000000..0224a84 --- /dev/null +++ b/src/pages/add-device/scan/components/scan-hero/index.vue @@ -0,0 +1,116 @@ + + + + + diff --git a/src/pages/add-device/scan/index.vue b/src/pages/add-device/scan/index.vue new file mode 100644 index 0000000..0a2307b --- /dev/null +++ b/src/pages/add-device/scan/index.vue @@ -0,0 +1,70 @@ + + + + + diff --git a/src/pages/add-device/scan/scan.scss b/src/pages/add-device/scan/scan.scss new file mode 100644 index 0000000..e93dce2 --- /dev/null +++ b/src/pages/add-device/scan/scan.scss @@ -0,0 +1,78 @@ +.scan-page { + min-height: 100vh; + background: $color-bg-page; + display: flex; + flex-direction: column; + box-sizing: border-box; +} + +.scan-page__scroll { + flex: 1; + height: 0; +} + +.scan-page__body { + padding-bottom: calc(32rpx + env(safe-area-inset-bottom)); +} + +.scan-page__status { + padding: 0 40rpx; + margin-bottom: 36rpx; + display: flex; + flex-direction: column; + align-items: center; +} + +.scan-page__title { + display: block; + font-size: 36rpx; + font-weight: 700; + color: $color-text; + margin-bottom: 16rpx; + line-height: 1.4; + text-align: center; +} + +.scan-page__hint { + display: flex; + align-items: center; + justify-content: center; + gap: 12rpx; +} + +.scan-page__hint-dot { + width: 12rpx; + height: 12rpx; + border-radius: 50%; + background: #4a9eff; + flex-shrink: 0; +} + +.scan-page__hint-text { + font-size: 26rpx; + color: #98a2b3; + line-height: 1.5; + text-align: center; +} + +.scan-page__section-head { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 32rpx 16rpx; +} + +.scan-page__section-title { + font-size: 30rpx; + font-weight: 600; + color: $color-text; +} + +.scan-page__section-timer { + font-size: 24rpx; + color: #98a2b3; +} + +.scan-page__list { + padding: 0 32rpx; +} diff --git a/src/pages/add-device/scan/scan.types.ts b/src/pages/add-device/scan/scan.types.ts new file mode 100644 index 0000000..20b5dea --- /dev/null +++ b/src/pages/add-device/scan/scan.types.ts @@ -0,0 +1,7 @@ +export interface ScannedDeviceItem { + id: string + name: string + serialMasked: string + /** 是否可添加(未知设备为 false 时箭头置灰) */ + canAdd: boolean +} diff --git a/src/pages/add-device/scan/use-scan-device.ts b/src/pages/add-device/scan/use-scan-device.ts new file mode 100644 index 0000000..fafe361 --- /dev/null +++ b/src/pages/add-device/scan/use-scan-device.ts @@ -0,0 +1,69 @@ +import { onUnmounted, ref } from 'vue' +import type { ScannedDeviceItem } from './scan.types' + +const MOCK_DEVICES: ScannedDeviceItem[] = [ + { + id: 'z2-purifier', + name: 'Z2 空气净化器', + serialMasked: '****A3B2', + canAdd: true, + }, + { + id: 'unknown', + name: '未知设备', + serialMasked: '****7C91', + canAdd: false, + }, +] + +const SCAN_SECONDS = 20 + +export function useScanDevice() { + const currentStep = 2 + const devices = ref(MOCK_DEVICES) + const selectedId = ref(MOCK_DEVICES[0]?.id ?? '') + const secondsLeft = ref(SCAN_SECONDS) + const scanning = ref(true) + + const timer = setInterval(() => { + if (secondsLeft.value <= 0) { + scanning.value = false + return + } + secondsLeft.value -= 1 + if (secondsLeft.value === 0) { + scanning.value = false + } + }, 1000) + + onUnmounted(() => { + clearInterval(timer) + }) + + function onSelectDevice(id: string) { + selectedId.value = id + } + + function onDeviceAction(device: ScannedDeviceItem) { + selectedId.value = device.id + if (!device.canAdd) { + uni.showToast({ title: '暂无法添加该设备', icon: 'none' }) + return + } + const q = [ + `name=${encodeURIComponent(device.name)}`, + `serial=${encodeURIComponent(device.serialMasked)}`, + ].join('&') + uni.navigateTo({ url: `/pages/add-device/pair/index?${q}` }) + } + + return { + currentStep, + devices, + selectedId, + secondsLeft, + scanning, + onSelectDevice, + onDeviceAction, + } +} diff --git a/src/pages/add-device/use-add-device.ts b/src/pages/add-device/use-add-device.ts new file mode 100644 index 0000000..56f0a18 --- /dev/null +++ b/src/pages/add-device/use-add-device.ts @@ -0,0 +1,48 @@ +import { staticAssets } from '@/utils/static-assets' +import type { BindFeatureItem, BindStepItem } from './add-device.types' + +export const BIND_STEPS: BindStepItem[] = [ + { id: 1, label: '开启蓝牙' }, + { id: 2, label: '扫描设备' }, + { id: 3, label: '配对连接' }, + { id: 4, label: '完成配置' }, +] + +export const BIND_FEATURES: BindFeatureItem[] = [ + { + id: 'discover', + title: '发现附近设备', + desc: '通过蓝牙扫描发现您周围的电子智能设备', + icon: staticAssets.iconBindDiscover, + }, + { + id: 'pair', + title: '快速配对连接', + desc: '一键完成设备配对,无需复杂的网络配置', + icon: staticAssets.iconBindPair, + }, + { + id: 'secure', + title: '本地安全控制', + desc: '蓝牙直连控制,数据加密传输,保护您的隐私', + icon: staticAssets.iconBindSecure, + }, +] + +export function useAddDevice() { + const currentStep = 1 + + function onOpenBluetooth() { + uni.navigateTo({ url: '/pages/add-device/scan/index' }) + } + + function onBindLater() { + uni.navigateBack() + } + + return { + currentStep, + onOpenBluetooth, + onBindLater, + } +} diff --git a/src/pages/agreement/agreement.api.ts b/src/pages/agreement/agreement.api.ts new file mode 100644 index 0000000..2cc1a89 --- /dev/null +++ b/src/pages/agreement/agreement.api.ts @@ -0,0 +1,11 @@ +import { request } from '@/api/request' +import type { AgreementDetail, AgreementType } from './agreement.types' + +/** GET /app/auth/agreement/detail — 协议详情 */ +export function fetchAgreementDetail(type: AgreementType) { + return request({ + url: '/app/auth/agreement/detail', + method: 'POST', + data: { type }, + }) +} diff --git a/src/pages/agreement/agreement.types.ts b/src/pages/agreement/agreement.types.ts new file mode 100644 index 0000000..a5e6917 --- /dev/null +++ b/src/pages/agreement/agreement.types.ts @@ -0,0 +1,10 @@ +export type AgreementType = 'service' | 'privacy' + +/** /app/auth/agreement/detail 返回 data 结构 */ +export interface AgreementDetail { + agreementNo?: string + name?: string + versionName?: string + content?: string + publishTime?: string +} diff --git a/src/pages/agreement/index.vue b/src/pages/agreement/index.vue new file mode 100644 index 0000000..b1f2008 --- /dev/null +++ b/src/pages/agreement/index.vue @@ -0,0 +1,52 @@ + + + + + diff --git a/src/pages/agreement/use-agreement.ts b/src/pages/agreement/use-agreement.ts new file mode 100644 index 0000000..ad3b7a0 --- /dev/null +++ b/src/pages/agreement/use-agreement.ts @@ -0,0 +1,51 @@ +import { onLoad } from '@dcloudio/uni-app' +import { ref } from 'vue' +import { getErrorMessage } from '@/utils/api-error' +import { fetchAgreementDetail } from './agreement.api' +import type { AgreementDetail, AgreementType } from './agreement.types' + +const DEFAULT_TITLE: Record = { + service: '用户服务协议', + privacy: '隐私政策', +} + +function normalizeDetail(type: AgreementType, data: AgreementDetail) { + const title = data.name?.trim() || DEFAULT_TITLE[type] + const content = data.content?.trim() || '' + return { title, content } +} + +export function useAgreement() { + const type = ref('service') + const title = ref(DEFAULT_TITLE.service) + const content = ref('') + const loading = ref(false) + + async function loadDetail(agreementType: AgreementType) { + loading.value = true + title.value = DEFAULT_TITLE[agreementType] + content.value = '' + try { + const data = await fetchAgreementDetail(agreementType) + const normalized = normalizeDetail(agreementType, data ?? {}) + title.value = normalized.title + content.value = normalized.content + if (!content.value) { + uni.showToast({ title: '协议内容为空', icon: 'none' }) + } + } catch (err) { + uni.showToast({ title: getErrorMessage(err, '加载协议失败'), icon: 'none' }) + } finally { + loading.value = false + } + } + + onLoad((query) => { + const t = query?.type as AgreementType + const agreementType = t === 'privacy' || t === 'service' ? t : 'service' + type.value = agreementType + loadDetail(agreementType) + }) + + return { title, content, loading } +} diff --git a/src/pages/change-password/change-password.api.ts b/src/pages/change-password/change-password.api.ts new file mode 100644 index 0000000..f3a7477 --- /dev/null +++ b/src/pages/change-password/change-password.api.ts @@ -0,0 +1,17 @@ +import { request } from '@/api/request' +import type { ForgetPasswordResetBody } from '@/types/api' +import type { ChangePasswordForm } from './change-password.types' + +/** POST /app/auth/forgetPassword/doReset — 凭重置令牌设置新密码 */ +export function resetPasswordByToken(data: ChangePasswordForm) { + const body: ForgetPasswordResetBody = { + resetToken: data.resetToken, + newPassword: data.newPassword, + confirmPassword: data.confirmPassword, + } + return request({ + url: '/app/auth/forgetPassword/doReset', + method: 'POST', + data: body, + }) +} diff --git a/src/pages/change-password/change-password.types.ts b/src/pages/change-password/change-password.types.ts new file mode 100644 index 0000000..3c5c091 --- /dev/null +++ b/src/pages/change-password/change-password.types.ts @@ -0,0 +1,5 @@ +export interface ChangePasswordForm { + newPassword: string + confirmPassword: string + resetToken: string +} diff --git a/src/pages/change-password/index.vue b/src/pages/change-password/index.vue new file mode 100644 index 0000000..99b1cda --- /dev/null +++ b/src/pages/change-password/index.vue @@ -0,0 +1,49 @@ + + + + + diff --git a/src/pages/change-password/use-change-password.ts b/src/pages/change-password/use-change-password.ts new file mode 100644 index 0000000..2b34937 --- /dev/null +++ b/src/pages/change-password/use-change-password.ts @@ -0,0 +1,55 @@ +import { onLoad } from '@dcloudio/uni-app' +import { ref } from 'vue' +import { getErrorMessage } from '@/utils/api-error' +import { isValidPassword } from '@/utils/validate' +import { resetPasswordByToken } from './change-password.api' + +export function useChangePassword() { + const password = ref('') + const confirmPassword = ref('') + const resetToken = ref('') + const loading = ref(false) + + onLoad((query) => { + const raw = (query?.resetToken as string) || '' + resetToken.value = raw ? decodeURIComponent(raw) : '' + if (!resetToken.value) { + uni.showToast({ title: '请先完成验证码校验', icon: 'none' }) + setTimeout(() => uni.navigateBack(), 1500) + } + }) + + async function submit() { + if (!resetToken.value) { + uni.showToast({ title: '重置令牌无效', icon: 'none' }) + return + } + if (!isValidPassword(password.value)) { + uni.showToast({ title: '密码至少 6 位', icon: 'none' }) + return + } + if (password.value !== confirmPassword.value) { + uni.showToast({ title: '两次密码不一致', icon: 'none' }) + return + } + if (loading.value) return + loading.value = true + try { + await resetPasswordByToken({ + resetToken: resetToken.value, + newPassword: password.value, + confirmPassword: confirmPassword.value, + }) + uni.showToast({ title: '修改成功', icon: 'success' }) + setTimeout(() => { + uni.reLaunch({ url: '/pages/login/index' }) + }, 800) + } catch (err) { + uni.showToast({ title: getErrorMessage(err, '修改失败,请重试'), icon: 'none' }) + } finally { + loading.value = false + } + } + + return { password, confirmPassword, submit } +} diff --git a/src/pages/device/components/device-empty/index.vue b/src/pages/device/components/device-empty/index.vue new file mode 100644 index 0000000..348e71b --- /dev/null +++ b/src/pages/device/components/device-empty/index.vue @@ -0,0 +1,118 @@ + + + + + diff --git a/src/pages/device/components/device-header/index.vue b/src/pages/device/components/device-header/index.vue new file mode 100644 index 0000000..88991e3 --- /dev/null +++ b/src/pages/device/components/device-header/index.vue @@ -0,0 +1,96 @@ + + + + + diff --git a/src/pages/device/components/hot-devices/index.vue b/src/pages/device/components/hot-devices/index.vue new file mode 100644 index 0000000..f1bffe6 --- /dev/null +++ b/src/pages/device/components/hot-devices/index.vue @@ -0,0 +1,97 @@ + + + + + diff --git a/src/pages/device/components/room-tabs/index.vue b/src/pages/device/components/room-tabs/index.vue new file mode 100644 index 0000000..868c929 --- /dev/null +++ b/src/pages/device/components/room-tabs/index.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/src/pages/device/device.scss b/src/pages/device/device.scss new file mode 100644 index 0000000..1671998 --- /dev/null +++ b/src/pages/device/device.scss @@ -0,0 +1,6 @@ +.device-page { + min-height: 100vh; + background: $color-bg-page; + padding-bottom: calc(120rpx + env(safe-area-inset-bottom)); + box-sizing: border-box; +} diff --git a/src/pages/device/device.types.ts b/src/pages/device/device.types.ts new file mode 100644 index 0000000..ee5b96d --- /dev/null +++ b/src/pages/device/device.types.ts @@ -0,0 +1,10 @@ +export interface RoomTab { + id: string + name: string +} + +export interface HotDeviceItem { + id: string + name: string + desc: string +} diff --git a/src/pages/device/index.vue b/src/pages/device/index.vue new file mode 100644 index 0000000..c169a3b --- /dev/null +++ b/src/pages/device/index.vue @@ -0,0 +1,47 @@ + + + + + diff --git a/src/pages/device/use-device.ts b/src/pages/device/use-device.ts new file mode 100644 index 0000000..38e9e03 --- /dev/null +++ b/src/pages/device/use-device.ts @@ -0,0 +1,66 @@ +import { ref } from 'vue' +import type { HotDeviceItem, RoomTab } from './device.types' + +const DEFAULT_ROOMS: RoomTab[] = [ + { id: 'all', name: '全部' }, + { id: 'living', name: '客厅' }, +] + +const DEFAULT_HOT_DEVICES: HotDeviceItem[] = [ + { + id: 'z2', + name: 'Z2 空气净化器', + desc: '高效除醛 · 智能检测', + }, + { + id: 'lamp', + name: '智能护眼台灯', + desc: '无频闪 · 多场景模式', + }, +] + +export function useDevice() { + const homeName = ref('我的家') + const rooms = ref(DEFAULT_ROOMS) + const activeRoomId = ref('all') + const hotDevices = ref(DEFAULT_HOT_DEVICES) + const bindStepHint = ref(3) + + function selectRoom(id: string) { + activeRoomId.value = id + } + + function onAddRoom() { + uni.showToast({ title: '添加房间功能开发中', icon: 'none' }) + } + + function onStartBind() { + uni.navigateTo({ url: '/pages/add-device/index' }) + } + + function onGoMall() { + uni.showToast({ title: '商城功能开发中', icon: 'none' }) + } + + function onNotify() { + uni.showToast({ title: '消息功能开发中', icon: 'none' }) + } + + function onHomePicker() { + uni.showToast({ title: '切换家庭功能开发中', icon: 'none' }) + } + + return { + homeName, + rooms, + activeRoomId, + hotDevices, + bindStepHint, + selectRoom, + onAddRoom, + onStartBind, + onGoMall, + onNotify, + onHomePicker, + } +} diff --git a/src/pages/forgot-password/forgot-password.api.ts b/src/pages/forgot-password/forgot-password.api.ts new file mode 100644 index 0000000..94eda0e --- /dev/null +++ b/src/pages/forgot-password/forgot-password.api.ts @@ -0,0 +1,16 @@ +import { request } from '@/api/request' +import type { ForgetPasswordVerifyBody, ForgetPasswordVerifyResult } from '@/types/api' +import type { ForgotPasswordForm } from './forgot-password.types' + +/** POST /app/auth/forgetPassword/verifyCode — 校验手机号与验证码,返回重置令牌 */ +export function verifyForgetPasswordCode(data: ForgotPasswordForm) { + const body: ForgetPasswordVerifyBody = { + phonenumber: data.phone.trim(), + smsCode: data.code.trim(), + } + return request({ + url: '/app/auth/forgetPassword/verifyCode', + method: 'POST', + data: body, + }) +} diff --git a/src/pages/forgot-password/forgot-password.types.ts b/src/pages/forgot-password/forgot-password.types.ts new file mode 100644 index 0000000..191b7e8 --- /dev/null +++ b/src/pages/forgot-password/forgot-password.types.ts @@ -0,0 +1,4 @@ +export interface ForgotPasswordForm { + phone: string + code: string +} diff --git a/src/pages/forgot-password/index.vue b/src/pages/forgot-password/index.vue new file mode 100644 index 0000000..1ae80ab --- /dev/null +++ b/src/pages/forgot-password/index.vue @@ -0,0 +1,48 @@ + + + + + diff --git a/src/pages/forgot-password/use-forgot-password.ts b/src/pages/forgot-password/use-forgot-password.ts new file mode 100644 index 0000000..7ba232c --- /dev/null +++ b/src/pages/forgot-password/use-forgot-password.ts @@ -0,0 +1,47 @@ +import { ref } from 'vue' +import { useSmsCode } from '@/hooks/use-sms-code' +import { getErrorMessage } from '@/utils/api-error' +import { isValidPhone, isValidSmsCode } from '@/utils/validate' +import { SMS_CODE_SCENE } from '@/types/api' +import { verifyForgetPasswordCode } from './forgot-password.api' + +export function useForgotPassword() { + const phone = ref('') + const code = ref('') + const loading = ref(false) + + const { counting, seconds, sendCode } = useSmsCode(SMS_CODE_SCENE.reset) + + function onSendCode() { + sendCode(phone.value) + } + + async function nextStep() { + if (!isValidPhone(phone.value)) { + uni.showToast({ title: '请输入正确手机号', icon: 'none' }) + return + } + if (!isValidSmsCode(code.value)) { + uni.showToast({ title: '请输入验证码', icon: 'none' }) + return + } + if (loading.value) return + loading.value = true + try { + const res = await verifyForgetPasswordCode({ phone: phone.value, code: code.value }) + if (!res.resetToken) { + uni.showToast({ title: '验证失败,未获取重置令牌', icon: 'none' }) + return + } + uni.navigateTo({ + url: `/pages/change-password/index?resetToken=${encodeURIComponent(res.resetToken)}`, + }) + } catch (err) { + uni.showToast({ title: getErrorMessage(err, '验证失败,请重试'), icon: 'none' }) + } finally { + loading.value = false + } + } + + return { phone, code, counting, seconds, onSendCode, nextStep } +} diff --git a/src/pages/index/index.vue b/src/pages/index/index.vue index ecac4f5..e69de29 100644 --- a/src/pages/index/index.vue +++ b/src/pages/index/index.vue @@ -1,41 +0,0 @@ - - - - - diff --git a/src/pages/login/index.vue b/src/pages/login/index.vue new file mode 100644 index 0000000..26673fd --- /dev/null +++ b/src/pages/login/index.vue @@ -0,0 +1,69 @@ + + + + + diff --git a/src/pages/login/login.api.ts b/src/pages/login/login.api.ts new file mode 100644 index 0000000..664a852 --- /dev/null +++ b/src/pages/login/login.api.ts @@ -0,0 +1,29 @@ +import { request } from '@/api/request' +import type { AuthTokenResult, LoginPasswordBody, LoginSmsBody } from '@/types/api' +import type { LoginCodeForm, LoginPasswordForm } from './login.types' + +/** POST /app/auth/login/sms — 短信验证码登录 */ +export function loginBySms(data: LoginCodeForm) { + const body: LoginSmsBody = { + phonenumber: data.phone.trim(), + smsCode: data.code.trim(), + } + return request({ + url: '/app/auth/login/sms', + method: 'POST', + data: body, + }) +} + +/** POST /app/auth/login/password — 账号密码登录(账号可为用户名或手机号) */ +export function loginByPassword(data: LoginPasswordForm) { + const body: LoginPasswordBody = { + account: data.account.trim(), + password: data.password, + } + return request({ + url: '/app/auth/login/password', + method: 'POST', + data: body, + }) +} diff --git a/src/pages/login/login.scss b/src/pages/login/login.scss new file mode 100644 index 0000000..0b4183f --- /dev/null +++ b/src/pages/login/login.scss @@ -0,0 +1,59 @@ +.login-page { + min-height: 100vh; + background: #fff; + padding: 0 $page-padding-x 48rpx; +} + +.login-page__hero { + padding-top: 120rpx; + margin-bottom: 64rpx; +} + +.login-page__welcome { + margin-top: 80rpx; +} + +.login-page__title { + display: block; + font-size: 48rpx; + font-weight: 700; + color: $color-text; + margin-bottom: 16rpx; +} + +.login-page__subtitle { + display: block; + font-size: 26rpx; + color: $color-text-secondary; + line-height: 1.5; +} + +.login-page__links { + display: flex; + justify-content: space-between; + align-items: center; + margin: 8rpx 0 48rpx; +} + +.login-page__links--single { + justify-content: flex-start; +} + +.login-page__link { + font-size: 28rpx; + color: $color-link; +} + +.login-page__footer { + margin-top: auto; + padding-top: 48rpx; +} + +.login-page__agreement { + margin-bottom: 32rpx; +} + +.login-page__register { + margin-top: 32rpx; + text-align: center; +} diff --git a/src/pages/login/login.types.ts b/src/pages/login/login.types.ts new file mode 100644 index 0000000..31d4958 --- /dev/null +++ b/src/pages/login/login.types.ts @@ -0,0 +1,11 @@ +export type LoginMode = 'code' | 'password' + +export interface LoginCodeForm { + phone: string + code: string +} + +export interface LoginPasswordForm { + account: string + password: string +} diff --git a/src/pages/login/use-login.ts b/src/pages/login/use-login.ts new file mode 100644 index 0000000..94e0f9e --- /dev/null +++ b/src/pages/login/use-login.ts @@ -0,0 +1,96 @@ +import { ref } from 'vue' +import { useSmsCode } from '@/hooks/use-sms-code' +import { isValidPassword, isValidPhone, isValidSmsCode } from '@/utils/validate' +import { saveAuthToken } from '@/api/auth' +import { getErrorMessage } from '@/utils/api-error' +import { SMS_CODE_SCENE } from '@/types/api' +import { loginByPassword, loginBySms } from './login.api' +import type { LoginMode } from './login.types' + +export function useLogin() { + const mode = ref('code') + const agreed = ref(true) + const phone = ref('') + const code = ref('') + const account = ref('') + const password = ref('') + const loading = ref(false) + + const { counting, seconds, sendCode } = useSmsCode(SMS_CODE_SCENE.login) + + function switchMode(next: LoginMode) { + mode.value = next + } + + function onSendCode() { + sendCode(phone.value) + } + + async function submit() { + if (!agreed.value) { + uni.showToast({ title: '请先阅读并同意协议', icon: 'none' }) + return + } + if (loading.value) return + + loading.value = true + try { + if (mode.value === 'code') { + if (!isValidPhone(phone.value)) { + uni.showToast({ title: '请输入正确手机号', icon: 'none' }) + return + } + if (!isValidSmsCode(code.value)) { + uni.showToast({ title: '请输入验证码', icon: 'none' }) + return + } + const smsRes = await loginBySms({ phone: phone.value, code: code.value }) + saveAuthToken(smsRes) + } else { + if (!account.value.trim()) { + uni.showToast({ title: '请输入账号', icon: 'none' }) + return + } + if (!isValidPassword(password.value)) { + uni.showToast({ title: '密码至少 6 位', icon: 'none' }) + return + } + const pwdRes = await loginByPassword({ + account: account.value, + password: password.value, + }) + saveAuthToken(pwdRes) + } + uni.showToast({ title: '登录成功', icon: 'success' }) + uni.reLaunch({ url: '/pages/device/index' }) + } catch (err) { + uni.showToast({ title: getErrorMessage(err, '登录失败,请检查信息'), icon: 'none' }) + } finally { + loading.value = false + } + } + + function goRegister() { + uni.navigateTo({ url: '/pages/register/index' }) + } + + function goForgotPassword() { + uni.navigateTo({ url: '/pages/forgot-password/index' }) + } + + return { + mode, + agreed, + phone, + code, + account, + password, + counting, + seconds, + switchMode, + onSendCode, + submit, + goRegister, + goForgotPassword, + } +} diff --git a/src/pages/mine/index.vue b/src/pages/mine/index.vue new file mode 100644 index 0000000..4df17d3 --- /dev/null +++ b/src/pages/mine/index.vue @@ -0,0 +1,44 @@ + + + + + diff --git a/src/pages/register/index.vue b/src/pages/register/index.vue new file mode 100644 index 0000000..126db0e --- /dev/null +++ b/src/pages/register/index.vue @@ -0,0 +1,70 @@ + + + + + diff --git a/src/pages/register/register.api.ts b/src/pages/register/register.api.ts new file mode 100644 index 0000000..748d800 --- /dev/null +++ b/src/pages/register/register.api.ts @@ -0,0 +1,18 @@ +import { request } from '@/api/request' +import type { RegisterBody } from '@/types/api' +import type { RegisterForm } from './register.types' + +/** POST /app/auth/register */ +export function registerUser(data: RegisterForm) { + const body: RegisterBody = { + phonenumber: data.phone.trim(), + smsCode: data.code.trim(), + password: data.password, + confirmPassword: data.confirmPassword, + } + return request({ + url: '/app/auth/register', + method: 'POST', + data: body, + }) +} diff --git a/src/pages/register/register.types.ts b/src/pages/register/register.types.ts new file mode 100644 index 0000000..fbebe2b --- /dev/null +++ b/src/pages/register/register.types.ts @@ -0,0 +1,6 @@ +export interface RegisterForm { + phone: string + code: string + password: string + confirmPassword: string +} diff --git a/src/pages/register/use-register.ts b/src/pages/register/use-register.ts new file mode 100644 index 0000000..4ad26d9 --- /dev/null +++ b/src/pages/register/use-register.ts @@ -0,0 +1,73 @@ +import { ref } from 'vue' +import { useSmsCode } from '@/hooks/use-sms-code' +import { isValidPassword, isValidPhone, isValidSmsCode } from '@/utils/validate' +import { SMS_CODE_SCENE } from '@/types/api' +import { registerUser } from './register.api' + +export function useRegister() { + const phone = ref('') + const code = ref('') + const password = ref('') + const confirmPassword = ref('') + const agreed = ref(false) + const loading = ref(false) + + const { counting, seconds, sendCode } = useSmsCode(SMS_CODE_SCENE.register) + + function onSendCode() { + sendCode(phone.value) + } + + async function submit() { + if (!agreed.value) { + uni.showToast({ title: '请先阅读并同意协议', icon: 'none' }) + return + } + if (!isValidPhone(phone.value)) { + uni.showToast({ title: '请输入正确手机号', icon: 'none' }) + return + } + if (!isValidSmsCode(code.value)) { + uni.showToast({ title: '请输入验证码', icon: 'none' }) + return + } + if (!isValidPassword(password.value)) { + uni.showToast({ title: '密码至少 6 位', icon: 'none' }) + return + } + if (password.value !== confirmPassword.value) { + uni.showToast({ title: '两次密码不一致', icon: 'none' }) + return + } + if (loading.value) return + loading.value = true + try { + await registerUser({ + phone: phone.value, + code: code.value, + password: password.value, + confirmPassword: confirmPassword.value, + }) + uni.showToast({ title: '注册成功', icon: 'success' }) + setTimeout(() => { + uni.navigateBack() + }, 800) + } catch { + uni.showToast({ title: '注册失败,请稍后重试', icon: 'none' }) + } finally { + loading.value = false + } + } + + return { + phone, + code, + password, + confirmPassword, + agreed, + counting, + seconds, + onSendCode, + submit, + } +} diff --git a/src/plugins/register-global-components.ts b/src/plugins/register-global-components.ts new file mode 100644 index 0000000..90b48cb --- /dev/null +++ b/src/plugins/register-global-components.ts @@ -0,0 +1,24 @@ +import type { App, Component } from 'vue' +import AgreementCheckbox from '@/components/agreement-checkbox/index.vue' +import AppNavbar from '@/components/app-navbar/index.vue' +import BaseInput from '@/components/base-input/index.vue' +import BrandLogo from '@/components/brand-logo/index.vue' +import PrimaryButton from '@/components/primary-button/index.vue' +import SmsCodeBtn from '@/components/sms-code-btn/index.vue' +import AppTabBar from '@/components/app-tab-bar/index.vue' + +const globalComponents: Record = { + 'app-tab-bar': AppTabBar, + 'agreement-checkbox': AgreementCheckbox, + 'app-navbar': AppNavbar, + 'base-input': BaseInput, + 'brand-logo': BrandLogo, + 'primary-button': PrimaryButton, + 'sms-code-btn': SmsCodeBtn, +} + +export function registerGlobalComponents(app: App) { + Object.entries(globalComponents).forEach(([name, component]) => { + app.component(name, component) + }) +} diff --git a/src/static/icon-checked@2x.png b/src/static/icon-checked@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..5f87dd5609529e5e21849c16544c915a92d540e8 GIT binary patch literal 797 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 literal 0 HcmV?d00001 diff --git a/src/static/tab-device-active.png b/src/static/tab-device-active.png new file mode 100644 index 0000000000000000000000000000000000000000..987e4786873ac28a9dd73927ef525a221a730094 GIT binary patch literal 434 zcmV;j0ZsmiP)Px#1am@3R0s$N2z&@+hyVZqTuDShR5*?8mA!GoKoCaX9+^l~f=aMEFdc{vAVG5x zRs}!>XCe)e>A)peC0qwq2_%HvIyuHN0~%y-;_)|g$^E?jcIReS=#XSDxcVs>QjRCx>=FYkFR$^6E zSp<6%LRbT7k|eM3;74-KrEvgcA%s#mnHYeN>uQNerm8uR0%cJYFQ<*(#Z>w_ZvY=c cSRZ@&C%?(Cfh@-)7XSbN07*qoM6N<$f~ohdivR!s literal 0 HcmV?d00001 diff --git a/src/static/tab-device.png b/src/static/tab-device.png new file mode 100644 index 0000000000000000000000000000000000000000..fa88452633fda052daa68db57edb910ed58710f3 GIT binary patch literal 382 zcmV-^0fGLBP)200001b5ch_0Itp) z=>Px#1am@3R0s$N2z&@+hyVZqC`m*?R5*>rl(B8XU=)P!UW85Q5g5TEBsD<_6F?>i zCAfh!kP;?YM>tqf9tgVx-0rP8s=PH0lBk)ue5CDwo7=hSh z*IN=}s}hLg+eVc`@%{V%1Px#1am@3R0s$N2z&@+hyVZq*-1n}R7i=nmCbSDKoCY>w{D!+4CDxmBQTo*A*?S< z>9i&SK?L$1s3}fnBUnPf8Q=(3A?P#*3eD_gR3+935H9ykQcu7B`$q7^Cf7JwYg1L7 zh{$~iVFAYNm7fNzR;y=T+qTbLd$`9eB8ykq)~7SW6aCB^S!PL;88@X5W@Sl zSH+jx#R{yoV^tkB7hMQpsH$bGVwYvv_9lR}cAO+h9zqz$S7qG!N<`8)2SNzj5W=Dh zbP~`SYzh2w&aIQA8T8yvtGGXr*wx<3Gx%jF0ddGC7%Z`tniY2e_^AK@002ovPDHLkV1l}+@ml}@ literal 0 HcmV?d00001 diff --git a/src/static/tab-mine.png b/src/static/tab-mine.png new file mode 100644 index 0000000000000000000000000000000000000000..4d62eaa505d13c5a6916cadfed5821027b73910f GIT binary patch literal 461 zcmV;;0W$uHP)Px#1am@3R0s$N2z&@+hyVZqcS%G+R7i={lg%zeK^(=;|MrWWHW4B52qc6xJJKSW zig*DJPz!o%;jUcnqP6w_HWqC{o9-+uiG*hmV&h|@ZtLE^ML*Eg%$Tlvb~AHwe)DyJ zeix~j!ew)nn6?1S0$>2wOq{F5?PAY>NXa||XuJC=J8r_>(FQbkUJ!YhI4L@bA*461 zMJSm^2mml61IRowfXXKjdSrkr1%S{a1hDVtW$8?u(+1R*4+%OuG# z(mF-!f61@NvW(DFeL4`N37@=C05BMj9`jv2Vc&p=^#Bzs7WUjUfi?k*boDTv7&xxI zSC6ose^v$zl+8H?oq#bWK3)sN4+Ly{EZLV*K;+z90nsUduu}N9Tm+DE7xznl0tSn_ z<6ekw35fnP9BBOCmjHcER^!fnYwlOk{19h#!<)N}`W(#soa=0IN;kYM##(nk#fkwK z?ioNB!G$%{8V1`73wY%+vKo)uNpLa0_Xb$K#i;5F(tdeF2(Rq`00000NkvXXu0mjf DkF&l+ literal 0 HcmV?d00001 diff --git a/src/static/图标 4.png b/src/static/图标 4.png new file mode 100644 index 0000000000000000000000000000000000000000..b20a45ae586dd8f04aa307e034fb4dd95a6b5775 GIT binary patch literal 352 zcmV-m0iXVfP)Px#1am@3R0s$N2z&@+hyVZq3Q0skR49?fk}*!hKoCXWEK8A&6FAEW@Da=npg~D+ zr1%DK0&)ZA21$csXhKH`H%LlK)6qey2+8r#Bo-o$Whn)}8qLfb{jXg(K(cS(rX8nZ zW6}IJ>!w-Nj;RL2K*i2MeHA-*NqEr+_LA!x6<$;ry?5?URT!yZ<~@XWwHds$AGg!+ z(@lGWzX#iCs9xHSfAAwNKijB+6va*o~!`e(+tHMxLHY(vPntU|@OBb8{%BO0o|AXzeDAu|ZgL5^X yK~-I`X1N0!Sw9sVouLGKVR$v)$CE?cmihsy8(?c6L?q?_0000Px#1am@3R0s$N2z&@+hyVZrA4x<(R7i>CmOXD8K@f(Y*-)b>wfQJgQBgY$E?l`m zBC_DP!tNnJG_rN%{iBtT(g=MOrja0}Aj+W%Q-Mn?gGtT%u`xIoxE~STC!a@T zFrOwh@59>QT;T2_d|x#CpyOmj$pe)I{7u}9w-ETkz!x0b< z&i9<6TGNhP8RgaDBjHTL(lH1qja;|xEbTHAV^7D&{wM`tZ>1!LVuG`_uF@dsu)*F+ z37CVgozBHx;EuupFANQIu~(Z)=-*ksgiTYYGbFsn)0#I72V{G0S#&EbDoj`N{?YTR zTss5b?{UU=%aK<`DQulxDP#iYWZ5>e8^^o42WS2InHXA;1=#Xn0f=%aVh)!E$olo> zwbQvs0hp8y)k@^_sqk8qy=}lMOhx$_wwaVJuhaP09{~s9fZSL2GjP7By=!XTN8oZ& z_1--c5nrn<1B7#+GOxWGJQ9{cY}DUU?BrPt!Jqy|AToe%{TE37PB~aDgPFiNF=E=l za8mUS;$Gk4>7S8BB>#x~a-z?17YF15zOl;Nl3 P00000NkvXXu0mjf{og5l literal 0 HcmV?d00001 diff --git a/src/static/图标 9.png b/src/static/图标 9.png new file mode 100644 index 0000000000000000000000000000000000000000..880de4e68236f69681806b7566238f65a537494e GIT binary patch literal 302 zcmV+}0nz@6P)Px#1am@3R0s$N2z&@+hyVZp*hxe|R5*>blFhLJK@>*Moo>Za2xKrJczYHSPEFp8@cvQKl9ByjD- zo~vpC-dfXLKX9!XK-rkCAYtz6!Vnm}+VJ<;1Mqft%*nG}lK=n!07*qoM6N<$g2#!0 ACIA2c literal 0 HcmV?d00001 diff --git a/src/static/组 27.png b/src/static/组 27.png new file mode 100644 index 0000000000000000000000000000000000000000..7b3c1b38dfa426ad03a7bc1f0168594564cc02c5 GIT binary patch literal 695 zcmV;o0!aOdP)Px#1am@3R0s$N2z&@+hyVZrVM#w=@-cmox1RiMCpOAkQ^ma< zjjv6swR}(GYtvfX+tF5X(rvNr{c`2umpA6u-Oba}XAUYor>aOIVs+D#cxyP0I#t+a|urrpYT+RgZ>;j5J%8UqTgBekd^ zmY*7ipCkcOM4E2ihL;r7)7ZI*fmosxTSf`3OmtN&VLKLr4dmO+5y&CL6Si}vmY!)` z7@?jOP*l`PrrgDLay--$#BAg;)NZY|C%Px#1am@3R0s$N2z&@+hyVZu8%ab#RA_ZB+jp-U!(!c!@^lZWB3D&qCrEZ)~(HZ6BaL7$NH0zq3OH%?LrhNR?@ri!tZ z8otxetEANYF3zL#6$1aN$ZQlz28An`BK@BR#q4rTmNk^h${mF~H-WZQ7io@Bw}&qR zUJ+e-;Xj5FN!cM3hJY>qn>=$&OP$_OP>fWctT9v`nLpNmnF`Zx51rK`rBn`HC8buL zW^2U^z?MRuo3D~mtDG#u5d7H2OsaiL= z>Due($N?2CV81yDAu*elwhT}#QK0|YYet{HeQ(<)ui#A2299;9c09TUTMSK1<@o-m zICJSD0BVy$q`iqf+r4N37rDIT#dMwrPv#k)STuY%qVnwtZH1m2$FiYVqQKV!F~iK! z&H$$m22q=oW!pcFW*Hbv5K9yQIPqD4FF)U4MM4eBKeYk+ue~3>|ONiUmVeg;|pe` z)L`1sRJ>s9GkdmsP4%&Hy^GeC(ps9Ba(IN2nWAxQWs?jAN)l2T-E<$-m^S2bFMf{5 z<;_R_%DvV7S;4fSu;w<7Urgss^-C39tt}ox0fk(?6wN9Dfbz&xPf@!}*=C6HtHIS+C|7^1T_w8rHFNIN;wMg zaYdI*+OEtb;gH+3830`}sR)IqwyJUvq3~3Zk~>Zdu?s1=qvT;2NIEUX29kLgR>G=? zOTm!SYOEpzLt$0KOaI_WE+NS{Ey^^KjFeoWl5K0M7(?NT)4EKfaHUc*W5mczk$zy* zQDNp7Eo+C5y6^%Z;1yA+(+f;HBCj!~rB3hCdY_N3cxl-@Bj6R$j7}$liyu_e#teUzC0EM7WI$QuK1w+D00pL>BN{Qyb{k{MYRObc@00000NkvXXu0mjfXBUvM literal 0 HcmV?d00001 diff --git a/src/static/组 286@2x.png b/src/static/组 286@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..7783e42476e68a2f94166f09a36cb33935b431c1 GIT binary patch literal 1312 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 literal 0 HcmV?d00001 diff --git a/src/static/组 468@2x.png b/src/static/组 468@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..947ac17e228569e421bffe371eebd5f0f9123425 GIT binary patch literal 2468 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 Q;{X5v07*qoM6N<$f&n|3<^TWy literal 0 HcmV?d00001 diff --git a/src/static/组 600@2x.png b/src/static/组 600@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..dccec49a16aa9c7d677209f632366fd78a2313a9 GIT binary patch literal 57173 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 literal 0 HcmV?d00001 diff --git a/src/static/组 9.png b/src/static/组 9.png new file mode 100644 index 0000000000000000000000000000000000000000..5d66af553615e506a74c6393dfdb250035bfb216 GIT binary patch literal 208 zcmeAS@N?(olHy`uVBq!ia0vp^oIuRV!3HF!2OrA@QjEnx?oJHr&dIz4a#)I;JVQ8u zpoSx*1IVxPba4#fn3;R}ATN`m2*bsnB1}_~IPaYi;>%H8qZ-keog}s+vPt&Fp<_-z z=6UoKp36US^+(Xdk9K#@|4E5Hvq+}5Y;WhL8)Bd3H*|dCX4u#AB2tYt`BPAdFn3Umd7r>mdKI;Vst E0OU?jVgLXD literal 0 HcmV?d00001 diff --git a/src/static/蓝牙-F.png b/src/static/蓝牙-F.png new file mode 100644 index 0000000000000000000000000000000000000000..2ac9b5cb6a0589fb369652ac1a5fd5558e984177 GIT binary patch literal 5637 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 Ef^PMW-~a#s literal 0 HcmV?d00001 diff --git a/src/styles/index.scss b/src/styles/index.scss new file mode 100644 index 0000000..b1b34bc --- /dev/null +++ b/src/styles/index.scss @@ -0,0 +1,13 @@ +@import './variables.scss'; + +page { + background-color: $color-bg-page; + color: $color-text; + font-size: 28rpx; + box-sizing: border-box; +} + +view, +text { + box-sizing: border-box; +} diff --git a/src/styles/variables.scss b/src/styles/variables.scss new file mode 100644 index 0000000..42cf8c0 --- /dev/null +++ b/src/styles/variables.scss @@ -0,0 +1,14 @@ +$color-primary: #1ec671; +$color-primary-dark: #18a85d; +$color-text: #1a1a1a; +$color-text-secondary: #666666; +$color-text-placeholder: #c0c0c0; +$color-border: #e8e8e8; +$color-bg-page: #f7f8fa; +$color-bg-input: #ffffff; +$color-link: #1ec671; + +$page-padding-x: 48rpx; +$radius-input: 48rpx; +$radius-button: 24rpx; +$shadow-button: 0 8rpx 24rpx rgba(30, 198, 113, 0.35); diff --git a/src/types/api.ts b/src/types/api.ts new file mode 100644 index 0000000..18ea324 --- /dev/null +++ b/src/types/api.ts @@ -0,0 +1,62 @@ +/** 发送验证码场景(与后端 SmsCodeSceneConstants 一致) */ +export const SMS_CODE_SCENE = { + register: 'register', + login: 'login', + reset: 'reset', + changePhoneOld: 'change_phone_old', + changePhoneNew: 'change_phone_new', +} as const + +export type SmsCodeScene = (typeof SMS_CODE_SCENE)[keyof typeof SMS_CODE_SCENE] + +/** 常见后端统一响应(如 RuoYi:code 200 为成功) */ +export interface ApiResponse { + code?: number + msg?: string + message?: string + data?: T +} + +export interface SendSmsCodeBody { + phonenumber: string + scene: SmsCodeScene +} + +export interface LoginSmsBody { + phonenumber: string + smsCode: string +} + +export interface LoginPasswordBody { + account: string + password: string +} + +export interface RegisterBody { + phonenumber: string + smsCode: string + password: string + confirmPassword: string +} + +export interface AuthTokenResult { + token?: string + accessToken?: string +} + +/** POST /app/auth/forgetPassword/verifyCode */ +export interface ForgetPasswordVerifyBody { + phonenumber: string + smsCode: string +} + +export interface ForgetPasswordVerifyResult { + resetToken: string +} + +/** POST /app/auth/forgetPassword/doReset */ +export interface ForgetPasswordResetBody { + resetToken: string + newPassword: string + confirmPassword: string +} diff --git a/src/utils/api-error.ts b/src/utils/api-error.ts new file mode 100644 index 0000000..fdffdfc --- /dev/null +++ b/src/utils/api-error.ts @@ -0,0 +1,34 @@ +import type { ApiResponse } from '@/types/api' + +export class ApiError extends Error { + code?: number + + constructor(message: string, code?: number) { + super(message) + this.name = 'ApiError' + this.code = code + } +} + +export function isApiSuccess(body: ApiResponse): boolean { + if (body.code === undefined) return true + return body.code === 200 || body.code === 0 +} + +export function unwrapApiData(body: ApiResponse & Record): T { + if (body.data !== undefined && body.data !== null) { + return body.data as T + } + return body as T +} + +export function getErrorMessage(err: unknown, fallback = '请求失败'): string { + if (err instanceof ApiError) return err.message + if (err instanceof Error && err.message) return err.message + if (err && typeof err === 'object' && 'data' in err) { + const data = (err as { data?: ApiResponse }).data + if (data?.msg) return data.msg + if (data?.message) return data.message + } + return fallback +} diff --git a/src/utils/static-assets.ts b/src/utils/static-assets.ts new file mode 100644 index 0000000..2d60f56 --- /dev/null +++ b/src/utils/static-assets.ts @@ -0,0 +1,66 @@ +/** 设计稿静态图标(src/static) */ +import logoCenter from '@/static/logo-center@2x.png' +import iconPhone from '@/static/icon-phone@2x.png' +import iconCode from '@/static/icon-code@2x.png' +import iconChecked from '@/static/icon-checked@2x.png' +import iconPassword from '@/static/路径.png' +import iconHomeSelector from '@/static/图标 4.png' +import iconHomeArrow from '@/static/组 9.png' +import iconNotify from '@/static/组 27.png' +import iconDeviceEmpty from '@/static/图标 8.png' +import iconDeviceNameEdit from '@/static/图标 9.png' +import iconBindComplete from '@/static/组 41@2x.png' +import iconBluetooth from '@/static/蓝牙-F.png' +import iconBindDiscover from '@/static/组 281@2x.png' +import iconBindPair from '@/static/组 286@2x.png' +import iconBindSecure from '@/static/组 291@2x.png' +import iconScanDevice from '@/static/组 600@2x.png' +import iconScanDeviceAction from '@/static/组 468@2x.png' +import iconScanDeviceActionDisabled from '@/static/组 476@2x.png' +import tabDevice from '@/static/tab-device-active.png' +import tabDeviceActive from '@/static/tab-device.png' +import tabMine from '@/static/tab-mine-active.png' +import tabMineActive from '@/static/tab-mine.png' + +export const staticAssets = { + /** 组 15106@2x.png — 登录区中心大图标 */ + logo: logoCenter, + /** 手机号@2x.png */ + iconPhone, + /** 验证码@2x.png */ + iconCode, + /** 选中@2x.png */ + iconChecked, + /** 路径.png — 密码框 */ + iconPassword, + /** 图标 4.png — 首页「我的家」 */ + iconHomeSelector, + /** 组 9.png — 「我的家」下拉箭头 */ + iconHomeArrow, + /** 组 27.png — 设备页右上角通知 */ + iconNotify, + /** 图标 8.png — 设备空状态中心图标 */ + iconDeviceEmpty, + /** 图标 9.png — 完成配置「设备名称」编辑 */ + iconDeviceNameEdit, + /** 组 41@2x.png — 完成配置成功示意图 */ + iconBindComplete, + /** 蓝牙-F.png — 添加设备页蓝牙示意图 */ + iconBluetooth, + /** 组 281@2x.png — 添加设备「发现附近设备」 */ + iconBindDiscover, + /** 组 286@2x.png — 添加设备「快速配对连接」 */ + iconBindPair, + /** 组 291@2x.png — 添加设备「本地安全控制」 */ + iconBindSecure, + /** 组 600@2x.png — 扫描设备页示意图 */ + iconScanDevice, + /** 组 468@2x.png — 发现设备列表「可添加」箭头 */ + iconScanDeviceAction, + /** 组 476@2x.png — 发现设备列表「不可添加」箭头 */ + iconScanDeviceActionDisabled, + tabDevice, + tabDeviceActive, + tabMine, + tabMineActive, +} as const diff --git a/src/utils/validate.ts b/src/utils/validate.ts new file mode 100644 index 0000000..3e82020 --- /dev/null +++ b/src/utils/validate.ts @@ -0,0 +1,11 @@ +export function isValidPhone(phone: string): boolean { + return /^1\d{10}$/.test(phone.trim()) +} + +export function isValidSmsCode(code: string): boolean { + return /^\d{4,6}$/.test(code.trim()) +} + +export function isValidPassword(password: string): boolean { + return password.length >= 6 && password.length <= 32 +} diff --git a/vite.config.ts b/vite.config.ts index 46e36fe..85cabba 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,7 +1,28 @@ -import { defineConfig } from "vite"; +import { defineConfig, loadEnv } from "vite"; import uni from "@dcloudio/vite-plugin-uni"; // https://vitejs.dev/config/ -export default defineConfig({ +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, process.cwd(), ""); + const proxyTarget = + env.VITE_API_PROXY_TARGET || "http://192.168.0.222:8082"; + + return { plugins: [uni()], + server: { + proxy: { + "/app": { + target: proxyTarget, + changeOrigin: true, + }, + }, + }, + css: { + preprocessorOptions: { + scss: { + additionalData: `@import "@/styles/variables.scss";`, + }, + }, + }, +}; });