# 健康数据中心 — 实现计划 **源文档**: docs/superpowers/specs/2026-08-08-health-data-center-design.md **日期**: 2026-08-09 **分支**: cfclub --- ## 一、Codebase 现状核查(与 spec 差异) | 项 | spec 描述 | 实际现状 | 影响 | |---|---|---|---| | 身维度首页路径 | `pages/body/index.vue` | **不存在**;实际为 `pages/body-detail/index.vue`(pages.json:883-890) | 改造目标文件需修正 | | 迁移编号 | spec §8.2 写"迁移194" | 当前最高迁移为 **迁移203**(notices表) | 新迁移编号应为 迁移204 | | dimension-questionnaire 维度 | spec §6.1 要新增 cognitive/mental/relationship | 现有仅 7 个体质维度(growth/sleep/vision/immunity/nutrition/gut/exercise) | 需新增题目+后端 DIMENSIONS 列表扩展 | | LangGraph 端点 | spec §7.2 调用 `analysis/run` | Python 侧 `/api/v1/analysis/run` 存在(`cfc-langgraph/app/api/adapter.py:124`);Java 侧需走 AiGateway | 需补充 AiGateway 方法 | | health_plans 表 | spec §7.3 建表 | 不存在 | 需新建 | | RadarChart 复用 | spec 未说明 | `components/RadarChart.vue:34-43` 已支持 props `{dimensions, scores, avgScores}` | **无需扩展**,直接复用 | --- ## 二、任务分解 ### Batch A — 数据库 + 实体(串行,依赖无) **任务 1: 迁移204 — 创建 health_plans 表** 文件: `cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java`(在迁移203后追加) DDL(与 spec §7.3 一致,补充 deleted 字段支持软删除): ```sql CREATE TABLE IF NOT EXISTS health_plans ( id BIGINT AUTO_INCREMENT PRIMARY KEY, family_id BIGINT NOT NULL COMMENT '家庭ID', member_ids VARCHAR(200) NOT NULL COMMENT '目标成员ID列表(逗号分隔)', dimensions VARCHAR(100) NOT NULL COMMENT '维度列表(逗号分隔)', goal TEXT NOT NULL COMMENT '用户输入的目标', plan_content TEXT COMMENT 'LangGraph生成的方案内容(JSON/HTML)', member_name VARCHAR(50) COMMENT '成员名称摘要', deleted TINYINT DEFAULT 0 COMMENT '软删标记(0=正常 1=已删)', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_family (family_id), INDEX idx_created (created_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康方案汇总记录'; ``` 同步修改 `cfc-backend/src/main/resources/schema.sql`(文件末尾追加 CREATE TABLE)。 **QA 场景**: - `mvn clean compile` 通过 - 启动后查日志 "已创建health_plans表" - 连接数据库执行 `DESCRIBE health_plans` 确认字段存在 --- **任务 2: HealthPlan 实体 + Mapper** 新建: - `cfc-backend/src/main/java/com/etotem/cfc/entity/HealthPlan.java`(字段与 DDL 一一对应) - `cfc-backend/src/main/java/com/etotem/cfc/mapper/HealthPlanMapper.java`(extends BaseMapper) **QA 场景**: - `mvn clean compile` 通过,无编译错误 --- ### Batch B — 服务层 + API 契约(可并行) **任务 3: HealthPlanService + ServiceImpl** 新建: - `cfc-backend/src/main/java/com/etotem/cfc/service/HealthPlanService.java` - `cfc-backend/src/main/java/com/etotem/cfc/service/impl/HealthPlanServiceImpl.java` 接口方法: ```java Long savePlan(Long familyId, Long adminId, String memberIds, String dimensions, String goal, String planContent, String memberName); List listPlans(Long familyId, Integer page, Integer size); HealthPlan getPlan(Long id, Long userId); // 校验 familyId 归属 void deletePlan(Long id, Long userId); // 软删除:deleted=1,校验归属 ``` **QA 场景**: - `mvn clean compile` 通过 - 单元测试(如有)或手动调接口验证 CRUD --- **任务 4: AiGateway — 新增 generateHealthPlan 方法** 文件: `cfc-backend/src/main/java/com/etotem/cfc/service/AiGateway.java` 新增方法(与现有 `generateQuestionnaire` 行185-216 模式一致): ```java public String generateHealthPlan(Map inputs) { if (!enabled || baseUrl == null) return null; try { String url = baseUrl + "/api/v1/analysis/run"; HttpEntity entity = new HttpEntity<>(JSON.toJSONString(inputs), headers()); ResponseEntity resp = restTemplate.postForEntity(url, entity, String.class); // 解析返回的 content/result 字段 return extractContent(resp.getBody()); } catch (Exception e) { log.warn("AiGateway generateHealthPlan 失败: {}", e.getMessage()); return null; } } ``` **QA 场景**: - `mvn clean compile` 通过 - 单元测试或手动调 AiGateway(Python 服务未运行时返回 null,不抛异常) --- **任务 5: DimensionScoreService DIMENSIONS 列表扩展** 文件: `cfc-backend/src/main/java/com/etotem/cfc/service/impl/DimensionScoreServiceImpl.java` 在 `DIMENSIONS` 列表(行39-47)末尾追加: ```java new String[]{"cognitive", "认知能力"}, new String[]{"mental", "心理健康"}, new String[]{"relationship", "关系质量"}, ``` **注意**: `submitQuestionnaire` 方法(行208-237)无维度白名单校验,传入新维度码可直接写入 health_dimension_scores 表。但 `getDimensionOverview` 和 `getDimensionHistory` 依赖 DIMENSIONS 列表来展示,扩展后前端可正常获取数据。 **QA 场景**: - `mvn clean compile` 通过 - 调 `POST /api/dimension/overview?dimension=cognitive` 返回数据结构与现有维度一致 --- **任务 6: 前端 API 函数声明(无需等后端编译)** 文件: `cfc-frontend/utils/api.js` 新增: ```javascript export const generateHealthPlan = (data) => { return request('/api/health/plan/generate', 'POST', data) } export const getHealthPlanList = (familyId) => { return request('/api/health/plan/list', 'POST', { familyId }) } export const saveHealthPlan = (data) => { return request('/api/health/plan/save', 'POST', data) } export const deleteHealthPlan = (id) => { return request('/api/health/plan/delete', 'POST', { id }) } ``` **QA 场景**: - 语法检查:`node -e "require('./utils/api.js')"` 无 Unexpected token --- **任务 7: pages.json 注册 health-plan-summary 页面** 文件: `cfc-frontend/pages.json`,在 `pages/health` 分包末尾追加: ```json { "path": "health-plan-summary", "style": { "navigationBarTitleText": "方案汇总" } } ``` **QA 场景**: - HBuilderX 或命令行检查 pages.json JSON 语法:`node -e "JSON.parse(require('fs').readFileSync('pages.json','utf8'))"` 无异常 --- ### Batch C — Controller 层(依赖 Batch A+B 的实体/服务) **任务 8: HealthPlanController(小程序前端接口)** 新建: `cfc-backend/src/main/java/com/etotem/cfc/controller/health/HealthPlanController.java` 端点: | 方法 | 路径 | 说明 | |---|---|---| | POST | `/api/health/plan/generate` | 调 LangGraph 生成方案并返回结果 | | POST | `/api/health/plan/save` | 保存方案到 health_plans 表 | | POST | `/api/health/plan/list` | 查询当前用户家庭的历史方案列表 | | POST | `/api/health/plan/detail` | 查询方案详情 | | POST | `/api/health/plan/delete` | 软删除方案 | **QA 场景**: - `mvn clean compile` 通过 - 用 curl 手动调 `/api/health/plan/list` 返回 JSON(未登录返回 401) --- **任务 9: AdminHealthPlanController(管理后台接口,可选 MVP)** 新建: `cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminHealthPlanController.java` 端点: - `POST /api/admin/health-plans/list` — 分页查询全平台方案列表 - `POST /api/admin/health-plans/delete` — 管理员强制删除 **QA 场景**: - `mvn clean compile` 通过 --- ### Batch D — 小程序前端页面(依赖 Batch C 的 API 端点) **任务 10: dimension-questionnaire.vue 扩展** 文件: `cfc-frontend/pages/body-detail/dimension-questionnaire.vue`(546 行) 改动: 1. `dimensions` 数组(行95-103)新增 3 项(spec §6.1) 2. `questions` 对象(行105-221)新增 cognitive/mental/relationship 三组题库(spec §6.2-6.4,每组 8 题) 3. `onLoad` 增加 `dim` 参数支持(spec §6.5): ```javascript onLoad(options) { if (options.dim) { this.selectedDimension = options.dim this.step = 2 // 跳过选维度,直接进入答题 } // 原有逻辑保留 } ``` 4. 答题完成结果页新增"返回数据中心"按钮(跳回 body-detail/index.vue) **QA 场景**: - HBuilderX 打包无语法错误 - 微信开发者工具打开 `/pages/body-detail/dimension-questionnaire?dim=cognitive&memberId=1`,直接进入答题页 --- **任务 11: body-detail/index.vue 改造为健康数据中心** 文件: `cfc-frontend/pages/body-detail/index.vue`(676 行) 改造要点: 1. **新增 Tab 栏**(PageBanner 下方):身(橙)/智(靛)/心(粉)/行(绿),切换时仅刷新雷达图区域 2. **雷达图随 Tab 切换**: - 身 → 现有七维雷达图(growth/sleep/vision/immunity/nutrition/gut/exercise) - 智 → 五维蛛网图(记忆力/注意力/逻辑推理/空间想象/语言表达),color=#6366F1 - 心 → 五维图(情绪稳定性/抗压能力/自我认知/社交意愿/幸福感),color=#FF6B9D - 行 → 五维图(亲子关系/夫妻关系/亲友关系/同事关系/社区参与),color=#10B981 3. **移除 funcList 功能网格**(运动/饮食/作息/冥想/舌诊/健康维度) 4. **新增"看见·健康数据中心"区块**: - 数据采集卡片1:上传健康报告 → `/pages/health/report-upload` - 数据采集卡片2:在线测评(认知/心理/关系 三个入口,各带开始/已完成分数) - 数据查看卡片:最近报告/测评摘要(无数据时显示空状态提示) - 方案汇总卡片:上次方案摘要 + [生成新方案] 按钮 → `/pages/health/health-plan-summary` 5. **弱化活动/商品/文章区**(收窄宽度至 80%,opacity 0.7) 6. 保留:RadarChart、FamilyMemberStrip、FamilyEnergyBar、AIFloatingAvatar **QA 场景**: - HBuilderX 打包无语法错误 - 微信开发者工具:切换身/智/心/行 Tab,雷达图标签和颜色随之变化;无数据时显示"上传报告后显示个人数据" --- **任务 12: 新建 health-plan-summary.vue** 新文件: `cfc-frontend/pages/health/health-plan-summary.vue` 页面结构(spec §7.1): - 顶部导航栏(← 返回) - 生成新方案表单: - 维度多选(健康/认知/心理/社会性) - 成员多选(从家庭列表选取,调用 getVisibleFamilyMembers) - 目标输入框(textarea,placeholder:"例如:改善孩子睡眠质量") - [生成方案] 按钮 → 调 generateHealthPlan API - 方案结果展示区(HTML 富文本渲染): - [保存方案] → 调 saveHealthPlan API - [重新生成] → 再次调 generateHealthPlan - 历史方案列表(按时间倒序,调 getHealthPlanList) **QA 场景**: - HBuilderX 打包无语法错误 - 微信开发者工具:填写表单 → 生成方案 → 保存 → 返回列表出现新条目 --- ## 三、依赖关系图 ``` 任务1 (迁移204) ──→ 任务2 (实体+Mapper) ──→ 任务3 (Service) │ 任务6 (API函数) ←──────────────────────────────────────┘ 任务7 (pages.json) ←───────────────────────────────────┘ │ ┌────────────────────────┼────────────────────────┐ ▼ ▼ ▼ 任务4 (AiGateway) 任务5 (DIMENSIONS) 任务8 (Controller) │ │ │ └────────────────────────┴────────────────────────┘ │ 任务9 (AdminController,可选) │ ┌────────────────────────┴────────────────────────┐ ▼ ▼ 任务10 (问卷扩展) 任务11 (body-detail改造) │ │ └────────────────────────┬────────────────────────┘ ▼ 任务12 (方案汇总页) ``` --- ## 四、执行顺序(3个批次) ### 批次 1(串行,无并行依赖) - 任务 1 → 任务 2(实体依赖 DDL) ### 批次 2(4项可并行) - 任务 3(HealthPlanService,依赖任务2) - 任务 4(AiGateway,无依赖) - 任务 5(DIMENSIONS扩展,无依赖) - 任务 6(API函数声明,无依赖) - 任务 7(pages.json注册,无依赖) ### 批次 3(串行,依赖批次2) - 任务 8(Controller,依赖任务3+4) - 任务 10(问卷扩展,依赖任务5) - 任务 11(body-detail改造,依赖任务8 API端点) - 任务 12(方案汇总页,依赖任务8+10+11) --- ## 五、风险点 | 风险 | 影响 | 缓解 | |---|---|---| | LangGraph Python 服务 `/analysis/run` 未部署或返回异常 | 方案生成功能不可用 | AiGateway 已有熔断器,返回 null;Controller 层做 mock fallback(返回预置方案文本) | | body-detail/index.vue 现有样式复杂(676行,含大量残留样式) | 改造时易引入样式冲突 | 保留 RadarChart/FamilyMemberStrip/FamilyEnergyBar 组件引用,仅替换 funcList 下方区块;新样式加 `.health-center-` 前缀避免冲突 | | dimension-questionnaire 跳转 URL 参数变化 | 现有链接失效 | `dim` 参数为可选,不传时走原有选维度流程 | | 五维雷达图标签数据从哪来 | 智/心/行 Tab 的 radarDimensions 如何填充 | 复用 `getDimensionOverview`,传 dimension=cognitive/mental/relationship;后端 DIMENSIONS 列表扩展后,返回对应五维分值 | | 迁移204 幂等性 | 重复部署不报错 | 使用 `CREATE TABLE IF NOT EXISTS`(与现有迁移203一致) | --- ## 六、验收标准 1. **任务1+2**: `mvn clean compile` 通过;数据库有 health_plans 表,含 deleted 字段 2. **任务3+8**: 调 `POST /api/health/plan/save` 写入一条记录;调 `POST /api/health/plan/list` 返回该记录 3. **任务4**: AiGateway.generateHealthPlan 在无 Python 服务时返回 null(不抛异常) 4. **任务5**: 调 `POST /api/dimension/overview?dimension=cognitive` 返回正常数据结构(即使分值为0) 5. **任务10**: 微信开发者工具打开 `?dim=cognitive&memberId=1` 直接进入答题页,8题完整可作答 6. **任务11**: 切换身/智/心/行 Tab,雷达图标签和颜色正确变化;数据中心区块可见 7. **任务12**: 生成方案 → 保存 → 返回列表出现新条目;历史方案可点击查看 8. **编译**: 无 Java 编译错误(`mvn clean compile`);无 Vue/JS 语法错误(HBuilderX 打包无 Unexpected token)