Files
hms/docs/superpowers/specs/2026-05-21-miniprogram-production-ready-design.md
iven ce0561001f docs(spec): 小程序上线前全面改进设计规格
5 Phase / 14 天改进方案,基于 MCP 实操审查 + 3 专家组并行分析
(UX/前端工程/产品安全),涵盖 8 CRITICAL / 11 HIGH / 22 MEDIUM 问题

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

309 lines
12 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` | Tab 切换可能丢失对话 |
### HIGH11 项)
| # | 问题 | 文件 |
|---|------|------|
| H1 | 积分任务按钮无 onClick | `mall/index.tsx:187` |
| H2 | 告警无微信推送能力 | `alerts/index.tsx` |
| H3 | 体征录入校验不一致 | `health/index.tsx:112` vs `input/index.tsx:33` |
| H4 | 医生端工作台加载卡死 | `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) {
const decrypted = xorEncrypt(decoded, ENCRYPTION_KEY);
// 验证解密结果是否为有效数据JSON 或 JWT
if (decrypted.startsWith('{') || decrypted.startsWith('eyJ') || decrypted.length > 0) {
return decrypted;
}
}
} catch {
// fallthrough — 可能是未加密的旧数据
}
// fallback: 兼容未加密的旧数据
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 行: `Taro.getStorageSync('current_patient_id')` → 改用 `getCachedPatientId()``secureGet('current_patient_id')`
- `src/stores/auth.ts` 第 266 行: `secureRemove('analytics_queue')``Taro.removeStorageSync('analytics_queue')`analytics 用明文存储)
### P0-3. 验证
- 注入认证 → reLaunch → 首页正确显示登录态
- 医生端工作台数据正常加载
- 请求头 `X-Patient-Id` 正确携带
---
## Phase 1Day 2-4核心体验修复
### P1-1. 咨询创建添加症状描述
**文件**: `src/pages/consultation/create/index.tsx`
**改动**:
- 添加多行 `<Input type='textarea'>` 字段placeholder: "请描述您的症状或问题"
- 标记为建议填写(非必填),但未填写时 Toast 提醒"建议描述症状以便医生更快响应"
- 提交时将描述传递给后端 API后端 DTO 已支持 `description` 字段)
### P1-2. 体征录入统一范围校验
**新建文件**: `src/utils/vital-ranges.ts`
```typescript
export const VITAL_RANGES = {
systolic_bp: { min: 60, max: 250, label: '收缩压' },
diastolic_bp: { min: 40, max: 150, label: '舒张压' },
heart_rate: { min: 20, max: 300, label: '心率' },
blood_glucose: { min: 1, max: 50, label: '血糖' },
weight: { min: 1, max: 500, label: '体重' },
} as const;
export function validateVital(type: keyof typeof VITAL_RANGES, value: number): string | null {
const range = VITAL_RANGES[type];
if (value < range.min || value > range.max) {
return `${range.label}应在 ${range.min}-${range.max} 之间`;
}
return null;
}
```
**修改文件**: `src/pages/health/index.tsx` 第 112-115 行,调用 `validateVital()` 替代简单的非空检查。`src/pages/pkg-health/input/index.tsx` 复用同一校验函数(移除内联校验逻辑)。
### 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({})` 而非仅打印 warn
- loading 为 false 且 dashboard 为 null 时,显示降级 UI统计卡片显示 `-`+ 错误提示 + 重试按钮
- 不再永久卡在 `<Loading />`
### P1-5. 首页医护人员跳转优化
**文件**: `src/pages/index/index.tsx`
**改动**: 将 `shouldRedirect` 计算提前到组件顶层useMemo`useDidShow` 之前就确定跳转。跳转使用 `redirectTo` 替代 `reLaunch`,保留分包预加载状态。
---
## 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 行: 添加编译时保护
- `config/prod.ts` 中确保 `TARO_APP_DEV_USER``TARO_APP_DEV_PASS` 注入为空字符串
**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 级别 |