# 管理后台架构 ## 概述 管理后台是一个基于 uni-app + Vue 3 的单页应用,编译目标为 H5(浏览器),部署在腾讯云 COS 上作为静态站点。通过调用服务端 Admin API 实现设备管理、用户管理、订阅管理等后台功能。 **技术栈:** uni-app + Vue 3 + Pinia + Vite **AppID:** `__UNI__HOXADMIN` **路由模式:** Hash(`#/pages/dashboard/index`) --- ## 目录结构 ``` admin-console/ ├── index.html # SPA 入口 HTML ├── package.json ├── vite.config.js # Vite 配置(仅引入 uni 插件) ├── scripts/ │ └── deploy-cos.js # COS 静态站点部署脚本 └── src/ ├── App.vue # 根组件(登录状态检查) ├── main.js # 应用入口(createSSRApp + Pinia) ├── manifest.json # uni-app 配置 ├── pages.json # 页面路由定义 ├── config/ │ └── env.js # 环境配置(API 地址) ├── components/ │ └── AdminLayout.vue # 侧边栏 + 顶栏布局组件 ├── store/ │ └── user.js # Pinia 用户状态(token、管理员信息) ├── utils/ │ ├── request.js # HTTP 请求封装(Bearer token 注入) │ ├── export.js # CSV 导出工具 │ └── format.js # 日期格式化 ├── styles/ │ └── common.css # 表格、徽标、按钮等公共样式 └── pages/ ├── login/index.vue # 登录页 ├── dashboard/index.vue # 仪表盘 ├── device/index.vue # 设备列表 ├── device-detail/index.vue # 设备详情 ├── user/index.vue # 用户列表 ├── user-detail/index.vue # 用户详情 ├── subscription/index.vue# 订阅管理 ├── record/index.vue # 护理记录 ├── log/index.vue # 操作日志 └── settings/index.vue # 系统设置 ``` 共 10 个页面,1 个全局组件,1 个 Store。 --- ## 应用启动流程 ``` index.html → main.js │ ├── createSSRApp(App) ├── app.use(createPinia()) └── App.vue onLaunch() │ ├── useUserStore().isLoggedIn() │ ├── 检查 Storage 中 admin_token 是否存在 │ └── 检查 admin_token_expiry 是否未过期(7天有效) │ ├── 已登录 → 停留在当前页 └── 未登录 → uni.reLaunch('/pages/login/index') ``` --- ## 页面一览 ### 登录页 | 项目 | 说明 | |------|------| | 路径 | `/pages/login/index` | | 导航栏 | 自定义(无系统导航栏) | | API | `POST /api/v1/admin/login` | | 功能 | 用户名+密码登录,成功后跳转到仪表盘 | 登录成功后: 1. `userStore.login()` 保存 token 和管理员信息 2. `uni.reLaunch('/pages/dashboard/index')` ### 仪表盘 | 项目 | 说明 | |------|------| | 路径 | `/pages/dashboard/index` | | API | `GET /api/v1/admin/dashboard`, `GET /api/v1/admin/records?page=1&page_size=5` | | 功能 | 4 个统计卡片 + 最近护理表格 + 快捷导航 | 4 个统计指标: | 指标 | 字段 | 图标 | |------|------|------| | 绑定设备数 | device_count | 📱 | | 注册用户数 | user_count | 👥 | | 护理次数 | treatment_count | 💆 | | 活跃订阅 | subscription_count | 💳 | ### 设备管理 | 项目 | 说明 | |------|------| | 路径 | `/pages/device/index` | | API | `GET /api/v1/admin/devices`, `POST /api/v1/admin/devices` | | 功能 | 设备列表(分页、搜索)、预生成产品码、CSV 导出 | 状态显示:未激活、在线、离线、禁用 ### 设备详情 | 项目 | 说明 | |------|------| | 路径 | `/pages/device-detail/index` | | API | `GET /api/v1/admin/devices/:id`, `POST .../unbind`, `POST .../command` | | 功能 | 设备信息网格、绑定历史、护理记录、解绑、远程指令 | ### 用户管理 | 项目 | 说明 | |------|------| | 路径 | `/pages/user/index` | | API | `GET /api/v1/admin/users` | | 功能 | 用户列表(分页、搜索)、订阅状态徽标、CSV 导出 | ### 用户详情 | 项目 | 说明 | |------|------| | 路径 | `/pages/user-detail/index` | | API | `GET /api/v1/admin/users/:user_id` | | 功能 | 用户信息、统计行(护理次数/时长/消费)、最近护理记录 | ### 订阅管理 | 项目 | 说明 | |------|------| | 路径 | `/pages/subscription/index` | | API | `GET /api/v1/admin/subscriptions`, `POST /api/v1/admin/subscriptions` | | 功能 | 统计行、Tab 过滤(全部/月度/年度/试用/过期)、创建订阅弹窗、CSV 导出 | 创建订阅表单字段:user_id, plan, amount, days ### 护理记录 | 项目 | 说明 | |------|------| | 路径 | `/pages/record/index` | | API | `GET /api/v1/admin/records` | | 功能 | 记录列表(分页、日期范围筛选、关键字搜索)、区域位掩码解码显示、CSV 导出 | 区域解码映射:1=左脸 2=右脸 4=额头 8=下巴 16=鼻 32=左眼 64=右眼 ### 操作日志 | 项目 | 说明 | |------|------| | 路径 | `/pages/log/index` | | API | `GET /api/v1/admin/logs` | | 功能 | 时间线样式日志列表、类型过滤、角色徽标(管理员/系统/用户)、CSV 导出 | ### 系统设置 | 项目 | 说明 | |------|------| | 路径 | `/pages/settings/index` | | API | `GET /api/v1/admin/settings`, `POST /api/v1/admin/settings` | | 功能 | 三组配置 + 保存/重置按钮 | 配置项分组: | 分组 | 字段 | |------|------| | 基本配置 | system_name, admin_email, timezone | | 订阅定价 | monthly_price, yearly_price, trial_days | | 功能开关 | enable_register, enable_binding, enable_free_mode, maintenance_mode | --- ## 全局布局组件 — AdminLayout 每个页面(除登录页)都包裹在 `` 组件中: ``` ┌─────────────────────────────────────────────┐ │ Sidebar (220px) │ Header │ │ │ 页面标题 管理员名 退出 │ │ 🌸 光子美容仪 ├──────────────────────────┤ │ 管理后台 │ │ │ │ Content () │ │ ▸ 仪表盘 │ │ │ ▸ 设备管理 │ │ │ ▸ 用户管理 │ │ │ ▸ 订阅管理 │ │ │ ▸ 护理记录 │ │ │ ▸ 操作日志 │ │ │ ▸ 系统设置 │ │ │ │ │ └─────────────────────────────────────────────┘ ``` - **Props**:`currentPage` — 当前页面路径,用于高亮侧边栏菜单项 - **侧边栏导航**:使用 `uni.redirectTo`(替换页面,不累积页面栈) - **退出登录**:`store.clearToken()` → `uni.reLaunch('/pages/login/index')` - **管理员名称**:从 `userStore.adminInfo.real_name` 读取 --- ## 状态管理 — Pinia Store ### user.js ```js // State token: ref('') // 从 Storage 初始化 adminInfo: ref(null) // { admin_id, username, real_name, role } // Actions setToken(val) // 保存 token + 计算过期时间(7天后) clearToken() // 清除 token + adminInfo + Storage login({ username, password }) // POST /admin/login → setToken + adminInfo isLoggedIn() // 检查 token 非空 && 未过期 ``` Token 存储位置:`uni.setStorageSync('admin_token')` 过期时间存储:`uni.setStorageSync('admin_token_expiry')` 有效期:7 天(客户端计算) --- ## HTTP 请求层(request.js) ### 请求流程 ``` 页面调用 get/post(url, data) │ ▼ request(options) │ ├── 从 Storage 读取 admin_token ├── 注入 Authorization: Bearer ├── 拼接 BASE_URL + url │ ▼ uni.request() │ ├── code === 0 → resolve(res.data.data) ├── code === 1001/1002 → 清除 token → reLaunch 到 login → reject └── 其他 code → reject(res.data) ``` ### 注意事项 - Token 直接从 `uni.getStorageSync('admin_token')` 读取,不通过 Pinia store - 没有 token 刷新机制(不同于小程序端),token 过期后走 1001/1002 重定向登录 - PUT 方法已定义但目前没有页面使用 --- ## CSV 导出(export.js) 所有列表页都有"导出"按钮,调用 `exportCSV(filename, headers, rows)` : 1. 构建 CSV 字符串(带 BOM `` 确保 Excel 正确识别编码) 2. 创建 Blob → 临时 `` 元素 → 触发下载 3. 仅 H5 端可用(使用 `document.createElement`) --- ## 样式体系 ### 品牌色 | 色值 | 用途 | |------|------| | #E6508C | 主色(按钮、活跃状态、链接) | | #001529 | 侧边栏背景(深蓝) | | #f0f2f5 | 页面背景 | | #52c41a | 成功状态徽标 | | #faad14 | 警告状态徽标 | | #ff4d4f | 错误状态徽标 | ### 公共样式(common.css) 被 device / user / subscription / record / log / device-detail / user-detail 7 个页面引用,包含: - **Flex 表格**:`.data-table`, `.t-header`, `.t-row`, `.t-th`, `.t-td`, `.flex1/.flex2/.flex3` - **徽标**:`.badge`, `.badge-success/.warning/.error/.default/.blue` - **按钮**:`.btn-primary`(粉色), `.btn-default`(白色边框), `.btn-sm` - **分页**:`.pagination`, `.btn-page`, `.page-info` - **工具栏**:`.toolbar`, `.header-actions`, `.search-input` - **卡片**:`.page-card` - **操作链接**:`.action-link`(粉色文字) Dashboard 和 Login 各自独立定义样式,不引用 common.css。 --- ## 路由与导航 ### pages.json 页面注册顺序 1. login(入口页) 2. dashboard 3. device → device-detail 4. user → user-detail 5. subscription 6. record 7. log 8. settings 没有 tabBar。登录页使用自定义导航栏,其他页面使用系统导航栏(白底黑字)。 ### 导航方式 | 方法 | 使用场景 | |------|---------| | `uni.reLaunch` | 登录跳转、退出登录(清空页面栈) | | `uni.redirectTo` | 侧边栏菜单切换(替换当前页) | | `uni.navigateTo` | 进入详情页(压栈,可返回) | | `uni.navigateBack` | 详情页返回列表 | --- ## 鉴权流程 ``` 1. 启动 App.vue onLaunch() └── isLoggedIn() → 检查 admin_token 存在 + admin_token_expiry 未过期 ├── 否 → uni.reLaunch('/pages/login/index') └── 是 → 正常显示 2. 登录 POST /admin/login └── 成功 → setToken(token) ├── Storage: admin_token = token ├── Storage: admin_token_expiry = now + 7天 └── 内存: adminInfo = { admin_id, username, real_name, role } 3. 每次 API 请求 └── request.js 从 Storage 读取 admin_token → Bearer header ├── 服务端返回 1001/1002 → 清 token → 跳登录 └── 正常响应 → 继续 4. 退出 └── AdminLayout 退出按钮 → clearToken() → reLaunch 登录 ``` --- ## 部署 ### 构建 ```bash npm run build:h5 # uni build -p h5,输出到 dist/build/h5/ ``` ### 部署到 COS ```bash npm run deploy:cos # 构建 + 上传到 COS ``` `scripts/deploy-cos.js`: - 从 `server/.env` 读取 COS 凭证 - 上传 `dist/build/h5/` 下所有文件到 COS - 文件路径前缀:`admin/`(可通过 `ADMIN_COS_PREFIX` 环境变量配置) - 自动设置正确的 `Content-Type` ### 环境配置 | 环境 | API 地址 | |------|---------| | local | `http://localhost:3000` | | test | `https://1426323813-ilxkhlxf4p.ap-guangzhou.tencentscf.com` | | prod | 待配置 | 当前设置:`ENV = 'test'`