feat: 集成 KieAI 服务,移除 models-integration 子项目
- 添加 Gemini 2.5 Flash 对话接口(流式+非流式) - 添加 NanoBanana 图像生成/编辑接口 - 添加 Sora2 视频生成接口(文生视频、图生视频、去水印) - 移除 models-integration 子项目(功能已迁移至主后端) - 新增测试文档和 Playwright E2E 配置 - 更新前端页面和 API 接口 - 更新后端配置和日志处理
This commit is contained in:
@@ -5,6 +5,11 @@
|
||||
<image class="share-icon" :src="iconShare" mode="aspectFit"></image>
|
||||
</view>
|
||||
|
||||
<!-- 数据来自缓存时的提示 -->
|
||||
<view v-if="loadError" class="cache-notice">
|
||||
<text>当前数据来自缓存,可能不是最新</text>
|
||||
</view>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<scroll-view class="content-scroll" scroll-y>
|
||||
<!-- 食物大图 -->
|
||||
@@ -90,6 +95,10 @@ export default {
|
||||
iconShare: 'https://www.figma.com/api/mcp/asset/f9f0d7b9-89c0-48d4-9e04-7140229e42f0',
|
||||
iconSearch: 'https://www.figma.com/api/mcp/asset/aa6bb75b-0a9d-43cb-aaa4-6a71993fbd4d',
|
||||
loading: false,
|
||||
/** 加载失败时的具体错误信息,用于调试;有值时页面会展示「当前数据来自缓存」提示 */
|
||||
loadError: '',
|
||||
/** 入参 id/name,用于 API 失败时用 name 填充展示 */
|
||||
pageParams: { id: '', name: '' },
|
||||
foodData: {
|
||||
name: '',
|
||||
category: '',
|
||||
@@ -124,13 +133,20 @@ export default {
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
if (options.id) {
|
||||
this.loadFoodData(options.id)
|
||||
this.pageParams = { id: options.id || '', name: options.name || '' }
|
||||
// 打印入参便于排查:后端详情接口仅接受 Long 类型 id
|
||||
console.log('[food-detail] onLoad params:', this.pageParams)
|
||||
const rawId = options.id
|
||||
const isNumericId = rawId !== undefined && rawId !== '' && !isNaN(Number(rawId))
|
||||
if (isNumericId) {
|
||||
this.loadFoodData(Number(rawId))
|
||||
} else if (options.name) {
|
||||
this.loadFoodData(options.name)
|
||||
// 无有效 id 仅有 name 时,不请求接口(避免传 name 导致后端 NumberFormatException),直接展示默认数据 + 当前名称
|
||||
this.loadError = '暂无该食物详情数据,展示参考数据'
|
||||
this.applyDefaultFoodData(false)
|
||||
this.foodData.name = decodeURIComponent(String(options.name))
|
||||
} else {
|
||||
// 无参数时使用默认数据
|
||||
this.foodData = { ...this.defaultFoodData }
|
||||
this.applyDefaultFoodData()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -145,8 +161,29 @@ export default {
|
||||
url: `/pages/tool/food-encyclopedia?category=${this.foodData.categoryType || 'all'}`
|
||||
})
|
||||
},
|
||||
/** 用 defaultFoodData 填充页面,保证 keyNutrients/nutritionTable 为非空数组以便 .nutrient-card / .nutrition-row 正常渲染;clearError 为 true 时顺带清空 loadError */
|
||||
applyDefaultFoodData(clearError = true) {
|
||||
if (clearError) this.loadError = ''
|
||||
this.foodData = {
|
||||
...this.defaultFoodData,
|
||||
name: this.defaultFoodData.name || '—',
|
||||
category: this.defaultFoodData.category || '—',
|
||||
safetyTag: this.defaultFoodData.safetyTag || '—',
|
||||
image: this.defaultFoodData.image || '',
|
||||
keyNutrients: [...(this.defaultFoodData.keyNutrients || [])],
|
||||
nutritionTable: [...(this.defaultFoodData.nutritionTable || [])]
|
||||
}
|
||||
},
|
||||
/** 保证数组非空,空时返回 fallback 的副本,用于 API 解析结果避免空数组导致列表不渲染 */
|
||||
ensureNonEmptyArray(arr, fallback) {
|
||||
if (Array.isArray(arr) && arr.length > 0) return arr
|
||||
return Array.isArray(fallback) ? [...fallback] : []
|
||||
},
|
||||
async loadFoodData(id) {
|
||||
this.loading = true
|
||||
this.loadError = ''
|
||||
// 打印 API 请求参数便于确认(后端需要 Long 类型 id)
|
||||
console.log('[food-detail] getFoodDetail request param:', { id, type: typeof id })
|
||||
try {
|
||||
const res = await getFoodDetail(id)
|
||||
const data = res.data || res
|
||||
@@ -158,17 +195,26 @@ export default {
|
||||
categoryType: data.categoryType || data.categoryCode || 'all',
|
||||
safetyTag: data.safetyTag || data.safety || this.getSafetyTag(data),
|
||||
image: data.image || data.imageUrl || data.coverImage || this.defaultFoodData.image,
|
||||
keyNutrients: this.parseKeyNutrients(data),
|
||||
nutritionTable: this.parseNutritionTable(data)
|
||||
keyNutrients: this.ensureNonEmptyArray(this.parseKeyNutrients(data), this.defaultFoodData.keyNutrients),
|
||||
nutritionTable: this.ensureNonEmptyArray(this.parseNutritionTable(data), this.defaultFoodData.nutritionTable)
|
||||
}
|
||||
} else {
|
||||
// API 返回空数据,使用默认数据
|
||||
this.foodData = { ...this.defaultFoodData }
|
||||
this.applyDefaultFoodData()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载食物数据失败:', error)
|
||||
// 加载失败使用默认数据
|
||||
this.foodData = { ...this.defaultFoodData }
|
||||
const errMsg = (error && (error.message || error.msg || error)) ? String(error.message || error.msg || error) : '未知错误'
|
||||
console.error('[food-detail] 加载食物数据失败:', error)
|
||||
// a. 将 loadError 置为具体错误信息(用于调试)
|
||||
this.loadError = errMsg
|
||||
// b. 使用 defaultFoodData 填充页面,保证用户能看到基础界面;不清空 loadError 以便展示「当前数据来自缓存」提示
|
||||
this.applyDefaultFoodData(false)
|
||||
// 若有入参 name,用其覆盖展示名称,避免显示默认「五谷香」
|
||||
if (this.pageParams && this.pageParams.name) {
|
||||
try {
|
||||
this.foodData.name = decodeURIComponent(String(this.pageParams.name))
|
||||
} catch (e) {}
|
||||
}
|
||||
uni.showToast({
|
||||
title: '数据加载失败',
|
||||
icon: 'none'
|
||||
@@ -237,6 +283,18 @@ export default {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 数据来自缓存提示 */
|
||||
.cache-notice {
|
||||
padding: 16rpx 32rpx;
|
||||
background: #fff8e1;
|
||||
border-bottom: 1rpx solid #ffe082;
|
||||
text-align: center;
|
||||
text {
|
||||
font-size: 24rpx;
|
||||
color: #f57c00;
|
||||
}
|
||||
}
|
||||
|
||||
/* 分享按钮 */
|
||||
.share-btn-top {
|
||||
position: fixed;
|
||||
@@ -457,6 +515,10 @@ export default {
|
||||
&.medium {
|
||||
background: #ffa500;
|
||||
}
|
||||
|
||||
&.high {
|
||||
background: #e53935;
|
||||
}
|
||||
}
|
||||
|
||||
.nutrition-label {
|
||||
|
||||
Reference in New Issue
Block a user