diff --git a/msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolKieAIServiceImpl.java b/msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolKieAIServiceImpl.java index a7e2adc..036448d 100644 --- a/msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolKieAIServiceImpl.java +++ b/msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolKieAIServiceImpl.java @@ -390,6 +390,7 @@ public class ToolKieAIServiceImpl implements ToolKieAIService { return emitter; } + /** BUG-005: 仅使用 request.getMessages() 透传用户/助手消息,不注入硬编码 prompt */ private Map buildGeminiRequestBody(KieAIGeminiChatRequest request, boolean stream) { Map body = new HashMap<>(); List> messagesOut = new ArrayList<>(); diff --git a/msh_single_uniapp/api/models-api.js b/msh_single_uniapp/api/models-api.js index 4a55ebd..162df30 100644 --- a/msh_single_uniapp/api/models-api.js +++ b/msh_single_uniapp/api/models-api.js @@ -332,11 +332,12 @@ function queryAsrStatus(taskId) { * @returns {Promise} 对话响应 */ /** - * KieAI Gemini 2.5 Flash 对话(非流式) + * KieAI Gemini 对话(POST /api/front/kieai/gemini/chat) + * 文本对话请求体: { messages: [{ role: 'user', content: 用户输入 }], stream: false } * @param {object} data 请求体 - * @param {Array} data.messages 消息列表 [{ role: 'user'|'assistant'|'system', content: string }] + * @param {Array} data.messages 消息列表 [{ role: 'user'|'assistant'|'system', content: string|Array }] * @param {boolean} data.stream 是否流式,默认 false - * @returns {Promise} 响应 data 为 Gemini API 格式 { choices: [{ message: { content } }] } + * @returns {Promise} 响应 data 为 Gemini 格式 { choices: [{ message: { content } }] },回复取 data.choices[0].message.content */ function kieaiGeminiChat(data) { return request('/api/front/kieai/gemini/chat', { diff --git a/msh_single_uniapp/api/user.js b/msh_single_uniapp/api/user.js index 3822223..b203284 100644 --- a/msh_single_uniapp/api/user.js +++ b/msh_single_uniapp/api/user.js @@ -19,6 +19,13 @@ export function getUserInfo(){ return request.get('user'); } +/** + * 获取用户信息(GET /api/front/user/info,用于打卡后刷新积分等) + */ +export function getFrontUserInfo(){ + return request.get('user/info'); +} + /** * 设置用户分享 * diff --git a/msh_single_uniapp/pages/tool/ai-nutritionist.vue b/msh_single_uniapp/pages/tool/ai-nutritionist.vue index 6a13767..8f3599e 100644 --- a/msh_single_uniapp/pages/tool/ai-nutritionist.vue +++ b/msh_single_uniapp/pages/tool/ai-nutritionist.vue @@ -610,21 +610,24 @@ export default { this.scrollToBottom(); }, - /** 从 Gemini 响应 message.content 提取展示文本(支持 string 或 parts 数组) */ + /** 从 Gemini 响应 data.choices[0].message.content 提取展示文本(支持 string / parts 数组 / { parts } 对象) */ extractReplyContent(content) { if (content == null) return ''; if (typeof content === 'string') return content; if (Array.isArray(content)) { return content.map(part => (part && part.text) ? part.text : '').filter(Boolean).join(''); } + if (typeof content === 'object' && Array.isArray(content.parts)) { + return content.parts.map(part => (part && part.text) ? part.text : '').filter(Boolean).join(''); + } return String(content); }, async sendToAI(content, type) { this.isLoading = true; - // BUG-005:文本/多模态必须走 KieAI Gemini,回复仅从 data.choices[0].message.content 取,不得使用固定话术 - // 请求体: { messages: [{ role: 'user', content }], stream: false } + // BUG-005:文本/多模态必须走 KieAI Gemini;请求体 { messages: [{ role: 'user', content: 用户输入 }], stream: false } + // 回复仅从 data.choices[0].message.content 取,禁止使用 getAIResponse 等固定话术 if (type === 'text' || type === 'multimodal') { try { const messages = [{ role: 'user', content: content }]; @@ -634,10 +637,10 @@ export default { const data = response.data; const choice = data.choices && data.choices[0]; const msgObj = choice && choice.message; - const rawContent = msgObj && msgObj.content; - const reply = rawContent != null ? this.extractReplyContent(rawContent) : ''; - // 成功时仅展示模型返回内容 - this.messageList.push({ role: 'ai', content: reply || '未能获取到有效回复。' }); + const rawContent = msgObj != null ? msgObj.content : undefined; + const reply = rawContent != null && rawContent !== undefined ? this.extractReplyContent(rawContent) : ''; + // BUG-005: 仅展示接口返回的 data.choices[0].message.content,不使用固定话术 + this.messageList.push({ role: 'ai', content: reply.trim() || '未能获取到有效回复。' }); } else { const msg = (response && response.message) || '发起对话失败'; this.messageList.push({ role: 'ai', content: '请求失败:' + msg }); diff --git a/msh_single_uniapp/pages/tool/calculator-result.vue b/msh_single_uniapp/pages/tool/calculator-result.vue index e02082c..63a1b17 100644 --- a/msh_single_uniapp/pages/tool/calculator-result.vue +++ b/msh_single_uniapp/pages/tool/calculator-result.vue @@ -487,12 +487,12 @@ export default { align-items: center; justify-content: center; gap: 16rpx; - transition: all 0.3s; - border-bottom: none; + transition: all 0.2s ease; box-sizing: border-box; + /* 未激活:明显变灰,无下划线 */ color: #9ca3af; font-weight: 400; - /* 未激活:明显变灰,无下划线 */ + border-bottom: none; .tab-icon { font-size: 28rpx; color: #9ca3af; @@ -503,12 +503,12 @@ export default { color: #9ca3af; font-weight: 400; } - /* 激活:加粗、主色、橙色底部下划线 */ + /* 激活:加粗、主色、橙色底部下划线(BUG-002) */ &.active { background: transparent; - border-bottom: 3px solid #f97316; color: #f97316; font-weight: 700; + border-bottom: 3px solid #f97316; .tab-text { color: #f97316; font-weight: 700; diff --git a/msh_single_uniapp/pages/tool/checkin.vue b/msh_single_uniapp/pages/tool/checkin.vue index 0919f1f..8aa0cf4 100644 --- a/msh_single_uniapp/pages/tool/checkin.vue +++ b/msh_single_uniapp/pages/tool/checkin.vue @@ -241,17 +241,20 @@ export default { return; } try { - const { setSignIntegral, getUserInfo } = await import('@/api/user.js'); + const { setSignIntegral, getFrontUserInfo, getUserInfo } = await import('@/api/user.js'); const { getUserPoints } = await import('@/api/tool.js'); // 子问题 A:不得在 API 返回成功前修改 currentPoints,避免打卡前积分提前跳变 - // 子问题 B-1:打卡接口为 GET /api/front/user/sign/integral(setSignIntegral),与 /api/front/user/checkin 同属签到类接口 + // 打卡接口:GET /api/front/user/sign/integral(setSignIntegral),等同于 checkin 签到 await setSignIntegral(); - this.todaySigned = true; - - // 子问题 B-2/B-3:仅用服务端返回值更新积分,禁止前端本地 +30。调用 GET /api/front/user(getUserInfo)刷新用户信息,将返回的 integral 赋给 currentPoints - const userRes = await getUserInfo(); + // 子问题 B:仅在打卡成功后用服务端数据更新积分。先 GET /api/front/user/info 刷新用户积分,禁止硬编码 +30 + let userRes = null; + try { + userRes = await getFrontUserInfo(); // GET /api/front/user/info + } 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 { @@ -261,6 +264,9 @@ export default { this.currentPoints = serverPoints; } } + + // 积分已从服务端更新后再更新打卡状态并跳转,避免“已打卡”与积分不同步 + this.todaySigned = true; } catch (e) { const msg = (typeof e === 'string' ? e : (e && (e.message || e.msg))) || '打卡失败'; if (msg.includes('今日已签到') || msg.includes('不可重复')) { diff --git a/msh_single_uniapp/pages/tool/food-detail.vue b/msh_single_uniapp/pages/tool/food-detail.vue index f2de6ce..24fedae 100644 --- a/msh_single_uniapp/pages/tool/food-detail.vue +++ b/msh_single_uniapp/pages/tool/food-detail.vue @@ -14,13 +14,13 @@ - + - {{ foodData.name || '—' }} + {{ displayName }} - {{ foodData.category || '—' }} - {{ foodData.safetyTag || '—' }} + {{ displayCategory }} + {{ displaySafetyTag }} @@ -34,7 +34,7 @@ {{ nutrient.name }} @@ -56,7 +56,7 @@ @@ -132,21 +132,58 @@ export default { } } }, + computed: { + // 保证 .food-name-overlay / .nutrient-card / .nutrition-row 在 defaultFoodData 状态下也有非空数据可渲染 + displayName() { + return (this.foodData && this.foodData.name) ? this.foodData.name : (this.defaultFoodData.name || '—') + }, + displayCategory() { + return (this.foodData && this.foodData.category) ? this.foodData.category : (this.defaultFoodData.category || '—') + }, + displaySafetyTag() { + return (this.foodData && this.foodData.safetyTag) ? this.foodData.safetyTag : (this.defaultFoodData.safetyTag || '—') + }, + displayImage() { + return (this.foodData && this.foodData.image) ? this.foodData.image : (this.defaultFoodData.image || '') + }, + displayKeyNutrients() { + const arr = this.foodData && this.foodData.keyNutrients + if (Array.isArray(arr) && arr.length > 0) return arr + const def = this.defaultFoodData.keyNutrients + return Array.isArray(def) && def.length > 0 ? def : [{ name: '—', value: '—', unit: '', status: '—' }] + }, + displayNutritionTable() { + const arr = this.foodData && this.foodData.nutritionTable + if (Array.isArray(arr) && arr.length > 0) return arr + const def = this.defaultFoodData.nutritionTable + return Array.isArray(def) && def.length > 0 ? def : [{ name: '—', value: '—', unit: '', level: 'low', levelText: '—' }] + } + }, onLoad(options) { - this.pageParams = { id: options.id || '', name: options.name || '' } + options = options || {} + // 若列表误将 name 传成 id(如 id=羊肉(熟)),用 id 作为展示用 name,避免请求接口 400 + const rawId = options.id + const rawName = options.name + const hasNonNumericId = rawId !== undefined && rawId !== '' && isNaN(Number(rawId)) + const displayName = rawName || (hasNonNumericId ? rawId : '') + this.pageParams = { + id: (rawId !== undefined && rawId !== '') ? String(rawId) : '', + name: (displayName !== undefined && displayName !== '') ? String(displayName) : '' + } // 打印入参便于排查:后端详情接口仅接受 Long 类型 id,传 name 会导致 400 console.log('[food-detail] onLoad options:', options) console.log('[food-detail] onLoad pageParams (id/name):', this.pageParams) - const rawId = options.id const isNumericId = rawId !== undefined && rawId !== '' && !isNaN(Number(rawId)) if (isNumericId) { - this.loadFoodData(Number(rawId)) - } else if (options.name) { - // 无有效 id 仅有 name 时,不请求接口(避免传 name 导致后端 NumberFormatException),直接展示默认数据 + 当前名称 + const numId = Number(rawId) + console.log('[food-detail] 使用数字 id 请求详情,不传 name 字段:', numId) + this.loadFoodData(numId) + } else if (this.pageParams.name) { + // 无有效 id、仅有 name 时,不请求接口(避免传 name 导致后端 NumberFormatException),直接展示默认数据 + 当前名称 this.loadError = '暂无该食物详情数据,展示参考数据' this.applyDefaultFoodData(false) try { - this.foodData.name = decodeURIComponent(String(options.name)) + this.foodData.name = decodeURIComponent(this.pageParams.name) } catch (e) {} } else { this.applyDefaultFoodData() @@ -192,8 +229,10 @@ export default { console.log('[food-detail] getFoodDetail request param:', { id, type: typeof id }) try { const res = await getFoodDetail(id) - const data = res.data || res - console.log('[food-detail] getFoodDetail 响应:', data ? { hasName: !!data.name, hasImage: !!data.image, keys: Object.keys(data) } : null) + // 打印响应结构便于确认:request 成功时 resolve 的是 res.data,即 { code: 200, data: {...} } + console.log('[food-detail] getFoodDetail 响应结构:', res ? { hasData: !!res.data, code: res.code, keys: Object.keys(res || {}) } : null) + 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) if (data && data.name) { // 解析API返回的食物数据 this.foodData = { @@ -219,12 +258,14 @@ export default { uni.showToast({ title: '数据加载失败', icon: 'none' }) } } catch (error) { - const errMsg = (error && (error.message || error.msg || error.errMsg || error)) ? String(error.message || error.msg || error.errMsg || error) : '未知错误' + // a. 将 loadError 置为具体错误信息(用于调试):兼容 Error、{ message/msg }、字符串 + const errMsg = (error && (error.message || error.msg || error.errMsg)) + ? String(error.message || error.msg || error.errMsg) + : (typeof error === 'string' ? error : (error ? String(error) : '未知错误')) console.error('[food-detail] 加载食物数据失败:', error) console.error('[food-detail] loadError(用于调试):', errMsg) - // a. 将 loadError 置为具体错误信息(用于调试) this.loadError = errMsg - // b. 使用 defaultFoodData 填充页面,保证用户能看到基础界面;不清空 loadError 以便展示「当前数据来自缓存」提示 + // b. 使用 defaultFoodData 填充页面,保证用户能看到基础界面而不是空白 this.applyDefaultFoodData(false) // 若有入参 name,用其覆盖展示名称,避免显示默认「五谷香」 if (this.pageParams && this.pageParams.name) { @@ -232,7 +273,7 @@ export default { this.foodData.name = decodeURIComponent(String(this.pageParams.name)) } catch (e) {} } - // c. 页面已通过 v-if="loadError" 显示「当前数据来自缓存,可能不是最新」;再弹出轻提示 + // c. 页面通过 v-if="loadError" 显示「当前数据来自缓存,可能不是最新」;再弹出轻提示 uni.showToast({ title: '数据加载失败', icon: 'none' diff --git a/msh_single_uniapp/pages/tool/food-encyclopedia.vue b/msh_single_uniapp/pages/tool/food-encyclopedia.vue index 2d1a510..95b3176 100644 --- a/msh_single_uniapp/pages/tool/food-encyclopedia.vue +++ b/msh_single_uniapp/pages/tool/food-encyclopedia.vue @@ -105,7 +105,7 @@ @@ -126,7 +126,7 @@ {{ nut.label || '—' }} @@ -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 diff --git a/msh_single_uniapp/pages/tool/knowledge-detail.vue b/msh_single_uniapp/pages/tool/knowledge-detail.vue index b02ab82..b979fbc 100644 --- a/msh_single_uniapp/pages/tool/knowledge-detail.vue +++ b/msh_single_uniapp/pages/tool/knowledge-detail.vue @@ -25,6 +25,9 @@ {{ error }} + + 暂无内容 + @@ -50,8 +53,9 @@ export default { }; }, onLoad(options) { - if (options.id) { - this.id = options.id; + const rawId = options.id; + if (rawId != null && rawId !== '' && String(rawId).trim() !== '' && String(rawId) !== 'undefined') { + this.id = String(rawId).trim(); } else { this.loading = false; this.error = '缺少文章 ID'; @@ -144,7 +148,8 @@ export default { color: #333; } .loading-wrap, -.error-wrap { +.error-wrap, +.empty-wrap { padding: 80rpx 30rpx; text-align: center; color: #999; @@ -153,4 +158,7 @@ export default { .error-wrap { color: #f56c6c; } +.empty-wrap { + color: #9fa5c0; +} diff --git a/msh_single_uniapp/pages/tool/nutrition-knowledge.vue b/msh_single_uniapp/pages/tool/nutrition-knowledge.vue index 7aee6d4..8a19501 100644 --- a/msh_single_uniapp/pages/tool/nutrition-knowledge.vue +++ b/msh_single_uniapp/pages/tool/nutrition-knowledge.vue @@ -128,7 +128,7 @@ export default { data() { return { currentTab: 'nutrients', - scrollViewHeight: 'calc(100vh - 120rpx)', // 为 tab 预留高度,微信小程序 scroll-view 需明确高度 + scrollViewHeight: '100vh', // 默认值,onReady 中按机型计算为 px,保证微信小程序 scroll-view 有明确高度 guideList: [], articleList: [], nutrientList: [ @@ -188,8 +188,25 @@ export default { // 有 id 时切换到科普文章 tab,switchTab 内会调用 loadKnowledgeList 加载列表 this.switchTab('articles'); } else { - // 无 id 时默认当前 tab 为「营养素」,不请求接口;用户点击「饮食指南」或「科普文章」时由 switchTab 触发 loadKnowledgeList + // 无 id 时默认当前 tab 为「营养素」;切换到「饮食指南」或「科普文章」时由 switchTab 触发 loadKnowledgeList this.currentTab = 'nutrients'; + // 确保列表初始为数组,避免未加载时为 undefined;切换 Tab 后再加载对应列表 + this.guideList = Array.isArray(this.guideList) ? this.guideList : []; + this.articleList = Array.isArray(this.articleList) ? this.articleList : []; + } + }, + onReady() { + // 微信小程序 scroll-view 必须使用明确高度,且 calc(vh - rpx) 兼容性差,改为用 px 计算 + try { + const systemInfo = uni.getSystemInfoSync(); + const statusBarHeight = systemInfo.statusBarHeight || 0; + const navHeight = 44; + const tabBarRpx = 120; + const windowWidth = systemInfo.windowWidth || 375; + const tabBarPx = Math.ceil((windowWidth / 750) * tabBarRpx); + this.scrollViewHeight = `calc(100vh - ${statusBarHeight + navHeight + tabBarPx}px)`; + } catch (e) { + this.scrollViewHeight = 'calc(100vh - 140px)'; } }, methods: { @@ -216,16 +233,21 @@ export default { page: 1, limit: 50 }); - // 兼容 CommonPage:result.data.list,或 result.data/result.data.records 为数组 + // 兼容多种返回结构:result.data.list / result.data.records / result.data 为数组 / result.list let rawList = []; if (result && result.data) { - if (Array.isArray(result.data.list)) { - rawList = result.data.list; - } else if (Array.isArray(result.data.records)) { - rawList = result.data.records; - } else if (Array.isArray(result.data)) { - rawList = result.data; + const d = result.data; + if (Array.isArray(d.list)) { + rawList = d.list; + } else if (Array.isArray(d.records)) { + rawList = d.records; + } else if (Array.isArray(d)) { + rawList = d; + } else if (d && Array.isArray(d.data)) { + rawList = d.data; } + } else if (result && Array.isArray(result.list)) { + rawList = result.list; } // Normalize id: backend may return knowledgeId, id, or knowledge_id (BeanUtil/JSON) const list = (rawList || []).map(item => { @@ -233,6 +255,7 @@ export default { return { ...item, id, + knowledgeId: item.knowledgeId ?? id, desc: item.desc || item.summary || '', time: item.time || (item.publishedAt || item.createdAt ? this.formatKnowledgeTime(item.publishedAt || item.createdAt) : ''), views: item.views != null ? item.views : (item.viewCount != null ? item.viewCount : 0), @@ -240,19 +263,20 @@ export default { coverImage: item.coverImage || item.cover_image || '' }; }); + // 始终赋值为数组,绝不设为 undefined if (this.currentTab === 'guide') { - this.guideList = list; + this.guideList = Array.isArray(list) ? list : []; } else if (this.currentTab === 'articles') { - this.articleList = list; + this.articleList = Array.isArray(list) ? list : []; } } catch (error) { console.error('加载知识列表失败:', error); const msg = (error && (typeof error === 'string' ? error : (error.message || error.msg))) || '加载列表失败'; uni.showToast({ - title: String(msg), + title: String(msg).substring(0, 20), icon: 'none' }); - // 失败时清空当前 tab 列表并确保始终为数组,不设为 undefined + // 失败时清空当前 tab 列表,确保始终为数组,绝不设为 undefined if (this.currentTab === 'guide') { this.guideList = []; } else if (this.currentTab === 'articles') { @@ -275,13 +299,19 @@ export default { goToDetail(event) { const id = event.currentTarget.dataset.itemId; const knowledgeId = event.currentTarget.dataset.itemKid; - const finalId = knowledgeId || id; - if (!finalId) { - uni.showToast({ title: "暂无详情", icon: "none" }); + const finalId = knowledgeId ?? id; + // 仅当 knowledgeId 或 id 存在且有效时才跳转,否则提示暂无详情 + if (finalId == null || finalId === '' || String(finalId).trim() === '' || String(finalId) === 'undefined') { + uni.showToast({ title: '暂无详情', icon: 'none' }); + return; + } + const idStr = String(finalId).trim(); + if (idStr === 'undefined') { + uni.showToast({ title: '暂无详情', icon: 'none' }); return; } uni.navigateTo({ - url: `/pages/tool/knowledge-detail?id=${finalId}` + url: `/pages/tool/knowledge-detail?id=${encodeURIComponent(idStr)}` }); } } diff --git a/msh_single_uniapp/pages/tool_main/community.vue b/msh_single_uniapp/pages/tool_main/community.vue index 50a1214..ee1f303 100644 --- a/msh_single_uniapp/pages/tool_main/community.vue +++ b/msh_single_uniapp/pages/tool_main/community.vue @@ -4,32 +4,32 @@ 推荐 最新 关注 热门 @@ -46,7 +46,7 @@ 📭 暂无内容 - 关注更多用户,查看他们的打卡动态 + 关注更多用户,查看他们的打卡动态 快来发布第一条打卡动态吧 @@ -144,7 +144,7 @@ import { checkLogin, toLogin } from '@/libs/login.js' export default { data() { return { - currentTab: 'recommend', + currentTab: '推荐', postList: [], page: 1, limit: 10, @@ -173,12 +173,18 @@ export default { this.loadMore() }, methods: { + // Tab 中文转接口参数(不改动接口逻辑,仅在此处映射) + getTabApiValue() { + const map = { '推荐': 'recommend', '最新': 'latest', '关注': 'follow', '热门': 'hot' } + return map[this.currentTab] || 'recommend' + }, + // 切换Tab switchTab(tab) { if (this.currentTab === tab) return // 关注Tab需要登录 - if (tab === 'follow' && !checkLogin()) { + if (tab === '关注' && !checkLogin()) { uni.showToast({ title: '请先登录查看关注内容', icon: 'none' @@ -214,7 +220,7 @@ export default { try { const res = await getCommunityList({ - tab: this.currentTab, + tab: this.getTabApiValue(), page: this.page, limit: this.limit }) @@ -385,7 +391,7 @@ export default { try { const res = await getCommunityList({ - tab: this.currentTab, + tab: this.getTabApiValue(), page: this.page, limit: this.limit }) @@ -415,18 +421,18 @@ export default { }) }, - // 格式化数量显示 + // 格式化数量显示(中文单位) formatCount(count) { if (!count) return '0' if (count >= 10000) { - return (count / 10000).toFixed(1) + 'w' + return (count / 10000).toFixed(1) + '万' } else if (count >= 1000) { - return (count / 1000).toFixed(1) + 'k' + return (count / 1000).toFixed(1) + '千' } return String(count) }, - // 帖子类型英文转中文显示(仅用于展示,保证 label 均为中文) + // 帖子类型英文/拼音转中文显示(仅用于展示,保证 label 均为中文) getMealTypeLabel(mealType) { if (!mealType) return '分享' const map = { @@ -435,7 +441,16 @@ export default { dinner: '晚餐', snack: '加餐', share: '分享', - checkin: '打卡' + checkin: '打卡', + zaocan: '早餐', + wucan: '午餐', + wancan: '晚餐', + jiacan: '加餐', + fenxiang: '分享', + daka: '打卡', + morning: '早餐', + noon: '午餐', + night: '晚餐' } const str = String(mealType).trim() const lower = str.toLowerCase()