Files
msh-system/docs/测试问题分析报告_2026-03-22.md
Claude 584ff3b666 docs: 新增测试问题分析报告(食谱计算器/AI营养师/食物百科/健康知识)
针对测试反馈的四个问题进行代码级分析,包含根因定位、涉及文件和修复建议。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 10:18:18 +08:00

302 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 测试问题分析报告
> **日期:** 2026-03-22
> **分析人:** Claude AI
> **项目:** 民生汇 - 慢性肾病营养管理小程序
---
## 一、食谱计算器 —— 用油量计算逻辑错误
### 问题描述
食谱计算器计算出的用油量偏多,不符合实际膳食指导标准。
### 问题定位
**文件:** `msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolCalculatorServiceImpl.java`
**位置:** `generateFoodPortions()` 方法,第 516 行
```java
// 当前代码(有误)
list.add(createFoodPortion(7, "油脂类10g", round(5.7 * energyRatio)));
```
### 根因分析
油脂类的份数计算使用了系数 `5.7`,与第 510 行谷薯类的系数完全相同:
```java
list.add(createFoodPortion(1, "谷薯50g", round(5.7 * energyRatio))); // 谷薯类
list.add(createFoodPortion(7, "油脂类10g", round(5.7 * energyRatio))); // 油脂类(错误地使用了同一系数)
```
其中 `energyRatio = energy / 2000.0`。以标准体重 60kg 患者为例:
- 每日能量目标 = 60 × 35 = 2100 kcal
- energyRatio = 2100 / 2000 = 1.05
- 油脂份数 = 5.7 × 1.05 ≈ **6.0 份**
- 每份 10g即每天需要 **60g 油**
根据《中国居民膳食指南》和《慢性肾脏病患者膳食指导2017CKD 患者每日食用油推荐量为 **25-30g**,当前计算结果严重偏高。
### 修复建议
将油脂类的计算系数从 `5.7` 调整为 `2.5`
```java
// 修复后
list.add(createFoodPortion(7, "油脂类10g", round(2.5 * energyRatio)));
```
修复后以同一患者为例2.5 × 1.05 ≈ 2.6 份,即每日约 26g 油,符合膳食指南要求。
### 优先级:高
---
## 二、AI 营养师 —— 反应慢、内容凌乱、体验差
### 问题描述
AI 营养师功能反应比较慢,提供的内容比较凌乱,逻辑性差,整体体验感不好。
### 问题定位
**后端文件:** `msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolAiNutritionistServiceImpl.java`
**前端文件:** `msh_single_uniapp/pages/tool/ai-nutritionist.vue`
### 根因分析
#### 1. AI 服务未真正接入(核心原因)
后端 `sendMessage()` 方法(第 89-97 行)使用的是 **Mock 模拟回复**,并未真正接入 AI 服务:
```java
// Mock AI response
// In real system, this would call AI service
// Here we just set a mock response for now
message.setAiResponse("这是一个模拟的AI回复。");
message.setAiResponseStatus("success");
```
虽然项目中存在 `ToolCozeServiceImpl`Coze AI 服务)的实现,但 `sendMessage()` 方法中并未调用它。
#### 2. 前端轮询机制导致延迟感
前端通过定时轮询 `getResponse()` 接口等待 AI 回复:
- 用户发送消息 → 后端同步写入 mock 数据 → 前端轮询获取结果
- 轮询间隔本身带来延迟,加上网络开销,用户体感"反应慢"
#### 3. 缺乏流式响应
当前架构没有 SSEServer-Sent Events或 WebSocket 支持,无法实现"逐字输出"的流式效果,用户需等待完整回复后才能看到内容。
### 修复建议
1. **正式接入 Coze AI 服务**:在 `sendMessage()` 中调用 `ToolCozeServiceImpl`,真正发起 AI 对话请求
2. **优化 Prompt 模板**设计结构化的系统提示词System Prompt要求 AI 回复遵循固定格式(如:摘要 → 营养分析 → 建议 → 注意事项),提升内容逻辑性
3. **引入 SSE 流式返回**:后端通过 SSE 推送逐步响应,前端实时渲染,提升交互体验
4. **增加加载状态动效**:在等待回复期间展示更好的"正在思考"动画
### 优先级:高
---
## 三、食物百科 —— 成分表不全、图片错误、缺少重量标注
### 问题描述
食物百科中食物成分表内容不全,图片显示有错误,而且没有标注具体是多少重量的食物所提供的营养成分。
### 问题定位
**后端文件:** `msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolFoodServiceImpl.java`
**前端文件:** `msh_single_uniapp/pages/tool/food-detail.vue`
**数据模型:** `msh_crmeb_22/crmeb-common/src/main/java/com/zbkj/common/model/tool/V2Food.java`
### 根因分析
#### 3.1 食物成分表内容不全
后端 `getDetail()` 方法返回的营养字段不完整:
```java
// 当前返回的字段
map.put("energy", food.getEnergy());
map.put("protein", food.getProtein());
map.put("fat", food.getFat());
map.put("carbohydrate", food.getCarbohydrate());
map.put("potassium", food.getPotassium());
map.put("phosphorus", food.getPhosphorus());
map.put("sodium", food.getSodium());
```
`V2Food` 模型中定义了但未返回的字段包括:
- `calcium`(钙)
- `iron`(铁)
- `vitaminC`(维生素 C
- `nutrientsJson`(扩展营养素 JSON
- `recommendedAmount`(推荐摄入量)
前端 `food-detail.vue``parseNutritionTable()` 方法尝试解析 `calcium``purine` 等字段,但后端根本没有返回这些数据。
#### 3.2 图片显示错误
两个层面的图片问题:
**a) 前端硬编码了 Figma 临时 URL**
`food-detail.vue``nutrient-detail.vue` 中的默认图片使用了 Figma API 的临时资源 URL
```javascript
iconWhyImportant: 'https://www.figma.com/api/mcp/asset/51e00c9b-5719-4391-9dff-68b70d24aece'
```
这些 URL 是开发阶段从 Figma 设计稿导出的临时链接,过期后图片无法加载。
**b) 后端 AI 生图服务不稳定**
`DishImageService` 通过 AI 生成食物图片并上传 OSS。当 AI API 调用失败时,部分食物的 `image` 字段为空或仍为旧的无效 URL导致前端图片显示异常。
#### 3.3 缺少重量标注
后端接口返回数据中 **没有 `servingSize`(份量基准)字段**。前端虽然显示了"每100g"标签,但这是硬编码的静态文本,并非根据实际数据动态展示。如果数据库中部分食物的营养数据不是基于 100g 标准录入的,就会产生误导。
### 修复建议
1. **补充后端返回字段**:在 `getDetail()` 中增加 `calcium``iron``vitaminC``nutrientsJson``recommendedAmount` 等字段的返回
2. **替换 Figma 临时 URL**:将所有 Figma API 链接替换为上传至 OSS 的稳定图片资源
3. **增加图片容错处理**:前端为食物图片增加 `@error` 事件处理,在图片加载失败时显示占位图
4. **增加重量标注字段**:数据库添加 `serving_size` 字段(如"每100g"、"每份(50g)"),后端返回后前端动态显示
### 优先级:中高
---
## 四、健康知识营养素板块 —— 所有选项显示同一内容
### 问题描述
健康知识中营养素板块,所有的营养素选项(蛋白质、钾、磷、钠、钙、水分)点击后显示的都是同一个内容。
### 问题定位
**列表页:** `msh_single_uniapp/pages/tool/nutrition-knowledge.vue` 第 39 行
**详情页:** `msh_single_uniapp/pages/tool/nutrient-detail.vue` 第 114 行、第 137-277 行
### 根因分析
这是一个**前端事件传参 + 数据默认值**联合导致的 Bug。
#### Bug 链条
**第一环:列表页事件传参问题**
`nutrition-knowledge.vue` 中营养素卡片的点击事件:
```html
@click="goToNutrientDetail" :data-nutrient-index="index"
```
`goToNutrientDetail` 方法通过 `event.currentTarget.dataset.nutrientIndex` 获取索引值。但在**微信小程序**环境中,`dataset` 的属性名会被自动转换为全小写(`nutrientindex` 而非 `nutrientIndex`),导致取值为 `undefined`
当 index 为 `undefined` 时,`this.nutrientList[undefined]` 返回 `undefined`,方法执行 `if (!item) return` 直接退出,**不发起页面跳转**。
即使跳转成功,中文参数 `name` 经过 `encodeURIComponent` 编码后,在某些小程序版本中解码可能不正确,导致 `nutrientMap[name]` 匹配失败。
**第二环:详情页默认数据兜底**
`nutrient-detail.vue``data()` 中硬编码了**"钠Sodium"**作为默认数据:
```javascript
data() {
return {
nutrientData: {
name: '钠',
english: 'Sodium (Na)',
icon: '🧂',
// ... 其他钠的数据
}
}
}
```
`loadNutrientData(name)``name` 为空或不匹配 `nutrientMap` 的任何 key 时,`nutrientData` 不会被更新,页面始终显示默认的"钠"内容。
**结果**:无论点击哪个营养素,要么不跳转,要么跳转后参数丢失,最终所有页面都显示默认的"钠"内容。
### 修复建议
**修复 1改用直接传参方式推荐**
```html
<!-- nutrition-knowledge.vue -->
<!-- 修改前 -->
@click="goToNutrientDetail" :data-nutrient-index="index"
<!-- 修改后 -->
@click="goToNutrientDetail(index)"
```
```javascript
// 方法改为接收 index 参数
goToNutrientDetail(index) {
const item = this.nutrientList[index];
if (!item) return;
uni.navigateTo({
url: `/pages/tool/nutrient-detail?name=${encodeURIComponent(item.name)}`
});
}
```
**修复 2详情页增加参数解码和容错**
```javascript
// nutrient-detail.vue
onLoad(options) {
if (options.name) {
const name = decodeURIComponent(options.name);
this.loadNutrientData(name);
}
}
```
**修复 3默认数据改为空状态**
`data()` 中的默认 `nutrientData` 改为空对象,并在页面增加空状态提示,避免误导用户。
### 优先级:高
---
## 问题优先级汇总
| 序号 | 问题 | 类型 | 优先级 | 影响范围 |
|------|------|------|--------|----------|
| 一 | 食谱计算器用油量偏多 | 算法缺陷 | 高 | 所有使用计算器的用户 |
| 二 | AI 营养师体验差 | 功能缺失 | 高 | 所有使用 AI 营养师的用户 |
| 三 | 食物百科成分表/图片/重量 | 数据不完整 | 中高 | 所有查看食物详情的用户 |
| 四 | 营养素板块显示重复 | 前端 Bug | 高 | 所有查看营养素详情的用户 |
---
## 涉及文件清单
### 后端Java
| 文件 | 问题 |
|------|------|
| `ToolCalculatorServiceImpl.java` | 问题一:油脂系数错误 |
| `ToolAiNutritionistServiceImpl.java` | 问题二AI 服务未接入 |
| `ToolFoodServiceImpl.java` | 问题三:返回字段不完整 |
| `V2Food.java` | 问题三:缺少 serving_size 字段 |
### 前端Vue
| 文件 | 问题 |
|------|------|
| `pages/tool/food-detail.vue` | 问题三:图片 URL、重量标注 |
| `pages/tool/nutrient-detail.vue` | 问题三/四Figma URL、默认数据 |
| `pages/tool/nutrition-knowledge.vue` | 问题四:事件传参兼容性 |