Files
hms/docs/superpowers/specs/2026-04-24-hms-miniprogram-iteration-design.md
iven 19be2a08c7
Some checks failed
CI / security-audit (push) Has been cancelled
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
docs(miniprogram): 新增小程序迭代设计规格 + 25 Task 实施计划
设计规格:4 Sprint 混合策略(Sprint 0 修基础 → Sprint 1-3 模块打磨),
覆盖 18 个问题,含健康数据、预约挂号、报告详情、安全加固、增长基础。

实施计划:25 个 Task,4 个 Chunk,经 4 轮审查修复关键问题:
- Task 10 依赖后端 today 端点 status/reference_range 字段
- Task 14/15 补全 StepIndicator 连接线 + WeekCalendar 完整实现
- Task 21 request.ts Token 加密绕过修复
- Task 22 手机号解密前后端 API 契约明确(推荐 code 模式)
- Task 24 埋点补充核心页面手动调用
- Task 25 hooks 无条件调用修复
2026-04-24 12:08:13 +08:00

470 lines
19 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.
# HMS 患者小程序迭代设计规格
> **版本**: v1.0
> **日期**: 2026-04-24
> **状态**: 草案
> **关联**: 小程序初版设计 `2026-04-23-hms-miniprogram-design.md`
---
## 1. 概述
### 1.1 背景
小程序初版已完成 21 个页面、7 个 API service 的基础实现,覆盖登录、健康数据、预约挂号、检验报告、随访管理、用药提醒、健康资讯、个人中心。当前处于**开发阶段**,工程质量和用户体验存在明显短板,距测试阶段尚有差距。
### 1.2 问题全景
| 优先级 | 问题 | 影响 |
|--------|------|------|
| P0 | 大量重复代码profile/reports ≈ report/index, profile/followups ≈ followup/index | 维护成本翻倍 |
| P0 | 预约详情通过 Storage 缓存传递而非 API 获取 | 数据不一致 |
| P0 | EmptyState 导入方式不一致导致运行时报错 | 页面崩溃 |
| P0 | 手机号绑定后端硬编码 `"13800000000"` | 无法上线 |
| P0 | `getTodaySummary()` 调用的后端端点不存在 | 首页/健康页数据无法加载 |
| P1 | ErrorState 组件定义但未使用 | 错误处理不统一 |
| P1 | mixins.scss 定义但未使用 | 样式重复内联 |
| P1 | 无全局错误边界 | 页面崩溃无兜底 |
| P1 | tryRefreshToken 静默吞异常 | 调试困难 |
| P1 | 趋势图缓存永不过期 | 数据过时 |
| P1 | 随访详情获取低效listTasks().find() | 性能浪费 |
| P1 | 首页/健康页缺少 loading 状态 | 体验空白 |
| P1 | 用药提醒纯本地 Storage | 换设备即丢失(后续版本解决) |
| P2 | 路径别名 @/* 未使用 | 代码可读性差 |
| P2 | 无 schema 验证库 | 表单验证脆弱 |
| P2 | 趋势图纯 CSS 柱状图 | 无交互能力 |
| P2 | 用药提醒时间选择器未实现 | 功能不完整 |
| P2 | 无日志/埋点/上报 | 无法追踪问题 |
### 1.3 迭代策略:混合策略
采用**先基建再模块**的混合策略,分 4 个 Sprint 交付:
```
Sprint 0 (2-3天) Sprint 1 (3-4天) Sprint 2 (3-4天) Sprint 3 (4-5天)
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 工程基础修复 │ → │ 健康数据打磨 │ → │ 预约+通知 │ → │ 报告/随访/ │
│ │ │ │ │ │ │ 安全+增长 │
│ · 消除重复代码│ │ · ECharts图表│ │ · 步骤指示器 │ │ · 指标卡片 │
│ · 统一错误处理│ │ · 缓存TTL │ │ · 周视图日历 │ │ · Token加密 │
│ · 修复数据传递│ │ · zod验证 │ │ · 订阅消息 │ │ · 手机号解密 │
│ · 统一Loading │ │ · 状态色卡片 │ │ · 时段可视化 │ │ · 埋点+分享 │
└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘
```
**原则**Sprint 0 铺路,后续每个 Sprint 都受益于基础设施改善。Sprint 0 只修不建,不引入新依赖。
---
## 2. Sprint 0工程基础修复
**目标**:消除最痛的工程问题,为后续所有 Sprint 铺路。约束 2-3 天完成。
### 2.1 修复阻断性 API 端点缺失
**现状**:前端 `services/health.ts``getTodaySummary()` 调用 `GET /health/vital-signs?date=today`,但后端路由中**不存在此端点**。后端仅有 `GET /health/patients/{id}/vital-signs`(需 patient_id 路径参数)。这意味着首页"今日健康"卡片和健康页的数据从一开始就**无法加载**。
**方案**
- 后端在 `erp-health` 新增小程序专用端点 `GET /health/vital-signs/today`,通过 JWT `user_id` 自动关联 patient类似已有的 `GET /health/vital-signs/trend` 模式)
- 前端 `services/health.ts``getTodaySummary()` 调整为调用新端点
- 此项为 **Sprint 0 最高优先级**,阻塞首页和健康页基本功能
**涉及文件**
- 后端新增:`erp-health` handler + 路由注册
- 修改:`services/health.ts`
### 2.2 消除重复页面
**现状**`pages/report/index``pages/profile/reports/index` 几乎完全重复,`pages/followup/index``pages/profile/followups/index` 同理。且 `report/index``followup/index` 没有明确的导航入口。
**方案**
1. 删除 `pages/report/index``pages/followup/index` 及其 SCSS 文件
2.`app.config.ts` 移除对应路由注册
3. 首页快捷入口和 profile 菜单统一指向 `profile/reports``profile/followups`
4. 如果后续需要独立入口,则抽取共享组件 `components/ReportList``components/FollowupList`,两个页面只做薄壳路由
**涉及文件**
- 删除:`pages/report/index.tsx``pages/report/index.scss`
- 删除:`pages/followup/index.tsx``pages/followup/index.scss`
- 修改:`app.config.ts`(移除路由)
- 修改:`pages/index/index.tsx`(快捷入口路径)
### 2.2 统一错误处理
**现状**`ErrorState` 组件已定义但未被任何页面使用,各页面内联 `showToast` 错误提示。无全局错误边界。
**方案**
1. 所有列表页、详情页统一使用 `ErrorState` 组件,替换内联错误提示
2.`app.tsx` 添加 React Error Boundary 组件,兜底页面崩溃
3. 新建 `components/ErrorBoundary/index.tsx`
4. 修复 `tryRefreshToken` 的 catch 块,添加 `console.error` 日志
**涉及文件**
- 新增:`components/ErrorBoundary/index.tsx`
- 修改:`app.tsx`(包裹 ErrorBoundary
- 修改:所有列表页和详情页(替换内联错误处理为 ErrorState
- 修改:`services/request.ts`tryRefreshToken 日志)
### 2.3 修复数据传递问题
**预约详情**
- 移除 `appointment_detail_cache` Storage 传递
- 改为进入页面时通过 `GET /health/appointments/:id` 获取数据
- **后端需新增此端点**(当前仅有列表 `GET`、创建 `POST`、状态更新 `PUT`,缺少单条查询 `GET`
**随访详情**
- 后端**需新增** `GET /health/follow-up-tasks/:id` 单条查询端点(当前 `{id}` 路由仅注册了 `PUT``DELETE`,缺少 `GET`
- 前端替换 `listTasks().find()` 为直接按 ID 查询
> **注意**:以上后端新增端点为 Sprint 0 前置阻塞项。如果后端资源有限,前端先做"调用端点"的准备代码,后端并行实现。
**涉及文件**
- 修改:`pages/appointment/detail/index.tsx`
- 修改:`services/appointment.ts`(新增 getDetail 方法)
- 修改:`services/followup.ts`(新增 getTaskDetail 方法)
- 后端新增:`erp-health` 预约单条查询 + 随访单条查询端点
### 2.4 统一 Loading 状态
**现状**:首页和健康页的 `loading` 状态已在 store 中定义但未在 UI 层消费。详情页使用内联 `<Text>加载中...</Text>`
**方案**
1. 首页和健康页在数据加载时展示 `Loading` 组件
2. 所有详情页统一使用 `Loading` 组件替换内联文字
3. 预约创建页三步骤切换时也展示 loading
**涉及文件**
- 修改:`pages/index/index.tsx`(消费 loading 状态)
- 修改:`pages/health/index.tsx`(消费 loading 状态)
- 修改:所有详情页 tsx替换内联加载文字
### 2.5 杂项修复
| 项目 | 方案 |
|------|------|
| EmptyState 导入 bug | 首页 `import { EmptyState }` 改为 `import EmptyState`(默认导入) |
| 路径别名启用 | `services/``stores/` 层的 import 逐步改为 `@/` 别名 |
| mixins.scss 复用 | 新写的页面样式使用 `@include card``@include flex-center``@include safe-bottom` |
---
## 3. Sprint 1健康数据模块打磨
**目标**:升级健康数据录入、展示和趋势分析体验,从"能用"到"好用"。
### 3.1 健康卡片状态色
**现状**:四张健康卡片(血压/心率/血糖/体重)样式统一灰色,无状态区分。
**方案**
每张卡片根据指标状态着色:
- **正常**:左侧绿色边条 + 绿色"正常 ─"标签
- **偏高**:左侧红色边条 + 红色"偏高 ▲{差值}"标签
- **偏低**:左侧红色边条 + 红色"偏低 ▼{差值}"标签
- **无数据**:灰色,保持现状
异常指标数值变红,卡片底部显示参考范围。
**后端配合**:后端需在新增的 `GET /health/vital-signs/today` 端点中返回 `status`normal/high/low`reference_range`。前端 `TodaySummary` 类型同步新增 `reference_range` 字段(当前已有 `status` 字段但后端无对应返回)。
**涉及文件**
- 修改:`pages/health/index.tsx`(卡片样式逻辑)
- 修改:`pages/index/index.tsx`(首页健康卡片同步更新)
- 修改:`services/health.ts`(类型定义增加 status 字段)
### 3.2 ECharts 趋势图
**现状**:纯 CSS div 柱状图,无交互、无缩放、无 tooltip。
**方案**
引入 `echarts-taro3-react`(设计规格中已规划)。**前置条件**Sprint 1 开始前需做技术预研spike验证 `echarts-taro3-react` 在 Taro 4.2.0 + webpack5 下的兼容性。如果不可用,备选方案为 `echarts-for-weixin` + 手动封装为 React 组件。
实现:
- **折线图**:数据点连线,异常点标红放大
- **参考范围色带**:正常值区间以半透明绿色背景显示
- **Tooltip**:长按/点击显示具体数值和日期
- **时间范围切换**7天/30天/90天 三个 tab
- **缓存 TTL**:趋势数据缓存 5 分钟后自动过期,强制重新请求
**涉及文件**
- 新增:`components/TrendChart/index.tsx``components/TrendChart/index.scss`
- 重写:`pages/health/trend/index.tsx`
- 修改:`stores/health.ts`(缓存 TTL 机制)
- 新增依赖:`echarts-taro3-react`
### 3.3 表单验证升级
**现状**:所有表单验证为手动 if 判断,无 schema 约束。
**方案**
引入 `zod`~3KB gzip为每个表单定义验证 schema
```typescript
// 示例:体征录入验证
const vitalSignSchema = z.object({
indicator_type: z.enum(['blood_pressure', 'heart_rate', 'blood_sugar', 'weight']),
value: z.number().positive(),
extra: z.object({ systolic: z.number().min(60).max(250).optional(),
diastolic: z.number().min(40).max(150).optional() }).optional(),
measured_at: z.string().datetime().optional(),
note: z.string().max(200).optional()
});
```
异常值即时警告(如收缩压 > 180 显示红色提示"请及时就医")。
录入成功后自动刷新首页卡片 + 清除趋势缓存。
**涉及文件**
- 修改:`pages/health/input/index.tsx`zod schema 验证)
- 修改:`stores/health.ts`(录入成功后清除缓存)
- 新增依赖:`zod`
---
## 4. Sprint 2预约挂号 + 通知触达
**目标**:优化预约三步流程体验,建立微信订阅消息通知机制。
### 4.1 三步流程升级
**现状**:三个步骤(选科室 → 选医生 → 选日期时段)无进度指示,排班信息为纯文字列表。
**方案**
**步骤指示器**
- 新增 `components/StepIndicator/index.tsx`
- 顶部固定 1→2→3 步骤条,当前步骤高亮,已完成步骤可点击回退
- 步骤间切换带过渡动画
**科室选择**
- 从文字列表改为宫格卡片(图标 + 科室名 + 医生数)
- 每个科室卡片可点击,选中后高亮边框
**排班日历**
- 新增 `components/WeekCalendar/index.tsx` 周视图日历
- 有排班的日期标记绿点,无排班的日期灰色
- 点击日期展示该日可用时段卡片
- 时段卡片按剩余名额着色:>3 绿色、1-3 橙色、0 灰色不可选
**涉及文件**
- 新增:`components/StepIndicator/index.tsx`
- 新增:`components/WeekCalendar/index.tsx`
- 重写:`pages/appointment/create/index.tsx`
### 4.2 微信订阅消息
**现状**:无任何推送通知机制。
**方案**
1. 后端在微信公众平台注册订阅消息模板:
- 预约就诊提醒(就诊前 1 天推送)
- 随访任务提醒(截止前 1 天推送)
- 报告出具通知(新报告发布时推送)
2. 前端在关键场景引导用户订阅:
- 预约成功后弹出订阅授权
- 随访提交后引导订阅下次提醒
3. 后端定时任务检查待推送消息并触发
4. **降级设计**:用户拒绝订阅时,消息仍写入 `erp-message` 消息中心。小程序"我的"页面顶部显示未读消息数量红点,作为消息触达的备选渠道。
**涉及文件**
- 修改:`pages/appointment/detail/index.tsx`(预约成功后订阅引导)
- 修改:`pages/followup/detail/index.tsx`(随访提交后订阅引导)
- 后端新增:`erp-server` 订阅消息模板注册 + 定时推送任务
---
## 5. Sprint 3报告/随访/个人中心 + 安全 + 增长
**目标**:打磨剩余模块,完成安全加固和增长基础建设,达到可测试状态。
### 5.1 报告详情页升级
**现状**:所有指标卡片样式相同,无法一眼区分正常/异常。
**方案**
指标卡片按状态着色:
- **正常**:绿色背景 + 绿色"✓ 正常"标签 + 绿色数值
- **偏高**:红色背景 + 红色"↑ 偏高"标签 + 红色数值
- **偏低**:红色背景 + 红色"↓ 偏低"标签 + 红色数值
顶部汇总标签:`2 项异常 · 1 项正常`,一眼掌握整体状况。
**涉及文件**
- 修改:`pages/report/detail/index.tsx`
- 修改:`pages/profile/reports/index.tsx`(如果仍独立存在)
### 5.2 随访 UX 细节
- 任务卡片增加截止日期倒计时("还剩 2 天",红色紧迫)
- 过期任务灰色标记
- 提交记录后增加"提交成功"确认动画checkmark 缩放)
**涉及文件**
- 修改:`pages/profile/followups/index.tsx`
- 修改:`pages/followup/detail/index.tsx`
### 5.3 个人中心改进
**用药提醒**
- 实现时间选择器 Picker替换当前静态文本
- 增加"提醒开关"enabled/disabled
- 注:用药提醒数据仍为本地 Storage 存储,**后端同步作为后续版本事项**。MVP 阶段接受"换设备即丢失"的限制。
**就诊人管理**
- 增加编辑功能(当前只能添加不能编辑)
- 复用 `family-add` 页面,传入已有数据进入编辑模式
**涉及文件**
- 修改:`pages/profile/medication/index.tsx`(时间 Picker
- 修改:`pages/profile/family/index.tsx`(编辑入口)
- 修改:`pages/profile/family-add/index.tsx`(编辑模式支持)
### 5.4 安全加固
#### 5.4.1 Token 安全
**现状**Access Token 和 Refresh Token 明文存储在 `Taro.setStorageSync`
**方案**
MVP 阶段采用简化方案:微信小程序的 Storage 本身有沙箱隔离,明文存储的边际风险有限。做以下最低成本改进:
- 使用 `wx.getRandomValues()` 生成随机密钥,单独 key 存储
- Token 存储时用此密钥做简单混淆XOR 或 AES-ECB 单块加密)
- 目的:防止 Storage 被直接明文读取,非追求密码学安全级别
> **后续版本**:如果合规要求提高,再升级为完整的 AES-GCM 方案。
**涉及文件**
- 新增:`utils/crypto.ts`(轻量混淆工具)
- 修改:`stores/auth.ts`Storage 读写走混淆层)
#### 5.4.2 手机号真实解密
**现状**`wechat_service.rs` 第 82 行硬编码 `"13800000000"`
**方案**
- 后端接入微信 `phonenumber.getPhoneNumber` 接口
- 使用 `encryptedData` + `iv` + `session_key` 解密真实手机号
- 前端无需改动(已传递正确的 encryptedData 和 iv
**涉及文件**
- 修改:`crates/erp-auth/src/service/wechat_service.rs`
#### 5.4.3 用户协议与隐私政策
- 新增 `pages/agreement/index.tsx` 页面
- 登录页增加"阅读并同意《用户协议》和《隐私政策》"勾选
- 权限使用说明文案(获取手机号用途声明)
**涉及文件**
- 新增:`pages/agreement/index.tsx`
- 修改:`pages/login/index.tsx`(协议勾选)
- 修改:`app.config.ts`(新增路由)
### 5.5 增长基础
#### 5.5.1 数据埋点
新增 `services/analytics.ts`,轻量事件记录:
```typescript
// 核心事件类型
type AnalyticsEvent =
| { type: 'page_view'; page: string; duration_ms?: number }
| { type: 'feature_use'; feature: string; action: string }
| { type: 'error'; message: string; stack?: string }
```
- 页面进入/离开自动记录 `page_view`
- 关键操作(录入数据、创建预约、提交随访)记录 `feature_use`
- 捕获的错误记录 `error`
- MVP 阶段:事件写入本地 `console.info` + Taro Storage 缓存(最近 100 条)
- 后续版本:批量上报到后端 `POST /api/v1/analytics/events`
**涉及文件**
- 新增:`services/analytics.ts`
- 修改:`app.tsx`(全局页面进入/离开监听)
#### 5.5.2 分享能力
- 文章详情页支持分享到微信好友/朋友圈
- 自定义分享卡片(标题 + 摘要 + 封面图)通过 `onShareAppMessage``onShareTimeline` 实现
> **后续版本**:健康报告 Canvas 分享图片生成、PC 扫码登录。
**涉及文件**
- 修改:`pages/article/detail/index.tsx`onShareAppMessage + onShareTimeline
---
## 6. 文件变更总览
### 新增文件
| 文件 | 说明 | Sprint |
|------|------|--------|
| `components/ErrorBoundary/index.tsx` | 全局错误边界 | 0 |
| `components/TrendChart/index.tsx` | ECharts 趋势图 | 1 |
| `components/StepIndicator/index.tsx` | 步骤指示器 | 2 |
| `components/WeekCalendar/index.tsx` | 周视图日历 | 2 |
| `pages/agreement/index.tsx` | 用户协议/隐私政策 | 3 |
| `utils/crypto.ts` | Token 加密工具 | 3 |
| `services/analytics.ts` | 数据埋点 | 3 |
### 删除文件
| 文件 | 原因 | Sprint |
|------|------|--------|
| `pages/report/index.tsx` | 与 profile/reports 重复 | 0 |
| `pages/report/index.scss` | 同上 | 0 |
| `pages/followup/index.tsx` | 与 profile/followups 重复 | 0 |
| `pages/followup/index.scss` | 同上 | 0 |
### 新增依赖
| 依赖 | 用途 | 体积 | Sprint |
|------|------|------|--------|
| `echarts-taro3-react` | 交互式图表 | 封装层 ~5KB + echarts 按需 ~100-200KB (gzip) | 1 |
| `zod` | 表单 schema 验证(长期投资) | ~3KB (gzip) | 1 |
---
## 7. 约束与风险
| 风险 | 应对策略 |
|------|---------|
| ECharts 增大包体积 | 按需引入 echarts 模块,不引入全量包;监控主包大小不超过 2MB |
| 微信订阅消息需用户主动触发 | 在预约成功、随访提交等高意愿场景引导订阅 |
| Token 加密增加启动耗时 | AES-GCM 加解密 < 1ms可忽略 |
| zod 增加包体积 | 3KB gzip远小于自写验证代码量 |
| Sprint 0 范围膨胀 | 严格只修不建,不引入新依赖,不重构架构 |
| 后端端点未实现阻塞前端 | Sprint 0/1 的端点基本已实现Sprint 2 订阅消息、Sprint 3 analytics 需后端配合 |
---
## 8. 验收标准
每个 Sprint 完成时必须满足:
- [ ] `pnpm build:weapp` 生产构建通过
- [ ] 微信开发者工具无编译错误
- [ ] 所有涉及页面真机预览功能正常
- [ ] 无 console.error 或未捕获异常
- [ ] 已修改的页面 loading/error/empty 三态完整
- [ ] 所有代码已提交