fix: 修复手动测试发现的3项问题

1. food-encyclopedia: 修复 v-for key id:index TypeError
   - :key 改为 :key="index",避免 WeChat 小程序 key 表达式异常
   - filteredFoodList 加本地搜索过滤 + null item 过滤
   - normalizeFoodItem 新增英文→中文分类名映射(grain→谷薯类等)
   - loadFoodList/handleSearch 过滤 null 条目

2. ToolKnowledgeServiceImpl: 修复 TooManyResultsException
   - getNutrientDetail 查询新增 LIMIT 1 + ORDER BY knowledge_id DESC
   - 防止 DB 中同名营养素存在多条导致 selectOne 异常

3. ai-nutritionist: 统一走 Coze API,移除 KieAI Gemini 路径
   - sendToAI 文本/多模态均改为 api.cozeChat + pollChatStatus
   - 支持 type='text'/'multimodal'/图片 三种消息类型构建

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Developer
2026-03-25 14:54:31 +08:00
parent ba08abd374
commit 355895dba2
3 changed files with 71 additions and 53 deletions

View File

@@ -96,10 +96,10 @@
<view class="food-list-container">
<view class="food-count"> {{ filteredFoodList.length }} 种食物</view>
<view class="food-list">
<view
class="food-item"
v-for="(item, index) in filteredFoodList"
:key="item.id != null ? item.id : index"
<view
class="food-item"
v-for="(item, index) in filteredFoodList"
:key="index"
@click="goToFoodDetail(item)"
>
<view class="food-image-wrapper">
@@ -244,7 +244,10 @@ export default {
},
computed: {
filteredFoodList() {
return this.foodList
const list = (this.foodList || []).filter(item => item != null);
if (!this.searchText || !this.searchText.trim()) return list;
const kw = this.searchText.trim().toLowerCase();
return list.filter(item => item.name && item.name.toLowerCase().includes(kw));
},
},
onLoad(options) {
@@ -264,7 +267,7 @@ export default {
});
const rawList = this.getRawFoodList(result);
this.imageErrorIds = {};
this.foodList = (rawList || []).map(item => this.normalizeFoodItem(item));
this.foodList = (rawList || []).map(item => this.normalizeFoodItem(item)).filter(i => i != null);
} catch (error) {
console.error('加载食物列表失败:', error);
}
@@ -319,6 +322,13 @@ export default {
}
},
normalizeFoodItem(item) {
if (!item) return null;
// 英文分类 key → 中文显示名称
const categoryNameMap = {
grain: '谷薯类', vegetable: '蔬菜类', fruit: '水果类',
meat: '肉蛋类', seafood: '水产类', dairy: '奶类',
bean: '豆类', nut: '坚果类', all: '全部'
};
const safetyMap = {
suitable: { safety: '放心吃', safetyClass: 'safe' },
moderate: { safety: '限量吃', safetyClass: 'limited' },
@@ -368,12 +378,16 @@ export default {
? (typeof rawId === 'number' ? rawId : Number(rawId))
: undefined;
// 保证列表项必有 image/imageUrl空时由 getFoodImage 用 defaultPlaceholder和 nutrition 数组(.nutrition-item 数据来源)
// 将英文分类名映射为中文,若已是中文则保留
const rawCategory = item.category || item.categoryName || item.category_name || '';
const category = categoryNameMap[rawCategory] || rawCategory;
return {
...item,
id: numericId,
image: image || '',
imageUrl: image || '',
category: item.category || '',
category,
safety: safety.safety,
safetyClass: safety.safetyClass,
nutrition: Array.isArray(nutrition) ? nutrition : []
@@ -404,7 +418,7 @@ export default {
});
const rawList = this.getRawFoodList(result);
this.imageErrorIds = {};
this.foodList = (rawList || []).map(item => this.normalizeFoodItem(item));
this.foodList = (rawList || []).map(item => this.normalizeFoodItem(item)).filter(i => i != null);
} catch (error) {
console.error('搜索失败:', error);
}