Files
hms/wiki/miniprogram.md
iven 11777e3b68 docs(wiki): 多主题系统文档更新 + .gitignore 清理
- frontend.md: 4 套主题视觉人格 + 技术架构 + 温润东方详细 token
- index.md: 症状导航更新
- miniprogram.md: 小程序审计报告
- .gitignore: 排除 .logs/ plans/ playwright-report/ 临时文件
2026-04-28 00:20:20 +08:00

524 lines
23 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-04-27
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`
**设计哲学**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 遍地配。
## 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 → 跳转首页
```
### 页面结构40 个页面15 个目录)
#### 患者端页面
| 页面路径 | 说明 |
|----------|------|
| `pages/login/index` | 登录页(微信登录 + 协议勾选) |
| `pages/index/index` | 首页(今日健康、快捷服务) |
| `pages/health/index` | 健康上报Tab 页) |
| `pages/health/input/index` | 健康数据录入Zod 验证) |
| `pages/health/trend/index` | 健康趋势(体征数据折线图) |
| `pages/health/daily-monitoring/index` | 日常监测数据 |
| `pages/appointment/index` | 预约列表 |
| `pages/appointment/create/index` | 预约挂号 |
| `pages/appointment/detail/index` | 预约详情 |
| `pages/article/index` | 健康资讯列表 |
| `pages/article/detail/index` | 文章详情 |
| `pages/report/detail/index` | 健康报告详情 |
| `pages/ai-report/list/index` | AI 分析报告列表 |
| `pages/ai-report/detail/index` | AI 分析报告详情 |
| `pages/followup/detail/index` | 随访详情 |
| `pages/consultation/index` | 咨询列表Tab 页) |
| `pages/consultation/detail/index` | 咨询详情 |
| `pages/mall/index` | 积分商城Tab 页) |
| `pages/mall/detail/index` | 商品详情 |
| `pages/mall/exchange/index` | 积分兑换 |
| `pages/mall/orders/index` | 积分订单 |
| `pages/events/index` | 线下活动 |
| `pages/profile/index` | 个人中心Tab 页) |
| `pages/profile/family/index` | 家庭成员管理 |
| `pages/profile/family-add/index` | 添加家庭成员 |
| `pages/profile/reports/index` | 我的报告 |
| `pages/profile/followups/index` | 我的随访 |
| `pages/profile/medication/index` | 用药记录 |
| `pages/profile/settings/index` | 设置 |
| `pages/legal/user-agreement` | 用户服务协议 |
| `pages/legal/privacy-policy` | 隐私政策 |
#### 医护端页面8 个)
| 页面路径 | 说明 |
|----------|------|
| `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` | 报告详情 |
### 服务层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` 按需引入 |
### 待优化
| 问题 | 级别 | 说明 |
|------|------|------|
| URL 拼接构建查询参数 | P2 | `request.ts` 应支持 params 对象 |
| 加密密钥硬编码 | ~~P0~~ 已解决 | 已外部化到 `TARO_APP_ENCRYPTION_KEY` 环境变量 |
| Auth token 日志输出 | P0 | 生产环境需移除 console.log |
| 生产配置 | P2 | `urlCheck`/`minified` 需区分环境 |
| 积分商城降级 UI | P0 | 未关联患者档案时 Tab 页空白,需引导用户建档 |
| daily-monitoring 无 Zod 验证 | P1 | 对齐 health/input 的验证标准 |
| 文章列表返回草稿 | P1 | 患者端应只展示 `published` 状态文章 |
| Token 刷新竞态 | P0 | 多个 API 同时 401 时各自独立刷新,可能锁死用户 |
| ECharts 全量引入 | P2 | 趋势页 455KiB按需引入可减少 ~80% |
### 注意事项
- `Taro.login()` 的 code 一次性使用,每次调用会返回新 code
- `session_key` 缓存 5 分钟TTL过期需重新登录
- 微信开发者工具中 `getPhoneNumber` 需要真机调试或使用测试号
- Redis 不可达时限流降级为 fail-open不影响登录
## 6. MCP 联调(微信开发者工具自动化)
> 通过 MCP (Model Context Protocol) 工具直接操控微信开发者工具中的小程序模拟器,实现页面导航、元素交互、数据读取等操作。
### 6.1 前置条件
1. **后端运行**`cargo run` 启动 `http://localhost:3000`
2. **小程序以 dev 模式构建** — 必须使用 `NODE_ENV=development` 构建(详见 §6.4
```bash
cd apps/miniprogram
# 清空加密密钥(必须,否则 encrypt/decrypt 不一致)
echo 'TARO_APP_API_URL=http://localhost:3000/api/v1
TARO_APP_ENCRYPTION_KEY=' > .env
# dev 模式构建
NODE_ENV=development npx taro build --type weapp
```
3. **微信开发者工具已打开项目** — 加载 `apps/miniprogram/dist/`
4. **自动化端口已开启** — `project.config.json` 中 `"automationAudits": true`,端口 9420
> ⚠️ **为什么必须 dev 模式?** 生产构建(`pnpm build:weapp`)设置 `NODE_ENV=production``secure-storage.ts` 的 `decrypt()` 在密钥为空时会抛异常 "TARO_APP_ENCRYPTION_KEY 未设置,生产环境禁止明文读取"。dev 模式允许空密钥走明文存储。
### 6.2 建立连接
```
调用 mp_ensureConnection → 自动连接 ws://localhost:9420
```
成功后返回系统信息SDK 版本、设备型号、屏幕尺寸等)。
### 6.3 常用 MCP 操作
| 操作 | MCP 工具 | 说明 |
|------|----------|------|
| 查看当前页面 | `mp_currentPage` | 返回路径、尺寸、数据 |
| 导航到页面 | `mp_navigate` | `navigateTo` / `switchTab` / `reLaunch` |
| 返回上一页 | `mp_navigate` | `transition: "navigateBack"` |
| 截图 | `mp_screenshot` | ⚠️ 当前版本超时,见下方限制 |
| 读取 storage | `mp_callWx` | method: `getStorageSync` |
| 写入 storage | `mp_callWx` | method: `setStorageSync` |
| 查找元素 | `page_getElement` | CSS 选择器,返回标签/文本/尺寸 |
| 查找多个元素 | `page_getElements` | CSS 选择器数组 |
| 点击元素 | `element_tap` | CSS 选择器定位后点击 |
| 输入文本 | `element_input` | CSS 选择器 + 值 |
| 读取组件数据 | `element_getData` | 获取自定义组件的 data |
| 设置组件数据 | `element_setData` | 修改自定义组件的 data |
| 读取页面数据 | `page_getData` | 获取当前页面 data 对象 |
| 设置页面数据 | `page_setData` | 修改当前页面 data |
| 调用页面方法 | `page_callMethod` | 调用当前页面上暴露的方法 |
| 等待元素出现 | `page_waitElement` | 轮询等待选择器匹配 |
### 6.4 绕过微信登录
MCP 无法模拟微信 OAuth`Taro.login()` 返回的 code 走真实微信 `jscode2session` 接口DevTools 模拟器会返回 mock code 导致后端 500
#### 方案一:明文 token 注入(推荐,用于自动化测试)
**原理:** 以 dev 模式重编译(空加密密钥),`secure-storage.ts` 走明文路径,直接用 `wx.setStorageSync` 写入。
**步骤:**
```
1. 准备:确保已按 §6.1 以 dev 模式构建
2. 获取 admin token:
POST http://localhost:3000/api/v1/auth/login
Body: { "username": "admin", "password": "Admin@2026" }
返回: { data: { access_token, refresh_token, user: { id, username, ... } } }
3. 通过 automator.evaluate() 注入明文 storage:
mp.evaluate((at, rt, ud, ur, tid, pid) => {
wx.setStorageSync('access_token', at);
wx.setStorageSync('refresh_token', rt);
wx.setStorageSync('user_data', ud);
wx.setStorageSync('user_roles', ur);
wx.setStorageSync('tenant_id', tid);
wx.setStorageSync('current_patient_id', pid);
wx.setStorageSync('current_patient', {
id: pid, name: 'TestPatient', gender: 'male',
birth_date: '1990-01-15', status: 'active'
});
}, accessToken, refreshToken, userDataJson, rolesJson, tenantId, patientId);
4. reLaunch 到首页:
mp.reLaunch('/pages/index/index');
await sleep(3000);
```
**关键 tenant/patient ID**
- `tenant_id`: `019d80da-7a2c-7820-b0a3-3d5266a3a324`
- `current_patient_id`: `019dcd34-bc4d-72c1-8c19-77ce1f4839d6`
**为什么不用 mp_callWx**
- 加密 token~2100 字符)通过 `mp_callWx setStorageSync` 传输时会被截断/损坏
- `automator.evaluate()` 直接在小程序 JS 上下文执行,无传输问题
- `require('./utils/secure-storage')` 在 evaluate 中不可用webpack 用数字 ID 注册模块),只能用 `wx.setStorageSync`
#### 方案二:加密 token 注入(用于真实环境测试)
如果需要测试加密存储路径(密钥非空),可以:
```
1. 恢复 .env 中的加密密钥:
TARO_APP_ENCRYPTION_KEY=0a17b71d46064b06f993c9c202b342425e311a79f5be026d830562e7ad51f522
2. 重新构建: pnpm build:weapp
3. 使用 inject-auth.cjs 脚本(内含 CryptoJS AES 加密逻辑)
```
> ⚠️ 实测发现加密 token 通过 MCP 传输存在截断问题,方案二仅在需要测试加密存储时使用。
#### 注意事项
- 写完 storage 后**必须立即 reLaunch**,否则 app 的 API 请求会因 token 无效触发 401 → logout 清空 storage
- `reLaunch` 后等待 2-3 秒再进行后续操作,页面需要时间加载和初始化
- 恢复 `.env` 加密密钥后需重新构建,否则 `secure-storage.ts` 行为不一致
### 6.5 TabBar 页面列表
以下页面是 TabBar 页面,必须用 `switchTab` 导航(不能用 `navigateTo`
- `pages/index/index` — 首页
- `pages/health/index` — 健康数据
- `pages/consultation/index` — 咨询
- `pages/mall/index` — 积分商城
- `pages/profile/index` — 个人中心
### 6.6 已知限制
| 问题 | 原因 | 替代方案 |
|------|------|----------|
| **mp_screenshot 超时** | `miniprogram-automator` 的 `screenshot()` 方法在当前 DevTools 版本卡死 | 用 `page_getElement` + `page_getData` 获取 UI 状态 |
| **navigateTo 超出 10 页栈** | 小程序 webview 限制最多 10 层页面栈 | 批量测试用 `reLaunch` 逐页导航 |
| **加密 token MCP 传输截断** | AES 加密后的 token~2100 字符)通过 `mp_callWx` 传输时损坏 | 使用 `evaluate()` 在 JS 上下文内直接写入 |
| **require() 在 evaluate 不可用** | webpack 用数字 ID 注册模块,非文件路径 | 直接使用 `wx.setStorageSync` / `wx.getStorageSync` |
| **CryptoJS 非全局变量** | CryptoJS 打包在 webpack bundle 内evaluate 上下文无法访问 | 绕过加密dev 模式 + 空密钥 |
| **auth 重定向** | request interceptor 检测 401 后自动跳转 login 并清空 storage | 确保 token 有效reLaunch 后等待 2-3 秒 |
| **生产构建 decrypt 抛异常** | 空密钥 + `NODE_ENV=production` 时 `decrypt()` 直接 throw | 使用 `NODE_ENV=development` 构建 |
### 6.7 自动化审计脚本
| 脚本 | 用途 | 运行 |
|------|------|------|
| `apps/miniprogram/inject-auth.cjs` | 注入明文 tokendev 模式) | `node inject-auth.cjs` |
| `apps/miniprogram/audit-pages.cjs` | 批量审计 24 个非 TabBar 页面 | `node audit-pages.cjs` |
| `apps/miniprogram/audit-detail-pages.cjs` | 审计 11 个详情页(假 ID 优雅降级) | `node audit-detail-pages.cjs` |
| `apps/miniprogram/e2e-final.cjs` | 完整 E2E 链路验证11 UI + 10 API | `node e2e-final.cjs` |
#### 批量审计脚本模板
```javascript
const automator = require('miniprogram-automator');
async function main() {
const mp = await automator.connect({ wsEndpoint: 'ws://localhost:9420' });
const results = { ok: [], crash: [], login: [] };
for (const pageUrl of PAGES) {
try {
await mp.reLaunch(`/${pageUrl}`);
await new Promise(r => setTimeout(r, 2000));
const current = await mp.currentPage();
if (current.path === pageUrl.split('?')[0]) {
results.ok.push(pageUrl);
} else if (current.path === 'pages/login/index') {
results.login.push(pageUrl);
} else {
results.crash.push(pageUrl);
}
} catch (e) {
results.crash.push(pageUrl);
}
}
console.log(`OK: ${results.ok.length}, Login: ${results.login.length}, Crash: ${results.crash.length}`);
await mp.disconnect();
}
main();
```
### 6.8 审计结果2026-04-27 实测)
通过 MCP 自动化工具对全部 40 个页面进行渲染审计:
| 类别 | 数量 | 结果 |
|------|------|------|
| TabBar 页面 | 5 | 5/5 OK |
| 患者端子页面 | 24 | 24/24 OK |
| 详情页(假 ID | 11 | 11/11 优雅降级 |
| **合计** | **40** | **40/40 全部通过** |
核心 API 数据链路验证13 端点):
| API 端点 | 状态 | 说明 |
|----------|------|------|
| GET /health/vital-signs/today | 200 | |
| GET /health/patients | 200 | |
| GET /health/doctors | 200 | |
| GET /health/appointments | 200 | |
| GET /health/consultation-sessions | 200 | |
| GET /health/follow-up-tasks | 200 | |
| GET /health/articles | 200 | |
| GET /health/points/products | 200 | |
| GET /ai/analysis/history | 200 | |
| GET /health/points/account | 404 | admin 无患者档案(预期) |
| GET /health/points/transactions | 404 | 同上 |
| GET /health/points/orders | 404 | 同上 |
| GET /health/points/checkin/status | 404 | 同上 |
> 积分端点 404 说明:路由已注册在 `module.rs:454-484``resolve_patient_id()` 要求登录用户关联患者档案。admin 是管理员账号,无患者档案,因此返回 "当前用户未关联患者档案"。这是正确的业务逻辑。
详细审计报告见 `docs/discussions/2026-04-27-miniprogram-audit-report.md`。
## 7. 变更记录
| 日期 | 变更 |
|------|------|
| 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 页面,记录登录流程、环境配置、历史陷阱 |