feat: 更新前端多个页面和后端服务

- 前端: 更新AI营养师、计算器、打卡、食物详情等页面
- 前端: 更新食物百科、知识详情、营养知识页面
- 前端: 更新社区首页
- 后端: 更新ToolKieAIServiceImpl服务
- API: 更新models-api.js和user.js
This commit is contained in:
2026-03-07 22:26:37 +08:00
parent 1632801880
commit f692c75f7b
11 changed files with 221 additions and 85 deletions

View File

@@ -105,7 +105,7 @@
<view class="food-image-wrapper">
<image
class="food-image"
:src="getFoodImage(item) || defaultPlaceholder"
:src="getFoodImage(item)"
mode="aspectFill"
@error="onFoodImageError(item)"
></image>
@@ -126,7 +126,7 @@
<view class="nutrition-list">
<view
class="nutrition-item"
v-for="(nut, idx) in (item.nutrition || item.nutrients || [])"
v-for="(nut, idx) in getNutritionList(item)"
:key="idx"
>
<text class="nutrition-label">{{ nut.label || '—' }}</text>
@@ -278,12 +278,34 @@ export default {
return [];
},
getFoodImage(item) {
if (!item) return this.defaultPlaceholder;
const id = item.id != null ? item.id : item.foodId;
if (id != null && this.imageErrorIds[String(id)]) return this.defaultPlaceholder;
const raw = item.imageUrl || item.image || item.img || item.pic || item.coverImage || '';
const url = raw && (raw.startsWith('//') || raw.startsWith('http')) ? raw : (raw && raw.startsWith('/') ? (HTTP_REQUEST_URL || '') + raw : raw);
// 兼容后端 image / image_url / 前端 imageUrl、img、pic、coverImage、cover_image
const raw = item.imageUrl || item.image || item.image_url || item.img || item.pic || item.coverImage || item.cover_image || '';
const s = (raw && String(raw).trim()) || '';
if (!s || s === 'null' || s === 'undefined') return this.defaultPlaceholder;
const url = (s.startsWith('//') || s.startsWith('http')) ? s : (s.startsWith('/') ? (HTTP_REQUEST_URL || '') + s : s);
return (url && String(url).trim()) ? url : this.defaultPlaceholder;
},
getNutritionList(item) {
if (!item) return [];
const arr = item.nutrition || item.nutrients || item.nutritions;
if (Array.isArray(arr) && arr.length > 0) return arr;
// 无数组时从扁平字段组装,确保列表始终有营养简介
const list = [];
const push = (label, val, unit) => {
const value = (val != null && val !== '') ? String(val) + (unit || '') : '—';
list.push({ label, value, colorClass: 'green' });
};
push('能量', item.energy, 'kcal');
push('蛋白质', item.protein, 'g');
push('钾', item.potassium, 'mg');
push('磷', item.phosphorus, 'mg');
push('钠', item.sodium, 'mg');
push('钙', item.calcium, 'mg');
return list;
},
onFoodImageError(item) {
const id = item.id != null ? item.id : item.foodId;
if (id != null && !this.imageErrorIds[String(id)]) {
@@ -300,9 +322,11 @@ export default {
};
const safety = item.safety != null ? { safety: item.safety, safetyClass: item.safetyClass || 'safe' } : (safetyMap[item.suitabilityLevel] || { safety: '—', safetyClass: 'safe' });
// 图片:兼容 image/imageUrl/img/pic/coverImage,相对路径补全为完整 URL空则留空由 getFoodImage 用占位图
const rawImg = item.imageUrl || item.image || item.img || item.pic || item.coverImage || '';
const imageUrl = (rawImg && (rawImg.startsWith('//') || rawImg.startsWith('http'))) ? rawImg : (rawImg && rawImg.startsWith('/') ? (HTTP_REQUEST_URL || '') + rawImg : rawImg);
// 图片:兼容 image/image_url/imageUrl/img/pic/coverImage/cover_image相对路径补全空或无效则由 getFoodImage 用占位图
const rawImg = item.imageUrl || item.image || item.image_url || item.img || item.pic || item.coverImage || item.cover_image || '';
const rawStr = (rawImg && String(rawImg).trim()) || '';
const validRaw = rawStr && rawStr !== 'null' && rawStr !== 'undefined';
const imageUrl = validRaw && (rawStr.startsWith('//') || rawStr.startsWith('http')) ? rawStr : (validRaw && rawStr.startsWith('/') ? (HTTP_REQUEST_URL || '') + rawStr : (validRaw ? rawStr : ''));
const image = imageUrl || '';
// 营养简介:优先 item.nutrition其次 item.nutrients兼容后端否则由扁平字段 energy/protein/potassium 等组装
@@ -386,7 +410,7 @@ export default {
},
goToFoodDetail(item) {
// 后端详情接口仅接受 Long 类型 id仅在有有效数字 id 时传 id始终传 name 供详情页失败时展示
const rawId = item.id != null ? item.id : ''
const rawId = item.id != null ? item.id : (item.foodId != null ? item.foodId : '')
const numericId = (rawId !== '' && rawId !== undefined && !isNaN(Number(rawId))) ? Number(rawId) : null
const namePart = item.name ? `&name=${encodeURIComponent(item.name)}` : ''
const url = numericId !== null