# 功能开发详细设计文档 > **版本:** v1.0 > **日期:** 2026-03-25 > **依据:** 《测试问题分析报告_2026-03-22》 > **项目:** 慢生活- 慢性肾病营养管理小程序 --- ## 目录 1. [公共前置工作(数据库变更 + 数据初始化)](#一公共前置工作) 2. [页面一:食谱计算器结果页](#二页面一食谱计算器结果页) 3. [页面二:AI 营养师对话页](#三页面二ai-营养师对话页) 4. [页面三:食物百科列表页](#四页面三食物百科列表页) 5. [页面四:食物百科详情页](#五页面四食物百科详情页) 6. [页面五:健康知识营养素列表页](#六页面五健康知识营养素列表页) 7. [页面六:营养素详情页](#七页面六营养素详情页) 8. [联调与测试检查清单](#八联调与测试检查清单) --- ## 一、公共前置工作 > 以下数据库变更和数据初始化需在所有页面开发之前完成。 ### 1.1 数据库表结构变更 #### 1.1.1 `v2_food` 表新增字段 ```sql -- 新增嘌呤含量字段 ALTER TABLE v2_food ADD COLUMN purine DECIMAL(10,2) DEFAULT NULL COMMENT '嘌呤含量(mg)'; -- 新增营养成分对应的重量基准字段 ALTER TABLE v2_food ADD COLUMN serving_size VARCHAR(50) DEFAULT '每100g' COMMENT '营养成分对应的食物重量基准,如"每100g"、"每份(50g)"'; ``` **验证方式:** 执行后 `DESC v2_food;` 确认新增列存在。 #### 1.1.2 `v2_food` 数据模型变更 **文件:** `msh_crmeb_22/crmeb-common/src/main/java/com/zbkj/common/model/tool/V2Food.java` ```java // 新增字段 /** 嘌呤含量(mg) */ @TableField("purine") private BigDecimal purine; /** 营养成分重量基准 */ @TableField("serving_size") private String servingSize; ``` ### 1.2 食物营养数据补全 **参考来源:** https://www.ishen365.com/article/cereal (爱肾网-食物营养成分表) **执行方式:** 编写数据迁移脚本(SQL 或 Python),将 ishen365 上各分类(谷薯类、蔬菜类、水果类、肉蛋类、水产类、奶类、豆类、坚果类)的食物营养数据批量更新到 `v2_food` 表。 **需要补全的字段:** | 字段 | 说明 | 数据来源 | |------|------|----------| | `calcium` | 钙含量(mg) | ishen365 / 《中国食物成分表》 | | `iron` | 铁含量(mg) | ishen365 / 《中国食物成分表》 | | `vitamin_c` | 维生素C含量(mg) | ishen365 / 《中国食物成分表》 | | `purine` | 嘌呤含量(mg) | ishen365 / 专业嘌呤数据库 | | `serving_size` | 重量基准 | 统一填写 `"每100g"` | **示例 SQL:** ```sql -- 以大米为例 UPDATE v2_food SET calcium = 13, iron = 2.3, vitamin_c = 0, purine = 18.4, serving_size = '每100g' WHERE name = '大米' AND category = '谷薯类'; ``` **负责人:** 后端开发 **预计工时:** 1 天(含数据核对) ### 1.3 营养素知识内容 AI 生成并落库 **目标:** 为 `v2_knowledge` 表插入 6 条营养素科普记录(蛋白质、钾、磷、钠、钙、水分),内容由 Coze AI 生成。 **执行方式:** 编写后端管理脚本或一次性接口。 **详见 [页面六:营养素详情页 - 步骤 1](#步骤-1后端---ai-生成营养素内容并落库) 。** --- ## 二、页面一:食谱计算器结果页 ### 页面信息 | 项目 | 说明 | |------|------| | 前端路径 | `msh_single_uniapp/pages/tool/calculator-result.vue` | | 后端服务 | `ToolCalculatorServiceImpl.java` | | API 接口 | `POST /api/front/tool/calculator/calculate` | | 关联接口 | `GET /api/front/tool/calculator/result/{id}` | ### 问题概述 食谱计算器中油脂类份数计算系数错误(与谷薯类使用了相同系数 5.7),导致计算出的每日用油量约 60g,远超膳食指南推荐的 25-30g。 ### 开发任务 #### 任务 2-1:修改油脂类计算系数(后端) **文件:** `msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolCalculatorServiceImpl.java` **方法:** `generateFoodPortions()` **修改内容:** ```java // ❌ 修改前(第 516 行) list.add(createFoodPortion(7, "油脂类10g", round(5.7 * energyRatio))); // ✅ 修改后 list.add(createFoodPortion(7, "油脂类10g", round(2.5 * energyRatio))); ``` **验证计算:** | 患者 | 标准体重 | 每日能量 | energyRatio | 修改前油脂份数 | 修改后油脂份数 | 修改后每日用油 | |------|----------|----------|-------------|----------------|----------------|----------------| | 男性170cm | 63kg | 2205 kcal | 1.10 | 6.3份(63g) | 2.8份(28g) | 28g | | 女性160cm | 54kg | 1890 kcal | 0.95 | 5.4份(54g) | 2.4份(24g) | 24g | | 男性175cm | 66.5kg | 2328 kcal | 1.16 | 6.6份(66g) | 2.9份(29g) | 29g | **预计工时:** 0.5 小时 **自测要点:** 输入不同体型参数,验证结果页中"油脂类"份数是否在 2-3 份范围内。 --- ## 三、页面二:AI 营养师对话页 ### 页面信息 | 项目 | 说明 | |------|------| | 前端路径 | `msh_single_uniapp/pages/tool/ai-nutritionist.vue` | | 后端服务 | `ToolAiNutritionistServiceImpl.java` | | Coze 服务 | `ToolCozeServiceImpl.java` | | Coze 控制器 | `CozeController.java` | | 前端 API | `msh_single_uniapp/api/tool.js` + `api/models-api.js` | ### 问题概述 后端 `sendMessage()` 使用 Mock 回复;前端文本消息走 KieAI Gemini、图片消息走 Coze,双路径导致体验混乱且响应慢。 ### 开发任务 #### 任务 3-1:后端 sendMessage() 对接 Coze API(后端) **文件:** `ToolAiNutritionistServiceImpl.java` **修改内容:** 将 `sendMessage()` 方法中的 Mock 逻辑替换为 Coze API 调用。 ```java // 注入 Coze 服务 @Autowired private ToolCozeServiceImpl cozeService; // sendMessage() 方法内,替换第 89-97 行的 Mock 逻辑: // ❌ 删除 message.setAiResponse("这是一个模拟的AI回复。"); message.setAiResponseStatus("success"); // ✅ 替换为 try { CozeChatRequest chatRequest = new CozeChatRequest(); chatRequest.setBotId("7591133240535449654"); chatRequest.setUserId(String.valueOf(userId)); chatRequest.setContent(message.getContent()); if (conversationId != null) { chatRequest.setConversationId(conversationId.toString()); } CreateChatResp resp = cozeService.chat(chatRequest); // 从 Coze 响应中提取 AI 回复内容 String aiContent = extractAiContent(resp); message.setAiResponse(aiContent); message.setAiResponseStatus("success"); message.setAiResponseTime(new Date()); } catch (Exception e) { log.error("Coze AI 回复失败, userId={}", userId, e); message.setAiResponse("抱歉,AI 营养师暂时无法回答,请稍后再试。"); message.setAiResponseStatus("failed"); } ``` **新增辅助方法:** ```java /** * 从 Coze 对话响应中提取 AI 回复文本 */ private String extractAiContent(CreateChatResp resp) { if (resp == null) return "暂无回复"; // 根据 Coze SDK 实际返回结构提取 answer 类型消息 // 需调用 listMessages() 获取 role=assistant, type=answer 的消息 String conversationId = resp.getConversationId(); String chatId = resp.getId(); List messages = cozeService.listMessages(conversationId, chatId); return messages.stream() .filter(m -> "assistant".equals(m.getRole()) && "answer".equals(m.getType())) .map(Message::getContent) .findFirst() .orElse("暂无回复"); } ``` **预计工时:** 2 小时 #### 任务 3-2:前端统一对话路径为 Coze(前端) **文件:** `msh_single_uniapp/pages/tool/ai-nutritionist.vue` **修改范围:** `sendMessage()` / `handleSend()` 方法 **当前逻辑(需重构):** - 文本消息 → `api.kieaiGeminiChat()` (KieAI Gemini) - 图片消息 → `api.cozeChat()` + 轮询 `api.cozeRetrieveChat()` **修改为:** 统一走 Coze SSE 流式端点 ```javascript // ✅ 统一的消息发送方法 async sendToCoze(content, images = []) { // 1. 如果有图片,先上传到 Coze 获取 file_id let messageContent = content; if (images.length > 0) { const fileIds = []; for (const img of images) { const res = await api.cozeUploadFile(img.path); fileIds.push({ type: 'image', file_id: res.id }); } // 构建多模态消息 messageContent = JSON.stringify([ { type: 'text', text: content }, ...fileIds ]); } // 2. 添加用户消息到列表 this.messageList.push({ role: 'user', content, images }); // 3. 添加 AI 占位消息(用于流式填充) const aiMsg = { role: 'ai', content: '', loading: true }; this.messageList.push(aiMsg); this.scrollToBottom(); // 4. 调用 Coze 非流式接口(小程序不支持 SSE,改用轮询) try { const chatRes = await api.cozeChat({ botId: this.botId, userId: this.userId, additionalMessages: [{ role: 'user', content: messageContent, content_type: images.length > 0 ? 'object_string' : 'text' }], conversationId: this.conversationId || undefined }); this.conversationId = chatRes.conversation_id; const chatId = chatRes.id; // 5. 轮询等待完成(每 1.5 秒,最多 40 次 = 60 秒) let status = ''; let attempts = 0; while (status !== 'completed' && attempts < 40) { await this.sleep(1500); const statusRes = await api.cozeRetrieveChat(this.conversationId, chatId); status = statusRes.status; attempts++; if (status === 'failed') throw new Error('AI 回复失败'); } // 6. 获取 AI 回复 const msgRes = await api.cozeMessageList(this.conversationId, chatId); const answer = msgRes.find(m => m.role === 'assistant' && m.type === 'answer'); aiMsg.content = answer ? answer.content : '暂无回复'; } catch (e) { aiMsg.content = '抱歉,AI 营养师暂时无法回答,请稍后再试。'; console.error('Coze 对话失败:', e); } finally { aiMsg.loading = false; this.isLoading = false; this.scrollToBottom(); } } ``` **需要删除/注释的代码:** - `api.kieaiGeminiChat()` 调用逻辑(文本消息路径 A) - 图片消息的独立处理分支(合并到统一方法中) **保留的能力:** - 图片选择 + 预览(最多 3 张) - 语音输入(ASR 转文本后作为文本发送) - 快捷问题按钮 - 清空对话历史 **预计工时:** 4 小时 #### 任务 3-3:优化 Coze Bot Prompt(运营配置) **操作位置:** Coze 平台 → Bot `7591133240535449654` → 系统提示词 **建议系统提示词:** ``` 你是慢生活小程序的 AI 营养师,专注于慢性肾脏病(CKD)患者的饮食营养指导。 回复规范: 1. 【一句话建议】用一句话直接回答用户问题 2. 【营养分析】针对用户的具体情况,从蛋白质、钾、磷、钠、能量等维度分析 3. 【推荐方案】给出 2-3 个具体可操作的饮食建议 4. 【注意事项】提醒需要警惕的风险,如高钾、高磷食物 约束: - 不提供药物建议,仅限饮食营养指导 - 涉及具体用药、透析方案时,提醒用户咨询主治医生 - 语言通俗易懂,避免过多专业术语 - 每条建议附带具体食材示例 ``` **预计工时:** 0.5 小时 #### 自测检查清单 - [ ] 发送文本消息 → AI 在 5 秒内开始回复 - [ ] 发送图片+文本 → AI 能识别图片并给出营养建议 - [ ] 多轮对话 → AI 能基于上下文连续回答 - [ ] 清空对话 → 重新开始新会话 - [ ] 网络异常 → 显示友好错误提示,不崩溃 - [ ] AI 回复内容具有结构性(有摘要、分析、建议) --- ## 四、页面三:食物百科列表页 ### 页面信息 | 项目 | 说明 | |------|------| | 前端路径 | `msh_single_uniapp/pages/tool/food-encyclopedia.vue` | | 后端服务 | `ToolFoodServiceImpl.java` | | API 接口 | `GET /api/front/tool/food/search` / `GET /api/front/tool/food/list` | ### 问题概述 食物列表中部分食物图片显示异常(空白或破图)。 ### 开发任务 #### 任务 4-1:批量刷新食物图片(后端/运维) **已有接口:** `POST /api/front/tool/food/refresh-images?limit=20` **执行流程(无需额外开发):** ``` 调用接口 → ToolFoodServiceImpl.refreshFoodImages(limit) → 查询 v2_food 中 image 为空或非 OSS 链接的记录 → 遍历调用 DishImageService.ensureFoodImageAndUpdateDb(foodId) → KieAI 生成食物照片 → 下载 → 压缩至 ≤100KB → 上传阿里云 OSS(路径: foods/xxx.jpg) → 更新 v2_food.image 字段 → 写入 v2_dish_image_cache 缓存表 ``` **操作步骤:** ```bash # 多次调用,每次处理 20 条,直到所有食物都有有效图片 curl -X POST "https://your-domain/api/front/tool/food/refresh-images?limit=20" \ -H "Authori-zation: {管理员token}" # 查询还有多少条无图记录 SELECT COUNT(*) FROM v2_food WHERE status='active' AND (image IS NULL OR image = '' OR image NOT LIKE '%aliyuncs.com%'); ``` **预计工时:** 0.5 天(含执行等待时间,KieAI 生图每张约 20-60 秒) #### 任务 4-2:前端图片加载容错(前端) **文件:** `msh_single_uniapp/pages/tool/food-encyclopedia.vue` **修改内容:** 为食物列表图片增加 `@error` 兜底。 ```html ``` ```javascript methods: { onImageError(e, item) { // 图片加载失败时替换为占位图 item.image = '/static/images/food-placeholder.png'; } } ``` **需要新增的占位图:** `/static/images/food-placeholder.png`(一个通用的食物灰色占位图,建议 200x200px) **预计工时:** 1 小时 --- ## 五、页面四:食物百科详情页 ### 页面信息 | 项目 | 说明 | |------|------| | 前端路径 | `msh_single_uniapp/pages/tool/food-detail.vue` | | 后端服务 | `ToolFoodServiceImpl.java` → `getDetail()` | | API 接口 | `GET /api/front/tool/food/detail/{id}` | | 数据模型 | `V2Food.java` | ### 问题概述 1. 营养成分表只显示 7 项(缺少钙、铁、维生素C、嘌呤) 2. 图标使用了 Figma 临时 URL,过期后不显示 3. "每100g" 标注是硬编码,不根据实际数据变化 ### 开发任务 #### 任务 5-1:后端接口补充返回字段(后端) **文件:** `ToolFoodServiceImpl.java` → `getDetail()` 方法 **在现有 `map.put(...)` 代码块之后,追加以下字段返回:** ```java // ====== 新增返回字段 ====== map.put("calcium", food.getCalcium()); // 钙(mg) map.put("iron", food.getIron()); // 铁(mg) map.put("vitaminC", food.getVitaminC()); // 维生素C(mg) map.put("purine", food.getPurine()); // 嘌呤(mg) —— 新增字段 map.put("servingSize", food.getServingSize()); // 重量基准 —— 新增字段 map.put("nutrientsJson", food.getNutrientsJson());// 扩展营养素JSON map.put("recommendedAmount", food.getRecommendedAmount()); // 推荐摄入量 ``` **同时在 `search()` 方法的列表返回中,也补充 `servingSize` 字段:** ```java map.put("servingSize", food.getServingSize()); ``` **预计工时:** 0.5 小时 #### 任务 5-2:前端成分表解析增加新字段(前端) **文件:** `msh_single_uniapp/pages/tool/food-detail.vue` **修改 `parseNutritionTable()` 或 `computed` 中的成分表构建逻辑,确保包含以下完整字段:** ```javascript // displayNutritionTable 的构建逻辑 buildNutritionTable(data) { const table = [ { name: '能量', value: data.energy, unit: 'kcal', level: this.getLevel('energy', data.energy) }, { name: '蛋白质', value: data.protein, unit: 'g', level: this.getLevel('protein', data.protein) }, { name: '脂肪', value: data.fat, unit: 'g', level: 'normal' }, { name: '碳水化合物', value: data.carbohydrate, unit: 'g', level: 'normal' }, { name: '钾', value: data.potassium, unit: 'mg', level: this.getLevel('potassium', data.potassium) }, { name: '磷', value: data.phosphorus, unit: 'mg', level: this.getLevel('phosphorus', data.phosphorus) }, { name: '钠', value: data.sodium, unit: 'mg', level: this.getLevel('sodium', data.sodium) }, // ====== 以下为新增 ====== { name: '钙', value: data.calcium, unit: 'mg', level: 'normal' }, { name: '铁', value: data.iron, unit: 'mg', level: 'normal' }, { name: '维生素C', value: data.vitaminC, unit: 'mg', level: 'normal' }, { name: '嘌呤', value: data.purine, unit: 'mg', level: this.getLevel('purine', data.purine) }, ]; // 过滤掉值为 null/undefined 的条目(后端可能部分食物没有数据) return table.filter(item => item.value != null && item.value !== ''); } ``` **预计工时:** 1 小时 #### 任务 5-3:动态显示重量标注(前端) **文件:** `msh_single_uniapp/pages/tool/food-detail.vue` **修改前(硬编码):** ```html 每100g 每100g ``` **修改后(动态):** ```html {{ foodData.servingSize || '每100g' }} {{ foodData.servingSize || '每100g' }} ``` **在数据解析方法中,从 API 返回值提取 `servingSize`:** ```javascript // API 返回数据解析时 this.foodData.servingSize = res.data.servingSize || '每100g'; ``` **预计工时:** 0.5 小时 #### 任务 5-4:替换 Figma 临时 URL(前端) **文件:** `msh_single_uniapp/pages/tool/food-detail.vue` **需替换的 URL(第 95-96 行 `data()` 中):** | 变量名 | 当前值(Figma 临时 URL) | 替换方案 | |--------|--------------------------|----------| | `iconShare` | `https://www.figma.com/api/mcp/asset/f9f0d7b9-...` | 替换为 OSS 图片或本地 `/static/icons/share.png` | | `iconSearch` | `https://www.figma.com/api/mcp/asset/aa6bb75b-...` | 替换为 OSS 图片或本地 `/static/icons/search.png` | | `defaultFoodData.image` | `https://www.figma.com/api/mcp/asset/bf4ff04c-...` | 替换为 `/static/images/food-placeholder.png` | **同时检查 `nutrient-detail.vue` 中的 Figma URL(第 111-113 行):** | 变量名 | 替换方案 | |--------|----------| | `iconWhyImportant` | 本地 `/static/icons/why-important.png` | | `iconRecommendation` | 本地 `/static/icons/recommendation.png` | | `iconSuggestions` | 本地 `/static/icons/suggestions.png` | **操作方式:** 1. 从 Figma 设计稿导出对应图标为 PNG(建议 64x64px,2x 为 128x128px) 2. 放置到 `msh_single_uniapp/static/icons/` 目录 3. 或上传到 OSS 获取稳定 URL 后替换 **预计工时:** 1 小时 #### 自测检查清单 - [ ] 食物详情页显示完整的 11 项营养成分(能量、蛋白质、脂肪、碳水、钾、磷、钠、钙、铁、维C、嘌呤) - [ ] 如果某食物缺少部分营养数据(如嘌呤为 null),该项不显示而非显示 "null" - [ ] 重量标注动态显示(如"每100g"),而非硬编码 - [ ] 分享图标、搜索图标正常显示 - [ ] 默认占位图在食物图片加载失败时正常显示 - [ ] 与 ishen365 上同一食物的数据进行交叉对比,确保数值一致 --- ## 六、页面五:健康知识营养素列表页 ### 页面信息 | 项目 | 说明 | |------|------| | 前端路径 | `msh_single_uniapp/pages/tool/nutrition-knowledge.vue` | | 后端服务 | `ToolKnowledgeServiceImpl.java` | | API 接口 | 营养素列表本地静态、饮食指南/科普文章从 `GET /api/front/tool/knowledge/list` 获取 | ### 问题概述 营养素卡片的点击事件使用 `dataset` 传参,在微信小程序中因属性名大小写转换导致取值失败,用户点击后无法跳转到对应的详情页。 ### 开发任务 #### 任务 6-1:修复营养素卡片点击传参(前端) **文件:** `msh_single_uniapp/pages/tool/nutrition-knowledge.vue` **修改前(第 39 行):** ```html ``` **修改后:** ```html ``` **修改 `methods` 中的 `goToNutrientDetail` 方法:** ```javascript // ❌ 修改前 goToNutrientDetail(event) { const index = event.currentTarget.dataset.nutrientIndex; const item = this.nutrientList[index]; if (!item) return; uni.navigateTo({ url: `/pages/tool/nutrient-detail?name=${encodeURIComponent(item.name)}` }); } // ✅ 修改后 goToNutrientDetail(index) { const item = this.nutrientList[index]; if (!item) return; uni.navigateTo({ url: `/pages/tool/nutrient-detail?name=${encodeURIComponent(item.name)}` }); } ``` **预计工时:** 0.5 小时 #### 自测检查清单 - [ ] 依次点击 6 个营养素卡片(蛋白质、钾、磷、钠、钙、水分),均能正常跳转 - [ ] 在微信开发者工具 + 真机预览中均测试通过 - [ ] 跳转后详情页标题与点击的营养素名称一致 --- ## 七、页面六:营养素详情页 ### 页面信息 | 项目 | 说明 | |------|------| | 前端路径 | `msh_single_uniapp/pages/tool/nutrient-detail.vue` | | 后端服务 | `ToolKnowledgeServiceImpl.java` → `getNutrientDetail(name)` | | API 接口 | `GET /api/front/tool/knowledge/nutrient/{name}` | | 数据表 | `v2_knowledge`(type='nutrients') | | 封面图生成 | `POST /api/front/tool/knowledge/fill-cover-images?limit=6` | ### 问题概述 1. 详情页数据完全来自前端硬编码的 `nutrientMap`,未调用后端接口 2. `data()` 默认值为"钠"的内容,参数丢失时所有页面都显示"钠" 3. 内容质量参差不齐,需要用 AI 生成专业科普内容 ### 开发任务 #### 步骤 1:后端 — AI 生成营养素内容并落库 **实现方式 A(推荐):编写后端管理接口** **新建文件或在 `ToolController.java` 中增加管理端接口:** ```java /** * 批量生成营养素科普内容(管理端一次性调用) */ @PostMapping("/admin/tool/knowledge/generate-nutrients") public CommonResult generateNutrientContent() { String[] nutrients = {"蛋白质", "钾", "磷", "钠", "钙", "水分"}; int success = 0; for (String nutrient : nutrients) { try { // 1. 检查是否已存在 V2Knowledge existing = knowledgeDao.selectOne( new LambdaQueryWrapper() .eq(V2Knowledge::getType, "nutrients") .eq(V2Knowledge::getNutrientName, nutrient) ); if (existing != null) { log.info("营养素 {} 已存在,跳过", nutrient); continue; } // 2. 调用 Coze AI 生成内容 CozeChatRequest req = new CozeChatRequest(); req.setBotId("7591133240535449654"); req.setContent(buildNutrientPrompt(nutrient)); CreateChatResp resp = cozeService.chat(req); String content = extractAiContent(resp); // 3. 写入 v2_knowledge 表 V2Knowledge knowledge = new V2Knowledge(); knowledge.setType("nutrients"); knowledge.setNutrientName(nutrient); knowledge.setTitle(nutrient + " — CKD患者膳食管理"); knowledge.setContent(content); // JSON 格式 knowledge.setSummary("了解" + nutrient + "在慢性肾病饮食中的重要性"); knowledge.setStatus("published"); knowledge.setSortOrder(success + 1); knowledge.setCreatedAt(new Date()); knowledgeDao.insert(knowledge); success++; } catch (Exception e) { log.error("生成营养素内容失败: {}", nutrient, e); } } // 4. 自动补充封面图 knowledgeService.fillMissingCoverImages(6); return CommonResult.success("成功生成 " + success + " 条营养素内容"); } private String buildNutrientPrompt(String nutrient) { return "请为慢性肾脏病(CKD)患者生成关于「" + nutrient + "」的科普内容。\n" + "要求严格按以下 JSON 格式返回(不要包含 markdown 标记):\n" + "{\n" + " \"name\": \"营养素名称\",\n" + " \"english\": \"英文名\",\n" + " \"icon\": \"一个合适的emoji\",\n" + " \"description\": \"一句话描述\",\n" + " \"status\": \"控制建议(如:需控制/严格控制/适量补充)\",\n" + " \"statusDesc\": \"状态补充说明\",\n" + " \"importance\": \"为什么重要(2-3句话)\",\n" + " \"recommendation\": \"推荐摄入量(按CKD分期分别说明)\",\n" + " \"foodSources\": [\"食物来源1\", \"食物来源2\", ...最多6个],\n" + " \"riskWarning\": \"风险提示(2-3句话)\",\n" + " \"suggestions\": [\"建议1\", \"建议2\", ...共6条实用建议],\n" + " \"disclaimer\": \"以上建议仅供参考,具体方案请咨询您的主治医生或营养师\"\n" + "}"; } ``` **实现方式 B(备选):直接执行 SQL 手动插入** 如果 AI 生成效果不理想,可手动整理内容后直接 SQL 插入: ```sql INSERT INTO v2_knowledge (type, nutrient_name, title, content, summary, status, sort_order, created_at) VALUES ('nutrients', '蛋白质', '蛋白质 — CKD患者膳食管理', '{"name":"蛋白质","english":"Protein","icon":"🥩",...}', '了解蛋白质在CKD饮食中的重要性', 'published', 1, NOW()), ('nutrients', '钾', '钾 — CKD患者膳食管理', '{"name":"钾","english":"Potassium (K)","icon":"🍌",...}', '高钾血症的预防与饮食管理', 'published', 2, NOW()), -- ... 其余 4 条 ; ``` **插入后,调用封面图生成接口:** ```bash POST /api/front/tool/knowledge/fill-cover-images?limit=6 ``` **预计工时:** 2 小时 #### 步骤 2:前端 — 详情页改为从后端 API 获取数据 **文件:** `msh_single_uniapp/pages/tool/nutrient-detail.vue` **修改 1:`data()` 默认值改为空状态** ```javascript // ❌ 修改前:默认是"钠"的完整数据 data() { return { nutrientData: { name: '钠', english: 'Sodium (Na)', icon: '🧂', ... } } } // ✅ 修改后:默认为空,显示加载状态 data() { return { loading: true, loadError: false, nutrientData: { name: '', english: '', icon: '', description: '', status: '', statusDesc: '', importance: '', recommendation: '', foodSources: [], riskWarning: '', suggestions: [], disclaimer: '' } } } ``` **修改 2:`onLoad()` 中增加参数解码** ```javascript onLoad(options) { if (options.name) { const name = decodeURIComponent(options.name); uni.setNavigationBarTitle({ title: name }); this.loadNutrientData(name); } else { this.loadError = true; } } ``` **修改 3:`loadNutrientData()` 改为优先调用 API,兜底使用本地数据** ```javascript async loadNutrientData(name) { this.loading = true; try { // 优先从后端获取(v2_knowledge 表, type='nutrients') const { getNutrientDetail } = await import('@/api/tool.js'); const result = await getNutrientDetail(encodeURIComponent(name)); if (result && result.data && result.data.content) { const detail = result.data; // content 字段存储的是 JSON 字符串 const parsed = typeof detail.content === 'string' ? JSON.parse(detail.content) : detail.content; this.nutrientData = parsed; this.loading = false; return; } } catch (error) { console.warn('从后端获取营养素详情失败,降级使用本地数据:', error); } // 降级:使用本地硬编码数据(保留原有 nutrientMap 作为兜底) const localData = this.getLocalNutrientData(name); if (localData) { this.nutrientData = localData; } else { this.loadError = true; } this.loading = false; }, getLocalNutrientData(name) { const nutrientMap = { '蛋白质': { name: '蛋白质', english: 'Protein', icon: '🥩', ... }, '钾': { ... }, // ... 保留原有 6 项数据作为兜底 }; return nutrientMap[name] || null; } ``` **修改 4:增加加载态和错误态的模板** ```html ``` **预计工时:** 2 小时 #### 步骤 3:替换 Figma 图标 URL 同 [任务 5-4](#任务-5-4替换-figma-临时-url前端),替换 `nutrient-detail.vue` 中第 111-113 行的 3 个 Figma URL。 **预计工时:** 0.5 小时 #### 自测检查清单 - [ ] 从列表页点击每个营养素 → 跳转到对应的详情页 - [ ] 详情页标题/图标/内容与营养素名称一致(不再全部显示"钠") - [ ] 后端 `v2_knowledge` 有数据时 → 显示后端数据 - [ ] 后端数据不存在时 → 降级显示本地硬编码数据 - [ ] 图标正常显示(不是 Figma 的破图) - [ ] 6 种营养素的内容专业准确,格式统一 --- ## 八、联调与测试检查清单 ### 全量回归测试 | 序号 | 测试场景 | 涉及页面 | 预期结果 | |------|----------|----------|----------| | 1 | 食谱计算器 → 输入标准体型参数 → 查看结果 | calculator-result | 油脂类份数在 2-3 份之间(每日 20-30g) | | 2 | AI 营养师 → 发送文字问题 | ai-nutritionist | 5 秒内收到结构化回复 | | 3 | AI 营养师 → 发送图片+文字 | ai-nutritionist | AI 识别图片并给出营养分析 | | 4 | AI 营养师 → 连续多轮对话 | ai-nutritionist | 上下文连贯,不丢失历史 | | 5 | 食物百科 → 浏览列表 | food-encyclopedia | 所有食物有图片,无破图 | | 6 | 食物百科 → 点击查看详情 | food-detail | 成分表完整(11 项),有重量标注 | | 7 | 食物详情 → 对比 ishen365 | food-detail | 营养数值与参考站点一致 | | 8 | 健康知识 → 点击"蛋白质" | nutrition-knowledge → nutrient-detail | 跳转正常,显示蛋白质内容 | | 9 | 健康知识 → 依次点击全部 6 项 | nutrient-detail | 每项内容不同,不再全部显示"钠" | | 10 | 健康知识 → 弱网/断网测试 | nutrient-detail | 降级显示本地数据,不崩溃 | ### 工时汇总 | 任务 | 页面 | 开发人员 | 预计工时 | |------|------|----------|----------| | 数据库变更 + 数据补全 | 公共 | 后端 | 1 天 | | 任务 2-1 油脂系数修改 | 计算器结果页 | 后端 | 0.5h | | 任务 3-1 后端对接 Coze | AI 营养师 | 后端 | 2h | | 任务 3-2 前端统一 Coze | AI 营养师 | 前端 | 4h | | 任务 3-3 Coze Bot Prompt | AI 营养师 | 运营 | 0.5h | | 任务 4-1 批量刷新图片 | 食物列表 | 后端/运维 | 0.5 天 | | 任务 4-2 图片容错 | 食物列表 | 前端 | 1h | | 任务 5-1 接口补字段 | 食物详情 | 后端 | 0.5h | | 任务 5-2 成分表新字段 | 食物详情 | 前端 | 1h | | 任务 5-3 动态重量标注 | 食物详情 | 前端 | 0.5h | | 任务 5-4 替换 Figma URL | 食物详情+营养素详情 | 前端 | 1h | | 任务 6-1 修复传参 Bug | 营养素列表 | 前端 | 0.5h | | 步骤 1 AI 生成内容落库 | 营养素详情 | 后端 | 2h | | 步骤 2 前端改 API 获取 | 营养素详情 | 前端 | 2h | | 联调 + 回归测试 | 全部 | 全员 | 1 天 | | **合计** | | | **约 4.5 人天** |