# 报告快速分析扩展实现计划 > **面向 AI 代理的工作者:** 必需子技能:使用 superpowers:subagent-driven-development(推荐)或 superpowers:executing-plans 逐任务实现此计划。步骤使用复选框(`- [ ]`)语法来跟踪进度。 **目标:** 在 `gut-flora-detail.vue` 和 `report-confirm.vue` 新增「报告快速分析」入口,后端支持 `draftId` 与 `reportId` 双模式,复用现有免费版限制与 AI 链路。 **架构:** 后端 `FamilyContextService` 新增 `buildContextFromDraft`,`AIChatController` 扩展 `buildChatInputs` 与 `reportAnalyze` 校验;前端 `api.js` 扩展 `reportQuickAnalyze` 可选 `draftId`,两个 Vue 页面复用 `report-detail.vue` 的浮层结构与样式。 **技术栈:** Spring Boot 2.7.18 (Java 8) + MyBatis-Plus + Redis + uni-app Vue 2 小程序(Options API,禁可选链/禁 CSS Grid/禁 `:key` 表达式) --- ## 文件结构 ### 后端(cfc-backend) | 文件 | 操作 | 职责 | |------|------|------| | `src/main/java/com/etotem/cfc/service/FamilyContextService.java` | 修改 | 注入 `HealthReportDraftService` + `ObjectMapper`,新增 `buildContextFromDraft(userId, draftId)` | | `src/main/java/com/etotem/cfc/controller/ai/AIChatController.java` | 修改 | 1. `buildChatInputs`:识别 `draftId` 参数,调用新方法
2. `reportAnalyze`:校验 `reportId || draftId`,为空则报错 | ### 前端(cfc-frontend) | 文件 | 操作 | 职责 | |------|------|------| | `utils/api.js` | 修改 | `reportQuickAnalyze(query, reportId, conversationId, draftId)` 新增第四参数 | | `pages/health/gut-flora-detail.vue` | 修改 | 复用浮层逻辑/样式,FAB 条件 `!isEditing && (reportId || draftId)` | | `pages/health/report-confirm.vue` | 修改 | 复用浮层逻辑/样式,FAB 条件 `!parsing && draftId` | --- ## 任务 1:后端 FamilyContextService 新增 buildContextFromDraft **文件:** - 修改:`cfc-backend/src/main/java/com/etotem/cfc/service/FamilyContextService.java` **背景:** 现有 `buildContext(userId, reportId)` 读已入库报告;草稿场景需读 `HealthReportDraft.payloadJson` 并解析为 `Map` 注入上下文。 - [ ] **步骤 1:新增 import** 在 import 区(现有 import 在 20-62 行)添加: ```java import com.etotem.cfc.service.HealthReportDraftService; import com.fasterxml.jackson.databind.ObjectMapper; ``` - [ ] **步骤 2:注入依赖** 在 `@Resource private ReportSurveyService reportSurveyService;`(约 36 行)之后添加: ```java @Resource private HealthReportDraftService healthReportDraftService; @Resource private ObjectMapper objectMapper; ``` (`FamilyContextService` 目前未注入 `ObjectMapper`,需一并补上) - [ ] **步骤 3:新增 buildContextFromDraft 方法** 在 `buildContext(Long userId, Long reportId, Long surveyId)` 方法结束后(约 222 行)添加: ```java /** * 基于草稿 ID 构建上下文(用于快速分析) * * @param userId 当前用户 * @param draftId 草稿 ID * @return 上下文 Map,包含 reportDetail(从 payloadJson 还原) */ public Map buildContextFromDraft(Long userId, Long draftId) { Map ctx = buildContext(userId); if (draftId == null) { return ctx; } HealthReportDraft draft = healthReportDraftService.getDraftById(draftId); if (draft == null || !draft.getUserId().equals(userId)) { // 权限不通过静默返回基础上下文 return ctx; } // 解析 payloadJson Map reportDetail = null; try { if (draft.getPayloadJson() != null && !draft.getPayloadJson().isEmpty()) { reportDetail = objectMapper.readValue(draft.getPayloadJson(), Map.class); } } catch (Exception e) { log.warn("解析草稿 payload 失败: {}", e.getMessage()); } if (reportDetail != null) { ctx.put("reportDetail", reportDetail); } return ctx; } ``` - [ ] **步骤 4:编译验证** 运行:`cd cfc-backend && mvn clean compile -q -Dmaven.test.skip=true` 预期:BUILD SUCCESS(无输出即成功) - [ ] **步骤 5:Commit** ```bash git add cfc-backend/src/main/java/com/etotem/cfc/service/FamilyContextService.java git commit -m "feat: FamilyContextService 新增 buildContextFromDraft 支持草稿上下文" ``` --- ## 任务 2:后端 AIChatController 扩展 buildChatInputs + reportAnalyze **文件:** - 修改:`cfc-backend/src/main/java/com/etotem/cfc/controller/ai/AIChatController.java` **背景:** 现有 `buildChatInputs` 只处理 `reportId`/`surveyId`/`selfCheckId`;需加 `draftId` 分支。`reportAnalyze` 校验需接受 `draftId`。 - [ ] **步骤 1:修改 buildChatInputs 增加 draftId 处理** 在 `buildChatInputs` 方法内(约 136-207 行),`surveyId` 解析之后(约 154 行)添加: ```java String draftIdStr = params.get("draftId"); Long draftId = null; if (draftIdStr != null && !draftIdStr.trim().isEmpty()) { draftId = Long.valueOf(draftIdStr); } ``` 修改上下文组装分支(约 155-162 行): ```java Map inputs; if (reportId != null) { inputs = familyContextService.buildContext(userId, reportId); } else if (draftId != null) { inputs = familyContextService.buildContextFromDraft(userId, draftId); } else { inputs = familyContextService.buildContext(userId); } ``` - [ ] **步骤 2:修改 reportAnalyze 校验** 在 `reportAnalyze` 方法内(约 213-265 行),`reportIdStr` 获取后(约 215 行)添加 `draftIdStr` 获取,修改校验为: ```java String reportIdStr = params.get("reportId"); String draftIdStr = params.get("draftId"); if ((reportIdStr == null || reportIdStr.trim().isEmpty()) && (draftIdStr == null || draftIdStr.trim().isEmpty())) { return Result.error("报告ID或草稿ID不能为空"); } Long reportId = null; if (reportIdStr != null && !reportIdStr.trim().isEmpty()) { reportId = Long.valueOf(reportIdStr); } ``` 原有 `Long reportId = Long.valueOf(reportIdStr);`(约 222 行)删除。 - [ ] **步骤 3:编译验证** 运行:`cd cfc-backend && mvn clean compile -q -Dmaven.test.skip=true` 预期:BUILD SUCCESS - [ ] **步骤 4:Commit** ```bash git add cfc-backend/src/main/java/com/etotem/cfc/controller/ai/AIChatController.java git commit -m "feat: AIChatController 支持 draftId 参数的快速分析" ``` --- ## 任务 3:前端 api.js 扩展 reportQuickAnalyze **文件:** - 修改:`cfc-frontend/utils/api.js:1420` - [ ] **步骤 1:修改函数签名** ```javascript // 报告快速分析(免费版每日1次,付费版多轮) export const reportQuickAnalyze = (query, reportId, conversationId, draftId) => { var data = { query: query } if (reportId) data.reportId = reportId if (draftId) data.draftId = draftId if (conversationId) data.conversationId = conversationId return request('/api/ai/report/analyze', 'POST', data) } ``` - [ ] **步骤 2:语法校验** 运行:`node -e "const fs=require('fs');const src=fs.readFileSync('cfc-frontend/utils/api.js','utf8');const m=src.match(/export const reportQuickAnalyze[\s\S]*?\n}/);if(!m){console.log('FAIL');process.exit(1)}console.log('OK')"` 预期:OK - [ ] **步骤 3:Commit** ```bash git add cfc-frontend/utils/api.js git commit -m "feat(frontend): api.js reportQuickAnalyze 新增可选 draftId 参数" ``` --- ## 任务 4:前端 gut-flora-detail.vue 新增快速分析浮层 **文件:** - 修改:`cfc-frontend/pages/health/gut-flora-detail.vue` **策略:** 直接复制 `report-detail.vue` 的 `quickAnalyze` data/computed/methods/template/style,仅改入口条件与 import。 - [ ] **步骤 1:import 新增** 第 335 行第一个 import 行添加 `reportQuickAnalyze, getMyMembership`: ```javascript import { getReportDetail, editHealthReport, queryKnowledgeBase, queryIndicatorKnowledge, getGutFloraAnalysis, confirmReportPreview, discardReportDraft, parseReportDraft, getHealthFoods, getFoodCautionList, getFamilyMemberList, addFamilyMember, reportQuickAnalyze, getMyMembership } from '../../utils/api.js' ``` - [ ] **步骤 2:data 新增 quickAnalyze 对象** 在 `extractedAge: ''`(约 394 行)之后添加: ```javascript // 报告快速分析浮层 quickAnalyze: { show: false, sending: false, messages: [], // [{role:'user'|'ai', content}] inputText: '', conversationId: '', usedToday: false // 免费版今日已用(前端预判,最终以后端为准) } ``` - [ ] **步骤 3:computed 新增 quickQuestions** 在 `draftMemberText` computed 后(约 460 行)添加: ```javascript quickQuestions: function() { return [ '这份报告最需要关注哪些异常指标?', '报告中提示了哪些健康风险?', '有哪些改善建议?' ] } ``` - [ ] **步骤 4:methods 新增浮层逻辑** 在 `draftMemberText` 方法后(约 460 行)添加: ```javascript // ===== 报告快速分析 ===== openQuickAnalyze: function() { this.quickAnalyze.show = true var self = this // 预判免费版今日额度(仅用于 UI 展示,最终以后端校验为准) getMyMembership().then(function(res) { var level = res.data && res.data.memberLevel if (level && level !== 'FREE') { self.quickAnalyze.usedToday = false } }).catch(function() {}) }, closeQuickAnalyze: function() { this.quickAnalyze.show = false }, sendQuickQuestion: function(q) { if (q && q.trim()) { this.quickAnalyze.inputText = q this.sendQuickAnalyze() } }, sendQuickAnalyze: function() { var self = this var text = (this.quickAnalyze.inputText || '').trim() if (!text || this.quickAnalyze.sending) return this.quickAnalyze.messages.push({ role: 'user', content: text }) this.quickAnalyze.inputText = '' this.quickAnalyze.sending = true // 传 reportId 或 draftId var rid = this.reportId var did = this.draftId reportQuickAnalyze(text, rid, this.quickAnalyze.conversationId, did).then(function(res) { if (res.code === 200 && res.data) { self.quickAnalyze.messages.push({ role: 'ai', content: res.data.answer || '(无回答)' }) if (res.data.conversationId) { self.quickAnalyze.conversationId = res.data.conversationId } } else { var msg = res.message || '分析失败' if (msg.indexOf('每日仅限') !== -1) { self.quickAnalyze.usedToday = true } self.quickAnalyze.messages.push({ role: 'ai', content: msg }) } }).catch(function() { self.quickAnalyze.messages.push({ role: 'ai', content: 'AI 服务暂不可用,请稍后重试' }) }).finally(function() { self.quickAnalyze.sending = false }) } ``` - [ ] **步骤 5:template 新增 FAB + 浮层** 在 `` 之后(约 229 行),`` 之前,且在编辑 FAB(✏️)之前插入: ```html 🎯 快速分析 报告快速分析 ✕ 免费版今日次数已用完,升级会员解锁更多分析 {{ msg.content }} AI 正在分析中... {{ q }} 发送 ``` - [ ] **步骤 6:style 复用** 在 `