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

297 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 小程序上线前全面改进设计规格
> 日期: 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` 开头,导致解密分支永远不执行。
**修复**:
```typescript
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.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 行
**方案**: 暂时隐藏未实现的"积分任务"入口,仅保留"签到打卡"和"兑换记录"。后续实现后恢复显示。
```diff
- <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` 计算提前到组件顶层useMemo`useDidShow` 之前就确定跳转。**注意**:首页是 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_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 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 级别 |