Files
hms/wiki/miniprogram.md
iven 28bcdc4208
Some checks failed
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
docs: 更新 wiki — Design Token 全面接入记录
- index.md: 更新关键数字(716 提交)、新增 Design Token 指标行
- miniprogram.md: 新增 §1.1 Design Token 系统完整文档(10 级字号表、
  4 结构 token、使用规则、关怀模式说明)、更新变更记录
2026-05-09 23:58:09 +08:00

706 lines
33 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: 微信小程序(患者端)
updated: 2026-05-08
status: active
tags: [miniprogram, taro, wechat, patient]
---
# 微信小程序(患者端)
> 从 [[index]] 导航。关联: [[infrastructure]] [[erp-server]] [[erp-health]]
## 1. 设计决策
- **Taro 4.2 + React 18** — 跨平台小程序框架React 生态
- **Zustand 状态管理** — 与 Web 前端一致的 store 模式
- **微信登录流程** — `Taro.login()` → code → 后端 `jscode2session` → openid → JWT
- **build 时注入环境变量** — `process.env` 在小程序运行时不存在,必须通过 `defineConstants` 编译时替换
- **不使用浏览器 Web API** — `btoa`/`atob` 等在小程序中不可用,用 Taro 原生 API 替代
- **Zod 输入验证** — 健康数据录入使用 Zod schema 验证
### 版本
Taro 4.2 / React 18 / TypeScript / Zustand 5 / Sass / Zod / ECharts 6按需引入
> **依赖统一2026-04-27** 移除 `echarts-taro3-react`(内嵌 Taro 3.0.8 + React 16导致 webpack 模块 ID 不一致 → `Cannot read property 'call' of undefined`)。改为自定义 `EcCanvas` 组件 + `echarts/core` 按需引入。`taro.js` chunk 从 152KB 降至 133KB。
### 1.1 设计系统:温润东方风
> 自 2026-04-27 起采用。所有新增/修改页面必须遵循此设计系统,保持视觉一致性。
>
> 设计系统源文件:`apps/miniprogram/src/styles/variables.scss` + `mixins.scss` + `tokens.scss`
**设计哲学**Kenya Hara 式东方极简。温润米底 + 单一赤土橙贯穿全场,留白呼吸感优先。
#### 色彩
| 角色 | 变量名 | 色值 | 用途 |
|------|--------|------|------|
| 强调色 | `$pri` | `#C4623A` | 按钮、链接、活跃 tab、品牌色 |
| 强调浅 | `$pri-l` | `#F0DDD4` | 图标底色、高亮背景 |
| 强调深 | `$pri-d` | `#8B3E1F` | 渐变终点、暗色变体 |
| 背景 | `$bg` | `#F5F0EB` | 页面底色warm cream |
| 卡片 | `$card` | `#FFFFFF` | 白色卡片 |
| 辅助底 | `$surface-alt` | `#EDE8E2` | 次级表面、分隔 |
| 主文字 | `$tx` | `#2D2A26` | warm black标题和正文 |
| 次文字 | `$tx2` | `#7A756E` | 辅助说明、标签 |
| 淡文字 | `$tx3` | `#A8A29E` | 占位、时间戳、箭头 |
| 边框 | `$bd` / `$bd-l` | `#E8E2DC` / `#F0EBE5` | 分隔线、卡片边框 |
| 成功 | `$acc` / `$acc-l` | `#5B7A5E` / `#E8F0E8` | sage green 正常状态 |
| 警告 | `$wrn` / `$wrn-l` | `#C4873A` / `#FFF3E0` | warm amber 偏高/待确认 |
| 危险 | `$dan` / `$dan-l` | `#B54A4A` / `#FDEAEA` | muted red 异常/删除 |
#### 字体
| 用途 | 字体栈 | 说明 |
|------|--------|------|
| 标题 / 数据数字 | `Georgia, Times New Roman, serif` | 衬线体,传递专业与温度。用 `@include serif-number` |
| 正文 / 辅助 | `-apple-system, PingFang SC, sans-serif` | 系统无衬线,清晰可读 |
| 章节标题 | `@include section-title` | 30rpx / bold / serif统一样式 |
#### 圆角 / 阴影 / 间距
| 元素 | 圆角 | 阴影 |
|------|------|------|
| 卡片 | `$r` 12px | `$shadow-md` (0 2px 12px rgba(45,42,38,0.08)) |
| 小元素(标签、输入框) | `$r-sm` 8px | `$shadow-sm` (0 1px 4px rgba(45,42,38,0.04)) |
| 浮层、弹窗 | `$r-lg` 16px | `$shadow-lg` (0 8px 32px rgba(45,42,38,0.12)) |
| 水平间距 | 页面两侧 24rpx卡片内 24-28rpx | |
| 垂直间距 | 区块间 24rpx卡片内标题与内容 20rpx | |
#### 组件规范
**按钮**:主按钮 `$pri` 实色 + 白字 + 圆角 12px + 阴影。次按钮 `$pri` 边框透明底。禁用 `$surface-alt` 底 + `$tx3` 字。
**状态标签**:用 `@include tag(背景色, 文字色)` 生成。不用粗边框、不用 emoji 状态图标。颜色映射:正常 → `$acc`/`$acc-l`,偏高 → `$wrn`/`$wrn-l`,异常 → `$dan`/`$dan-l`,默认 → `$tx2`/`$bd-l`
**健康数据卡片**2×2 网格,衬线大数字居中,小标签 + 状态标签在底部。数据用 `@include serif-number` 等宽数字。
**列表项**:白色卡片容器,每行 14-24rpx padding`$bd-l` 底部分隔。右侧箭头用 `` 字符。无左侧彩色 border accent。
**图标**:禁止用 emoji 作图标。用线性 SVG 或首字衬线体作为图标占位。图标底色用 `$pri-l`
**禁止事项**:紫色渐变 / emoji 作图标 / 圆角卡片+左彩色 border / 无意义渐变背景 / 装饰性 icon 遍地配。
#### Design Token 系统
> 自 2026-05-09 起全面接入。68 个 SCSS 文件59 页面 + 9 组件)全部使用 `var(--tk-*)` 引用字号和结构值。
>
> 源文件:`apps/miniprogram/src/styles/tokens.scss`
**架构原理**:通过 CSS 自定义属性Custom Properties实现单一真相源。`page {}` 定义正常值,`.elder-mode {}` 覆写关怀值,所有页面通过 `var(--tk-*)` 引用,关怀模式自动级联生效,无需逐页维护覆写。
**Taro 兼容性**Taro 编译器正确处理 CSS 自定义属性中的 px→rpx 转换(如 `--tk-font-h1: 26px``--tk-font-h1: 52rpx`)。
##### 字号 Token10 级)
| Token | 正常值 | 关怀值 | 倍率 | 语义 | 覆盖场景 |
|-------|--------|--------|------|------|----------|
| `--tk-font-hero` | 48px | 56px | ×1.17 | 装饰图标、空状态字符 | 大型装饰元素 |
| `--tk-font-h1` | 26px | 30px | ×1.15 | 页面/区块标题 | 99 处 |
| `--tk-font-h2` | 24px | 28px | ×1.17 | 副标题、日期 | 86 处 |
| `--tk-font-body-lg` | 28px | 34px | ×1.21 | 大正文、按钮 | 110 处 |
| `--tk-font-body` | 22px | 30px | ×1.36 | 正文、标签 | 92 处 |
| `--tk-font-body-sm` | 16px | 22px | ×1.38 | 中等正文、列表项 | 26 处 |
| `--tk-font-num` | 30px | 34px | ×1.13 | 数值显示 | 56 处 |
| `--tk-font-num-lg` | 34px | 40px | ×1.18 | 大数值、统计 | 21 处 |
| `--tk-font-cap` | 13px | 18px | ×1.38 | 说明文字、时间戳 | 54 处 |
| `--tk-font-micro` | 11px | 17px | ×1.55 | 角标、标签 | 22 处 |
##### 结构 Token
| Token | 正常值 | 关怀值 | 语义 |
|-------|--------|--------|------|
| `--tk-line-height` | 1.5 | 1.7 | 行高 |
| `--tk-touch-min` | 48px | 56px | 最小触控区域 |
| `--tk-btn-primary-h` | 56px | 64px | 主按钮高度 |
| `--tk-text-secondary` | #78716C | #5A554F | 辅助文字颜色(关怀模式提升对比度) |
##### 使用规则
**新增页面必须使用 token**,禁止硬编码 `font-size: Npx`
```scss
// 正确
.title { font-size: var(--tk-font-h1); }
.desc { font-size: var(--tk-font-cap); color: var(--tk-text-secondary); }
.number { font-size: var(--tk-font-num); @include serif-number; }
// 错误
.title { font-size: 26px; } // 禁止
```
**特殊值例外**(仅允许以下场景硬编码):
- `72px` — 积分余额大数字(装饰性,不走 token
- `80px` — 错误状态图标(组件内部装饰)
- `21px` — 长辈模式预览页(特殊 UI
##### 关怀模式Elder Mode
通过 Zustand store (`stores/ui.ts`) 的 `DisplayMode` 控制。`useElderClass()` hook 在根 View 添加 `.elder-mode` class触发所有 token 的关怀值级联。
`elder-mode.scss`~120 行仅保留结构布局覆写grid 列数、padding、gap所有字号/颜色覆写已通过 token 级联自动处理。
## 2. 关键文件 + 数据流
### 核心文件
| 文件 | 职责 |
|------|------|
| `apps/miniprogram/config/index.ts` | Taro 构建配置defineConstants 注入环境变量) |
| `apps/miniprogram/src/services/request.ts` | HTTP 请求封装401 自动刷新、错误处理) |
| `apps/miniprogram/src/services/auth.ts` | 微信登录/绑定手机号 API |
| `apps/miniprogram/src/stores/auth.ts` | 认证状态login/bindPhone/restore |
| `apps/miniprogram/src/utils/secure-storage.ts` | token 安全存储XOR + Base64 混淆) |
| `apps/miniprogram/project.config.json` | 微信开发者工具配置AppID、urlCheck |
### 微信登录流程
```
用户点击"微信一键登录"
Taro.login() → 临时 code
POST /auth/wechat/login { code }
后端调用微信 jscode2session → 获取 openid + session_key
查询 wechat_users 表
├── 已绑定 → 返回 { bound: true, token: JWT } → 跳转首页
└── 未绑定 → 返回 { bound: false, openid } → 显示"授权手机号"按钮
↓ 用户授权手机号
POST /auth/wechat/bind-phone { openid, encrypted_data, iv }
后端解密手机号 → 创建/关联用户 → 返回 JWT → 跳转首页
```
### 页面结构59 个页面6 个主包 + 10 个子包)
#### TabBar 页面3 个)
| 页面路径 | 说明 |
|----------|------|
| `pages/index/index` | 首页(今日健康、快捷服务) |
| `pages/health/index` | 健康上报Tab 页) |
| `pages/messages/index` | 消息中心Tab 页) |
#### 患者端主页面
| 页面路径 | 说明 |
|----------|------|
| `pages/login/index` | 登录页(微信登录 + 协议勾选) |
| `pages/consultation/index` | 咨询列表 |
| `pages/consultation/detail/index` | 咨询详情 |
| `pages/mall/index` | 积分商城 |
| `pages/profile/index` | 个人中心 |
| `pages/appointment/index` | 预约列表 |
| `pages/appointment/create/index` | 预约挂号 |
| `pages/appointment/detail/index` | 预约详情 |
| `pages/legal/user-agreement` | 用户服务协议 |
| `pages/legal/privacy-policy` | 隐私政策 |
#### 健康子包pkg-health4 个)
| 页面路径 | 说明 |
|----------|------|
| `pages/pkg-health/trend/index` | 健康趋势(体征数据折线图) |
| `pages/pkg-health/input/index` | 健康数据录入Zod 验证) |
| `pages/pkg-health/daily-monitoring/index` | 日常监测数据 |
| `pages/pkg-health/alerts/index` | 告警列表 |
#### 医生端子包doctor16 个)
| 页面路径 | 说明 |
|----------|------|
| `pages/doctor/index` | 医护首页/工作台 |
| `pages/doctor/patients/index` | 患者列表 |
| `pages/doctor/patients/detail/index` | 患者详情 |
| `pages/doctor/consultation/index` | 咨询管理 |
| `pages/doctor/consultation/detail/index` | 咨询详情 |
| `pages/doctor/followup/index` | 随访管理 |
| `pages/doctor/followup/detail/index` | 随访详情 |
| `pages/doctor/report/index` | 报告管理 |
| `pages/doctor/report/detail/index` | 报告详情 |
| `pages/doctor/alerts/index` | 告警管理 |
| `pages/doctor/alerts/detail/index` | 告警详情 |
| `pages/doctor/action-inbox/index` | 待办事项 |
| `pages/doctor/dialysis/index` | 透析管理 |
| `pages/doctor/dialysis/detail/index` | 透析详情 |
| `pages/doctor/dialysis/create/index` | 创建透析记录 |
| `pages/doctor/prescription/index` | 处方管理 |
| `pages/doctor/prescription/detail/index` | 处方详情 |
| `pages/doctor/prescription/create/index` | 开具处方 |
#### 商城子包pkg-mall3 个)
| 页面路径 | 说明 |
|----------|------|
| `pages/pkg-mall/exchange/index` | 积分兑换 |
| `pages/pkg-mall/orders/index` | 积分订单 |
| `pages/pkg-mall/detail/index` | 商品详情 |
#### 个人中心子包pkg-profile12 个)
| 页面路径 | 说明 |
|----------|------|
| `pages/pkg-profile/family/index` | 家庭成员管理 |
| `pages/pkg-profile/family-add/index` | 添加家庭成员 |
| `pages/pkg-profile/reports/index` | 我的报告 |
| `pages/pkg-profile/followups/index` | 我的随访 |
| `pages/pkg-profile/medication/index` | 用药记录 |
| `pages/pkg-profile/settings/index` | 设置 |
| `pages/pkg-profile/dialysis-records/index` | 透析记录 |
| `pages/pkg-profile/dialysis-records/detail/index` | 透析记录详情 |
| `pages/pkg-profile/dialysis-prescriptions/index` | 透析处方 |
| `pages/pkg-profile/dialysis-prescriptions/detail/index` | 透析处方详情 |
| `pages/pkg-profile/consents/index` | 知情同意 |
| `pages/pkg-profile/health-records/index` | 健康档案 |
| `pages/pkg-profile/diagnoses/index` | 诊断记录 |
#### 其他子包9 个)
| 页面路径 | 说明 |
|----------|------|
| `pages/ai-report/list/index` | AI 分析报告列表 |
| `pages/ai-report/detail/index` | AI 分析报告详情 |
| `pages/article/index` | 健康资讯列表 |
| `pages/article/detail/index` | 文章详情 |
| `pages/report/detail/index` | 健康报告详情 |
| `pages/followup/detail/index` | 随访详情 |
| `pages/events/index` | 线下活动 |
| `pages/device-sync/index` | 设备数据同步 |
### 服务层10+ 个文件)
| 文件 | 覆盖 |
|------|------|
| `request.ts` | HTTP 封装401 刷新、错误处理) |
| `auth.ts` | 微信登录/绑定手机号 |
| `health.ts` | 体征数据/健康趋势 |
| `patient.ts` | 患者信息/家庭成员 |
| `appointment.ts` | 预约挂号/详情/取消 |
| `followup.ts` | 随访任务/详情 |
| `article.ts` | 健康文章 |
| `report.ts` | 健康报告 |
| `analytics.ts` | 数据分析 |
| `wechat-templates.ts` | 微信模板消息 ID |
### 组件10 个)
| 组件 | 用途 |
|------|------|
| `EcCanvas` | ECharts Canvas 包装Taro 4 兼容,按需引入 echarts/core |
| `EmptyState` | 空状态占位 |
| `ErrorBoundary` | 错误边界捕获 |
| `ErrorState` | 错误状态展示 |
| `FamilyPicker` | 家庭成员选择器 |
| `HealthCard` | 健康数据卡片 |
| `Loading` | 加载状态 |
| `StepIndicator` | 步骤指示器 |
| `TrendChart` | 趋势图表(基于 EcCanvas |
| `WeekCalendar` | 周日历组件 |
### 集成契约
| 方向 | 模块 | 接口 | 触发时机 |
|------|------|------|---------|
| 调用 → | [[erp-server]] | `POST /auth/wechat/login` | 微信登录 |
| 调用 → | [[erp-server]] | `POST /auth/wechat/bind-phone` | 手机号绑定 |
| 调用 → | [[erp-health]] | `/api/v1/health/*` | 健康数据查询 |
| 调用 → | [[erp-server]] | `/api/v1/auth/refresh` | Token 刷新 |
## 3. 代码逻辑
### 环境变量注入(关键)
小程序运行时没有 `process.env`,必须在 `config/index.ts` 中通过 `defineConstants` 编译时替换:
```typescript
defineConstants: {
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
'process.env.TARO_APP_API_URL': JSON.stringify(process.env.TARO_APP_API_URL || 'http://localhost:3000/api/v1'),
'process.env.TARO_APP_ENCRYPTION_KEY': JSON.stringify(process.env.TARO_APP_ENCRYPTION_KEY || ''),
},
```
### Token 安全存储
`secure-storage.ts` 使用 XOR 混淆 + Base64 编码存储 token
- **不使用 `btoa`/`atob`** — 小程序环境不支持
- 使用 `Taro.arrayBufferToBase64` / `Taro.base64ToArrayBuffer` 替代
- 加密密钥通过 `TARO_APP_ENCRYPTION_KEY` 环境变量注入
### 请求封装request.ts
- 401 自动尝试 refresh token失败后跳转登录页
- 错误响应格式: `{ error: string, message: string }`(无 `success` 字段)
- 成功响应格式: `{ success: true, data: T }`
- 当前 URL 拼接方式构建查询参数(待重构为 params 对象)
### 健康数据录入验证
使用 Zod schema 验证用户输入的体征数据(血压、心率、体重、血糖),确保数据类型和范围合法。
## 4. 构建与调试
### 构建
```bash
cd apps/miniprogram && pnpm build:weapp # 生产构建
cd apps/miniprogram && npx taro build --type weapp # 等效
```
### 微信开发者工具配置
| 配置项 | 值 | 说明 |
|--------|-----|------|
| AppID | `wx20f4ef9cc2ec66c5` | 真实微信 AppID |
| urlCheck | `false` | 不校验合法域名(开发模式) |
| miniprogramRoot | `dist/` | 编译输出目录 |
### 后端微信配置
`crates/erp-server/config/default.toml`:
```toml
[wechat]
appid = "wx20f4ef9cc2ec66c5"
secret = "<通过环境变量 ERP__WECHAT__SECRET 设置>"
```
## 5. 活跃问题 + 陷阱
### 历史教训
| 问题 | 根因 | 修复 |
|------|------|------|
| 页面空白 `ReferenceError: process is not defined` | `process.env` 在运行时不存在 | `defineConstants` 编译时替换 |
| 登录成功但前端报失败 `btoa is not defined` | `secure-storage.ts` 使用 Web API | 改用 `Taro.arrayBufferToBase64` |
| 微信登录 500 `wechat_users.created_by 不存在` | 迁移创建的表缺少标准字段 | `ALTER TABLE` 补列 |
| 401 循环重定向 | 首页未登录时 `request.ts` 反复 redirectTo | 检查当前路径避免重复跳转 |
| `Cannot read property 'call' of undefined` | `echarts-taro3-react` 内嵌 Taro 3.0.8 + React 16与 Taro 4.2 + React 18 产生双实例webpack 模块 ID 不匹配 | 移除 `echarts-taro3-react`,改为自定义 `EcCanvas` + `echarts/core` 按需引入 |
### 待优化
| 问题 | 级别 | 说明 |
|------|------|------|
| ~~Auth token 日志输出~~ | ~~P0~~ 已解决 | terser `drop_console` 移除生产日志 |
| ~~Token 刷新竞态~~ | ~~P0~~ 已解决 | `refreshPromise` 单例 + GET 请求去重 |
| ~~ECharts 全量引入~~ | ~~P2~~ 已解决 | 分包后 echarts 514KB 仅在趋势页按需加载 |
| 积分商城降级 UI | P0 | 未关联患者档案时 Tab 页空白,需引导用户建档 |
| daily-monitoring 无 Zod 验证 | P1 | 对齐 health/input 的验证标准 |
| 文章列表返回草稿 | P1 | 患者端应只展示 `published` 状态文章 |
| URL 拼接构建查询参数 | P2 | `request.ts` 已支持 `buildQuery(params)` 但内部使用 |
| 生产配置 | P2 | `urlCheck`/`minified` 需区分环境 |
### 2026-04-30 审计发现
**三端对齐差距**(小程序 API 覆盖 76 个Web 235 个,后端 328 个):
| 发现 | 严重性 | 说明 |
|------|--------|------|
| ~~晚间血压数据永久丢失~~ | ~~CRITICAL~~ **已修复** | 新增 `blood_pressure_evening` indicator_type小程序录入页 + 日常监测页 + 后端 service + 集成测试均已覆盖 |
| ~~透析管理完全无入口~~ | ~~HIGH~~ **已修复** | 医生端新增 dialysis 3 页面 + prescription 3 页面,患者端新增 dialysis-records 2 页面 + dialysis-prescriptions 2 页面 |
| ~~知情同意完全无入口~~ | ~~HIGH~~ **已修复** | 患者端新增 `pkg-profile/consents` 页面 |
| 体温/血氧未映射 | MEDIUM | `body_temperature`/`spo2` 无 indicator_type |
| ~~健康记录小程序无入口~~ | ~~MEDIUM~~ **已修复** | 患者端新增 `pkg-profile/health-records` 页面 |
| ~~诊断记录小程序无入口~~ | ~~MEDIUM~~ **已修复** | 患者端新增 `pkg-profile/diagnoses` 页面 |
| 小程序无自动化测试 | HIGH | 已建立 MCP 自动化审计流程4 角色 236 次探测通过率 96.2% |
**功能域完成度**(小程序端):
| 功能域 | 完成度 | 关键差距 |
|--------|--------|---------|
| 咨询管理 | 95% | 无导出(预期) |
| 预约管理 | 90% | 基本对齐 |
| 随访管理 | 70% | 仅列表+创建 |
| 患者管理 | 85% | 无删除(预期) |
| 健康数据 | 80% | ~~丢失晚间血压~~ 已修复 |
| 告警系统 | 80% | 查看+处理+医护端管理 |
| 透析管理 | 70% | 医护端管理+患者端查看记录/处方 |
| 知情同意 | 60% | 患者端查看,无签署流程 |
| 健康档案 | 60% | 患者端可查看健康档案和诊断 |
| 统计仪表盘 | 30% | 仅医护端 3 个统计 |
| AI 分析 | 30% | 仅历史查看 |
### 注意事项
- `Taro.login()` 的 code 一次性使用,每次调用会返回新 code
- `session_key` 缓存 5 分钟TTL过期需重新登录
- 微信开发者工具中 `getPhoneNumber` 需要真机调试或使用测试号
- Redis 不可达时限流降级为 fail-open不影响登录
## 6. MCP 联调(微信开发者工具自动化)
> 通过 MCP (Model Context Protocol) 工具直接操控微信开发者工具中的小程序模拟器,实现页面导航、元素交互、数据读取等操作。
>
> **当前使用自建 MCP 服务器** `@hms/weapp-mcp``tools/weapp-mcp/`),基于 `@weapp-vite/miniprogram-automator@1.1.0`(社区活跃维护 fork
> 原有的 `@yfme/weapp-dev-mcp` 在 DevTools 2.01.2510290 版本后不兼容,已弃用。
### 6.1 前置条件
1. **后端运行**`cargo run` 启动 `http://localhost:3000`
2. **小程序以 dev 模式构建** — 必须使用 `NODE_ENV=development` 构建(详见 §6.4
```bash
cd apps/miniprogram
echo 'TARO_APP_API_URL=http://localhost:3000/api/v1
TARO_APP_ENCRYPTION_KEY=' > .env
NODE_ENV=development npx taro build --type weapp
```
3. **微信开发者工具已登录** — 确保已扫码登录CLI 命令需要登录态)
4. **微信开发者工具打开项目** — 手动打开,加载 `apps/miniprogram/dist/`
5. **自动化端口已开启** — `project.config.json` 中 `"automationAudits": true`
> ⚠️ **为什么必须 dev 模式?** 生产构建(`pnpm build:weapp`)设置 `NODE_ENV=production``secure-storage.ts` 的 `decrypt()` 在密钥为空时会抛异常。dev 模式允许空密钥走明文存储。
### 6.2 启动调试环境
> **铁律:只允许一个 DevTools 实例运行。** 多实例会导致 App 级别自动化命令全部超时(无法恢复,必须全部关闭重开)。
#### 方式 ALauncher.launch() 自动启动(推荐)
通过 `@weapp-vite/miniprogram-automator` 的 `Launcher` 自动启动 DevTools 并开启自动化端口,无需手动操作。
```bash
# 步骤 1确认没有残留的 DevTools 进程
tasklist | findstr wechatdevtools
# 如有残留taskkill /F /IM wechatdevtools.exe
# 步骤 2MCP 的 inject_auth 或 connect 工具会自动调用 Launcher.launch()
# 或者手动测试:
cd tools/weapp-mcp
node --input-type=module -e "
import { Launcher } from '@weapp-vite/miniprogram-automator';
const mp = await new Launcher().launch({
cliPath: 'D:/微信web开发者工具/cli.bat',
projectPath: 'G:/hms/apps/miniprogram/dist',
});
console.log('OK:', (await mp.systemInfo()).model);
// 不要 disconnect保持连接供 MCP 使用
"
# 自动在端口 9420 开启自动化
```
#### 方式 B手动启动 + CLI 开端口
```bash
# 步骤 1确认没有残留的 DevTools 进程
taskkill /F /IM wechatdevtools.exe
# 步骤 2手动打开微信开发者工具加载 apps/miniprogram/dist/
# 等待模拟器完全加载,确认无报错
# 步骤 3通过 CLI 开启自动化端口(端口 9420
"D:/微信web开发者工具/cli.bat" auto --project G:/hms/apps/miniprogram/dist --auto-port 9420
# 看到 "✔ auto" 即成功
# 步骤 4验证端口已开启
netstat -ano | findstr 9420
# 应看到 LISTENING 状态
```
成功后可通过 `mcp__weapp-local__connect` 连接。
### 6.3 常用 MCP 操作weapp-local 工具集)
| 操作 | MCP 工具 | 说明 |
|------|----------|------|
| 连接 | `connect` | 连接自动化端口,返回系统信息 |
| 断开 | `disconnect` | 断开连接 |
| 查看当前页面 | `current_page` | 返回路径、尺寸,可选包含 data |
| 导航到页面 | `navigate` | `navigateTo` / `switchTab` / `reLaunch` / `redirectTo` / `navigateBack` |
| 查找元素 | `get_element` | CSS 选择器,支持 `[index=N]` 语法 |
| 查找多个元素 | `get_elements` | CSS 选择器数组 |
| 点击元素 | `tap` | CSS 选择器定位后点击 |
| 输入文本 | `input` | CSS 选择器 + 值 |
| 滚动 | `scroll` | scroll-view 的 CSS 选择器 + x/y 坐标 |
| 读取页面数据 | `page_data` | 获取当前页面 data 对象(可选路径) |
| 设置页面数据 | `page_data` | JSON data 参数 → setData |
| 读取组件数据 | `element_data` | 组件 CSS 选择器 + 可选路径 |
| 设置组件数据 | `element_data` | CSS 选择器 + JSON data → setData |
| 调用页面方法 | `page_call_method` | 调用当前页面上暴露的方法 |
| 执行 JS | `evaluate` | 在 AppService 上下文执行 JavaScript |
| 调用 wx API | `call_wx` | 如 `getStorageSync`、`setStorageSync` |
| Mock wx API | `mock_wx` | Mock 一个 wx API 返回指定结果 |
| 恢复 wx API | `restore_wx` | 恢复被 mock 的 wx API |
| 获取 WXML | `get_wxml` | 获取元素的 WXML 结构 |
| 截图 | `screenshot` | ⚠️ 当前版本超时,见 §6.6 |
| 日志 | `get_logs` | 获取 console 日志和异常 |
| 等待元素 | `wait_for` | 轮询等待选择器匹配 |
| 注入认证 | `inject_auth` | 一键获取后端 token + 注入 storage + 跳转首页 |
| 审计页面 | `audit_page` | 导航 → 检查加载 → 收集错误 → 健康报告 |
| 批量审计 | `audit_all` | 批量审计所有小程序页面 |
### 6.4 绕过微信登录
MCP 无法模拟微信 OAuth`Taro.login()` 返回的 code 走真实微信 `jscode2session` 接口DevTools 模拟器会返回 mock code 导致后端 500
#### 方案一inject_auth 一键注入(推荐)
**原理:** 以 dev 模式重编译(空加密密钥),通过 MCP 的 `inject_auth` 工具自动完成:获取 admin token → evaluate 注入 storage → reLaunch 首页。
```
1. 准备:确保已按 §6.1 以 dev 模式构建,且已连接 MCPconnect
2. 调用 inject_auth 工具(默认参数即可):
- 自动 POST /auth/login 获取 admin token
- 通过 evaluate() 注入 access_token / refresh_token / user_data / tenant_id / patient_id
- reLaunch 到 /pages/index/index
- 等待 2 秒后返回当前页面路径
3. 成功后即可正常操作各页面
```
**关键 ID默认值**
- `tenant_id`: `019d80da-7a2c-7820-b0a3-3d5266a3a324`
- `patient_id`: `019dcd34-bc4d-72c1-8c19-77ce1f4839d6`
#### 方案二:手动分步注入
```bash
# 1. 获取 admin token
curl -X POST http://localhost:3000/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"Admin@2026"}'
# 2. 通过 MCP call_wx 逐个写入 storage
# 3. 通过 MCP navigate reLaunch 到首页
```
> ⚠️ 长字符串 token 通过 `call_wx` 传输可能截断。建议用 `inject_auth` 工具(内部用 evaluate 直接执行,无传输问题)。
### 6.5 TabBar 页面列表
以下页面是 TabBar 页面,必须用 `switchTab` 导航(不能用 `navigateTo`
- `pages/index/index` — 首页
- `pages/health/index` — 健康数据
- `pages/messages/index` — 消息中心
> 注意:咨询、商城、个人中心虽然显示为 Tab 样式,但实际不是 TabBar 页面(使用 `reLaunch` 导航)。
### 6.6 已知限制
| 问题 | 原因 | 替代方案 |
|------|------|----------|
| **screenshot 超时** | `App.captureScreenshot` 在 DevTools 2.01.2510290 不响应 | 用 `get_element` + `page_data` 获取 UI 状态 |
| **多实例导致全挂** | 两个 DevTools 实例抢端口App 域命令全部超时且不可恢复 | `taskkill /F /IM wechatdevtools.exe` 后重开 |
| **navigateTo 超出 10 页栈** | 小程序 webview 限制最多 10 层页面栈 | 批量测试用 `reLaunch` 逐页导航 |
| **CLI 需要登录** | `cli.bat auto` 要求 DevTools 已扫码登录 | 在 DevTools 中先扫码登录 |
| **SummerCompiler 报错** | `project.config.json` 缺少必要字段 | 已配置 `packNpmManually`/`packNpmRelationList`/`ignoreUploadUnusedFiles`/`condition` |
| **require() 在 evaluate 不可用** | webpack 用数字 ID 注册模块 | 直接用 `wx.setStorageSync` / `wx.getStorageSync` |
| **auth 重定向** | request interceptor 检测 401 后跳转 login 并清空 storage | 确保 token 有效reLaunch 后等待 2-3 秒 |
| **生产构建 decrypt 抛异常** | 空密钥 + `NODE_ENV=production` 时 `decrypt()` 直接 throw | 使用 `NODE_ENV=development` 构建 |
### 6.7 MCP 服务器架构
```
tools/weapp-mcp/
├── src/
│ ├── index.ts # MCP 服务器入口25 个工具注册
│ └── automator.ts # 连接管理器(@weapp-vite/miniprogram-automator
├── package.json # @hms/weapp-mcp, 依赖 @weapp-vite/miniprogram-automator@1.1.0
└── tsconfig.json
```
`.mcp.json` 配置:
```json
{
"weapp-local": {
"command": "node",
"args": ["tools/weapp-mcp/dist/index.js"],
"env": {
"WEAPP_WS_ENDPOINT": "ws://localhost:9420",
"WEAPP_CLI_PATH": "D:/微信web开发者工具/cli.bat",
"WEAPP_PROJECT_PATH": "G:/hms/apps/miniprogram/dist",
"HMS_API_URL": "http://localhost:3000/api/v1"
}
}
}
```
修改后需 `cd tools/weapp-mcp && npm run build` 重新编译。
### 6.8 自动化审计脚本
| 脚本 | 用途 | 运行 |
|------|------|------|
| `tools/weapp-mcp/scripts/audit-pages.mjs` | **多角色分批审计**(推荐) | `node scripts/audit-pages.mjs --role admin` |
| `apps/miniprogram/inject-auth.cjs` | 注入明文 tokendev 模式) | `node inject-auth.cjs` |
| `apps/miniprogram/e2e-final.cjs` | 完整 E2E 链路验证11 UI + 10 API | `node e2e-final.cjs` |
#### 分批审计脚本用法
```bash
# 在 tools/weapp-mcp/ 目录下运行
cd tools/weapp-mcp
# 审计指定角色admin/doctor/nurse/operator
node scripts/audit-pages.mjs --role admin
# 自定义批次大小(默认 10 页/批,每批重启 DevTools 防止多实例超时)
node scripts/audit-pages.mjs --role doctor --batch-size 8
# 输出:
# - 控制台实时显示每页状态OK / LOGIN_REDIRECT / ERROR
# - JSON 报告保存到 docs/qa/role-test-results/MP-{role}-audit.json
```
**设计要点:**
- 每批 N 页后自动 `taskkill` 所有 DevTools 进程并重新 `Launcher.launch()`,避免多实例导致 App 级命令超时
- 通过 `mp.evaluate()` 注入 storage`wx.setStorageSync`),写入 `access_token`/`refresh_token`/`user_data`/`user_roles`/`tenant_id`/`current_patient_id`
- 测试用户密码均为 `Admin@2026`(不是 `Test@2026`
> ⚠️ 旧脚本 `audit-pages.cjs` / `audit-detail-pages.cjs` 基于 `miniprogram-automator`(已弃用),请使用新的 `audit-pages.mjs`。
### 6.9 审计结果
#### 2026-05-08 多角色自动化审计59 页面 × 4 角色)
使用分批审计脚本对全部 59 个页面进行 4 角色全面审计236 次页面探测):
| 角色 | OK | LOGIN_REDIRECT | ERROR | 通过率 |
|------|-----|----------------|-------|--------|
| admin患者 | 57 | 0 | 2 | **96.6%** |
| doctor医生 | 58 | 1 | 0 | **98.3%** |
| nurse护士 | 57 | 0 | 2 | **96.6%** |
| operator运营 | 55 | 0 | 4 | **93.2%** |
| **合计** | **227** | **1** | **8** | **96.2%** |
**关键结论:**
- 8 个 ERROR 全部是 DevTools 模拟器随机 timeout非产品缺陷
- 1 个 LOGIN_REDIRECT 是 doctor 首页首次加载时序问题
- 所有 59 个页面在所有角色下均可正常渲染(排除测试工具因素)
- 测试用户密码均为 `Admin@2026`(之前误用 `Test@2026` 导致大量 LOGIN_REDIRECT
报告文件:`docs/qa/role-test-results/MP-{role}-audit.json`
#### 2026-04-27 初次审计40 页面,单角色)
通过 MCP 自动化工具对全部 40 个页面进行渲染审计:
| 类别 | 数量 | 结果 |
|------|------|------|
| TabBar 页面 | 5 | 5/5 OK |
| 患者端子页面 | 24 | 24/24 OK |
| 详情页(假 ID | 11 | 11/11 优雅降级 |
| **合计** | **40** | **40/40 全部通过** |
详细审计报告见 `docs/discussions/2026-04-27-miniprogram-audit-report.md`。
## 7. 变更记录
| 日期 | 变更 |
|------|------|
| 2026-05-09 | **Design Token 全面接入**68 SCSS 文件59 页面 + 9 组件)全面迁移 `font-size: Npx` → `var(--tk-*)`;重写 `tokens.scss` 校准 10 级字号 + 4 结构 token 匹配实际设计值;更新 `mixins.scss` 4 个 mixin 引用 token清理 12 个页面的本地 mixin 重复定义;`elder-mode.scss` 从 530 行缩减至 ~120 行(删除所有字号/颜色覆写仅保留结构布局634 token 引用 / 3 个特殊硬编码;新增 §1.1 Design Token 系统文档 |
| 2026-05-08 | **多角色自动化审计**4 角色admin/doctor/nurse/operator× 59 页面 = 236 次探测,综合通过率 96.2%;更新 §2 页面结构为 59 页面完整列表(含医生端 dialysis/prescription/action-inbox + 患者端 dialysis-records/prescriptions/consents/health-records/diagnoses更新 §5 审计发现(透析/知情同意/诊断/健康记录标记为已修复);更新 §6.5 TabBar 为 3 个;新增 §6.8 分批审计脚本;更新 §6.9 多角色审计结果 |
| 2026-05-08 | **MCP 联调全面重写**:自建 MCP 服务器 `@hms/weapp-mcp` 替代 `@yfme/weapp-dev-mcp`;基于 `@weapp-vite/miniprogram-automator@1.1.0`;新增 §6.2 启动步骤(登录+单实例铁律);更新工具列表为 weapp-local 25 个工具;新增 inject_auth 一键注入;新增 §6.7 MCP 服务器架构说明多实例冲突、CLI 登录、SummerCompiler 等已知限制 |
| 2026-05-01 | 审计发现更新CRITICAL 晚间血压丢失 / HIGH 透析+知情同意完全空白 / 功能域完成度矩阵 |
| 2026-04-28 | **全面性能优化**分包加载6 分包,主包 517KB→275KBvendors 192KB→36KBGET 请求去重+60s TTL 缓存points store 集中积分/签到状态todaySummary 60s TTL7 组件 React.memoTrendChart 双重渲染修复restoreAuth 提升 App 级别prod terser drop_consolecrypto-js 按需引入 |
| 2026-04-27 | **移除 echarts-taro3-react**:内嵌 Taro 3 + React 16 导致 webpack 模块加载失败,改为自定义 `EcCanvas` 组件 + `echarts/core` 按需引入;更新版本说明 + 历史教训 + 组件列表 |
| 2026-04-27 | **MCP 联调全面更新**§6.1 增加 dev 构建前置条件§6.4 重写为明文 token 注入法评估两种方案§6.6 补充 7 条已知限制,新增 §6.7 审计脚本说明 + §6.8 实测审计结果40/40 页面通过§5 补充 4 条审计发现 |
| 2026-04-27 | 新增 §6 MCP 联调章节连接、操作列表、绕过登录、已知限制、e2e 脚本 |
| 2026-04-26 | 全面更新40 页面(含 9 个医护端页面、15 目录、5 个 Tab 页、积分商城、线下活动 |
| 2026-04-25 | 全面更新20 页面、10 服务、9 组件、Zod 验证、加密密钥外部化说明 |
| 2026-04-24 | 创建小程序 wiki 页面,记录登录流程、环境配置、历史陷阱 |