# 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 层消费。详情页使用内联 `加载中...`。 **方案**: 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 三态完整 - [ ] 所有代码已提交