Files
hms/wiki/miniprogram.md
iven 9bd2d4c2e6 docs(mp): 五专家组最终审查报告 — 综合 7.4/10(B)
架构 7.5 + 性能 8.0 + 安全 7.5 + 工程 7.5 + UX 6.5
发现 HIGH×15 + MEDIUM×25
核心问题:隐私政策合规、patientId 架构绕过、测试覆盖不足、触摸反馈缺失
2026-05-15 08:05:47 +08:00

930 lines
49 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-10
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 自动刷新、并发限制、缓存、AbortSignal 取消、`resetForTesting()` 测试隔离) |
| `apps/miniprogram/src/services/auth.ts` | 微信登录/绑定手机号 API |
| `apps/miniprogram/src/stores/auth.ts` | 认证状态login/bindPhone/restore |
| `apps/miniprogram/src/stores/index.ts` | `resetAllStores()` 统一清理(解耦 store 间依赖) |
| `apps/miniprogram/src/hooks/usePageData.ts` | **统一页面数据加载 hook**(节流 + 下拉刷新 + 防重入) |
| `apps/miniprogram/src/pages/index/useHomeData.ts` | 首页数据 hook从 424 行页面组件中提取) |
| `apps/miniprogram/src/pages/health/useHealthData.ts` | 健康页数据 hook从 422 行页面组件中提取) |
| `apps/miniprogram/src/utils/secure-storage.ts` | token 安全存储(明文存储,保留接口兼容) |
| `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` | 设备数据同步 |
### Hook 层7 个)
| Hook | 用途 |
|------|------|
| `usePageData` | **统一页面数据加载**`useDidShow` 节流 + `usePullDownRefresh` + `loadingRef` 防重入 + `enabled` 条件守卫。44/58 页面已接入 |
| `useThrottledDidShow` | 带节流的 `useDidShow`(已迁移页面不再直接使用,保留兼容) |
| `useSafeTimeout` | 页面隐藏时自动 clearTimeout |
| `usePageRefresh` | 下拉刷新封装 |
| `usePagination` | 通用分页逻辑 |
| `useAuthRequired` | 登录态检查 |
| `useElderClass` | 长者模式 CSS class |
| `useLongPolling` | **通用长轮询**generation counter 防重叠 + useDidShow/Hide 可见性控制 + 失败退避delay = min(failCount×2s, 30s)+ enabled 条件守卫。咨询详情页(患者+医生端)已接入 |
### 服务层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 刷新 |
| 调用 → | [[erp-health]] | `GET /public/banners` | 访客首页轮播图(无需认证) |
| 调用 → | [[erp-health]] | `GET /public/banner-image/{id}` | 轮播图图片下载(`wx.downloadFile`,无需认证) |
| 调用 → | [[erp-health]] | `GET /public/articles` | 访客首页文章列表(无需认证) |
| 调用 → | [[erp-health]] | `GET /public/articles/{id}` | 访客文章详情(无需认证) |
## 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 || ''),
'process.env.TARO_APP_DEFAULT_TENANT_ID': JSON.stringify(process.env.TARO_APP_DEFAULT_TENANT_ID || ''),
},
```
### 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不影响登录
### 2026-05-14 全页面性能与稳定性审查5 专家组 × 58 页面)
> 组织 5 个并行专家组,逐一审查全部 58 个页面 + stores/hooks/utils/components/services 基础设施层。
> 审查重点开发者工具卡死、CPU 飙升、逻辑链路异常、内存泄漏。
#### 发现汇总
| 级别 | 数量 | 说明 |
|------|------|------|
| CRITICAL | 3 | 长轮询紧密递归、BLE 模块单例、TrendChart 同步 API |
| HIGH | 8 | 原生 HTML input、客户端过滤、Storage 渲染路径、messagesRef 不同步等 |
| MEDIUM | 15+ | 缺少 useThrottledDidShow、IIFE 渲染、loading 竞态等 |
#### CRITICAL 级别(已修复)
| # | 问题 | 文件 | 修复 |
|---|------|------|------|
| 1 | **长轮询 delay=0 紧密递归**成功轮询后无间隔立即递归后端快速响应时构成紧密循环CPU 飙升 | `consultation/detail`(患者+医生端) | 成功路径加 3s 间隔 + 连续失败上限 50 次 |
| 2 | **BLE 模块级单例**`BLEManager` 在模块顶层实例化,生命周期不与页面绑定;`liveReadings` 无上限增长 | `device-sync/index.tsx` | 改为 `useRef` 懒初始化 + `MAX_LIVE_READINGS=200` 上限 |
| 3 | **TrendChart 模块级同步 API**`Taro.getSystemInfoSync()` 在模块加载时执行,阻塞首帧 | `components/TrendChart` | 改为延迟求值 `getDPR()` 函数 |
#### HIGH 级别(已修复)
| # | 问题 | 文件 | 修复 |
|---|------|------|------|
| 1 | **原生 HTML `<input type='date'>`**Taro 不支持原生 HTML 标签,日期选择器完全不可用 | `doctor/followup/detail` | 替换为 `<Picker mode='date'>` |
| 2 | **告警详情用列表+客户端过滤**:加载 100 条列表找单条告警,超过 100 条永远找不到 | `doctor/alerts/detail` | 新增 `getAlert(id)` API 函数,单条查询 |
| 3 | **透析列表客户端过滤**:服务端返回全状态数据,前端用 `activeTab` 过滤导致分页不准确 | `doctor/dialysis/index` | 传 `status` 参数给服务端,移除客户端过滤 |
| 4 | **`getStorageSync` 在渲染路径**:组件顶层调用同步 Storage 读取,每次渲染都执行 IPC | `report/detail` | 改用 `useAuthStore((s) => s.currentPatient)` |
| 5 | **`handleSend``messagesRef` 未同步**:发送消息后 state 更新但 ref 未更新,长轮询可能重复拉取 | `consultation/detail`(患者+医生端) | 在 `setMessages` 回调中同步更新 ref |
| 6 | **医护工作台无自动刷新**:返回工作台时数据不更新 | `doctor/index` | 添加 `useThrottledDidShow` 10s 节流刷新 |
#### MEDIUM 级别(已知,按需修复)
| 问题 | 文件 | 说明 |
|------|------|------|
| `ai-report/list` 缺少 `useThrottledDidShow` | `ai-report/list` | 只在挂载时加载,从详情页返回不刷新 |
| `events/list` 缺少分页 | `events/index` | 固定加载 50 条,无滚动加载更多 |
| `device-sync` tryAutoSync 无并发保护 | `device-sync` | 快速进出页面可能重复上传 |
| `health/index` loadTrend 无并发保护 | `health/index` | **已修复:** 添加 `loadingRef` 防重入 |
| `doctor/prescription` handleSearch loading 竞态 | `doctor/prescription` | handleSearch 和 useEffect 的 loadData 可能闪烁 |
#### 架构建议(已完成标注 ✅)
1. ~~**统一数据加载模式**~~ ✅ 已完成:所有列表/详情页使用 `usePageData` hook44/58 页面已接入
2. ~~**长轮询通用化**~~ ✅ 已完成:抽取 `useLongPolling` hookgeneration counter + useDidShow/Hide + 失败退避),患者端+医生端 consultation/detail 已接入
3. ~~**服务端过滤优先**~~ ✅ 已完成:所有列表页的 Tab 过滤传参给后端
4. ~~**BLE 管理器生命周期**~~ ✅ 已完成:改为 `useRef` 懒初始化
5. ~~**getStorageSync 出渲染路径**~~ ✅ 已完成:通过 Zustand store 获取
### 2026-05-15 患者端登录后卡死深度审查3 专家组 × 请求链路 + 并发分析 + 端点可达性)
> 游客端卡死修复后,患者登录后又频繁出现开发者工具卡死。组织 3 个并行专家组深度审查。
> **根因:全局并发请求超过微信 10 个限制**,快速 Tab 切换时首页 5 个 + 健康页 4 个 + analytics 1 个 + 个人中心 2 个 = 12 个并发请求,超限排队阻塞 UI。
#### 审查发现
| 级别 | 问题 | 说明 |
|------|------|------|
| **根因** | **请求并发超微信 10 限制** | 快速 Tab 切换产生 12+ 并发请求,超出排队;慢请求占位导致后续全部阻塞 |
| HIGH | doRefresh 失败未设 isLoggingOut | Storage 清了但标志未设401 路径可能重入 |
| HIGH | 401 失败后 reLaunch 首页而非登录页 | 首页 restoreAuth 又发请求,可能循环 |
| MEDIUM | 长轮询 useDidShow 重启时新旧循环重叠 | pollingRef 设 true 时旧 setTimeout 回调也会继续 |
| MEDIUM | 首页 loadReminders 无并发保护 | useThrottledDidShow + pullDownRefresh 可同时触发 |
| MEDIUM | health store refreshToday 无防重入 | 两个页面同时调用会发两次请求 |
| MEDIUM | 健康页 loadTrend/loadAiSuggestions 无防重入 | 下拉刷新 + useThrottledDidShow 双倍请求 |
#### 端点可达性验证
**所有 33 个小程序 service 端点后端均已实现**,无 ghost endpoint特别确认 `/analytics/batch``/messages/unread-count``/ai/suggestions` 均存在)。
#### Tab 切换请求链路分析
每次 Tab 切换触发两层回调:
| 层级 | 操作 | 开销 |
|------|------|------|
| App 级 `useDidShow` | `restoreAuth()` + `restoreUI()` | 4-6 次同步 Storage IPC约 10-50ms |
| 页面级 `useThrottledDidShow` | 页面数据加载 | 1-5 个异步 API 请求timeout 15s |
**最坏情况(所有缓存过期):**
| 页面 | 并发请求数 |
|------|-----------|
| 首页 HomeDashboard | 5refreshToday + 3 个 loadReminders + getUnreadCount |
| 健康页 | 4refreshToday + getTrend + listPendingSuggestions + getHealthThresholds |
| 消息页 | 1listConsultations 或 listNotifications |
| 个人中心 | 2getAccount + getCheckinStatus |
| analytics flushEvents | 1POST /analytics/batch每 30s |
| **合计** | **13 个** — 超限 3 个 |
#### 修复清单6 项,全部已修复)
| # | 级别 | 文件 | 修复 |
|---|------|------|------|
| 1 | **HIGH** | `services/request.ts` | `doRefresh` 失败后设 `isLoggingOut=true` + 清理所有 Storage`current_patient`+ 清请求缓存 + 重置 headers 缓存 |
| 2 | **HIGH** | `services/request.ts` | 401 失败后跳 `/pages/login/index`(而非首页);重试成功后重置 `isLoggingOut` |
| 3 | **HIGH** | `services/request.ts` | **新增全局并发限制 `MAX_CONCURRENT=8`**acquireSlot/releaseSlot 队列机制,防止超过微信 10 个请求限制;用 try/finally 确保所有路径释放槽位 |
| 4 | MEDIUM | `pages/consultation/detail` + `doctor/consultation/detail` | 长轮询改为 **generation counter**`startLongPolling` 递增 generation旧循环检测到 generation 变化自动退出,杜绝新旧循环重叠 |
| 5 | MEDIUM | `pages/index/index.tsx` | 首页 `loadReminders` 添加 `remindersLoadingRef` 防重入 |
| 6 | MEDIUM | `pages/health/index.tsx` + `stores/health.ts` | 健康页整体 `loadingRef` 防重入health store `refreshToday` 添加全局 `refreshingToday` 去重 |
#### 并发限制器原理
```typescript
// request.ts — acquireSlot/releaseSlot 队列
const MAX_CONCURRENT = 8; // 留 2 个空位给系统请求
let activeRequests = 0;
const pendingQueue: Array<() => void> = [];
function acquireSlot(): Promise<void> {
if (activeRequests < MAX_CONCURRENT) {
activeRequests++;
return Promise.resolve();
}
return new Promise<void>((resolve) => { pendingQueue.push(resolve); });
}
function releaseSlot(): void {
activeRequests--;
const next = pendingQueue.shift();
if (next) { activeRequests++; next(); }
}
```
所有页面请求共享同一队列,保证同时最多 8 个网络请求在飞。超出自动排队,先到先得。
## 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_DEFAULT_TENANT_ID=019d80da-7a2c-7820-b0a3-3d5266a3a324
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` 构建 |
| **DevTools 频繁卡死** | Electron 多进程累积文件描述符泄漏14+ 进程 3.5GB+ 内存 | 见下方 §6.9 DevTools 性能优化 |
### 6.9 DevTools 性能优化
微信开发者工具基于 Electron多进程架构主进程 + 渲染进程 + GPU + 插件)在长时间运行后会出现 `EMFILE: too many open files` 导致卡死。
**常见触发场景:**
- 频繁 MCP 调用evaluate/reLaunch导致 fd 累积
- Taro 热重载文件监视器与 DevTools 自带监视器重叠
- 长时间(>2 小时)不重启 DevTools
**优化措施:**
```powershell
# 1. 定期清理 DevTools 进程(推荐每 30 分钟或卡顿时执行)
taskkill /F /IM wechatdevtools.exe
# 然后重新打开项目
# 2. 检查进程资源占用
Get-Process wechatdevtools | Measure-Object WorkingSet64 -Sum | Select Count, @{N='MB';E={[math]::Round($_.Sum/1MB)}}
# 正常:< 1500MB | 需要重启:> 3000MB
# 3. 关闭不必要的 DevTools 功能
# 设置 → 编辑器设置 → 取消勾选「文件保存时自动编译」
# 设置 → 代理 → 关闭(不用代理时)
```
**MCP 联调最佳实践:**
- 每轮 MCP 测试结束后 `disconnect` 断开连接
- 批量测试分批执行,每批 ≤ 10 页后重启 DevTools
- 避免在 DevTools 编辑器中同时打开多个文件
### 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-13 T40 UI 设计系统合规审计60 页面)
基于 `docs/qa/T40-miniprogram-ui-audit-plan.md` 对全部 60 个页面进行设计系统合规审计,覆盖 Design Token、SCSS 变量、色彩/圆角/字号/组件规范。
**审计结果汇总:**
| 级别 | 页面数 | 占比 |
|------|--------|------|
| PASS | 31 | 52% |
| PASS_WITH_ISSUES | 27 | 45% |
| NEEDS_WORK | 2 | 3% |
**发现并修复的问题(全部已修复):**
| 类别 | 数量 | 修复内容 |
|------|------|----------|
| HIGH | 2 | 趋势页缺 Loading/EmptyState文章列表缺 mixins 导入 |
| MEDIUM | 6 | 硬编码字号(72px→`--tk-font-display`)ErrorState 图标字号AI报告离调色板颜色(#7c3aed/#f0e6ff→`$pri`/`$pri-l`);医生患者列表 inline style咨询页 GuestGuard 统一3处 TSX inline 颜色提取为 SCSS 类 |
| LOW — `#fff` 统一 | 44 | 新增 `$white` 变量,所有 `color: #fff` → `$white``background: #fff` → `$card` |
| LOW — 圆角统一 | 14 | `8px` → `$r-xs`7文件`20px` → `$r-lg`2文件`16px` → `$r`1文件 |
| LOW — 静默 catch | 2 | article/health 的空 catch 块添加状态清理 |
| LOW — ErrorBoundary 重构 | 1 | 6 个 inline style 硬编码提取为 SCSS 类 + Design Token |
| LOW — 离调色板颜色 | 2 | `#0284C7`(冷蓝) → `$tx2``#94A3B8`(冷灰) → `$tx3` |
| LOW — `#FFFFFF` 统一 | 4 | index/exchange/mixins/variables 中 `#FFFFFF` → `$white` |
**设计系统新增:**
- `variables.scss`: 新增 `$white: #FFFFFF` 语义变量
- `tokens.scss`: 新增 `--tk-font-display` Token72px/80px大数字装饰用
- `ErrorBoundary`: 新增 SCSS 文件,从 inline style 迁移到设计系统
**影响文件:** 25 个 SCSS + 10 个 TSX + 1 个新增 SCSS + 2 个样式系统文件
报告文件:`docs/qa/role-test-results/T40-ui-audit-results.md`
#### 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-15 | **五专家组最终审查**:架构 7.5 + 性能 8.0 + 安全 7.5 + 工程 7.5 + UX 6.5 = 综合 7.4/10(B);发现 HIGH×15 + MEDIUM×25核心问题隐私政策与实际不一致、8 页面绕过 patientId 架构、测试覆盖不足10 service + 4 store + 8 hook 零测试)、触摸反馈缺失;详细报告见 `docs/discussions/2026-05-15-miniprogram-final-audit-five-experts.md` |
| 2026-05-15 | **架构重构 P3长轮询通用化 useLongPolling**:抽取 `useLongPolling` hookgeneration counter + useDidShow/Hide 可见性 + 失败退避 + enabled 守卫);患者端 + 医生端 consultation/detail 接入,删除 ~80 行重复代码;架构建议 #2 全部完成 ✅ |
| 2026-05-15 | **架构重构 P2request.ts 模块级状态收编 + AbortSignal + Analytics 受控**:提取 `ConcurrencyLimiter` 类(并发限制)、`ResponseCache` 类(缓存+去重+patientId 绑定);新增 `resetForTesting()` 测试隔离函数;`api.get/post/put/delete` 支持 `AbortSignal` 请求取消app.tsx Analytics 定时器改为 `useDidShow`/`useDidHide` 控制后台暂停;构建通过 + 测试 74/75 |
| 2026-05-15 | **患者端登录后卡死深度审查3 专家组)**:根因 — 全局并发请求超微信 10 限制排队阻塞;端点可达性验证 33/33 全部存在Tab 切换请求链路分析(最坏 13 并发);修复 HIGH×3doRefresh 状态清理 + 401 跳转登录页 + 全局并发限制 MAX_CONCURRENT=8+ MEDIUM×3长轮询 generation counter + 首页/健康页 loadingRef 防重入 + refreshToday 去重) |
| 2026-05-15 | **全量审计修复(第二轮)**:修复 CRITICAL×1pollingRef 未定义回归,咨询详情页 loadData 引用已删除的 pollingRef → 闭会话时 ReferenceError 崩溃HIGH×3 — 401 重试递归占用双 slot 改为循环结构释放后重入、4 个医生端列表页consultation/alerts/dialysis/prescription添加 loadingRef 防重入、safeNavigateTo 页栈溢出保护栈≥9 自动 redirectTo新增 `safeNavigateTo` 工具函数(`utils/navigate.ts` |
| 2026-05-15 | **setTimeout 无清理修复**:新增 `useSafeTimeout` hook页面隐藏时自动 clearTimeout10 个页面接入 — daily-monitoring2、exchange4、family-add、health/input、prescription detail/create、dialysis detail/create、appointment detail/create所有 fire-and-forget 定时器替换为 safeSetTimeout |
| 2026-05-14 | **全页面性能与稳定性审查5 专家组)**:审查 58 页面 + 基础设施层;修复 CRITICAL×3长轮询紧密递归、BLE 模块单例、TrendChart 同步 API+ HIGH×6原生 HTML input、客户端过滤→服务端、Storage 渲染路径、messagesRef 同步、工作台刷新、告警单条查询);新增 §5 审查发现章节 + 架构建议 |
| 2026-05-13 | **T40 UI 设计系统合规审计+修复**60 页面全覆盖审计PASS 31 / PASS_WITH_ISSUES 27 / NEEDS_WORK 2修复 HIGH×2 + MEDIUM×6 + LOW×67新增 `$white` 变量 + `--tk-font-display` Token44 处 `#fff` 统一为 `$white`14 处圆角硬编码统一为变量3 处 TSX inline 颜色提取为 SCSS 类ErrorBoundary 重构为 SCSS2 处静默 catch 修复2 处离调色板颜色修正 |
| 2026-05-10 | **访客首页改造**:轮播图接入 `/public/banners` API + `wx.downloadFile` 下载图片到本地临时路径;文章列表接入 `/public/articles` API文章详情页根据登录状态选择认证/公开 API`getPublicArticleDetail``.env` 新增 `TARO_APP_DEFAULT_TENANT_ID`;集成契约新增 4 个公开端点 |
| 2026-05-09 | **Design Token 全面接入**68 SCSS 文件全面迁移 `font-size: Npx` → `var(--tk-*)`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 页面,记录登录流程、环境配置、历史陷阱 |