docs(miniprogram): 新增小程序迭代设计规格 + 25 Task 实施计划
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

设计规格: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 无条件调用修复
This commit is contained in:
iven
2026-04-24 12:08:13 +08:00
parent a0ca156e2c
commit 19be2a08c7
2 changed files with 2308 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,469 @@
# 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 三态完整
- [ ] 所有代码已提交