Procházet zdrojové kódy

docs: 综合健康调研设计(8模块step式+URL传参模块组合+组件复用方案制定Step2)

iwt před 1 týdnem
rodič
revize
1e623550cd

+ 1 - 0
docs/superpowers/PROJECT-OVERVIEW.md

@@ -382,6 +382,7 @@
 | `2026-09-19-article-reading-quiz-design.md` | 🟢 已实施(补全现有阅读答题:article_quiz_records 表 + LangGraph article_quiz_graph 出题 + AiGateway 接入 + 提交答题落库 + CF值/五维能量双奖励 + profile_snapshot.quiz_summary 认知雷达) | 文章阅读后答题奖励 + 认知雷达记录设计 |
 | `2026-09-22-guide-popup-config-design.md` | ✅ 已实施(4表/guide_popup+step+trigger+log,9接口,ConfigurableGuide.vue组件+管理端2页;旧 sys_config 方案保留并行) | 弹窗引导页配置化管理设计 |
 | `2026-10-10-address-contact-backfill-realname-design.md` | 🟡 设计稿(后端统一回填方案:`RealNameBackfillService` 独立组件 + `UserAddressService.create/update` + `ConsigneeService.save` 两入口回填,realName 为空才回填,幂等) | 地址联系人回填真实姓名设计 |
+| `2026-10-16-comprehensive-health-survey-design.md` | 🟡 设计稿(8 模块 step 式综合调研:基本情况/当前状态/饮食偏好/疾病史/排便运动/睡眠/用药/过敏源;URL `modules=` 传参可只调研子集;ComprehensiveSurvey 组件复用至方案制定 Step2;仅当前成员;合并式增量保存;lifestyle 新 key currentStatus/exerciseFrequency/exerciseDuration;过敏源独立完整采集;饮食结构 3 项补采;零后端改动) | 综合健康调研 — 身体状态+饮食偏好合并 |
 
 ### 实施计划(plans/)
 

+ 181 - 0
docs/superpowers/specs/2026-10-16-comprehensive-health-survey-design.md

@@ -0,0 +1,181 @@
+# 综合健康调研(Comprehensive Health Survey)设计
+
+**日期:** 2026-10-16
+**状态:** 待审查
+**优先级:** P1
+**范围:** 纯前端(cfc-frontend),零后端改动、零数据库迁移
+
+## 一、背景与目标
+
+### 现状问题
+
+1. **调研入口割裂**:饮食偏好调研(`pages/diet/preferences.vue`,3 步问卷)与身体状态调研(`pages/health/health-status-form.vue`,健康档案页)是两套独立页面、两套交互,用户要分别填写。
+2. **入口跳转错位**:首页(`index-home` 自测大卡 + `health-main` 自检卡)的 `goSelfTest` 在无待填调研时直接跳方案制定页(`health-plan-summary`)——用户只是想做健康调研,却被带进"选成员→填问卷→生成方案→逐板块确认"的完整方案制定流程,无法只做调研。
+3. **数据分散**:身体状态走 `health_status` 表,饮食偏好走 `diet_preferences` 表;过敏原在 `diet_preferences.allergies`,过敏症状在 `health_status.lifestyle.allergySymptoms`;`health_status.lifestyle` JSON 无运动字段、无特殊状态字段。
+4. **采集缺口**:`diet_preferences` DTO 已有 `vegetableFruit/dairy/sugaryDrinks`(饮食结构)三字段但页面从未采集。
+
+### 目标(已确认)
+
+- **合并调研**:将身体状态 + 饮食偏好合并为一个 **8 模块的 step 式综合调研**,可**按需只调研其中几部分**(由调用方通过 URL 传参指定模块)。
+- **独立入口**:首页顶部调研入口(`index-home` + `health-main` 两处 `goSelfTest`)在无待填调研时跳转新的独立调研页——**仅调研,不进入方案制定**。
+- **方案制定共享**:方案制定页 Step2 的调研环节复用同一组件,与独立调研页**功能与数据共享**,但方案制定页整体流程(选成员→生成方案→逐板块确认)保持不变。
+- **零后端改动**:数据全部落现有 `health_status` + `diet_preferences` 两表;新字段(特殊状态、运动)走 `lifestyle` JSON 自由 key。
+
+## 二、方案选型(头脑风暴已确认)
+
+| # | 问题 | 决策 |
+|---|------|------|
+| 1 | 调研承载形态 | **组件化替换**:新建 `ComprehensiveSurvey.vue` 组件(`pages/health/components/`),首页新页面与方案制定页 Step2 **嵌入同一组件**,模块由 URL/父页传参控制 |
+| 2 | 模块选择机制 | **调用方传参**(URL query `modules=`,8 模块 key 逗号分隔),页面只渲染指定的 step |
+| 3 | 成员维度 | **仅当前成员**:`currentChildId`/`currentMemberId` 快照(与 `preferences.vue` 一致);方案制定页多成员 tab 由父页切换并传入对应成员快照(组件单成员视角,不冲突) |
+| 4 | 入口改造 | `index-home` 自测大卡 + `health-main` 自检卡**两处都改**,无待填调研时跳新调研页(仅调研不生成方案);方案制定页 Step2 同步替换组件 |
+| 5 | 当前状态模块 | 特殊状态单选:正常/哺乳期/备孕/怀孕/住院期/恢复期/其他 → `lifestyle` 新 key `currentStatus` |
+| 6 | 排便及运动模块 | 排便(现有 bowelFrequency/bowelForm)+ **新增**运动频率单选 + 每次时长单选 → `lifestyle` 新 key `exerciseFrequency`/`exerciseDuration` |
+| 7 | 过敏源模块 | 独立完整采集:过敏原多选(存 `diet_preferences.allergies`)+ 过敏症状多选(存 `lifestyle.allergySymptoms`),同时写两表;**饮食偏好模块不再含过敏原** |
+| 8 | 饮食结构补采 | 饮食偏好模块**补采** `vegetableFruit/dairy/sugaryDrinks`(后端 DTO 已有,现有页面漏采) |
+| 9 | 旧 wizard 处置 | `HealthQuestionnaireWizard.vue` + 5 个子 step 组件**标记 `DEPRECATED`**(保留文件,方案制定页替换后确认无引用再删;符合项目"废弃标记不直接删除"惯例) |
+
+## 三、架构
+
+```
+┌─ 入口 ─────────────────────────────────────────────────┐
+│ index-home 自测大卡 / health-main 自检卡 (goSelfTest)   │
+│   · 有待填调研 → survey-questionnaire(现状保留)       │
+│   · 无待填调研 → 新页面 pages/health/health-survey       │
+│       ?modules=basic,status,diet,disease,bowel,sleep,   │
+│                 med,allergy(8 模块逗号分隔,可任选子集)│
+└──────────────┬──────────────────────────────────────────┘
+               ▼
+┌─ 新页面 pages/health/health-survey.vue(包装层)───────┐
+│  · 解析 URL modules → 传给组件                        │
+│  · onLoad 解析当前成员(currentChildId/currentMemberId)│
+│  · 加载快照 → 渲染组件 → 保存 → navigateBack           │
+└──────────────┬──────────────────────────────────────────┘
+               ▼
+┌─ 新组件 pages/health/components/ComprehensiveSurvey.vue ─┐
+│  · props: modules[] + 成员快照(form/diet/lifestyle)    │
+│  · 按 modules 渲染对应 step(顶部进度条 + 底部上一步/     │
+│    下一步/完成)                                        │
+│  · 保存:只提交启用模块字段(合并式增量保存,不覆盖未     │
+│    调研模块旧数据)                                     │
+└──────────────┬────────────────────────────────────────────┘
+               ▼
+┌─ 方案制定页 health-plan-summary.vue Step2(复用组件)────┐
+│  · 多成员 tab 由父页管理,每个 tab 传对应成员快照         │
+│  · 传全量 8 模块(生成方案前的完整调研)                 │
+│  · 替换旧 HealthQuestionnaireWizard(标记 DEPRECATED)  │
+└────────────────────────────────────────────────────────────┘
+```
+
+**组件契约**(`ComprehensiveSurvey.vue`):
+
+- `props`:
+  - `modules: Array` — 启用模块 key 列表(`basic|status|diet|disease|bowel|sleep|med|allergy`)
+  - `form: Object` — health_status 快照(`{heightCm, weightKg, bloodPressure, bloodGlucose, lipidCholesterol, diseaseHistory, medications, lifestyle, notes}`)
+  - `diet: Object` — diet_preferences 快照(13 key 全量)
+  - `profile: Object` — 成员画像(gender/age,用于条件渲染)
+  - `submitting: Boolean`
+- `events`:
+  - `update-form` / `update-diet` — 值变更回传父页(父页维护两份快照,同 health-plan-summary 现有 `memberFormMap`/`memberDietMap` 模式)
+  - `submit` — 完成提交(父页负责组装 payload 调接口)
+
+## 四、8 模块字段映射
+
+| 模块 | URL key | 交互 | 字段 | 落库 |
+|------|---------|------|------|------|
+| 基本情况 | `basic` | 输入 | 身高/体重/血压/空腹血糖/总胆固醇 | `health_status` 列(heightCm/weightKg/bloodPressure/bloodGlucose/bloodLipids) |
+| 当前状态 | `status` | 单选 | 特殊状态:正常/哺乳期/备孕/怀孕/住院期/恢复期/其他 | `lifestyle.currentStatus`(新 key) |
+| 饮食偏好 | `diet` | chip+滑块 | 辣度/口味/菜系/烹饪方式/必吃/健康目标/宗教饮食/绝对忌口/饮食结构(蔬果·奶制品·含糖饮料) | `diet_preferences`(**不含过敏原**) |
+| 疾病史 | `disease` | 分组折叠多选+自定义 | 8 组 54 项预置清单 + 自定义疾病 + 确诊年月 | `health_status.disease_history`(JSON 数组 `{name, diagnosedAt}`) |
+| 排便及运动 | `bowel` | 单选×4 | 排便频率/粪便性状 + **新增**运动频率(不运动/每周1-2次/3-4次/5次以上)+ 每次时长(<30min/30-60min/>60min) | `lifestyle`(bowelFrequency/bowelForm + **新 key** exerciseFrequency/exerciseDuration) |
+| 睡眠情况 | `sleep` | 单选×2 | 睡眠时长/入睡时间 | `lifestyle`(sleepDuration/bedtime) |
+| 用药情况 | `med` | 动态行 | 药名+开始年月+已服时长(自动计算/记不清兜底)+服药时间 + "无长期用药"勾选 | `health_status.medications`(JSON 数组 `{name, startDate, durationText, time}`) |
+| 过敏源 | `allergy` | chip 多选×2 | 过敏原多选(预设 10 项+自定义)+ 过敏症状多选(无/皮疹/鼻痒/眼痒/哮喘样/食物过敏) | `diet_preferences.allergies` + `lifestyle.allergySymptoms` |
+
+> **条件渲染**(从现有 LifestyleStep 迁移,按 `profile.gender/age` 显示,**全部归属"当前状态"模块**内的"人群专属"子区):女性月经周期/备孕怀孕哺乳(menstrualCycle/specialPeriod)、儿童发育/挑食(growthDevelopment/pickyEating)、老年跌倒/骨质疏松风险(osteoporosisRisk/fallRisk)、青少年屏幕时间/情绪(screenTime/teenMood)。
+
+## 五、数据流与保存逻辑(关键)
+
+### 5.1 加载
+
+- 独立调研页 `onLoad`:`getHealthStatus(memberId)`(`POST /api/health-status/get`,memberId 空→账号级)+ `getDietPreferences({memberId})`(`POST /api/diet/preferences/current-member`)并行拉取,回填组件。
+- 方案制定页:沿用现有 `loadHealthForm`/`loadDietForm`(按成员 key 快照),传入组件。
+
+### 5.2 保存(合并式增量,核心设计)
+
+**原则:只提交启用模块的字段,绝不整体覆盖。**
+
+- **health_status 侧**:仅组装启用模块涉及字段进 payload;后端 `HealthStatusServiceImpl.saveForMember` 走 MyBatis-Plus `updateById`(默认只更新非 null 字段)→ 未调研模块的旧值自动保留。
+- **lifestyle JSON**:先加载现有 `lifestyle`(可能已含旧 key),**merge 新增/修改的 key 后整体提交**(`JSON.stringify`),避免覆盖未调研模块的 lifestyle key(如只调研"运动"时不动已填的睡眠数据)。
+- **diet_preferences 侧**:仅提交启用模块字段;`DietPreferencesService` 按 `family_member_id` 单行 upsert(`uk_member` 唯一键),未提交字段后端保留旧值(MP 更新非 null 字段)。
+- **空提交拦截**:所有启用模块均未填任何内容时,toast 提示不提交(对齐 `saveHealthStatus` 后端"至少填一项"校验语义)。
+
+### 5.3 校验
+
+- 身高 30–250、体重 3–300、血压正则 `^\d{2,3}\/\d{2,3}$`、血糖 1–30(沿用现有前端校验)。
+- 启用模块含 `diet` 时:**无必填**(原 preferences.vue"过敏/忌口至少一项"校验随过敏原移出而取消;绝对忌口 absoluteAvoid 仍留在 diet 模块,但不强制)。
+- 启用模块含 `allergy` 时:**无必填**(过敏原/症状均可空)。
+
+### 5.4 失败处理
+
+- 加载失败:toast 提示,页面可重试(保留现有 `loading` 语义)。
+- 保存失败:toast 展示后端错误,停留在当前 step,不返回。
+
+## 六、视觉
+
+- 沿用「暖灶」暖橙主题(与 `preferences.vue` 一致):
+  - 背景 `linear-gradient(180deg, #FFF7ED, #FFF3E6)`
+  - 主色 `#FF8C42`,chip 选中实心橙、未选米白
+  - 顶部进度条(模块数分阶)+ 底部"上一步/下一步/完成"固定栏
+  - 圆角卡片 + 暖橙阴影
+
+## 七、影响范围
+
+| 文件 | 操作 | 说明 |
+|------|------|------|
+| `cfc-frontend/pages/health/components/ComprehensiveSurvey.vue` | **新增** | 核心组件(8 模块 step 问卷) |
+| `cfc-frontend/pages/health/health-survey.vue` | **新增** | 独立调研包装页(解析 modules + 单成员快照 + 保存返回) |
+| `cfc-frontend/pages.json` | **新增** | 注册 `health/health-survey`(标题"健康调研"或"综合健康调研") |
+| `cfc-frontend/pages/index-home/index.vue` | **修改** | `goSelfTest` 无待填调研分支改跳新页 |
+| `cfc-frontend/pages/health-main/index.vue` | **修改** | `goSelfTest` 改跳新页 |
+| `cfc-frontend/pages/health/health-plan-summary.vue` | **修改** | Step2 用 ComprehensiveSurvey 替换 HealthQuestionnaireWizard(多成员 tab 保留) |
+| `cfc-frontend/pages/health/components/HealthQuestionnaireWizard.vue` + 5 子 step | **标记** | 文件头加 `DEPRECATED` 注释,保留不删 |
+| `docs/superpowers/plans/…` | **新增** | 后续实现计划 |
+
+**明确不改**:后端任何文件、`utils/api.js`(接口已封装)、`health_status`/`diet_preferences` 表结构(新字段全走 lifestyle JSON 自由 key)。
+
+## 八、边界与错误处理
+
+| 场景 | 处理 |
+|------|------|
+| URL `modules` 缺失 | 默认全量 8 模块(首页入口传全量;缺省时组件兜底全渲染) |
+| URL `modules` 含非法 key | 忽略非法项,仅渲染合法模块 |
+| `modules` 为空串 | 视为全量 |
+| 成员快照部分缺失(如无 diet 记录) | 空数组/空值回填,不报错 |
+| 旧 lifestyle 数据(无 currentStatus/exerciseFrequency 等新 key) | 新 key 默认值回填,不覆盖旧 key |
+| 只调研部分模块再保存 | 未启用模块字段不提交,旧数据保留(合并式增量) |
+| 数值非法(身高 999、血压 abc) | 前端校验拦截 + 后端兜底 |
+| 加载/保存网络失败 | toast + 可重试/停留当前 step |
+
+## 九、测试策略
+
+- **前端**:`node --check` 语法校验(提取 `<script>` 块,不打包——HBuilderX 打包由用户执行)。
+- **手工验证清单**:
+  1. 首页大卡无待填调研 → 跳新调研页(全 8 模块 step 流)
+  2. URL 只传 `modules=diet,bowel` → 仅渲染 2 个 step
+  3. 各模块交互(chip/slider/动态行/分组折叠)与 preferences.vue / 旧档案页一致
+  4. 保存后 health_status / diet_preferences 数据正确落库,回填正确
+  5. **增量验证**:先填疾病史+用药保存 → 再只调研"运动"保存 → 疾病史/用药数据仍在
+  6. 方案制定页 Step2 正常(多成员 tab 切换、各成员快照独立、生成方案成功)
+  7. 过敏源模块保存后:`diet_preferences.allergies` 与 `lifestyle.allergySymptoms` 均有数据
+  8. 饮食偏好模块含饮食结构 3 项采集(vegetableFruit/dairy/sugaryDrinks 落库)
+  9. 首页有待填调研时仍走 survey-questionnaire(现状保留)
+  10. 校验非法值拦截
+
+## 十、非目标(本次不做)
+
+- **不做**方案制定页整体流程改造(选成员/生成方案/逐板块确认逻辑保持现状,仅替换 Step2 调研组件)。
+- **不做**旧 `HealthQuestionnaireWizard` 删除——仅标记 DEPRECATED,待方案制定页替换上线后确认无引用再清理。
+- **不做**后台模板配置化(`survey_templates`)——8 模块字段本设计固化,模块组合由调用方 URL 传参控制。
+- **不做** `exercise_record` 打卡表与新运动字段的联动——新运动字段为调研快照性质,打卡体系独立运行。
+- **不做** report-survey 的过敏源/睡眠等数据同步修复(`ReportSurveyService.saveDietPreferencesFromSurvey` 不同步 allergies 的既有缺口,不在本次范围)。