|
|
@@ -0,0 +1,191 @@
|
|
|
+# 通用报告块渲染引擎(report-blocks)设计
|
|
|
+
|
|
|
+日期:2026-08-10
|
|
|
+状态:已确认(用户已逐节批准)
|
|
|
+
|
|
|
+## 背景与问题
|
|
|
+
|
|
|
+肠道菌群报告详情页存在字段错位 bug:`buildReportFromPayload`(HealthReportController:1553-1593)只填充第二套评分字段(`diversityScore` 等),而前端 `gut-flora-detail.vue` 的 `buildScores`(242-252)读取第一套字段(`gutDiversityScore` 等)→ 5 个评分圆环(菌群多样性/平衡/有益菌/有害菌/核心菌属)永远为 null 不显示。
|
|
|
+
|
|
|
+更深层的问题:系统存在多套并行报告链路(肠道菌群/体检/舌诊/DAN),每种报告各有一套页面、字段、接口,扩展成本高、易出字段错位类 bug。DAN 报告(独立 `dan_report_uploads` 表 + `DanReportController`)甚至没有独立详情页,只能复用上传页展示。
|
|
|
+
|
|
|
+## 目标
|
|
|
+
|
|
|
+构建**通用报告块渲染引擎**:
|
|
|
+
|
|
|
+1. 解析器输出统一 `blocks` 结构(JSON),前端按 `block.type` 通用渲染,**前端零字段名耦合**
|
|
|
+2. 覆盖报告类型:`gut_flora` / `dan` / `physical_exam` / `tongue`(体检/舌诊二期)
|
|
|
+3. 疾病风险按**风险等级分组**(重要风险 = 需注意/高风险/异常;其它风险 = 低风险),直接展示在详情页内
|
|
|
+4. 修复肠道菌群 9 项评分圆环显示问题(从源头消灭键名错位)
|
|
|
+5. 存量接口/页面零破坏(旧字段保留兼容,逐步迁移)
|
|
|
+
|
|
|
+## 架构总览
|
|
|
+
|
|
|
+```
|
|
|
+解析器(LangGraph / Java fallback / DanReportParseService)
|
|
|
+ ↓ 结构化数据(ParsedReportPayload / DanParsedReport)
|
|
|
+ReportBlockAssembler —— 按 reportType 组装 blocks[]
|
|
|
+ ↓ confirm/编辑保存时落库
|
|
|
+report_blocks 表(report_type + report_id 复合唯一键 + blocks JSON)
|
|
|
+ ↓ 查询
|
|
|
+getReportDetail / DanReportController.detail —— 返回 reportType + blocks(旧字段保留)
|
|
|
+ ↓
|
|
|
+前端 report-detail 统一外壳 + <report-blocks-renderer> 通用渲染
|
|
|
+```
|
|
|
+
|
|
|
+## 表结构
|
|
|
+
|
|
|
+```sql
|
|
|
+CREATE TABLE report_blocks (
|
|
|
+ id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
|
|
+ report_type VARCHAR(32) NOT NULL COMMENT 'gut_flora / dan / physical_exam / tongue',
|
|
|
+ report_id BIGINT NOT NULL COMMENT '各类型报告主键ID(health_reports.id 或 dan_report_uploads.id,各自序列不冲突)',
|
|
|
+ blocks JSON NOT NULL COMMENT '块数组 [{type,title,items,extra}]',
|
|
|
+ version INT DEFAULT 1,
|
|
|
+ created_at DATETIME,
|
|
|
+ updated_at DATETIME,
|
|
|
+ UNIQUE KEY uk_report_type_id (report_type, report_id)
|
|
|
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='报告通用展示块';
|
|
|
+```
|
|
|
+
|
|
|
+- `(report_type, report_id)` 复合唯一键 → 各类型 id 序列互不干扰
|
|
|
+- 落库用 **upsert**(存在则覆盖),幂等
|
|
|
+- 迁移入口:`DatabaseInitializer.runMigrations()`(按 AGENTS.md 迁移工作流,同步 `schema.sql`)
|
|
|
+
|
|
|
+## blocks schema(6 种块类型)
|
|
|
+
|
|
|
+```json
|
|
|
+[
|
|
|
+ { "type": "score", "title": "健康评分",
|
|
|
+ "items": [ {"label":"综合","value":57,"max":100}, {"label":"菌群多样性","value":50,"max":100} ] },
|
|
|
+
|
|
|
+ { "type": "risk_group", "title": "重要风险",
|
|
|
+ "items": [ {"name":"心脑血管疾病","value":"0.37","level":"注意"},
|
|
|
+ {"name":"抑郁症","value":"0.27","level":"低风险"} ] },
|
|
|
+
|
|
|
+ { "type": "indicator", "title": "营养素",
|
|
|
+ "items": [ {"name":"维生素D","value":"28.5","unit":"ng/mL","refRange":"30-100","status":"偏低"} ] },
|
|
|
+
|
|
|
+ { "type": "list", "title": "检出菌种",
|
|
|
+ "columns": [ {"key":"name","label":"菌种"}, {"key":"value","label":"相对丰度"}, {"key":"status","label":"状态"} ],
|
|
|
+ "items": [ {"name":"双歧杆菌","value":"12.4%","status":"正常"} ] },
|
|
|
+
|
|
|
+ { "type": "text", "title": "报告解读", "content": "..." },
|
|
|
+
|
|
|
+ { "type": "chart", "title": "维度雷达", "chartType": "radar", "data": {...} }
|
|
|
+]
|
|
|
+```
|
|
|
+
|
|
|
+| 块类型 | 覆盖现有 | 前端渲染 |
|
|
|
+|--------|---------|---------|
|
|
|
+| `score` | 肠道 9 项评分、DAN total_score/percentile | 评分圆环组 |
|
|
|
+| `risk_group` | 疾病风险(按等级分"重要风险/其它风险"两组) | 风险卡片 + 等级徽章 |
|
|
|
+| `indicator` | 营养素/肠道屏障指标(status 高亮) | 指标卡片/表格 |
|
|
|
+| `list` | 菌种列表、DAN 各维度得分 items | 通用列表(columns 驱动) |
|
|
|
+| `text` | 报告解读、DAN summary/suggestions | 段落 |
|
|
|
+| `chart` | 雷达图(body-detail 的 radarDimensions) | 预留(二期) |
|
|
|
+
|
|
|
+## 后端实现
|
|
|
+
|
|
|
+### ReportBlock 实体 + ReportBlockMapper
|
|
|
+
|
|
|
+MyBatis-Plus,`@TableName("report_blocks")`,blocks 列存 JSON 字符串。提供 `save(type, reportId, blocks)`(upsert)与 `getBlocks(type, reportId)`。
|
|
|
+
|
|
|
+### ReportBlockAssembler Service
|
|
|
+
|
|
|
+按 reportType 分发组装:
|
|
|
+
|
|
|
+| 方法 | 输入 | 输出 blocks |
|
|
|
+|------|------|------------|
|
|
|
+| `assembleGutFlora(ParsedReportPayload)` | LangGraph/Java fallback 的 payload | `score`(9 项评分) + `risk_group`(重要/其它两组) + `indicator`(营养素/肠道屏障) + `list`(菌种/病原菌/食物推荐) + `text`(解读) |
|
|
|
+| `assembleDan(DanParsedReport)` | DAN 解析结果 | `score`(total/percentile) + `list`(各维度得分按 category 分组) + `text`(summary/suggestions) |
|
|
|
+| `assembleExam(payload)` / `assembleTongue(...)` | 体检/舌诊(二期) | 对应块 |
|
|
|
+
|
|
|
+要点:
|
|
|
+
|
|
|
+- **命名注意**:`report_blocks.report_type` 是**报告类别**(`gut_flora`/`dan`/`physical_exam`/`tongue`);DAN 子类型 A2/B4(`DanReportUpload.reportType`)不是类别,由组装器放入 blocks 的 `extra`(如 `{"subType":"A2"}`)供前端展示徽章,两者不得混淆
|
|
|
+- 评分圆环:组装器从 payload 的**第二套键**(`balanceScore`/`diversityScore`/`beneficialScore`/`harmfulScore`/`coreGenusScore` + `overallScore`/`gutHealthScore`/`chronicDiseaseScore`/`nutritionScore`)组装 9 项,**前端不再碰实体字段名**
|
|
|
+- 疾病风险分组:`需注意/高风险/异常` → "重要风险"组;`低风险` → "其它风险"组
|
|
|
+- 组装失败不阻断 confirm:blocks 留空,详情页回退旧字段
|
|
|
+
|
|
|
+### 落库挂点(3 处)
|
|
|
+
|
|
|
+| 挂点 | 时机 | 写入 |
|
|
|
+|------|------|------|
|
|
|
+| `confirmDraft`(HealthReportController:888 createReport 后) | 肠道菌群/体检确认 | `save('gut_flora', report.id, blocks)` |
|
|
|
+| `confirmUpload`(DanReportUploadService) | DAN 确认 | `save('dan', upload.id, blocks)` |
|
|
|
+| `updateReportFromPayload` / `editHealthReport` / `DanReportController.edit` | 编辑保存后 | 重新组装覆盖 |
|
|
|
+
|
|
|
+### 接口
|
|
|
+
|
|
|
+- `getReportDetail`(HealthReportService:312)追加 `detail.put("blocks", ...)`,旧字段(report/indicators/gutFlora/diseaseRisks)全保留
|
|
|
+- `DanReportController.detail` 追加 blocks
|
|
|
+- 现有页面(body-detail/health-report.vue 等)零破坏
|
|
|
+
|
|
|
+## 前端实现
|
|
|
+
|
|
|
+### 通用渲染器 `components/report-blocks-renderer.vue`
|
|
|
+
|
|
|
+`props: { blocks: [] }`,`v-for` 按 `block.type` 分发 6 个子渲染组件:
|
|
|
+
|
|
|
+- `score-block`:评分圆环组(圆环 + 数值)
|
|
|
+- `risk-group-block`:风险卡片 + 等级徽章(重要风险红/橙,其它风险绿)
|
|
|
+- `indicator-block`:指标卡片(status 高亮)
|
|
|
+- `list-block`:通用列表(columns 驱动)
|
|
|
+- `text-block`:段落
|
|
|
+- `chart-block`:预留
|
|
|
+
|
|
|
+纯展示组件,零字段名耦合。遵循小程序限制(禁可选链/禁 CSS Grid/禁 `:key` 表达式)。
|
|
|
+
|
|
|
+### 统一详情页 `pages/health/report-detail.vue`
|
|
|
+
|
|
|
+外壳:报告头(类型徽章/姓名/日期)+ blocks 渲染区。**疾病风险直接展示在详情页内**(risk_group 块),不用再跳子页。
|
|
|
+
|
|
|
+### 入口切换
|
|
|
+
|
|
|
+- `report-list.vue` goToDetail(79-87):`gut_flora`/`dan` → 新详情页 `?reportType=&reportId=`;`physical_exam`/`tongue` 暂留原页面
|
|
|
+- DAN 维度页 `viewDanReport`(wisdom-detail:370 / mind-detail):从 `report-upload?draftId=` 改为新详情页
|
|
|
+- 旧详情页(gut-flora-detail 等)保留,验证通过后再删除
|
|
|
+
|
|
|
+### 编辑策略
|
|
|
+
|
|
|
+**blocks 是只读视图模型**。编辑走现有结构化接口(`editHealthReport` 等),保存后重新组装 blocks。渲染引擎不做编辑器。
|
|
|
+
|
|
|
+风险编辑:新详情页内联等级/值编辑 → 调 `editHealthReport` → 刷新 blocks。
|
|
|
+
|
|
|
+## 分期实施
|
|
|
+
|
|
|
+| 阶段 | 内容 | 验证 |
|
|
|
+|------|------|------|
|
|
|
+| 1 | 后端全链:迁移建表 + Assembler(gut_flora+dan) + 3 个落库挂点 + 接口返回 blocks | `mvn clean compile` + Assembler 单元测试(断言 9 项评分/风险分组/DAN items) |
|
|
|
+| 2 | 前端:blocks-renderer + 统一详情页 + report-list 切换 gut_flora/dan | 小程序模拟器打开 501999942 报告:9 项评分圆环全显示、风险两组卡片、营养素/菌种/解读 |
|
|
|
+| 3 | 疾病风险内联编辑 + DAN 维度页跳转 + 兼容验证 | 编辑风险保存后 blocks 刷新;DAN A2/B4 报告在维度页正常渲染 |
|
|
|
+| 4 | (可选)体检/舌诊接入 + 旧页面收尾删除 | 回归 |
|
|
|
+
|
|
|
+## 错误处理
|
|
|
+
|
|
|
+- blocks 空/缺失 → 详情页显示"暂无报告内容"并**回退旧字段渲染**(兼容期兜底)
|
|
|
+- 组装失败 → 不阻断 confirm,blocks 留空
|
|
|
+- 落库幂等(upsert)
|
|
|
+
|
|
|
+## 测试
|
|
|
+
|
|
|
+- 后端:`ReportBlockAssemblerTest`(gut_flora payload → blocks 断言:9 项评分齐全、风险分组正确、DAN items 映射)+ `mvn clean compile`
|
|
|
+- 前端:语法/结构校验(node --check),不改动打包流程(HBuilderX 打包)
|
|
|
+
|
|
|
+## 相关文件
|
|
|
+
|
|
|
+- `cfc-backend/src/main/java/com/etotem/cfc/controller/HealthReportController.java`(confirmDraft:880 / buildReportFromPayload:1553)
|
|
|
+- `cfc-backend/src/main/java/com/etotem/cfc/service/HealthReportService.java`(getReportDetail:312 / updateReportFromPayload:1134)
|
|
|
+- `cfc-backend/src/main/java/com/etotem/cfc/service/DanReportUploadService.java`(confirmUpload)
|
|
|
+- `cfc-backend/src/main/java/com/etotem/cfc/controller/DanReportController.java`(detail:63)
|
|
|
+- `cfc-backend/src/main/java/com/etotem/cfc/dto/ParsedReportPayload.java`(payload 结构)
|
|
|
+- `cfc-backend/src/main/java/com/etotem/cfc/service/DanReportParseService.java`(DanParsedReport:1645)
|
|
|
+- `cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java`(迁移入口)
|
|
|
+- `cfc-backend/src/main/resources/schema.sql`(同步建表)
|
|
|
+- `cfc-frontend/pages/health/report-list.vue`(goToDetail:79)
|
|
|
+- `cfc-frontend/pages/health/gut-flora-detail.vue`(buildScores:239)
|
|
|
+- `cfc-frontend/pages/health/gut-flora-risks-detail.vue`(风险页)
|
|
|
+- `cfc-frontend/pages/wisdom-detail/index.vue` / `pages/mind-detail/index.vue`(viewDanReport)
|
|
|
+- `cfc-frontend/components/`(新渲染器组件目录)
|
|
|
+- `docs/参考资料/501999942-某人.json`(实测样本)
|