diff --git a/docs/superpowers/specs/2026-05-21-miniprogram-production-ready-design.md b/docs/superpowers/specs/2026-05-21-miniprogram-production-ready-design.md
index 4d30cca..858793f 100644
--- a/docs/superpowers/specs/2026-05-21-miniprogram-production-ready-design.md
+++ b/docs/superpowers/specs/2026-05-21-miniprogram-production-ready-design.md
@@ -26,7 +26,7 @@
| 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 切换可能丢失对话 |
+| C8 | AI 聊天持久化需验证 | `messages/index.tsx` | wiki 标记已修复(后端消息加载 API 已添加),需验证实际效果 |
### HIGH(11 项)
@@ -35,7 +35,7 @@
| 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` |
+| 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` |
@@ -66,17 +66,13 @@ export function secureGet(key: string): string {
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;
- }
+ return xorEncrypt(decoded, ENCRYPTION_KEY);
}
} catch {
// fallthrough — 可能是未加密的旧数据
}
- // fallback: 兼容未加密的旧数据
+ // fallback: 兼容未加密的旧数据(明文 JSON/JWT 或其他值)
return raw;
}
```
@@ -86,10 +82,12 @@ export function secureGet(key: string): string {
### P0-2. 修复 Storage Key 前缀不一致
**文件**:
-- `src/services/request.ts` 第 149 行: `Taro.getStorageSync('current_patient_id')` → `secureGet('current_patient_id')`
+- `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 用明文存储)
+- `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. 验证
@@ -103,36 +101,24 @@ export function secureGet(key: string): string {
### P1-1. 咨询创建添加症状描述
-**文件**: `src/pages/consultation/create/index.tsx`
+**文件**:
+- `src/pages/consultation/create/index.tsx` — 添加 UI 字段
+- `src/services/consultation.ts` — `createSession` 函数类型签名添加 `description` 参数
**改动**:
- 添加多行 `` 字段(placeholder: "请描述您的症状或问题")
- 标记为建议填写(非必填),但未填写时 Toast 提醒"建议描述症状以便医生更快响应"
+- `services/consultation.ts` 的 `createSession` 参数类型扩展,添加 `description?: string`
- 提交时将描述传递给后端 API(后端 DTO 已支持 `description` 字段)
### P1-2. 体征录入统一范围校验
-**新建文件**: `src/utils/vital-ranges.ts`
+**文件**: `src/pages/health/index.tsx`, `src/pages/pkg-health/input/index.tsx`
-```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` 复用同一校验函数(移除内联校验逻辑)。
+**改动**:
+- 复用 `input/index.tsx` 中已有的 `num()` 校验函数(非创建新文件),提取到 `src/utils/vital-validation.ts`
+- `health/index.tsx` 第 112-115 行,调用提取后的 `num()` 替代简单的非空检查
+- 保留 `input/index.tsx` 中的动态阈值警告逻辑(`getWarnForIndicator`),不降级为静态范围
### P1-3. 积分任务入口修复
@@ -155,15 +141,15 @@ export function validateVital(type: keyof typeof VITAL_RANGES, value: number): s
**文件**: `src/pages/pkg-doctor-core/index.tsx`
**改动**:
-- `loadDashboard` catch 中设置 `setDashboard({})` 而非仅打印 warn
-- loading 为 false 且 dashboard 为 null 时,显示降级 UI(统计卡片显示 `-`)+ 错误提示 + 重试按钮
-- 不再永久卡在 ``
+- `loadDashboard` catch 中 `setDashboard({})` 确保组件不卡在永久 Loading
+- loading 为 false 且 dashboard 为空对象时,统计卡片显示 `-`(当前已实现)
+- 添加错误状态提示和重试按钮(当前缺少)
### P1-5. 首页医护人员跳转优化
**文件**: `src/pages/index/index.tsx`
-**改动**: 将 `shouldRedirect` 计算提前到组件顶层(useMemo),在 `useDidShow` 之前就确定跳转。跳转使用 `redirectTo` 替代 `reLaunch`,保留分包预加载状态。
+**改动**: 将 `shouldRedirect` 计算提前到组件顶层(useMemo),在 `useDidShow` 之前就确定跳转。**注意**:首页是 TabBar 页面,`redirectTo` 替换 TabBar 页面可能导致 TabBar 消失,需改为在 `useDidShow` 中使用 `reLaunch`(保留现有方式),但提前判断避免先渲染 Loading 再跳转。
---
@@ -238,8 +224,10 @@ export function validateVital(type: keyof typeof VITAL_RANGES, value: number): s
### P3-2. 安全加固
**开发快速登录保护**:
-- `login/index.tsx` 第 8-9 行: 添加编译时保护
-- `config/prod.ts` 中确保 `TARO_APP_DEV_USER` 和 `TARO_APP_DEV_PASS` 注入为空字符串
+- `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` 字段提取