设计规格: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 无条件调用修复
19 KiB
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,通过 JWTuser_id自动关联 patient(类似已有的GET /health/vital-signs/trend模式) - 前端
services/health.ts的getTodaySummary()调整为调用新端点 - 此项为 Sprint 0 最高优先级,阻塞首页和健康页基本功能
涉及文件:
- 后端新增:
erp-healthhandler + 路由注册 - 修改:
services/health.ts
2.2 消除重复页面
现状:pages/report/index 与 pages/profile/reports/index 几乎完全重复,pages/followup/index 与 pages/profile/followups/index 同理。且 report/index 和 followup/index 没有明确的导航入口。
方案:
- 删除
pages/report/index和pages/followup/index及其 SCSS 文件 - 从
app.config.ts移除对应路由注册 - 首页快捷入口和 profile 菜单统一指向
profile/reports和profile/followups - 如果后续需要独立入口,则抽取共享组件
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 错误提示。无全局错误边界。
方案:
- 所有列表页、详情页统一使用
ErrorState组件,替换内联错误提示 - 在
app.tsx添加 React Error Boundary 组件,兜底页面崩溃 - 新建
components/ErrorBoundary/index.tsx - 修复
tryRefreshToken的 catch 块,添加console.error日志
涉及文件:
- 新增:
components/ErrorBoundary/index.tsx - 修改:
app.tsx(包裹 ErrorBoundary) - 修改:所有列表页和详情页(替换内联错误处理为 ErrorState)
- 修改:
services/request.ts(tryRefreshToken 日志)
2.3 修复数据传递问题
预约详情:
- 移除
appointment_detail_cacheStorage 传递 - 改为进入页面时通过
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>。
方案:
- 首页和健康页在数据加载时展示
Loading组件 - 所有详情页统一使用
Loading组件替换内联文字 - 预约创建页三步骤切换时也展示 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:
// 示例:体征录入验证
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 天推送)
- 报告出具通知(新报告发布时推送)
-
前端在关键场景引导用户订阅:
- 预约成功后弹出订阅授权
- 随访提交后引导订阅下次提醒
-
后端定时任务检查待推送消息并触发
-
降级设计:用户拒绝订阅时,消息仍写入
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,轻量事件记录:
// 核心事件类型
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 三态完整
- 所有代码已提交