文件
Guoguo c5f6033ccf fix: improve UX and fix admin console display issues
Miniprogram:
- Add back buttons to all custom-nav pages
- Add debug/mock buttons (BLE connect, wear check, treatment)
  controlled by __DEV__ flag (hidden in prod)

Admin console:
- Fix log page using nonexistent fields (operator_type, target_type)
  now correctly reads admin_id/user_id/action/detail from API
- Fix subscription date fields (started_at→start_time, expired_at→expire_time)
- Wire up subscription detail/extend/renew action buttons
- Wire up device detail "查看完整日志" button
- Add "创建订阅" button to subscription toolbar
- Fix subscription status mapping (2=expired, 3=cancelled)

Docs:
- Add detailed architecture docs for server, miniprogram, admin console
2026-04-28 18:32:26 -07:00

15 KiB

小程序架构

概述

微信原生小程序(非 uni-app),使用 WXML + WXSS + JS 开发。通过 BLE 5.0 与光子美容仪设备通信,通过 HTTP 与腾讯云 SCF 后端通信。

AppID wxc4045074ef298510
项目名: hox-beauty
基础库: 3.15.2


目录结构

miniprogram/
├── app.js / app.json / app.wxss    # 应用入口
├── config/
│   └── env.js                       # 环境配置(API 地址)
├── pages/
│   ├── index/          # 首页(Tab)—— 设备状态、开始护理
│   ├── history/        # 记录(Tab)—— 护理历史列表
│   ├── profile/        # 我的(Tab)—— 个人中心
│   ├── login/          # 微信登录
│   ├── scan/           # 扫码绑定设备
│   ├── ble-connect/    # 蓝牙连接 + BLE 绑定
│   ├── bind-success/   # 绑定成功
│   ├── wear-check/     # 佩戴检测
│   ├── treatment-setup/# 护理模式选择
│   ├── auto-scan/      # 智能模式面部扫描
│   ├── treating/       # 护理进行中
│   ├── treatment-done/ # 护理完成
│   ├── subscribe-prompt/   # 订阅引导
│   ├── subscribe-plans/    # 订阅套餐选择
│   └── subscribe-success/  # 订阅成功
├── services/
│   ├── ble.js              # BLE 通信协议核心
│   └── command-sync.js     # 远程指令拉取与执行
├── utils/
│   ├── request.js          # HTTP 请求封装 + token 刷新
│   └── mock.js             # 开发用 mock(已禁用)
├── scripts/
│   └── test-ble-frame.js   # BLE 帧构建/解析单元测试
├── project.config.json
└── sitemap.json

共 15 个页面,无自定义组件,全部使用微信原生组件。


应用生命周期

app.js

globalData

字段 用途
userInfo 用户信息对象(来自 /api/v1/user/profile
userId 用户 ID 字符串
connectedDevice / currentDevice 当前绑定的设备信息
currentTreatment 最近一次护理结果(跨页传递)
statusBarHeight 系统状态栏高度(默认 44

onLaunch 流程:

  1. wx.getSystemInfoSync() → 读取 statusBarHeight
  2. checkLogin() → 从 Storage 读取 token
    • 有 token → 调用 loadProfile() 加载用户信息
    • 无 token → 设置 userInfo = null(各页面自行跳转登录)

页面导航流程

主要业务路径

                    ┌─────────┐
                    │  login  │ ← 无 token 时跳转
                    └────┬────┘
                         │ wx.switchTab
                         ▼
              ┌──────────────────────┐
              │   index(首页 Tab    │
              │  设备状态 / 订阅状态   │
              └──────┬───────────────┘
                     │
          ┌──────────┼──────────┐
          │ 无设备    │ 有设备    │ 有设备但无订阅
          ▼          ▼          ▼
       ┌──────┐  ┌────────┐  ┌─────────────────┐
       │ scan │  │ wear-  │  │subscribe-prompt │
       │ 扫码 │  │ check  │  │   订阅引导       │
       └──┬───┘  └───┬────┘  └───────┬─────────┘
          │          │               │
          ▼          ▼               ▼
    ┌───────────┐ ┌──────────────┐ ┌─────────────────┐
    │ble-connect│ │treatment-    │ │subscribe-plans  │
    │ 蓝牙配对   │ │setup 模式选择│ │   套餐选择       │
    └─────┬─────┘ └──────┬───────┘ └─────────────────┘
          │              │
          ▼         ┌────┴────┐
    ┌───────────┐   │         │
    │bind-      │ 普通模式  智能模式
    │success    │   │         │
    └───────────┘   │    ┌────┴─────┐
                    │    │auto-scan │
                    │    │ 面部扫描  │
                    │    └────┬─────┘
                    │         │
                    ▼         ▼
              ┌──────────────────┐
              │    treating      │
              │   护理进行中      │
              └────────┬─────────┘
                       │
                       ▼
              ┌──────────────────┐
              │  treatment-done  │
              │    护理完成       │
              └──────────────────┘

Tab 页

Tab 页面 说明
首页 index 设备连接状态、开始护理入口
记录 history 护理历史列表(分页、下拉刷新)
我的 profile 个人信息、订阅状态、设置

导航方式

  • Tab 间切换:wx.switchTab
  • 业务流程前进:wx.navigateTo(压栈)或 wx.redirectTo(替换)
  • 强制跳转:wx.reLaunch(清空页面栈,用于登录跳转)

页面详情

login — 微信登录

  • 调用 wx.getUserProfile() 获取头像昵称(2022.10 后始终返回「微信用户」和灰色头像)
  • 调用 app.doLogin()wx.login() 获取 code → POST /api/v1/auth/login
  • 登录后保存 tokentoken_expiry 到 Storage
  • 成功后 wx.switchTab 到首页

index — 首页

  • GET /api/v1/device/list 获取绑定设备列表
  • GET /api/v1/subscription 获取订阅状态
  • BLE:订阅 ble.on('status') 更新电量和设备状态
  • 连接设备后自动调用 ble.queryStatus()
  • 解绑操作:ble.disconnect() + POST /api/v1/device/unbind

scan — 扫码绑定

  • wx.scanCode({ onlyFromCamera: true, scanType: ['qrCode'] }) 扫描设备二维码
  • 支持手动输入 16 位 hex 设备号
  • POST /api/v1/device/bind → 获取 bind_token
  • 跳转到 ble-connect 页面

ble-connect — 蓝牙连接

  • 状态机:scanning → connecting → binding → done / error
  • BLE 扫描过滤名称含 HOXLIGHTMASK 的设备
  • 连接后发送 BIND 命令,等待 bind_result 通知
  • 成功后 POST /api/v1/device/bind/confirm

wear-check — 佩戴检测

  • 通过 ble.queryStatus() 检查设备 bind_status === 1
  • 确认佩戴后进入模式选择

treatment-setup — 模式选择

  • 普通模式(mode=0):全脸、10 分钟固定
  • 智能模式(mode=1):需要有效订阅,可选区域
  • 区域位掩码:左脸(0x01) 右脸(0x02) 额头(0x04) 下巴(0x08) 鼻部(0x10)
  • 普通模式直接发 setParams + startTreatment 开始护理
  • 智能模式先跳到 auto-scan 扫描

treating — 护理中

  • 订阅 6 个 BLE 事件:status, treatment_complete, exception, disconnected, reconnected, reconnect_failed
  • 本地 1 秒定时器作为倒计时后备
  • 接收设备 status 报告校正剩余时间
  • 每次 status 更新触发 commandSync.sync(deviceId) 拉取远程指令
  • 断连时暂停计时器,重连后恢复
  • 护理结束触发条件:设备完成通知 / 本地定时器归零 / 手动停止 / 重连失败

treatment-done — 护理完成

  • POST /api/v1/treatment/sync 同步记录到服务器
  • 展示护理时长、区域、模式等摘要

history — 护理记录

  • GET /api/v1/treatment/history?page=N&page_size=20 分页加载
  • 支持下拉刷新和触底加载
  • 累计统计:总次数、总时长、本月次数

profile — 我的

  • GET /api/v1/user/profile + GET /api/v1/subscription
  • 菜单入口:设备管理、护理记录、订阅管理、使用帮助、联系我们
  • 退出登录:清除 token → wx.reLaunch 到 login

BLE 通信协议

GATT 服务和特征值

服务 UUID 名称 特征值
FFE0 设备信息服务 FFE3 (设备信息 - 读)
FFE1 数据通信服务 FFE4 (命令 - 写), FFE5 (状态 - 通知), FFE6 (绑定信息)
FFE2 OTA 服务 FFE7 (OTA控制), FFE8 (OTA数据), FFE9 (OTA状态)

帧格式

┌──────┬──────┬────────┬──────┬─────────────────┬──────────┐
│ 0xAA │ 0x55 │ Length │ Type │ Payload         │ Checksum │
│ 1B   │ 1B   │ 1B     │ 1B   │ Length bytes    │ 1B (XOR) │
└──────┴──────┴────────┴──────┴─────────────────┴──────────┘
  • Header:固定 0xAA 0x55
  • LengthPayload 字节数
  • Type:命令/通知类型
  • Checksum:前面所有字节的 XOR 值

命令类型(小程序 → 设备)

类型 Payload
SET_PARAMS 0x01 region_mask, wavelength, brightness, duration_ms(4B大端), mode, seq
START 0x02 region_mask, seq
STOP 0x03 seq
QUERY_STATUS 0x04 seq
BIND 0x05 0x01, userId(8B), bindToken(8B), timestamp(4B大端), seq
UNBIND 0x06 0x02, userId(8B), seq

每条命令的 payload 末尾都有 seq 字节(0-255 循环),用于匹配 ACK 响应。

通知类型(设备 → 小程序)

类型 Payload
STATUS_REPORT 0x21 mode_state, region_mask, wavelength, brightness, remaining_ms(4B), error_code, command_seq, battery, temperature, bind_status, subscription — 共 14 字节
ACK 0x22 seq, error_code
TREATMENT_COMPLETE 0x31 session_id(8B), regions, total_duration_ms(4B), avg_pd
EXCEPTION 0x32 error_code, temperature
BIND_SUCCESS 0x33 result (0x00=成功)

命令重试机制

writeCommandWithRetry(type, payload, maxRetries=3)

  • 调用 writeCommand 发送命令
  • 失败时等待 500ms 后重试
  • 最多重试 3 次
  • 如果错误码为 0x0CBLE 断连),立即失败不重试
  • ACK 超时时间:5 秒

自动重连机制

  • 检测到 BLE 断连时自动触发
  • 等待 2 秒后第一次重连尝试
  • 最多 3 次,间隔 2 秒
  • 重连成功后重新发现服务、重新订阅特征值通知
  • 手动调用 disconnect() 时禁用自动重连

设备状态机

状态 说明
0x00 IDLE 空闲
0x01 SCANNING 面部扫描中
0x02 ACTIVE 护理中
0x03 PAUSED 已暂停
0x04 COMPLETED 已完成
0x05 ERROR 异常
0x06 OTA 固件升级中

区域位掩码

区域
0 0x01 左脸颊
1 0x02 右脸颊
2 0x04 额头
3 0x08 下巴
4 0x10 鼻部
5 0x20 左眼周
6 0x40 右眼周
全部 0x7F 全脸

设备错误码

名称 说明
0x00 SUCCESS 成功
0x01 ERR_REGION_INVALID 区域无效
0x02 ERR_REGION_EMPTY 区域为空
0x03 ERR_BRIGHTNESS_INVALID 亮度无效
0x04 ERR_DURATION_INVALID 时长无效
0x05 ERR_NOT_BOUND 未绑定
0x06 ERR_NO_SUBSCRIPTION 无订阅
0x07 ERR_TEMP_HIGH 温度过高
0x08 ERR_BATTERY_LOW 电量不足
0x09 ERR_ALREADY_RUNNING 已在运行
0x0A ERR_NOT_RUNNING 未在运行
0x0B ERR_OTA_FAILED OTA 失败
0x0C ERR_BLE_DISCONNECTED 蓝牙断连

远程指令同步(command-sync.js

服务端可以通过管理后台向设备下发远程指令,小程序在护理过程中定期拉取并执行:

commandSync.sync(deviceId)
     │
     ├── GET /api/v1/device/command/pending?device_id=xxx
     │   └── 返回待执行命令列表(status=1 → 标记为 status=2
     │
     ├── 逐条执行 BLE 命令(按 opcode 分发到对应函数)
     │
     └── POST /api/v1/device/command/result
         └── 上报执行结果(成功/失败)

HTTP 请求层(request.js

Token 自动刷新

  • 每次请求前检查 token_expiry:如果距离过期不到 24 小时,先调用 POST /api/v1/auth/refresh 刷新
  • 并发请求的刷新去重:使用 _refreshing 标志和 _refreshQueue 队列,保证同一时间只有一个刷新请求
  • 刷新成功后更新 Storage 中的 tokentoken_expiry

鉴权失败处理

  • 响应 code 为 1001 或 1002 时:清除 token → wx.reLaunch 到 login 页

请求头

Authorization: Bearer <token>
Content-Type: application/json
X-App-Version: 1.0.0
X-Platform: wechat

环境配置

环境 API 地址
local http://localhost:3000
test https://1426323813-ilxkhlxf4p.ap-guangzhou.tencentscf.com
prod 待配置(发布阻断项)

当前设置:ENV = 'test'


UI 模式

自定义导航栏

所有非 Tab 页使用 "navigationStyle": "custom",通过 statusBarHeight + 24px 的 padding-top 避开系统状态栏:

<view class="page-header" style="padding-top: {{statusBarHeight + 24}}px;">

品牌色

用途 色值
主色 #E6508C(粉色)
辅色 #DCB982(金色)
成功 #52c41a(绿色)
信息 #006699(蓝色)
错误 #ff4d4f(红色)

全局 CSS 类

定义在 app.wxss,包括:.page-header(带颜色变体)、.btn-primary(带颜色变体)、.btn-secondary.status-badge.device-card.tip-card、工具类(.mt-20, .mb-30, .text-muted 等)。


数据流汇总

登录 → 绑定 → 护理 → 同步

1. 登录
   wx.login() → code → POST /auth/login → token + user_info → Storage

2. 绑定设备
   扫码/手动输入 → device_id → POST /device/bind → bind_token
   → BLE 扫描 + 连接 → BIND 命令(0x05) → 等待 BIND_SUCCESS(0x33)
   → POST /device/bind/confirm → 自动创建 7 天试用订阅

3. 护理
   佩戴检测 → 模式选择 → SET_PARAMS(0x01) → START(0x02)
   → 护理中(实时 STATUS 更新 + 指令同步)
   → 完成(TREATMENT_COMPLETE 通知或本地定时器归零)

4. 记录同步
   POST /treatment/sync { session_id, device_id, regions, duration, mode, ... }