日期: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)甚至没有独立详情页,只能复用上传页展示。
构建通用报告块渲染引擎:
blocks 结构(JSON),前端按 block.type 通用渲染,前端零字段名耦合gut_flora / dan / physical_exam / tongue(体检/舌诊二期)解析器(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> 通用渲染
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 序列互不干扰DatabaseInitializer.runMigrations()(按 AGENTS.md 迁移工作流,同步 schema.sql)[
{ "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) | 预留(二期) |
MyBatis-Plus,@TableName("report_blocks"),blocks 列存 JSON 字符串。提供 save(type, reportId, blocks)(upsert)与 getBlocks(type, reportId)。
按 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"})供前端展示徽章,两者不得混淆balanceScore/diversityScore/beneficialScore/harmfulScore/coreGenusScore + overallScore/gutHealthScore/chronicDiseaseScore/nutritionScore)组装 9 项,前端不再碰实体字段名需注意/高风险/异常 → "重要风险"组;低风险 → "其它风险"组| 挂点 | 时机 | 写入 |
|---|---|---|
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 追加 blockscomponents/report-blocks-renderer.vueprops: { 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 暂留原页面viewDanReport(wisdom-detail:370 / mind-detail):从 report-upload?draftId= 改为新详情页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 | (可选)体检/舌诊接入 + 旧页面收尾删除 | 回归 |
ReportBlockAssemblerTest(gut_flora payload → blocks 断言:9 项评分齐全、风险分组正确、DAN items 映射)+ mvn clean compilecfc-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(实测样本)