fix: 修复关注按钮相关问题
- 食谱详情页: 修复 applyDefaultData 中未定义变量 id 的问题 - 帖子详情页: 优化 toggleFollow 方法,提前校验 author.id,兼容多种后端字段 - 为帖子详情页已关注状态添加灰色样式
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -217,11 +217,12 @@ export function getFoodList(data) {
|
|||||||
export function getFoodDetail(id) {
|
export function getFoodDetail(id) {
|
||||||
const numId = typeof id === 'number' && !isNaN(id) ? id : parseInt(String(id), 10);
|
const numId = typeof id === 'number' && !isNaN(id) ? id : parseInt(String(id), 10);
|
||||||
// 打印请求参数便于确认(后端仅接受 Long 类型 id,传 name 会 400)
|
// 打印请求参数便于确认(后端仅接受 Long 类型 id,传 name 会 400)
|
||||||
console.log('[api/tool] getFoodDetail 请求参数:', { id, numId, type: typeof id });
|
const apiPath = 'tool/food/detail/' + numId;
|
||||||
|
console.log('[api/tool] getFoodDetail 请求参数:', { id, numId, type: typeof id, apiPath });
|
||||||
if (isNaN(numId)) {
|
if (isNaN(numId)) {
|
||||||
return Promise.reject(new Error('食物详情接口需要数字ID,当前传入: ' + id));
|
return Promise.reject(new Error('食物详情接口需要数字ID,当前传入: ' + id));
|
||||||
}
|
}
|
||||||
return request.get('tool/food/detail/' + numId);
|
return request.get(apiPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -610,15 +610,18 @@ export default {
|
|||||||
this.scrollToBottom();
|
this.scrollToBottom();
|
||||||
},
|
},
|
||||||
|
|
||||||
/** 从 Gemini 响应 data.choices[0].message.content 提取展示文本(支持 string / parts 数组 / { parts } 对象) */
|
/** 从 Gemini 响应 data.choices[0].message.content 提取展示文本(支持 string / parts 数组 / { parts } / { text }) */
|
||||||
extractReplyContent(content) {
|
extractReplyContent(content) {
|
||||||
if (content == null) return '';
|
if (content == null) return '';
|
||||||
if (typeof content === 'string') return content;
|
if (typeof content === 'string') return content;
|
||||||
if (Array.isArray(content)) {
|
if (Array.isArray(content)) {
|
||||||
return content.map(part => (part && part.text) ? part.text : '').filter(Boolean).join('');
|
return content.map(part => (part && part.text) ? part.text : '').filter(Boolean).join('');
|
||||||
}
|
}
|
||||||
if (typeof content === 'object' && Array.isArray(content.parts)) {
|
if (typeof content === 'object') {
|
||||||
return content.parts.map(part => (part && part.text) ? part.text : '').filter(Boolean).join('');
|
if (Array.isArray(content.parts)) {
|
||||||
|
return content.parts.map(part => (part && part.text) ? part.text : '').filter(Boolean).join('');
|
||||||
|
}
|
||||||
|
if (typeof content.text === 'string') return content.text;
|
||||||
}
|
}
|
||||||
return String(content);
|
return String(content);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -489,19 +489,19 @@ export default {
|
|||||||
gap: 16rpx;
|
gap: 16rpx;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
/* 未激活:明显变灰,无下划线 */
|
/* 未激活:明显变灰,无下划线(BUG-002) */
|
||||||
color: #9ca3af;
|
color: #9ca3af;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
border-bottom: none;
|
border-bottom: 3rpx solid transparent;
|
||||||
.tab-icon {
|
.tab-icon {
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
color: #9ca3af;
|
color: inherit;
|
||||||
font-weight: 400;
|
font-weight: inherit;
|
||||||
}
|
}
|
||||||
.tab-text {
|
.tab-text {
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
color: #9ca3af;
|
color: inherit;
|
||||||
font-weight: 400;
|
font-weight: inherit;
|
||||||
}
|
}
|
||||||
/* 激活:加粗、主色、橙色底部下划线(BUG-002) */
|
/* 激活:加粗、主色、橙色底部下划线(BUG-002) */
|
||||||
&.active {
|
&.active {
|
||||||
|
|||||||
@@ -244,28 +244,16 @@ export default {
|
|||||||
const { setSignIntegral, getFrontUserInfo, getUserInfo } = await import('@/api/user.js');
|
const { setSignIntegral, getFrontUserInfo, getUserInfo } = await import('@/api/user.js');
|
||||||
const { getUserPoints } = await import('@/api/tool.js');
|
const { getUserPoints } = await import('@/api/tool.js');
|
||||||
|
|
||||||
// 子问题 A:不得在 API 返回成功前修改 currentPoints,避免打卡前积分提前跳变
|
// 子问题 A:在 API 返回成功前不得修改 currentPoints,避免打卡前积分提前跳变(禁止前端本地 +30 等)
|
||||||
// 打卡接口:GET /api/front/user/sign/integral(setSignIntegral),等同于 checkin 签到
|
// 打卡接口:GET /api/front/user/sign/integral(setSignIntegral),即本项目的签到/打卡接口
|
||||||
await setSignIntegral();
|
await setSignIntegral();
|
||||||
|
|
||||||
// 子问题 B:仅在打卡成功后用服务端数据更新积分。先 GET /api/front/user/info 刷新用户积分,禁止硬编码 +30
|
// 子问题 B:打卡成功后必须用服务端数据更新积分。发 GET /api/front/user/info 刷新用户积分,禁止硬编码 +30
|
||||||
let userRes = null;
|
const serverPoints = await this._fetchServerPoints(getFrontUserInfo, getUserInfo, getUserPoints);
|
||||||
try {
|
if (serverPoints != null) {
|
||||||
userRes = await getFrontUserInfo(); // GET /api/front/user/info
|
this.currentPoints = Number(serverPoints);
|
||||||
} catch (_) {
|
|
||||||
userRes = await getUserInfo().catch(() => null);
|
|
||||||
}
|
|
||||||
if (userRes && userRes.data && (userRes.data.integral != null || userRes.data.points != null)) {
|
|
||||||
this.currentPoints = userRes.data.integral ?? userRes.data.points ?? 0;
|
|
||||||
} else {
|
|
||||||
const pointsRes = await getUserPoints();
|
|
||||||
if (pointsRes && pointsRes.data) {
|
|
||||||
const serverPoints = pointsRes.data.totalPoints ?? pointsRes.data.points ?? pointsRes.data.availablePoints ?? 0;
|
|
||||||
this.currentPoints = serverPoints;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 积分已从服务端更新后再更新打卡状态并跳转,避免“已打卡”与积分不同步
|
|
||||||
this.todaySigned = true;
|
this.todaySigned = true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const msg = (typeof e === 'string' ? e : (e && (e.message || e.msg))) || '打卡失败';
|
const msg = (typeof e === 'string' ? e : (e && (e.message || e.msg))) || '打卡失败';
|
||||||
@@ -279,6 +267,37 @@ export default {
|
|||||||
url: '/pages/tool/checkin-publish'
|
url: '/pages/tool/checkin-publish'
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 从服务端拉取积分(用于打卡成功后刷新,积分值必须来自服务端,不可前端累加)
|
||||||
|
* 优先 GET /api/front/user/info,失败时回退到 user 或 tool/points/info
|
||||||
|
*/
|
||||||
|
async _fetchServerPoints(getFrontUserInfo, getUserInfo, getUserPoints) {
|
||||||
|
try {
|
||||||
|
const userRes = await getFrontUserInfo();
|
||||||
|
if (userRes && userRes.data) {
|
||||||
|
const d = userRes.data;
|
||||||
|
const p = d.integral ?? d.points ?? d.totalPoints ?? (d.user && (d.user.integral ?? d.user.points ?? d.user.totalPoints));
|
||||||
|
if (p != null) return p;
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
try {
|
||||||
|
const userRes = await getUserInfo();
|
||||||
|
if (userRes && userRes.data) {
|
||||||
|
const d = userRes.data;
|
||||||
|
const p = d.integral ?? d.points ?? d.totalPoints ?? (d.user && (d.user.integral ?? d.user.points ?? d.user.totalPoints));
|
||||||
|
if (p != null) return p;
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
try {
|
||||||
|
const pointsRes = await getUserPoints();
|
||||||
|
if (pointsRes && pointsRes.data) {
|
||||||
|
const d = pointsRes.data;
|
||||||
|
const p = d.totalPoints ?? d.points ?? d.availablePoints;
|
||||||
|
if (p != null) return p;
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
getTodayIndex() {
|
getTodayIndex() {
|
||||||
// 根据连续打卡数据计算当前是第几天
|
// 根据连续打卡数据计算当前是第几天
|
||||||
// streakDays 中已有 completed 状态,找第一个未完成的位置即为今天
|
// streakDays 中已有 completed 状态,找第一个未完成的位置即为今天
|
||||||
|
|||||||
@@ -133,9 +133,10 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
// 保证 .food-name-overlay / .nutrient-card / .nutrition-row 在 defaultFoodData 状态下也有非空数据可渲染
|
// 保证 .food-name-overlay / .nutrient-card / .nutrition-row 在 defaultFoodData 状态下也有非空数据可渲染(不为空字符串/空数组)
|
||||||
displayName() {
|
displayName() {
|
||||||
return (this.foodData && this.foodData.name) ? this.foodData.name : (this.defaultFoodData.name || '—')
|
const name = (this.foodData && this.foodData.name) ? this.foodData.name : (this.defaultFoodData && this.defaultFoodData.name) ? this.defaultFoodData.name : '—'
|
||||||
|
return (name != null && String(name).trim() !== '') ? String(name).trim() : '—'
|
||||||
},
|
},
|
||||||
displayCategory() {
|
displayCategory() {
|
||||||
return (this.foodData && this.foodData.category) ? this.foodData.category : (this.defaultFoodData.category || '—')
|
return (this.foodData && this.foodData.category) ? this.foodData.category : (this.defaultFoodData.category || '—')
|
||||||
@@ -149,14 +150,16 @@ export default {
|
|||||||
displayKeyNutrients() {
|
displayKeyNutrients() {
|
||||||
const arr = this.foodData && this.foodData.keyNutrients
|
const arr = this.foodData && this.foodData.keyNutrients
|
||||||
if (Array.isArray(arr) && arr.length > 0) return arr
|
if (Array.isArray(arr) && arr.length > 0) return arr
|
||||||
const def = this.defaultFoodData.keyNutrients
|
const def = this.defaultFoodData && this.defaultFoodData.keyNutrients
|
||||||
return Array.isArray(def) && def.length > 0 ? def : [{ name: '—', value: '—', unit: '', status: '—' }]
|
if (Array.isArray(def) && def.length > 0) return def
|
||||||
|
return [{ name: '—', value: '—', unit: '', status: '—' }]
|
||||||
},
|
},
|
||||||
displayNutritionTable() {
|
displayNutritionTable() {
|
||||||
const arr = this.foodData && this.foodData.nutritionTable
|
const arr = this.foodData && this.foodData.nutritionTable
|
||||||
if (Array.isArray(arr) && arr.length > 0) return arr
|
if (Array.isArray(arr) && arr.length > 0) return arr
|
||||||
const def = this.defaultFoodData.nutritionTable
|
const def = this.defaultFoodData && this.defaultFoodData.nutritionTable
|
||||||
return Array.isArray(def) && def.length > 0 ? def : [{ name: '—', value: '—', unit: '', level: 'low', levelText: '—' }]
|
if (Array.isArray(def) && def.length > 0) return def
|
||||||
|
return [{ name: '—', value: '—', unit: '', level: 'low', levelText: '—' }]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLoad(options) {
|
onLoad(options) {
|
||||||
@@ -171,21 +174,23 @@ export default {
|
|||||||
name: (displayName !== undefined && displayName !== '') ? String(displayName) : ''
|
name: (displayName !== undefined && displayName !== '') ? String(displayName) : ''
|
||||||
}
|
}
|
||||||
// 打印入参便于排查:后端详情接口仅接受 Long 类型 id,传 name 会导致 400
|
// 打印入参便于排查:后端详情接口仅接受 Long 类型 id,传 name 会导致 400
|
||||||
console.log('[food-detail] onLoad options:', options)
|
console.log('[food-detail] onLoad options:', JSON.stringify(options))
|
||||||
console.log('[food-detail] onLoad pageParams (id/name):', this.pageParams)
|
console.log('[food-detail] onLoad pageParams (id/name):', JSON.stringify(this.pageParams))
|
||||||
const isNumericId = rawId !== undefined && rawId !== '' && !isNaN(Number(rawId))
|
const isNumericId = rawId !== undefined && rawId !== '' && !isNaN(Number(rawId))
|
||||||
if (isNumericId) {
|
if (isNumericId) {
|
||||||
const numId = Number(rawId)
|
const numId = Number(rawId)
|
||||||
console.log('[food-detail] 使用数字 id 请求详情,不传 name 字段:', numId)
|
console.log('[food-detail] 使用数字 id 请求详情:', { id: numId, idType: typeof numId, name: this.pageParams.name })
|
||||||
this.loadFoodData(numId)
|
this.loadFoodData(numId)
|
||||||
} else if (this.pageParams.name) {
|
} else if (this.pageParams.name) {
|
||||||
// 无有效 id、仅有 name 时,不请求接口(避免传 name 导致后端 NumberFormatException),直接展示默认数据 + 当前名称
|
// 无有效 id、仅有 name 时,不请求接口(避免传 name 导致后端 NumberFormatException),直接展示默认数据 + 当前名称
|
||||||
|
console.log('[food-detail] 无数字 id,仅用 name 展示默认数据,不请求详情接口')
|
||||||
this.loadError = '暂无该食物详情数据,展示参考数据'
|
this.loadError = '暂无该食物详情数据,展示参考数据'
|
||||||
this.applyDefaultFoodData(false)
|
this.applyDefaultFoodData(false)
|
||||||
try {
|
try {
|
||||||
this.foodData.name = decodeURIComponent(this.pageParams.name)
|
this.foodData.name = decodeURIComponent(this.pageParams.name)
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
} else {
|
} else {
|
||||||
|
console.log('[food-detail] 无 id 与 name,展示默认数据')
|
||||||
this.applyDefaultFoodData()
|
this.applyDefaultFoodData()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -225,12 +230,26 @@ export default {
|
|||||||
async loadFoodData(id) {
|
async loadFoodData(id) {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
this.loadError = ''
|
this.loadError = ''
|
||||||
// 打印 API 请求参数便于确认(后端需要 Long 类型 id)
|
// 打印 API 请求参数便于确认(后端需要 Long 类型 id,传 name 会 400)
|
||||||
console.log('[food-detail] getFoodDetail request param:', { id, type: typeof id })
|
console.log('[food-detail] getFoodDetail API 请求参数:', {
|
||||||
|
id,
|
||||||
|
idType: typeof id,
|
||||||
|
pageParamsId: this.pageParams.id,
|
||||||
|
pageParamsName: this.pageParams.name
|
||||||
|
})
|
||||||
try {
|
try {
|
||||||
const res = await getFoodDetail(id)
|
const res = await getFoodDetail(id)
|
||||||
// 打印响应结构便于确认:request 成功时 resolve 的是 res.data,即 { code: 200, data: {...} }
|
// 打印响应结构便于确认:request 成功时 resolve 的是 res.data,即 { code: 200, data: {...} }
|
||||||
console.log('[food-detail] getFoodDetail 响应结构:', res ? { hasData: !!res.data, code: res.code, keys: Object.keys(res || {}) } : null)
|
console.log('[food-detail] getFoodDetail 响应结构:', res ? { hasData: !!(res && res.data), code: res && res.code, keys: Object.keys(res || {}) } : null)
|
||||||
|
if (!res) {
|
||||||
|
this.loadError = '接口未返回数据'
|
||||||
|
this.applyDefaultFoodData(false)
|
||||||
|
if (this.pageParams && this.pageParams.name) {
|
||||||
|
try { this.foodData.name = decodeURIComponent(String(this.pageParams.name)) } catch (e) {}
|
||||||
|
}
|
||||||
|
uni.showToast({ title: '数据加载失败', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
const data = res.data != null ? res.data : res
|
const data = res.data != null ? res.data : res
|
||||||
console.log('[food-detail] getFoodDetail 解析后 data:', data ? { hasName: !!data.name, hasImage: !!data.image, keys: Object.keys(data) } : null)
|
console.log('[food-detail] getFoodDetail 解析后 data:', data ? { hasName: !!data.name, hasImage: !!data.image, keys: Object.keys(data) } : null)
|
||||||
if (data && data.name) {
|
if (data && data.name) {
|
||||||
@@ -258,9 +277,11 @@ export default {
|
|||||||
uni.showToast({ title: '数据加载失败', icon: 'none' })
|
uni.showToast({ title: '数据加载失败', icon: 'none' })
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// a. 将 loadError 置为具体错误信息(用于调试):兼容 Error、{ message/msg }、字符串
|
// a. 将 loadError 置为具体错误信息(用于调试):兼容 Error、{ message/msg }、字符串、后端 res.data 对象
|
||||||
const errMsg = (error && (error.message || error.msg || error.errMsg))
|
const errMsg = (error && (error.message || error.msg || error.errMsg))
|
||||||
? String(error.message || error.msg || error.errMsg)
|
? String(error.message || error.msg || error.errMsg)
|
||||||
|
: (error && error.data && (error.data.message || error.data.msg))
|
||||||
|
? String(error.data.message || error.data.msg)
|
||||||
: (typeof error === 'string' ? error : (error ? String(error) : '未知错误'))
|
: (typeof error === 'string' ? error : (error ? String(error) : '未知错误'))
|
||||||
console.error('[food-detail] 加载食物数据失败:', error)
|
console.error('[food-detail] 加载食物数据失败:', error)
|
||||||
console.error('[food-detail] loadError(用于调试):', errMsg)
|
console.error('[food-detail] loadError(用于调试):', errMsg)
|
||||||
@@ -273,7 +294,8 @@ export default {
|
|||||||
this.foodData.name = decodeURIComponent(String(this.pageParams.name))
|
this.foodData.name = decodeURIComponent(String(this.pageParams.name))
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
// c. 页面通过 v-if="loadError" 显示「当前数据来自缓存,可能不是最新」;再弹出轻提示
|
console.log('[food-detail] 已用 defaultFoodData 填充页面,loadError=', this.loadError)
|
||||||
|
// c. 页面 v-if="loadError" 会显示「当前数据来自缓存,可能不是最新」;同时弹出轻提示
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '数据加载失败',
|
title: '数据加载失败',
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
|
|||||||
@@ -269,30 +269,35 @@ export default {
|
|||||||
console.error('加载食物列表失败:', error);
|
console.error('加载食物列表失败:', error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 兼容 result.data.list / result.list / result.data 为数组 等响应结构
|
// 兼容 result.data.list / result.list / result.data 为数组 等响应结构(后端 CommonPage 为 result.data.list)
|
||||||
getRawFoodList(result) {
|
getRawFoodList(result) {
|
||||||
if (!result) return [];
|
if (!result) return [];
|
||||||
|
// 若整个 result 就是列表数组(部分网关/封装可能直接返回数组)
|
||||||
|
if (Array.isArray(result)) return result;
|
||||||
const page = result.data !== undefined && result.data !== null ? result.data : result;
|
const page = result.data !== undefined && result.data !== null ? result.data : result;
|
||||||
if (page && Array.isArray(page.list)) return page.list;
|
if (page && Array.isArray(page.list)) return page.list;
|
||||||
if (Array.isArray(page)) return page;
|
if (page && Array.isArray(page)) return page;
|
||||||
|
// 部分接口直接返回 { list: [], total: 0 }
|
||||||
|
if (result && Array.isArray(result.list)) return result.list;
|
||||||
return [];
|
return [];
|
||||||
},
|
},
|
||||||
getFoodImage(item) {
|
getFoodImage(item) {
|
||||||
if (!item) return this.defaultPlaceholder;
|
if (!item) return this.defaultPlaceholder;
|
||||||
const id = item.id != null ? item.id : item.foodId;
|
const id = item.id != null ? item.id : item.foodId;
|
||||||
if (id != null && this.imageErrorIds[String(id)]) return this.defaultPlaceholder;
|
if (id != null && this.imageErrorIds[String(id)]) return this.defaultPlaceholder;
|
||||||
// 兼容后端 image / image_url / 前端 imageUrl、img、pic、coverImage、cover_image
|
// 兼容后端 image(ToolFoodServiceImpl 返回 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 raw = item.imageUrl != null ? item.imageUrl : (item.image != null ? item.image : (item.image_url || item.img || item.pic || item.coverImage || item.cover_image || ''));
|
||||||
const s = (raw && String(raw).trim()) || '';
|
const s = (raw != null && String(raw).trim()) ? String(raw).trim() : '';
|
||||||
if (!s || s === 'null' || s === 'undefined') return this.defaultPlaceholder;
|
if (!s || s === 'null' || s === 'undefined') return this.defaultPlaceholder;
|
||||||
const url = (s.startsWith('//') || s.startsWith('http')) ? s : (s.startsWith('/') ? (HTTP_REQUEST_URL || '') + s : s);
|
const url = (s.startsWith('//') || s.startsWith('http')) ? s : (s.startsWith('/') ? (HTTP_REQUEST_URL || '') + s : s);
|
||||||
return (url && String(url).trim()) ? url : this.defaultPlaceholder;
|
return (url && String(url).trim()) ? url : this.defaultPlaceholder;
|
||||||
},
|
},
|
||||||
getNutritionList(item) {
|
getNutritionList(item) {
|
||||||
if (!item) return [];
|
if (!item) return [];
|
||||||
|
// 优先使用已规范化的 nutrition,兼容后端 nutrients / nutritions 数组
|
||||||
const arr = item.nutrition || item.nutrients || item.nutritions;
|
const arr = item.nutrition || item.nutrients || item.nutritions;
|
||||||
if (Array.isArray(arr) && arr.length > 0) return arr;
|
if (Array.isArray(arr) && arr.length > 0) return arr;
|
||||||
// 无数组时从扁平字段组装,确保列表始终有营养简介
|
// 后端列表接口(ToolFoodServiceImpl)仅返回扁平字段 energy/protein/potassium/phosphorus 等,无数组时从此组装
|
||||||
const list = [];
|
const list = [];
|
||||||
const push = (label, val, unit) => {
|
const push = (label, val, unit) => {
|
||||||
const value = (val != null && val !== '') ? String(val) + (unit || '') : '—';
|
const value = (val != null && val !== '') ? String(val) + (unit || '') : '—';
|
||||||
@@ -322,27 +327,26 @@ export default {
|
|||||||
};
|
};
|
||||||
const safety = item.safety != null ? { safety: item.safety, safetyClass: item.safetyClass || 'safe' } : (safetyMap[item.suitabilityLevel] || { safety: '—', safetyClass: 'safe' });
|
const safety = item.safety != null ? { safety: item.safety, safetyClass: item.safetyClass || 'safe' } : (safetyMap[item.suitabilityLevel] || { safety: '—', safetyClass: 'safe' });
|
||||||
|
|
||||||
// 图片:兼容 image/image_url/imageUrl/img/pic/coverImage/cover_image,相对路径补全,空或无效则由 getFoodImage 用占位图
|
// 图片:后端列表返回 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 rawImg = item.image || item.imageUrl || item.image_url || item.img || item.pic || item.coverImage || item.cover_image || '';
|
||||||
const rawStr = (rawImg && String(rawImg).trim()) || '';
|
const rawStr = (rawImg != null && String(rawImg).trim()) ? String(rawImg).trim() : '';
|
||||||
const validRaw = rawStr && rawStr !== 'null' && rawStr !== 'undefined';
|
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 imageUrl = validRaw && (rawStr.startsWith('//') || rawStr.startsWith('http')) ? rawStr : (validRaw && rawStr.startsWith('/') ? (HTTP_REQUEST_URL || '') + rawStr : (validRaw ? rawStr : ''));
|
||||||
const image = imageUrl || '';
|
const image = imageUrl || '';
|
||||||
|
|
||||||
// 营养简介:优先 item.nutrition,其次 item.nutrients(兼容后端),否则由扁平字段 energy/protein/potassium 等组装
|
// 营养简介:优先 item.nutrition,其次 item.nutrients / item.nutritions(兼容后端),否则由扁平字段 energy/protein/potassium 等组装
|
||||||
let nutrition = item.nutrition;
|
let nutrition = item.nutrition;
|
||||||
|
const mapNut = (n) => ({
|
||||||
|
label: n.label || n.name || n.labelName || n.nutrientName || '—',
|
||||||
|
value: n.value != null ? String(n.value) : (n.amount != null ? String(n.amount) + (n.unit || '') : '—'),
|
||||||
|
colorClass: n.colorClass || 'green'
|
||||||
|
});
|
||||||
if (Array.isArray(nutrition) && nutrition.length > 0) {
|
if (Array.isArray(nutrition) && nutrition.length > 0) {
|
||||||
nutrition = nutrition.map(n => ({
|
nutrition = nutrition.map(mapNut);
|
||||||
label: n.label || n.name || n.labelName || '—',
|
|
||||||
value: n.value != null ? String(n.value) : '—',
|
|
||||||
colorClass: n.colorClass || 'green'
|
|
||||||
}));
|
|
||||||
} else if (Array.isArray(item.nutrients) && item.nutrients.length > 0) {
|
} else if (Array.isArray(item.nutrients) && item.nutrients.length > 0) {
|
||||||
nutrition = item.nutrients.map(n => ({
|
nutrition = item.nutrients.map(mapNut);
|
||||||
label: n.label || n.name || n.labelName || '—',
|
} else if (Array.isArray(item.nutritions) && item.nutritions.length > 0) {
|
||||||
value: n.value != null ? String(n.value) : '—',
|
nutrition = item.nutritions.map(mapNut);
|
||||||
colorClass: n.colorClass || 'green'
|
|
||||||
}));
|
|
||||||
} else {
|
} else {
|
||||||
// 后端列表仅返回扁平字段,无 nutrition/nutrients 数组,此处组装并始终展示主要项(空值显示 —)
|
// 后端列表仅返回扁平字段,无 nutrition/nutrients 数组,此处组装并始终展示主要项(空值显示 —)
|
||||||
nutrition = [];
|
nutrition = [];
|
||||||
@@ -363,11 +367,12 @@ export default {
|
|||||||
const numericId = (rawId !== undefined && rawId !== null && rawId !== '' && !isNaN(Number(rawId)))
|
const numericId = (rawId !== undefined && rawId !== null && rawId !== '' && !isNaN(Number(rawId)))
|
||||||
? (typeof rawId === 'number' ? rawId : Number(rawId))
|
? (typeof rawId === 'number' ? rawId : Number(rawId))
|
||||||
: undefined;
|
: undefined;
|
||||||
|
// 保证列表项必有 image/imageUrl(空时由 getFoodImage 用 defaultPlaceholder)和 nutrition 数组(.nutrition-item 数据来源)
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
id: numericId,
|
id: numericId,
|
||||||
image,
|
image: image || '',
|
||||||
imageUrl: image || undefined,
|
imageUrl: image || '',
|
||||||
category: item.category || '',
|
category: item.category || '',
|
||||||
safety: safety.safety,
|
safety: safety.safety,
|
||||||
safetyClass: safety.safetyClass,
|
safetyClass: safety.safetyClass,
|
||||||
|
|||||||
@@ -65,7 +65,7 @@
|
|||||||
class="knowledge-item"
|
class="knowledge-item"
|
||||||
v-for="(item, index) in (guideList || [])"
|
v-for="(item, index) in (guideList || [])"
|
||||||
:key="item.knowledgeId || item.id || index"
|
:key="item.knowledgeId || item.id || index"
|
||||||
@click="goToDetail" :data-item-id="item.id" :data-item-kid="item.knowledgeId"
|
@click="goToDetail($event, item, index, 'guide')"
|
||||||
>
|
>
|
||||||
<view class="knowledge-cover" v-if="item.coverImage || item.cover_image">
|
<view class="knowledge-cover" v-if="item.coverImage || item.cover_image">
|
||||||
<image :src="item.coverImage || item.cover_image" mode="aspectFill" class="cover-img" />
|
<image :src="item.coverImage || item.cover_image" mode="aspectFill" class="cover-img" />
|
||||||
@@ -96,7 +96,7 @@
|
|||||||
class="knowledge-item"
|
class="knowledge-item"
|
||||||
v-for="(item, index) in (articleList || [])"
|
v-for="(item, index) in (articleList || [])"
|
||||||
:key="item.knowledgeId || item.id || index"
|
:key="item.knowledgeId || item.id || index"
|
||||||
@click="goToDetail" :data-item-id="item.id" :data-item-kid="item.knowledgeId"
|
@click="goToDetail($event, item, index, 'articles')"
|
||||||
>
|
>
|
||||||
<view class="knowledge-cover" v-if="item.coverImage || item.cover_image">
|
<view class="knowledge-cover" v-if="item.coverImage || item.cover_image">
|
||||||
<image :src="item.coverImage || item.cover_image" mode="aspectFill" class="cover-img" />
|
<image :src="item.coverImage || item.cover_image" mode="aspectFill" class="cover-img" />
|
||||||
@@ -125,6 +125,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
|
navigationBarTitleText: '健康知识',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
currentTab: 'nutrients',
|
currentTab: 'nutrients',
|
||||||
@@ -184,15 +185,15 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLoad(options) {
|
onLoad(options) {
|
||||||
|
// 确保列表初始为数组,避免未加载时为 undefined
|
||||||
|
this.guideList = Array.isArray(this.guideList) ? this.guideList : [];
|
||||||
|
this.articleList = Array.isArray(this.articleList) ? this.articleList : [];
|
||||||
if (options && options.id) {
|
if (options && options.id) {
|
||||||
// 有 id 时切换到科普文章 tab,switchTab 内会调用 loadKnowledgeList 加载列表
|
// 有 id 时切换到科普文章 tab,switchTab 内会调用 loadKnowledgeList 加载列表
|
||||||
this.switchTab('articles');
|
this.switchTab('articles');
|
||||||
} else {
|
} else {
|
||||||
// 无 id 时默认当前 tab 为「营养素」;切换到「饮食指南」或「科普文章」时由 switchTab 触发 loadKnowledgeList
|
// 无 id 时默认显示「营养素」tab(本地静态数据);用户切换到「饮食指南」或「科普文章」时由 switchTab 触发 loadKnowledgeList 加载对应列表
|
||||||
this.currentTab = 'nutrients';
|
this.currentTab = 'nutrients';
|
||||||
// 确保列表初始为数组,避免未加载时为 undefined;切换 Tab 后再加载对应列表
|
|
||||||
this.guideList = Array.isArray(this.guideList) ? this.guideList : [];
|
|
||||||
this.articleList = Array.isArray(this.articleList) ? this.articleList : [];
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onReady() {
|
onReady() {
|
||||||
@@ -224,7 +225,7 @@ export default {
|
|||||||
if (this.currentTab === 'nutrients') {
|
if (this.currentTab === 'nutrients') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// type 与后端一致:guide / article(v2_knowledge 表 type 字段)
|
// type 与后端 v2_knowledge 表一致:guide=饮食指南,article=科普文章
|
||||||
const typeParam = this.currentTab === 'guide' ? 'guide' : 'article';
|
const typeParam = this.currentTab === 'guide' ? 'guide' : 'article';
|
||||||
try {
|
try {
|
||||||
const { getKnowledgeList } = await import('@/api/tool.js');
|
const { getKnowledgeList } = await import('@/api/tool.js');
|
||||||
@@ -264,10 +265,11 @@ export default {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
// 始终赋值为数组,绝不设为 undefined
|
// 始终赋值为数组,绝不设为 undefined
|
||||||
|
const safeList = Array.isArray(list) ? list : [];
|
||||||
if (this.currentTab === 'guide') {
|
if (this.currentTab === 'guide') {
|
||||||
this.guideList = Array.isArray(list) ? list : [];
|
this.guideList = safeList;
|
||||||
} else if (this.currentTab === 'articles') {
|
} else if (this.currentTab === 'articles') {
|
||||||
this.articleList = Array.isArray(list) ? list : [];
|
this.articleList = safeList;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载知识列表失败:', error);
|
console.error('加载知识列表失败:', error);
|
||||||
@@ -296,20 +298,24 @@ export default {
|
|||||||
url: `/pages/tool/nutrient-detail?name=${encodeURIComponent(item.name)}`
|
url: `/pages/tool/nutrient-detail?name=${encodeURIComponent(item.name)}`
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
goToDetail(event) {
|
goToDetail(event, item, index, tab) {
|
||||||
const id = event.currentTarget.dataset.itemId;
|
// 优先从传入的 item 取 knowledgeId 或 id,避免 dataset 序列化丢失
|
||||||
const knowledgeId = event.currentTarget.dataset.itemKid;
|
const fromItem = item != null ? (item.knowledgeId ?? item.id) : undefined;
|
||||||
const finalId = knowledgeId ?? id;
|
const fromDataset = event && event.currentTarget && event.currentTarget.dataset;
|
||||||
|
const id = fromDataset ? (fromDataset.itemId ?? fromDataset.item_id) : undefined;
|
||||||
|
const knowledgeId = fromDataset ? (fromDataset.itemKid ?? fromDataset.item_kid) : undefined;
|
||||||
|
let finalId = fromItem ?? knowledgeId ?? id;
|
||||||
|
if (finalId == null && tab != null && index != null) {
|
||||||
|
const list = tab === 'guide' ? this.guideList : this.articleList;
|
||||||
|
const listItem = Array.isArray(list) ? list[index] : null;
|
||||||
|
finalId = listItem != null ? (listItem.knowledgeId ?? listItem.id) : undefined;
|
||||||
|
}
|
||||||
// 仅当 knowledgeId 或 id 存在且有效时才跳转,否则提示暂无详情
|
// 仅当 knowledgeId 或 id 存在且有效时才跳转,否则提示暂无详情
|
||||||
if (finalId == null || finalId === '' || String(finalId).trim() === '' || String(finalId) === 'undefined') {
|
if (finalId == null || finalId === '' || String(finalId).trim() === '' || String(finalId) === 'undefined') {
|
||||||
uni.showToast({ title: '暂无详情', icon: 'none' });
|
uni.showToast({ title: '暂无详情', icon: 'none' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const idStr = String(finalId).trim();
|
const idStr = String(finalId).trim();
|
||||||
if (idStr === 'undefined') {
|
|
||||||
uni.showToast({ title: '暂无详情', icon: 'none' });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pages/tool/knowledge-detail?id=${encodeURIComponent(idStr)}`
|
url: `/pages/tool/knowledge-detail?id=${encodeURIComponent(idStr)}`
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -340,9 +340,13 @@ export default {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await getCommunityDetail(id)
|
const res = await getCommunityDetail(id)
|
||||||
// 兼容 CommonResult:res = { code, data: 详情对象 },取 res.data 作为详情
|
// 兼容 CommonResult:res = { code, data: 详情对象 } 或 { result: 详情对象 },取 data/result 作为详情
|
||||||
const data = (res && res.data !== undefined && res.data !== null) ? res.data : res
|
const data = (res && res.data !== undefined && res.data !== null)
|
||||||
|
? res.data
|
||||||
|
: (res && res.result !== undefined && res.result !== null)
|
||||||
|
? res.result
|
||||||
|
: res
|
||||||
|
|
||||||
// 格式化帖子数据
|
// 格式化帖子数据
|
||||||
this.formatPostData(data)
|
this.formatPostData(data)
|
||||||
|
|
||||||
@@ -412,7 +416,7 @@ export default {
|
|||||||
if (statsFromObj.length > 0 && !statsFromObj.every(s => s.value === '-')) return statsFromObj
|
if (statsFromObj.length > 0 && !statsFromObj.every(s => s.value === '-')) return statsFromObj
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) nutritionDataJson / nutrition_data_json(兼容后端驼峰与下划线;含 fill-nutrition 的 energyKcal/proteinG/potassiumMg/phosphorusMg、打卡字段 actualEnergy/actualProtein)
|
// 2) nutritionDataJson / nutrition_data_json(兼容后端驼峰与下划线;含 fill-nutrition 的 energyKcal/proteinG、嵌套格式 calories: { value, unit })
|
||||||
const jsonRaw = data.nutritionDataJson || data.nutrition_data_json
|
const jsonRaw = data.nutritionDataJson || data.nutrition_data_json
|
||||||
if (jsonRaw) {
|
if (jsonRaw) {
|
||||||
try {
|
try {
|
||||||
@@ -429,7 +433,7 @@ export default {
|
|||||||
if (!Array.isArray(nutritionData) && Object.keys(nutritionData).length === 0) {
|
if (!Array.isArray(nutritionData) && Object.keys(nutritionData).length === 0) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
// 2c) 有键的对象:兼容后端 fill-nutrition 的 energyKcal/proteinG 等
|
// 2c) 有键的对象:兼容后端嵌套 { calories: { value, unit }, protein: { value, unit }, ... } 与扁平 energyKcal/proteinG
|
||||||
const statsFromJson = this.buildNutritionStatsFromNutritionObject(nutritionData)
|
const statsFromJson = this.buildNutritionStatsFromNutritionObject(nutritionData)
|
||||||
// 若解析后全部为占位符 "-",视为无效数据,返回 [] 以便走打卡/服务端填充
|
// 若解析后全部为占位符 "-",视为无效数据,返回 [] 以便走打卡/服务端填充
|
||||||
if (statsFromJson.length > 0 && statsFromJson.every(s => s.value === '-')) {
|
if (statsFromJson.length > 0 && statsFromJson.every(s => s.value === '-')) {
|
||||||
@@ -441,6 +445,20 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2d) nutritionData / nutrition_data 为已解析对象(部分接口直接返回对象)
|
||||||
|
const nutritionDataObj = data.nutritionData || data.nutrition_data
|
||||||
|
if (nutritionDataObj && typeof nutritionDataObj === 'object' && !Array.isArray(nutritionDataObj) && Object.keys(nutritionDataObj).length > 0) {
|
||||||
|
const statsFromObj = this.buildNutritionStatsFromNutritionObject(nutritionDataObj)
|
||||||
|
if (statsFromObj.length > 0 && !statsFromObj.every(s => s.value === '-')) return statsFromObj
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2e) 详情中嵌套的打卡记录(若后端返回 checkInRecord/check_in_record,直接从中取营养)
|
||||||
|
const checkInRecord = data.checkInRecord || data.check_in_record
|
||||||
|
if (checkInRecord && typeof checkInRecord === 'object') {
|
||||||
|
const statsFromCheckin = this.buildNutritionStatsFromCheckinDetail(checkInRecord)
|
||||||
|
if (statsFromCheckin.length > 0 && statsFromCheckin.some(s => s.value !== '-' && s.value !== '')) return statsFromCheckin
|
||||||
|
}
|
||||||
|
|
||||||
// 3) dietaryData / mealData / dietary_data / meal_data(饮食打卡对象)
|
// 3) dietaryData / mealData / dietary_data / meal_data(饮食打卡对象)
|
||||||
const dietary = data.dietaryData || data.dietary_data || data.mealData || data.meal_data
|
const dietary = data.dietaryData || data.dietary_data || data.mealData || data.meal_data
|
||||||
if (dietary) {
|
if (dietary) {
|
||||||
@@ -565,8 +583,12 @@ export default {
|
|||||||
async fillNutritionStatsFromCheckin(checkInRecordId) {
|
async fillNutritionStatsFromCheckin(checkInRecordId) {
|
||||||
try {
|
try {
|
||||||
const res = await getCheckinDetail(checkInRecordId)
|
const res = await getCheckinDetail(checkInRecordId)
|
||||||
// 兼容:res 为 { code, data } 时取 res.data;部分封装直接返回 payload 则 res 即为 detail
|
// 兼容:res 为 { code, data } 或 { result } 时取 data/result;部分封装直接返回 payload 则 res 即为 detail
|
||||||
const detail = (res && res.data !== undefined && res.data !== null) ? res.data : res
|
const detail = (res && res.data !== undefined && res.data !== null)
|
||||||
|
? res.data
|
||||||
|
: (res && res.result !== undefined && res.result !== null)
|
||||||
|
? res.result
|
||||||
|
: res
|
||||||
if (!detail || typeof detail !== 'object') return
|
if (!detail || typeof detail !== 'object') return
|
||||||
const stats = this.buildNutritionStatsFromCheckinDetail(detail)
|
const stats = this.buildNutritionStatsFromCheckinDetail(detail)
|
||||||
// 仅当至少有一项有效值时才更新,避免展示全部为 "-" 的卡片
|
// 仅当至少有一项有效值时才更新,避免展示全部为 "-" 的卡片
|
||||||
@@ -706,7 +728,7 @@ export default {
|
|||||||
description: description,
|
description: description,
|
||||||
tags: tags,
|
tags: tags,
|
||||||
author: {
|
author: {
|
||||||
id: data.userId,
|
id: data.userId ?? data.authorId ?? data.user_id ?? data.author_id ?? null,
|
||||||
avatar: data.userAvatar || '👤',
|
avatar: data.userAvatar || '👤',
|
||||||
name: data.userName || '匿名用户',
|
name: data.userName || '匿名用户',
|
||||||
time: this.formatTime(data.createdAt)
|
time: this.formatTime(data.createdAt)
|
||||||
@@ -717,7 +739,7 @@ export default {
|
|||||||
likeCount: data.likeCount || 0,
|
likeCount: data.likeCount || 0,
|
||||||
favoriteCount: data.collectCount || 0,
|
favoriteCount: data.collectCount || 0,
|
||||||
commentCount: data.commentCount || 0,
|
commentCount: data.commentCount || 0,
|
||||||
checkInRecordId: data.checkInRecordId,
|
checkInRecordId: data.checkInRecordId ?? data.check_in_record_id ?? null,
|
||||||
videoUrl: data.videoUrl || null,
|
videoUrl: data.videoUrl || null,
|
||||||
videoStatus: data.videoStatus,
|
videoStatus: data.videoStatus,
|
||||||
hasVideo: data.hasVideo || false
|
hasVideo: data.hasVideo || false
|
||||||
@@ -852,12 +874,16 @@ export default {
|
|||||||
setTimeout(() => toLogin(), 1000)
|
setTimeout(() => toLogin(), 1000)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// 先检查 author.id 是否存在,再改状态、调 API,避免无效请求导致「操作失败」
|
||||||
|
const authorId = this.postData && this.postData.author && (this.postData.author.id ?? this.postData.author.userId)
|
||||||
|
if (authorId == null || authorId === '') {
|
||||||
|
uni.showToast({ title: '无法获取作者信息', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
const newFollowState = !this.isFollowed
|
const newFollowState = !this.isFollowed
|
||||||
this.isFollowed = newFollowState
|
this.isFollowed = newFollowState
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await apiToggleFollow(this.postData.author.id, newFollowState)
|
await apiToggleFollow(authorId, newFollowState)
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: newFollowState ? '已关注' : '取消关注',
|
title: newFollowState ? '已关注' : '取消关注',
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
@@ -1327,6 +1353,11 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.follow-btn.followed {
|
||||||
|
background: linear-gradient(135deg, #9fa5c0 0%, #8a90a8 100%);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* 营养统计卡片 */
|
/* 营养统计卡片 */
|
||||||
.nutrition-stats-card {
|
.nutrition-stats-card {
|
||||||
margin: 10rpx 32rpx;
|
margin: 10rpx 32rpx;
|
||||||
|
|||||||
@@ -50,8 +50,8 @@
|
|||||||
<text class="team-role">专业营养师</text>
|
<text class="team-role">专业营养师</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="follow-btn" @click="toggleFollow">
|
<view class="follow-btn" :class="{ followed: isFollowing }" @click="toggleFollow">
|
||||||
<text>+ 关注</text>
|
<text>{{ isFollowing ? '已关注' : '+ 关注' }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -353,9 +353,9 @@ export default {
|
|||||||
applyDefaultData() {
|
applyDefaultData() {
|
||||||
this.recipeData = { ...this.defaultRecipeData }
|
this.recipeData = { ...this.defaultRecipeData }
|
||||||
this.nutritionData = JSON.parse(JSON.stringify(this.defaultNutritionData))
|
this.nutritionData = JSON.parse(JSON.stringify(this.defaultNutritionData))
|
||||||
|
if (this.recipeId) {
|
||||||
// T09: 若无营养数据,调用 AI 回填
|
this.fillNutritionFromAI(this.recipeId)
|
||||||
this.fillNutritionFromAI(id)
|
}
|
||||||
this.mealPlan = JSON.parse(JSON.stringify(this.defaultMealPlan))
|
this.mealPlan = JSON.parse(JSON.stringify(this.defaultMealPlan))
|
||||||
this.warningList = [...this.defaultWarningList]
|
this.warningList = [...this.defaultWarningList]
|
||||||
},
|
},
|
||||||
@@ -715,6 +715,10 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.follow-btn.followed {
|
||||||
|
background: linear-gradient(180deg, #9fa5c0 0%, #8a90a8 100%);
|
||||||
|
}
|
||||||
|
|
||||||
/* 介绍卡片 */
|
/* 介绍卡片 */
|
||||||
.intro-card {
|
.intro-card {
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
|
|||||||
@@ -436,21 +436,38 @@ export default {
|
|||||||
getMealTypeLabel(mealType) {
|
getMealTypeLabel(mealType) {
|
||||||
if (!mealType) return '分享'
|
if (!mealType) return '分享'
|
||||||
const map = {
|
const map = {
|
||||||
|
// 英文
|
||||||
breakfast: '早餐',
|
breakfast: '早餐',
|
||||||
lunch: '午餐',
|
lunch: '午餐',
|
||||||
dinner: '晚餐',
|
dinner: '晚餐',
|
||||||
snack: '加餐',
|
snack: '加餐',
|
||||||
share: '分享',
|
share: '分享',
|
||||||
checkin: '打卡',
|
checkin: '打卡',
|
||||||
|
brunch: '早午餐',
|
||||||
|
tea: '茶点',
|
||||||
|
supper: '晚餐',
|
||||||
|
other: '其他',
|
||||||
|
morning: '早餐',
|
||||||
|
noon: '午餐',
|
||||||
|
night: '晚餐',
|
||||||
|
afternoon_tea: '下午茶',
|
||||||
|
afternoon: '下午茶',
|
||||||
|
midnight: '夜宵',
|
||||||
|
midnight_snack: '夜宵',
|
||||||
|
morning_snack: '早加餐',
|
||||||
|
night_snack: '夜宵',
|
||||||
|
// 拼音
|
||||||
zaocan: '早餐',
|
zaocan: '早餐',
|
||||||
wucan: '午餐',
|
wucan: '午餐',
|
||||||
wancan: '晚餐',
|
wancan: '晚餐',
|
||||||
jiacan: '加餐',
|
jiacan: '加餐',
|
||||||
fenxiang: '分享',
|
fenxiang: '分享',
|
||||||
daka: '打卡',
|
daka: '打卡',
|
||||||
morning: '早餐',
|
zaowucan: '早午餐',
|
||||||
noon: '午餐',
|
chadian: '茶点',
|
||||||
night: '晚餐'
|
qita: '其他',
|
||||||
|
xiawucha: '下午茶',
|
||||||
|
yexiao: '夜宵'
|
||||||
}
|
}
|
||||||
const str = String(mealType).trim()
|
const str = String(mealType).trim()
|
||||||
const lower = str.toLowerCase()
|
const lower = str.toLowerCase()
|
||||||
|
|||||||
8
scripts/run-t10-regression.sh
Normal file
8
scripts/run-t10-regression.sh
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Run T10 full regression tests and optionally update report.
|
||||||
|
# Usage: ./scripts/run-t10-regression.sh [--update-report]
|
||||||
|
set -e
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
echo "Running T10 regression tests..."
|
||||||
|
npx playwright test tests/e2e/bug-regression.spec.ts --grep "T10" --reporter=list
|
||||||
|
echo "Done. See docs/Testing/T10-full-regression-report.md to fill pass/fail from the output above."
|
||||||
Reference in New Issue
Block a user