- 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>
13 KiB
小程序上线前全面改进设计规格
日期: 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 实操: 连接微信 DevTools(iPhone 12 Pro 模拟器),注入 admin 认证,逐页检查加载/交互/错误
- 自动化审计:
audit_all批量检测 59 页面健康状态(35 OK / 1 CRASH / 23 路径不匹配) - 3 专家组并行: 每组阅读 13-20 个核心源文件,按 CRITICAL/HIGH/MEDIUM/LOW 分级输出
发现汇总(去重后 TOP 问题)
CRITICAL(8 项)
| # | 问题 | 文件 | 影响 |
|---|---|---|---|
| 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 已添加),需验证实际效果 |
HIGH(11 项)
| # | 问题 | 文件 |
|---|---|---|
| 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 0(Day 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/patientId,logout 时必须清除(PII 泄漏风险)
P0-3. 验证
- 注入认证 → reLaunch → 首页正确显示登录态
- 医生端工作台数据正常加载
- 请求头
X-Patient-Id正确携带
Phase 1(Day 2-4):核心体验修复
P1-1. 咨询创建添加症状描述
文件:
src/pages/consultation/create/index.tsx— 添加 UI 字段src/services/consultation.ts—createSession函数类型签名添加description参数
改动:
- 添加多行
<Input type='textarea'>字段(placeholder: "请描述您的症状或问题") - 标记为建议填写(非必填),但未填写时 Toast 提醒"建议描述症状以便医生更快响应"
services/consultation.ts的createSession参数类型扩展,添加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
改动:
loadDashboardcatch 中setDashboard({})确保组件不卡在永久 Loading- loading 为 false 且 dashboard 为空对象时,统计卡片显示
-(当前已实现) - 添加错误状态提示和重试按钮(当前缺少)
P1-5. 首页医护人员跳转优化
文件: src/pages/index/index.tsx
改动: 将 shouldRedirect 计算提前到组件顶层(useMemo),在 useDidShow 之前就确定跳转。注意:首页是 TabBar 页面,redirectTo 替换 TabBar 页面可能导致 TabBar 消失,需改为在 useDidShow 中使用 reLaunch(保留现有方式),但提前判断避免先渲染 Loading 再跳转。
Phase 2(Day 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 3(Day 9-12):品质打磨
P3-1. 老年模式交互增强
- 所有可点击元素最小尺寸 56px(通过
elder-modeCSS 变量覆盖) - 体征录入页:数值输入区增加 +/- 按钮辅助(每次步进 1)
- TabBar 图标字号 elder-mode 下放大 120%
- 考虑 P2 迭代添加语音输入(本次不实现,仅预留接口)
P3-2. 安全加固
开发快速登录保护:
login/index.tsx第 8-9 行:IS_DEV和IS_SIMULATOR已有保护- 实际风险:体验版(
envVersion === 'trial')也会显示开发登录按钮(IS_SIMULATOR为 true) - 修复:第 9 行改为
typeof __wxConfig !== 'undefined' && (__wxConfig as any).envVersion === 'develop'(仅开发版显示) config/index.ts中TARO_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 4(Day 13-14):全量回归测试
验证清单
- MCP
audit_all59 页面全部 OK(0 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 级别 |