Files
hms/docs/superpowers/specs/2026-05-21-miniprogram-production-ready-design.md
iven 43795b2fb7 docs(spec): 修正 spec review 反馈的 6 个问题
- secureGet 修复移除无意义的 length > 0 验证
- P0-2 补充 auth.ts logout 中 current_patient_id 清理链路
- P1-1 补充 consultation.ts service 层类型修改
- P1-2 改为复用 input/index.tsx 已有的 num() 校验
- H4 修正医生端描述(非卡死,缺重试)
- C7 修正开发登录保护方案(IS_SIMULATOR 体验版泄漏)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 15:25:57 +08:00

13 KiB
Raw Blame History

小程序上线前全面改进设计规格

日期: 2026-05-21 | 状态: Draft | 分支: feat/media-library-banner 方法: MCP 实操审查 + 3 专家组并行分析UX/前端工程/产品安全)

背景

通过 MCP 连接微信开发者工具实际操作 59 个页面,配合 3 个专家组UX/前端工程/产品安全)并行源码级审查,发现 8 CRITICAL / 11 HIGH / 22 MEDIUM 问题。本规格定义 5 个 Phase 的改进方案,目标 1-2 周内达到可公开测试标准。

审查方法

  • MCP 实操: 连接微信 DevToolsiPhone 12 Pro 模拟器),注入 admin 认证,逐页检查加载/交互/错误
  • 自动化审计: audit_all 批量检测 59 页面健康状态35 OK / 1 CRASH / 23 路径不匹配)
  • 3 专家组并行: 每组阅读 13-20 个核心源文件,按 CRITICAL/HIGH/MEDIUM/LOW 分级输出

发现汇总(去重后 TOP 问题)

CRITICAL8 项)

# 问题 文件 影响
C1 secureGet 解密逻辑失效 secure-storage.ts:60 用户关闭小程序后重开必须重新登录
C2 Storage key 前缀不一致 request.ts:149, health.ts:20 current_patient_id 写加密读明文X-Patient-Id header 缺失
C3 analytics_queue 前缀不一致 analytics.ts:38 vs auth.ts:266 logout 无法清除分析队列PII 泄漏风险
C4 咨询创建无症状描述输入 consultation/create/index.tsx 医生收到空咨询请求
C5 缺少紧急求助/SOS 入口 index.tsx HomeDashboard 患者突发状况无法快速求助
C6 Token 存储弱加密XOR + 硬编码 fallback secure-storage.ts:3 越狱设备可直接恢复 JWT token
C7 开发快速登录可能泄露到生产 login/index.tsx:8-9 攻击者获取 dev 凭证
C8 AI 聊天持久化需验证 messages/index.tsx wiki 标记已修复(后端消息加载 API 已添加),需验证实际效果

HIGH11 项)

# 问题 文件
H1 积分任务按钮无 onClick mall/index.tsx:187
H2 告警无微信推送能力 alerts/index.tsx
H3 体征录入校验不一致 health/index.tsx:112 vs input/index.tsx:33
H4 医生端工作台 API 失败无重试 pkg-doctor-core/index.tsx
H5 家属代理操作无权限模型 family/index.tsx:47, auth.ts:226
H6 首页医护人员 reLaunch 状态丢失 index.tsx:319-329
H7 PII 明文绕过 secure storage family/index.tsx:63
H8 化验报告缺少指标解读 reports/index.tsx:57
H9 趋势图缺少交互和 AI 解读 trend/index.tsx
H10 预约无防恶意占用机制 appointment/create/index.tsx
H11 API 无请求签名 request.ts:154

Phase 0Day 1基础设施修复

P0-1. 修复 secureGet 解密逻辑

文件: src/utils/secure-storage.ts

问题: secureGet 第 60 行 if (raw.startsWith('{') || raw.startsWith('eyJ')) 条件判断错误。secureSet 存储的 XOR 加密 + base64 编码数据通常以 A-Za-z0-9 开头,不以 {eyJ 开头,导致解密分支永远不执行。

修复:

export function secureGet(key: string): string {
  const prefixedKey = STORAGE_PREFIX + key;
  const raw = Taro.getStorageSync(prefixedKey);
  if (!raw || typeof raw !== 'string') return '';

  // 始终尝试 base64 解码 + XOR 解密secureSet 的写入格式)
  try {
    const decoded = fromBase64(raw);
    if (decoded) {
      return xorEncrypt(decoded, ENCRYPTION_KEY);
    }
  } catch {
    // fallthrough — 可能是未加密的旧数据
  }

  // fallback: 兼容未加密的旧数据(明文 JSON/JWT 或其他值)
  return raw;
}

验证: 登录 → 完全关闭小程序 → 重新打开 → 确认自动恢复登录态(非访客页面)

P0-2. 修复 Storage Key 前缀不一致

文件:

  • src/services/request.ts 第 149 行: Taro.getStorageSync('current_patient_id')secureGet('current_patient_id')(先修此处,确保内存缓存正确填充)
  • src/services/request.ts 第 218 行: Taro.removeStorageSync('current_patient_id')secureRemove('current_patient_id')
  • src/services/health.ts 第 20 行: 改用 getCachedPatientId()(依赖 request.ts 的内存缓存)
  • src/stores/auth.ts 第 264-265 行: secureRemove('current_patient') / secureRemove('current_patient_id') 需同步确认写入路径一致
  • src/stores/auth.ts 第 266 行: secureRemove('analytics_queue')Taro.removeStorageSync('analytics_queue')analytics 使用明文存储,清理也必须用明文)
  • src/services/analytics.ts: 确认 analytics_queue 包含 userId/patientIdlogout 时必须清除PII 泄漏风险)

P0-3. 验证

  • 注入认证 → reLaunch → 首页正确显示登录态
  • 医生端工作台数据正常加载
  • 请求头 X-Patient-Id 正确携带

Phase 1Day 2-4核心体验修复

P1-1. 咨询创建添加症状描述

文件:

  • src/pages/consultation/create/index.tsx — 添加 UI 字段
  • src/services/consultation.tscreateSession 函数类型签名添加 description 参数

改动:

  • 添加多行 <Input type='textarea'> 字段placeholder: "请描述您的症状或问题"
  • 标记为建议填写(非必填),但未填写时 Toast 提醒"建议描述症状以便医生更快响应"
  • services/consultation.tscreateSession 参数类型扩展,添加 description?: string
  • 提交时将描述传递给后端 API后端 DTO 已支持 description 字段)

P1-2. 体征录入统一范围校验

文件: src/pages/health/index.tsx, src/pages/pkg-health/input/index.tsx

改动:

  • 复用 input/index.tsx 中已有的 num() 校验函数(非创建新文件),提取到 src/utils/vital-validation.ts
  • health/index.tsx 第 112-115 行,调用提取后的 num() 替代简单的非空检查
  • 保留 input/index.tsx 中的动态阈值警告逻辑(getWarnForIndicator),不降级为静态范围

P1-3. 积分任务入口修复

文件: src/pages/mall/index.tsx 第 187-192 行

方案: 暂时隐藏未实现的"积分任务"入口,仅保留"签到打卡"和"兑换记录"。后续实现后恢复显示。

- <View className='mall-action'>
-   <View className='mall-action-icon mall-action-icon--task'>
-     <Text className='mall-action-icon-text'>★</Text>
-   </View>
-   <Text className='mall-action-label'>积分任务</Text>
- </View>
+ {/* TODO: 积分任务功能待实现后恢复 */}

P1-4. 医生端首页加载优化

文件: src/pages/pkg-doctor-core/index.tsx

改动:

  • loadDashboard catch 中 setDashboard({}) 确保组件不卡在永久 Loading
  • loading 为 false 且 dashboard 为空对象时,统计卡片显示 -(当前已实现)
  • 添加错误状态提示和重试按钮(当前缺少)

P1-5. 首页医护人员跳转优化

文件: src/pages/index/index.tsx

改动: 将 shouldRedirect 计算提前到组件顶层useMemouseDidShow 之前就确定跳转。注意:首页是 TabBar 页面,redirectTo 替换 TabBar 页面可能导致 TabBar 消失,需改为在 useDidShow 中使用 reLaunch(保留现有方式),但提前判断避免先渲染 Loading 再跳转。


Phase 2Day 5-8功能补全

P2-1. 紧急求助入口

文件: src/pages/index/index.tsx HomeDashboard 组件

改动:

  • 添加固定悬浮 SOS 按钮(右下角,红色圆形,直径 56px
  • 点击弹出确认弹窗:"是否拨打机构急救电话 xxx-xxxx"
  • 电话号码从后端配置 API 获取fallback 为租户配置的电话
  • 仅在患者登录状态显示admin/doctor 不显示)

P2-2. 告警微信推送订阅

文件: src/pages/pkg-health/alerts/index.tsx

改动:

  • 页面加载时调用 Taro.requestSubscribeMessage({ tmplIds: [CRITICAL_ALERT_TEMPLATE_ID] })
  • 复用 process.env.TARO_APP_WX_TEMPLATE_CRITICAL_ALERT 模板 ID
  • 用户拒绝时不阻塞页面使用,仅在 subscribeStatus 中记录
  • critical 级别告警列表项显示"已开启推送"或"点击开启推送"标识

P2-3. 趋势图交互增强

文件: src/pages/pkg-health/trend/index.tsx

改动:

  • ECharts 配置添加 tooltip 组件(trigger: 'axis'),点击/长按显示具体日期和数值
  • 血压趋势图添加收缩压+舒张压双线series 两条线)
  • 阈值区间以阴影区域显示markArea

P2-4. 个人中心菜单优化

文件: src/pages/profile/index.tsx

改动:

  • 17 项菜单分为 4 组,每组有分组标题:
健康档案: 健康记录 / 我的报告 / AI 分析 / 诊断记录 / 用药记录
就诊服务: 我的预约 / 我的随访 / 在线咨询
透析管理: 透析记录 / 透析处方 / 知情同意
账号设置: 积分商城 / 线下活动 / 就诊人管理 / 长辈模式 / 设备同步 / 设置
  • 图标:汉字替换为 <Text className='iconfont icon-xxx'> 或用 antd-mini 图标
  • 每组之间添加 12px 间距和分割线

P2-5. 家属管理安全修复

文件: src/pages/pkg-profile/family/index.tsx

改动:

  • edit_patient 数据改用 secureSet('edit_patient', JSON.stringify(patient)) 存储
  • 编辑页从 API 获取最新患者数据(GET /health/patients/{id}),不完全依赖 Storage 中的旧数据
  • Storage 数据添加 5 分钟过期检查

Phase 3Day 9-12品质打磨

P3-1. 老年模式交互增强

  • 所有可点击元素最小尺寸 56px通过 elder-mode CSS 变量覆盖)
  • 体征录入页:数值输入区增加 +/- 按钮辅助(每次步进 1
  • TabBar 图标字号 elder-mode 下放大 120%
  • 考虑 P2 迭代添加语音输入(本次不实现,仅预留接口)

P3-2. 安全加固

开发快速登录保护:

  • login/index.tsx 第 8-9 行: IS_DEVIS_SIMULATOR 已有保护
  • 实际风险:体验版(envVersion === 'trial')也会显示开发登录按钮(IS_SIMULATOR 为 true
  • 修复:第 9 行改为 typeof __wxConfig !== 'undefined' && (__wxConfig as any).envVersion === 'develop'(仅开发版显示)
  • config/index.tsTARO_APP_DEV_USER/PASS 已有空字符串 fallback无需额外修改 prod.ts

Tenant ID 安全:

  • request.ts 第 161 行: X-Tenant-Id 改为从 JWT payload 的 tid 字段提取
  • 不再从 Storage 读取 tenant_id

密码输入框:

  • login/index.tsx 第 160 行: type="text" 改为 type="safe-password"

预约防恶意占用:

  • appointment/create/index.tsx: 备注字段 maxlength=200
  • 提交按钮添加 loading 态防重复点击

P3-3. 空状态和错误处理统一

  • 所有列表页空状态统一使用 EmptyState 组件
  • Loading 组件拆分:<Loading /> 仅用于加载中,新增 <ListEnd text="没有更多了" /> 组件(无旋转动画)
  • API 失败统一显示 <ErrorRetry message="加载失败" onRetry={loadData} />

P3-4. ErrorBoundary 增强

  • App 级 ErrorBoundary 添加最大重试次数3 次),超限显示"请重启小程序"
  • 咨询详情、健康数据、AI 聊天 3 个高频页面添加独立 ErrorBoundary
  • componentDidCatch 调用 trackEvent('error_boundary', { error: String(error) })

P3-5. 访客首页优化

  • "查看全部"改为跳转 /pages/article/index(文章列表页)
  • "健康资讯"区域调用公开 API/api/v1/health/public/articles)展示真实文章
  • 3 个静态卡片(健康数据管理/积分商城/专业科普)改为动态文章数据

Phase 4Day 13-14全量回归测试

验证清单

  • MCP audit_all 59 页面全部 OK0 CRASH / 0 ERROR
  • 4 角色测试admin / doctor / patient / family
  • 长者模式全页面验证58/58 页面可操作)
  • 并发测试:快速 Tab 切换 10 次 + 长轮询运行 + 5 个并发 API
  • 生产构建:pnpm build 通过 + 主包 < 2MB + 分包各 < 2MB
  • secureGet 往返测试:登录 → 关闭 → 重开 → 确认自动恢复
  • 紧急求助功能端到端测试
  • 告警推送订阅流程测试

预期交付物

Phase 天数 交付物
Phase 0 1 secureGet 修复 + Storage key 一致性
Phase 1 3 核心体验修复(咨询/体征/商城/医生端/跳转)
Phase 2 4 功能补全SOS/推送/趋势图/菜单/家属)
Phase 3 4 品质打磨(老年模式/安全/空状态/ErrorBoundary
Phase 4 2 全量回归测试报告
合计 14 可公开测试版本

风险和缓解

风险 概率 缓解措施
secureGet 修复影响旧版本用户 migrateLegacyStorage 兼容两种格式
后端缺少部分 API告警推送模板、紧急电话 Phase 2 后端同步开发,前端先 mock
ECharts 双线血压趋势性能问题 限制数据点数量(最多 30 天)
微信审核拒绝SOS 按钮、订阅消息) SOS 改为"联系机构"文案,订阅消息仅 critical 级别