文件
jw-beauty/docs/architecture/03-管理后台架构.md
T
Guoguo b754926937 fix: clear stale token expiry on auth failure, correct doc inaccuracies
- request.js: also remove admin_token_expiry when clearing auth on
  1001/1002 response, preventing stale expiry value in storage
- 01-服务端架构.md: fix table count from 10 to 11
- 03-管理后台架构.md: add /api/v1 prefix to all API endpoint paths
2026-04-28 18:49:00 -07:00

12 KiB
原始文件 Blame 文件历史

管理后台架构

概述

管理后台是一个基于 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

每个页面(除登录页)都包裹在 <AdminLayout> 组件中:

┌─────────────────────────────────────────────┐
│  Sidebar (220px)  │  Header                  │
│                   │  页面标题    管理员名  退出 │
│  🌸 光子美容仪     ├──────────────────────────┤
│     管理后台       │                          │
│                   │  Content (<slot>)         │
│  ▸ 仪表盘         │                          │
│  ▸ 设备管理       │                          │
│  ▸ 用户管理       │                          │
│  ▸ 订阅管理       │                          │
│  ▸ 护理记录       │                          │
│  ▸ 操作日志       │                          │
│  ▸ 系统设置       │                          │
│                   │                          │
└─────────────────────────────────────────────┘
  • PropscurrentPage — 当前页面路径,用于高亮侧边栏菜单项
  • 侧边栏导航:使用 uni.redirectTo(替换页面,不累积页面栈)
  • 退出登录store.clearToken()uni.reLaunch('/pages/login/index')
  • 管理员名称:从 userStore.adminInfo.real_name 读取

状态管理 — Pinia Store

user.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 <token>
     ├── 拼接 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 → 临时 <a> 元素 → 触发下载
  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 登录

部署

构建

npm run build:h5          # uni build -p h5,输出到 dist/build/h5/

部署到 COS

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'