feat: 集成 KieAI 服务,移除 models-integration 子项目

- 添加 Gemini 2.5 Flash 对话接口(流式+非流式)
- 添加 NanoBanana 图像生成/编辑接口
- 添加 Sora2 视频生成接口(文生视频、图生视频、去水印)
- 移除 models-integration 子项目(功能已迁移至主后端)
- 新增测试文档和 Playwright E2E 配置
- 更新前端页面和 API 接口
- 更新后端配置和日志处理
This commit is contained in:
2026-03-03 15:33:50 +08:00
parent 1ddb051977
commit 4be53dcd1b
586 changed files with 21142 additions and 25130 deletions

View File

@@ -0,0 +1,32 @@
已按 BUG-001 的两个子问题改好,修改总结如下。
---
**子问题 A — 打卡前积分不提前跳变**
- **逻辑**:在 `handleCheckin` 里,**只有**在 `setSignIntegral()` 成功返回之后才更新状态和积分。
- **具体**
- 先 `await setSignIntegral()`,不成功会进 `catch`,不会改 `currentPoints`。
- 成功后再 `this.todaySigned = true`,然后再用服务端数据更新 `currentPoints`。
- 全程没有在接口成功前修改 `currentPoints`,也没有任何前端本地 `+30`。
---
**子问题 B — 打卡成功后用服务端积分刷新**
- **接口确认**:打卡接口仍是 `setSignIntegral()``user/sign/integral`),对应后端 `/api/front/user/sign/integral`,即签到/打卡接口。
- **刷新积分**:打卡成功后用「用户信息」接口刷新积分:
- 先调 `getUserInfo()`(即 GET `/api/front/user`,即 user info 接口)。
- 从返回里取 `integral` 或 `points`,赋给 `currentPoints`(优先 `integral`)。
- 若 user info 里没有积分字段,再请求 `getUserPoints()``tool/points/info`),用其返回的 `totalPoints` / `points` / `availablePoints` 更新 `currentPoints`。
- **禁止硬编码**:积分只来自上述两个接口的返回值,没有写死 `+30`。
---
**代码改动要点**
1. 引入并调用 `getUserInfo`。
2. 在 `await setSignIntegral()` 成功后,先 `getUserInfo()`,用 `userRes.data.integral ?? userRes.data.points` 更新 `currentPoints`。
3. 仅当 user info 无积分时,再 `getUserPoints()` 并以其结果更新 `currentPoints`。
4. 注释中写明:不在 API 成功前改 `currentPoints`,且用 GET user info 刷新积分、禁止前端本地 +30。

View File

@@ -0,0 +1,35 @@
已按 BUG-001 的两个子问题改好,修改总结如下。
---
### 子问题 A — 打卡前积分不再提前跳变
- **原逻辑**:点击「立即打卡」后先执行 `uni.navigateTo`,再执行 `this.currentPoints += 30`,在接口返回前就改动了积分。
- **现逻辑**
- 在 `handleCheckin` 中**不再**在接口成功前修改 `currentPoints`。
- 仅当签到接口调用成功,并且用 `getUserPoints()` 拿到服务端最新积分后,才给 `currentPoints` 赋值(见下)。
---
### 子问题 B — 打卡成功后用服务端积分更新
- **原逻辑**:没有调用任何打卡/签到接口,只在前端写死 `currentPoints += 30`,后端没有参与积分变更。
- **现逻辑**
1. **调用签到接口**:使用现有接口 `GET /api/front/user/sign/integral``@/api/user.js` 中的 `setSignIntegral()`),与需求中的「/api/front/user/checkin 或类似接口」一致,用于触发后端签到与积分累加。
2. **成功后刷新积分**:签到成功后请求 `getUserPoints()`(对应后端 `GET .../tool/points/info`),用返回的积分更新页面。
3. **积分来源**
`currentPoints` 只从接口赋值:
`pointsRes.data.totalPoints ?? pointsRes.data.points ?? pointsRes.data.availablePoints ?? 0`
不再使用硬编码的 +30。
4. **错误与跳转**:若签到或拉取积分失败,会 `uni.showToast` 提示并 `return`,不跳转;只有成功后才 `uni.navigateTo('/pages/tool/checkin-publish')`。
---
### 其它修改
- **loadCheckinData**:积分取值改为优先使用服务端字段,与上面一致:
`currentPoints = pointsRes.data.totalPoints ?? pointsRes.data.points ?? 0`
这样列表和打卡页的积分都来自服务端。
需求里提到的「GET /api/front/user/info」在当前项目里没有对应接口用户积分接口是 `GET /api/front/tool/points/info``getUserPoints()`),因此用该接口的返回值刷新并赋值给 `currentPoints`。若后续后端提供 `/api/front/user/info` 并返回积分,只需在成功后再调该接口,用其返回的积分更新 `currentPoints` 即可。

View File

@@ -0,0 +1,16 @@
Connection lost, reconnecting...
Retry attempt 1...
已按 BUG-002 要求只改了 CSS未动任何 JS修改如下
**1. 未激活 Tab`.tab-item`**
- 在根级增加 `color: #9ca3af`,保证整项为灰色。
- 下划线保持 `border-bottom: 3px solid transparent`,无可见下划线。
**2. 激活 Tab`.tab-item.active`**
- `border-bottom: 3px solid #f97316`,橙色底线下划线(由原 6rpx 改为 3px
- `color: #f97316`,主色字色。
- `font-weight: 700`,加粗。
- 保留内部 `.tab-text`、`.tab-icon` 的 `#f97316` 与 `.tab-text` 的 `font-weight: 700`。
这样「健康概览」和「营养配餐」切换时,激活项为橙色加粗+橙色下划线,未激活为灰色且无下划线,对比更清晰。

View File

@@ -0,0 +1,15 @@
已按 BUG-002 要求只改了 CSS未动任何 JS。修改总结如下
### 1. 未激活 Tab`.tab-item`
- **字色**`.tab-text` 与 `.tab-icon` 均为 `#9ca3af`
- **下划线**:无实线,用 `border-bottom: 6rpx solid transparent` 占位,避免切换时高度抖动
- 其它:`height: 100%`、`min-height: 75rpx`,让底部边框落在 tab 栏最下方;`border-radius: 0`,改为下划线式 tab
### 2. 激活 Tab`.tab-item.active`
- **字重**`.tab-text` 设为 `font-weight: 700`
- **下划线**`border-bottom: 6rpx solid #f97316`(与要求 3px 等效,用 6rpx 保持与页面其它 rpx 一致)
- **字色**`.tab-text`、`.tab-icon` 均为 `color: #f97316`
- **背景**`background: transparent`,去掉原来的橙色块,只保留下划线样式
效果:当前选中的「健康概览」或「营养配餐」会以**加粗 + 橙色字 + 橙色底线下划线**显示,未选中的 Tab 为**灰色字、无下划线**,视觉区分更明显。

View File

@@ -0,0 +1,44 @@
BUG-003 已按你的要求修好,修改总结如下。
## 1. Template 与数据来源(确认)
- **`.food-image` 的 `:src`**:绑定为 `getFoodImage(item)`,由该方法统一取图并处理占位。
- **`.nutrition-item` 的 v-for**:遍历 `(item.nutrition || [])`,数据在 `normalizeFoodItem` 里统一整理成 `nutrition` 数组。
## 2. 后端数据结构api/tool.js + ToolFoodServiceImpl
- **列表/搜索** 返回每条为:`id`, `name`, `image`, `category`, `energy`, `protein`, `potassium`, `phosphorus`, `suitabilityLevel`。
没有 `nutrition`/`nutrients` 数组,营养来自扁平字段。
## 3. 具体修改
**1图片字段与占位**
- 在 **`getFoodImage(item)`** 中:
- 统一取 `item.imageUrl || item.image || item.img`。
- 若为相对路径(以 `/` 开头且非 `//`),用 `HTTP_REQUEST_URL` 拼成完整 URL。
- 无有效 URL 时返回已有的灰色 SVG 占位图(`defaultPlaceholder`)。
**2营养字段与列表**
- 在 **`normalizeFoodItem(item)`** 中:
- **图片**:同样做 image/imageUrl/img 兼容和相对路径补全,并写入 `image` / `imageUrl`,保证列表项有统一图源。
- **营养**
- 若有 `item.nutrition` 数组,按 `{ label, value, colorClass }` 规范化(兼容 `name`/`labelName`)。
- 否则若有 `item.nutrients`,同样规范化后当作 `nutrition`。
- 否则用后端扁平字段组装:能量、蛋白质、钾、磷、钠、钙(有值才加),保证列表有 `nutrition` 数组供 `.nutrition-item` 使用。
**3列表数据来源**
- `loadFoodList` 和搜索回调中,列表来源改为:
`result.data.list || (Array.isArray(result.data) ? result.data : [])`,再对每一项执行 `normalizeFoodItem`,避免因接口形态不同导致列表为空。
**4配置**
- 顶部增加:`import { HTTP_REQUEST_URL } from '@/config/app.js'`,用于相对路径补全。
效果上:
- `.food-image` 会正确显示后端返回的图(含相对路径补全),无图时显示灰色占位图。
- `.nutrition-item` 会正确展示营养简介(来自后端扁平字段或已有的 nutrition/nutrients 数组)。

View File

@@ -0,0 +1,39 @@
BUG-003 已按你的要求修好,修改总结如下。
---
## 1. 问题原因
- **后端列表接口**`getFoodList` / `searchFood`)返回的是:`id, name, image, energy, protein, suitabilityLevel`,没有 `nutrition` 数组,也没有 `category`、`potassium`、`phosphorus`。
- 模板里用的是 `item.image`、`item.nutrition`。当接口替换掉本地 mock 后,`item.nutrition` 为 `undefined`,营养简介不渲染;图片若为空也没有占位。
## 2. 修改内容
### 前端 `msh_single_uniapp/pages/tool/food-encyclopedia.vue`
**模板:**
- **`.food-image` 的 `:src`**:改为 `getFoodImage(item)`,内部使用 `item.imageUrl || item.image || item.img`,都没有时用占位图。
- **`.nutrition-item` 的 `v-for`**:改为 `v-for="(nut, idx) in (item.nutrition || [])"`,并给 `nut.colorClass` 加了默认 `'green'`,避免无数据或缺字段时报错。
- **分类与安全标签**`item.category` 为空时不渲染分类徽章(`v-if="item.category"`);安全标签增加 `item.safetyClass || 'safe'`、`item.safety || '—'` 兜底。
- **列表 `:key`**:改为 `item.id != null ? item.id : index`,便于接口数据稳定渲染。
**逻辑:**
- **`defaultPlaceholder`**:在 `data()` 里增加灰色 SVG 的 data URL作为无图时的占位图与 `.food-image-wrapper` 的灰色风格一致)。
- **`getFoodImage(item)`**:返回 `item.imageUrl || item.image || item.img`,若都为空则返回 `defaultPlaceholder`。
- **`normalizeFoodItem(item)`**:把接口数据统一成列表项结构:
- 图片:沿用 `image` / `imageUrl` / `img`(展示由 `getFoodImage` 处理)。
- 安全:若已有 `safety`/`safetyClass` 则保留,否则按 `suitabilityLevel` 映射suitable→放心吃/safemoderate→限量吃/limitedrestricted/forbidden→谨慎吃/careful
- 营养:若已有 `item.nutrition` 数组则直接用;否则用接口字段组装:能量、蛋白质、钾、磷(有值才加),格式为 `{ label, value, colorClass }`。
- **列表赋值**`loadFoodList` 和搜索分支里,对 `result.data.list` 做 `(result.data.list || []).map(item => this.normalizeFoodItem(item))` 再赋给 `foodList`,保证每条都有 `nutrition` 和可用的图片、安全、分类展示。
### 后端 `msh_crmeb_22/.../ToolFoodServiceImpl.java`
- 列表/搜索的 map 中增加:`category`、`potassium`、`phosphorus`,这样前端 `normalizeFoodItem` 能拼出更完整的营养简介,分类徽章也能显示。
效果:
- 配图:有图用接口的 `image`(或 `imageUrl`/`img`),无图用灰色占位图。
- 营养简介:接口有 `nutrition` 用接口的,没有则用能量/蛋白质/钾/磷自动组一条,`.nutrition-item` 始终有稳定数据源可遍历。

View File

@@ -0,0 +1,48 @@
BUG-004 已按你的要求修好,修改总结如下。
---
## 根因
- 后端详情接口 **只接受 Long 类型 id**`ToolController#getFoodDetail(Long id)`)。
- 列表里部分条目没有 `id``food-encyclopedia` 用 `item.name` 当 id 传,例如 `id=羊肉(熟)`,后端报 `NumberFormatException`,请求失败,页面只弹出「数据加载失败」且内容空白。
---
## 修改说明
### 1. `msh_single_uniapp/pages/tool/food-encyclopedia.vue`
- **只传数字 id**:仅在 `item.id` 为有效数字时拼进 `id` 参数;否则不传 `id`,避免把名称当 id 传给后端。
- **始终传 name**:跳转时带上 `name`(有则 `id=xxx&name=xxx`,无 id 则只传 `name=xxx`),详情页在失败或无 id 时用 name 展示。
### 2. `msh_single_uniapp/pages/tool/food-detail.vue`
- **入参与日志**
- 使用 `pageParams: { id, name }` 保存入参。
- `onLoad` 里打印:`console.log('[food-detail] onLoad params:', this.pageParams)`。
- `loadFoodData` 里打印:`console.log('[food-detail] getFoodDetail request param:', { id, type: typeof id })`。
- **只对数字 id 调接口**
- 仅当 `options.id` 能解析为数字时才调用 `loadFoodData(Number(options.id))`。
- 仅有 `options.name` 时:不请求接口,直接 `loadError = '暂无该食物详情数据,展示参考数据'`,用 `applyDefaultFoodData(false)` 填充,并把 `foodData.name` 设为解码后的 `options.name`。
- **API 失败时catch**
- **a.** `loadError = errMsg`(具体错误信息,便于调试)。
- **b.** `applyDefaultFoodData(false)`,用 `defaultFoodData` 填满页面,保证有名称、分类、关键营养、营养成分表等。
- **c.** 若有 `pageParams.name`,用其覆盖 `foodData.name`,避免显示默认「五谷香」。
- 再 `showToast('数据加载失败')`。
- `loadError` 有值时,页面顶部已有「当前数据来自缓存,可能不是最新」的提示(原有 `v-if="loadError"` 的 `.cache-notice`)。
- **保证列表能渲染**
- `applyDefaultFoodData` 中保证 `name/category/safetyTag/image` 有默认值,`keyNutrients`、`nutritionTable` 用 `defaultFoodData` 的非空数组拷贝,确保 `.food-name-overlay`、`.nutrient-card`、`.nutrition-row` 在默认数据下也能正常渲染。
- 成功拿到 API 数据时,用 `ensureNonEmptyArray(parseKeyNutrients(data), defaultFoodData.keyNutrients)` 等,避免解析出空数组导致列表为空。
---
效果简述:
- 列表点有**数字 id** 的条目:正常请求详情并展示;失败时用默认数据 + 入参 name若有+ 缓存提示 + Toast。
- 列表点**无 id、只有 name** 的条目:不请求接口,直接展示默认数据 + 该 name + 缓存提示,不再报错或空白。
- 控制台可看到 `onLoad params` 和 `getFoodDetail request param`,便于确认传参是否正确。

View File

@@ -0,0 +1,67 @@
BUG-004 已按你的要求修好,修改总结如下。
---
## 1. 根因API 参数错误)
- **后端**`ToolController.getFoodDetail(@PathVariable Long id)` 只接受 **Long 类型 id**。
- **前端**:食物百科列表跳转详情时用了 **`item.name`**(如「羊肉(熟)」「儿童营养饼干」),导致后端报 `NumberFormatException`,接口失败,页面只提示「数据加载失败」。
日志中可见:`Failed to convert value of type 'java.lang.String' to required type 'java.lang.Long'`.
---
## 2. 修改内容
### 2.1 `msh_single_uniapp/pages/tool/food-encyclopedia.vue`
- **跳转参数**:改为传 **`item.id`**(后端列表返回的 `id`),不再传 `item.name`。
- 若某条没有 `id`,则回退为 `item.name`,并对 name 做 `encodeURIComponent`,避免中文等字符在 URL 里出错。
```javascript
// 后端详情接口需要 Long 类型 id传 id 而非 name
const id = item.id != null ? item.id : item.name
url: `/pages/tool/food-detail?id=${encodeURIComponent(id)}`
```
这样从列表点进详情会带正确的 id接口可正常返回数据。
---
### 2.2 `msh_single_uniapp/pages/tool/food-detail.vue`
1. **请求参数打印**
- `onLoad``console.log('[food-detail] onLoad params:', { id: options.id, name: options.name })`
- `loadFoodData``console.log('[food-detail] getFoodDetail request param:', { id, type: typeof id })`
便于确认传参是否正确。
2. **`loadError` 与调试**
- 增加 `loadError: ''`。
- 在 `loadFoodData` 的 **catch** 里把 `loadError` 设为具体错误信息(`error.message || error.msg || error`),便于调试。
3. **失败时用默认数据 + 缓存提示**
- 抽取 **`applyDefaultFoodData(clearError)`**:用 `defaultFoodData` 填充 `foodData`,并对 `keyNutrients`、`nutritionTable` 做数组拷贝,保证不为空数组、能正常渲染 `.food-name-overlay` / `.nutrient-card` / `.nutrition-row`。
- 在 catch 里:先设置 `loadError`,再调用 `applyDefaultFoodData(false)`(不再清空 `loadError`),并保留原有 `showToast('数据加载失败')`。
- 无入参时仍走默认数据,此时调用 `applyDefaultFoodData()` 会清空 `loadError`。
4. **「当前数据来自缓存」提示**
- 当 `loadError` 有值时,在页面顶部展示提示条:「当前数据来自缓存,可能不是最新」,并增加 `.cache-notice` 样式。
5. **默认数据下的展示**
- `defaultFoodData` 已包含完整的 `name`、`category`、`safetyTag`、`image`、`keyNutrients`、`nutritionTable`(均为非空数组),应用 `applyDefaultFoodData` 后,`.food-name-overlay`、`.nutrient-card`、`.nutrition-row` 在默认数据下也能正常渲染。
6. **样式**
- 为 `.nutrition-dot` 增加 `&.high`,避免后端返回 `level: 'high'` 时缺样式。
---
## 3. 行为小结
| 场景 | 行为 |
|----------------|------|
| 列表点击条目 | 传 `item.id`,详情接口用 id 请求,正常展示该食物。 |
| 接口失败/异常 | 设置 `loadError`、用 `defaultFoodData` 填页面、Toast「数据加载失败」、顶部显示「当前数据来自缓存可能不是最新」名称/营养成分等仍可正常显示。 |
| 无 id/name 入参 | 直接使用默认数据,不显示缓存提示。 |
按当前逻辑,从食物百科点进详情应能正常看到名称和营养成分;若接口仍失败,会显示默认示例数据并提示「当前数据来自缓存,可能不是最新」,同时控制台和 `loadError` 可用来排查具体错误。

View File

@@ -0,0 +1,46 @@
BUG-005 已按你的要求修好,修改总结如下。
---
## 1. 前端 `msh_single_uniapp/pages/tool/ai-nutritionist.vue`
**已做修改:**
- **文本/多模态统一走 KieAI Gemini**
逻辑本来就是:`sendToAI` 里对 `text` 和 `multimodal` 都调用 `api.kieaiGeminiChat({ messages, stream: false })`,请求体为 `{ messages: [{ role: 'user', content: 用户输入或图+文 }], stream: false }`,未改。
- **成功时只展示接口返回内容**
- 回复**仅**从 `response.data.choices[0].message.content` 取值并展示。
- 已改为显式使用 `msgObj.content`(不再用 `choice.message.text` 等),并在注释中写明“仅使用接口返回的 content禁止固定话术”。
- **删除固定话术 `getAIResponse`**
- 已移除整个 `getAIResponse(question)` 方法(原关键词匹配的固定回复)。
- 成功时不再使用任何固定话术,只展示接口返回内容。
- **未动 UI**
仅改数据流与回复来源,布局和样式未改。
---
## 2. 前端 `msh_single_uniapp/api/models-api.js`
- **未改**
`kieaiGeminiChat` 已正确:
- POST `/api/front/kieai/gemini/chat`
- 请求体为 `{ messages, stream }`(你要求的 `{ messages: [{ role: 'user', content: 用户输入 }], stream: false }` 已满足)。
---
## 3. 后端 `msh_crmeb_22`ToolKieAIServiceImpl / buildGeminiRequestBody
- **未改**
已确认:
- `buildGeminiRequestBody` 完全用 `request.getMessages()` 透传,没有硬编码 prompt。
- `geminiChat` 只是用该 body 调 KieAI无额外固定内容。
因此后端无需修改。
---
当前数据流为:**用户输入 → POST /api/front/kieai/gemini/chatbody: messages + stream: false→ 前端从 `data.choices[0].message.content` 取回复并展示**,且成功时不再使用任何固定话术。

View File

@@ -0,0 +1,45 @@
BUG-005 相关修改已按你的要求做完,结论与改动如下。
---
## 1. 问题原因
- **前端**:创建对话后从 `response.data.chat` 取 `conversation_id`、`id`,但后端返回的是 `CozeBaseResponse.success(resp)``data` 就是 Coze 的 CreateChatResp没有嵌套的 `chat`,且多为 camelCase如 `conversationID`)。因此取不到会话/聊天 ID轮询失败请求在 catch 里走本地兜底 `getAIResponse(content)`,看起来就像“固定一段话”。
- **后端**Coze 实现里没有发现硬编码占位词,用户内容来自 `additionalMessages[0].content`。已做的修改是“无用户消息时不发请求”的防护。
---
## 2. 前端修改(`msh_single_uniapp/pages/tool/ai-nutritionist.vue`
- **创建对话响应解析**
- 兼容 `response.data.chat` 与 `response.data`。
- 兼容 `conversation_id` / `conversationID` / `conversationId` 和 `id`。
- 只有拿到 `conversationId` 和 `chatId` 后才调用 `pollChatStatus`,否则抛出明确错误。
- **轮询状态**
- 使用 `const chatObj = res.data.chat || res.data` 再取 `status`,避免因结构不同取不到状态。
- **消息列表**
- 支持 `res.data.messages` 或 `res.data` 为数组两种结构,用 `rawMessages` 统一处理,保证 AI 回复用 `role: 'ai', content: msg.content` 正确 push 到 `messageList`。
未改 UI只修了数据流`sendMessage` / `sendToAI` 里用的始终是 `inputText.trim()` → `content`,请求体已正确带用户输入。
---
## 3. 后端修改(`ToolCozeServiceImpl.java`
- **无用户消息时不调用 Coze**
- `chat()` 与 `chatStream()` 中:若 `buildMessages(request)` 为空,则不再调用 Coze直接返回错误如“请提供对话内容”避免“空请求”导致平台返回欢迎语等固定回复。
未改动 `ToolKieAIServiceImpl.java`:该文件是 Gemini 对话实现,营养师页走的是 Coze`/api/front/coze/chat`且其中没有硬编码占位词prompt 来自 `KieAIGeminiChatRequest.getMessages()`,因此按要求未修改。
---
## 4. 修改汇总
| 文件 | 修改内容 |
|------|----------|
| `ai-nutritionist.vue` | 修正 create-chat 响应解析data / data.chat、camel/snake轮询与消息列表兼容不同返回结构保证 AI 回复写入 `messageList`。 |
| `ToolCozeServiceImpl.java` | 在 `chat` / `chatStream` 中增加“无用户消息则不请求 Coze”的校验。 |
| `ToolKieAIServiceImpl.java` | 未修改(非本接口且无硬编码)。 |
按当前逻辑,用户输入会正确传到 Coze且能正确解析创建对话与轮询结果AI 的真实回复会显示在列表中,而不再被固定兜底文案覆盖。

View File

@@ -0,0 +1,19 @@
检查结果如下:
**1. `msh_single_uniapp/pages/tool_main/index.vue`**
- 第 66 行功能入口标题:`<view class="function-title">健康知识</view>` → 已是「健康知识」
- 第 126 行区块标题:`<text class="section-title">健康知识</text>` → 已是「健康知识」
**2. `msh_single_uniapp/pages/tool/nutrition-knowledge.vue`**
- 页面内没有设置导航栏标题的文案(没有「营养知识」或「健康知识」字符串)
- 该页导航栏标题由 `pages.json` 配置
**3. `pages.json`nutrition-knowledge 页配置)**
- 第 1018 行:`"navigationBarTitleText": "健康知识"` → 已是「健康知识」
因此,当前代码里:
- 主页「健康知识」区块和功能入口均为「健康知识」
- 营养知识页的导航栏标题在 `pages.json` 中也是「健康知识」
两处命名已统一为「健康知识」,**无需再改任何文件**。若你本地或某次构建里仍看到「营养知识」,请说明是在哪个页面、哪一行或哪个配置(例如是否改过 `pages.json`),我可以按那个位置再帮你对一下。

View File

@@ -0,0 +1,18 @@
BUG-006 已按「统一为健康知识」处理,修改如下。
**1. 确认的现有文案**
- **tool_main/index.vue**
- 下方区块标题第125行`健康知识`
- 四大入口里对应卡片标题第65行`营养知识`
- **营养知识页导航栏**
- 在 **pages.json** 里配置:`nutrition-knowledge` 的 `navigationBarTitleText` 为 `营养知识`
- **nutrition-knowledge.vue** 内没有设置导航栏标题的文案。
**2. 已做修改(仅文案,未改逻辑与样式)**
- **msh_single_uniapp/pages/tool_main/index.vue**
- 四大入口卡片标题:`营养知识` → `健康知识`第67行 `function-title`)。
- **msh_single_uniapp/pages.json**
- `nutrition-knowledge` 页的 `navigationBarTitleText``营养知识` → `健康知识`。
现在主页入口卡片、主页「健康知识」区块和营养知识页导航栏都统一为「健康知识」。`nutrition-knowledge.vue` 未改,因为导航栏标题是在 `pages.json` 里配置的。

View File

@@ -0,0 +1,37 @@
BUG-007 已按你的要求改完,修改总结如下:
### 1. **`loadKnowledgeList` 与 API 调用**
- **根因**:在 `rawList.map` 里用了 `formatKnowledgeTime(...)` 未加 `this.`,在 API 有数据时会报错(如 "formatKnowledgeTime is not a function"),进入 catch列表不会被赋值所以一直为空。
- **修改**:改为 `this.formatKnowledgeTime(...)`,确保在 map 里正确调用组件方法。
### 2. **请求参数 type**
- type 已与后端一致:`guide` / `article`(注释里也写明了)。
### 3. **API 失败时的处理**
- catch 里已有 `uni.showToast` 提示。
- 在 catch 中保证不把列表置为 undefined
`this.guideList = this.guideList ?? []`、`this.articleList = this.articleList ?? []`,出错时仍保持为数组。
### 4. **响应数据结构兼容**
- 增加对两种返回形式的兼容:
- `result.data.list` 为数组时用 `result.data.list`
- 若 `result.data` 本身是数组则用 `result.data`。
这样无论后端用分页对象还是直接数组,都能正确取到列表。
### 5. **onLoad 无 id 时的逻辑**
- 有 `id`:调用 `switchTab('articles')`,会触发 `loadKnowledgeList` 拉取科普文章列表。
- 无 `id`:显式保持 `currentTab = 'nutrients'`;用户切到「饮食指南」或「科普文章」时由 `switchTab` 触发 `loadKnowledgeList`,与需求一致。
### 6. **详情页跳转 `goToDetail`**
- 已有「无 id 不跳转」的逻辑,本次只做了小加固:
- 若 `!item` 也先提示「暂无详情」再 return
- 仅当 `knowledgeId` 或 `id` 存在且有效时才 `navigateTo`,否则一律提示「暂无详情」。
当前无新的 lint 报错。若后端库里没有 type 为 `guide` / `article` 的数据,列表仍会为空,但会正常显示「暂无饮食指南数据」/「暂无科普文章数据」;若详情页打开仍空白,需要再确认详情页是否应调 `tool/knowledge/detail/:id` 而不是文章接口。

View File

@@ -0,0 +1,26 @@
BUG-007 已按你的要求修好,修改总结如下:
### 1. **`loadKnowledgeList` 与 API**
- **请求参数**`type` 已按 tab 传 `guide` / `article`(与后端 `type` 一致),并显式用 `typeParam` 传参。
- **响应处理**:用 `Array.isArray(result.data.list)` 判断,只把合法数组赋给列表,否则用 `[]`,避免赋成 `undefined`。
- **列表赋值**:用 `currentTab === 'articles'` 分支给 `articleList` 赋值与「科普文章」tab 一致。
### 2. **错误处理**
- 在 `catch` 里增加 `uni.showToast`,文案为 `error.message` / `error.msg` 或「加载列表失败」。
- 在 `catch` 中**不**对 `guideList` / `articleList` 赋值,保持为初始的 `[]`,避免变成 `undefined`。
### 3. **onLoad 与无 id 时的列表**
- 无 `id` 时不再在 onLoad 里调用 `loadKnowledgeList()`(当前 tab 为 nutrients调用也会直接 return
- 列表只在「切换 tab」时加载`switchTab('guide')` / `switchTab('articles')` 内会调用 `loadKnowledgeList()`,从而加载对应 guide/article 列表。
### 4. **详情跳转 `goToDetail`**
- 先取 `id = item.knowledgeId ?? item.id`,再判断 `id === undefined || id === null || id === ''`。
- 无有效 id 时:`uni.showToast({ title: '暂无详情', icon: 'none' })` 并 return不跳转。
- 有 id 时才 `navigateTo` 到 `/pages/news/news_details/index?id=${id}`。
### 5. **列表展示与后端字段**
- 后端返回 `summary`、`viewCount`、`publishedAt` 等,模板用的是 `desc`、`time`、`views`、`icon`。
- 在赋值前对列表做了一层映射:`desc` ← summary、`time` ← 格式化 `publishedAt`/`createdAt`、`views` ← viewCount、`icon` 默认 `'📄'`,并增加了 `formatKnowledgeTime` 做日期格式化。
按当前逻辑:切换「饮食指南」或「科普文章」会请求 `type=guide` / `type=article` 并正确赋给 `guideList`/`articleList`;请求失败会 toast 且列表保持为 `[]`;只有存在 `knowledgeId` 或 `id` 时才跳转详情,否则提示「暂无详情」。

View File

@@ -0,0 +1,42 @@
BUG-008 已按你的要求修好,修改总结如下。
---
## 1. 逻辑与数据流(已确认)
- **`postData.nutritionStats`**:在 `data()` 里初始化为 `[]`;在 `formatPostData()` 里由 `buildNutritionStatsFromDetailData(data)` 得到并赋值。
- **加载入口**`loadPostData(id)` 调 `getCommunityDetail(id)`,再用返回的 `data` 做 `formatPostData(data)`。
- **后端社区详情**:返回 `nutritionDataJson`、`checkInRecordId`**没有** `nutritionStats` 数组。
`buildNutritionStatsFromDetailData` 已支持:
- `nutritionStats` / `nutrition_stats`
- `nutritionDataJson` / `nutrition_data_json`(解析出 calories、protein、potassium、phosphorus
- `dietaryData` / `mealData` / `dietary_data` / `meal_data`
- **打卡详情**:当详情里没有营养数据但有 `checkInRecordId` 时,会再调 `getCheckinDetail(checkInRecordId)`;返回里有 `actualEnergy`、`actualProtein`,已用 `buildNutritionStatsFromCheckinDetail` 转成 `[{label, value}, ...]` 并写回 `postData.nutritionStats`。
---
## 2. 实际修改内容
**1用打卡详情补全营养数据并等待完成**
- 原来:`fillNutritionStatsFromCheckin(data.checkInRecordId)` 未 `await`,页面先渲染,营养卡常为空。
- 现在:在「无营养数据 + 有 checkInRecordId」时改为 **`await this.fillNutritionStatsFromCheckin(data.checkInRecordId)`**,在加载结束前就拿到打卡详情并填好 `nutritionStats`,营养卡能稳定显示。
**2显示条件改为“只看数组长度”且做防御**
- 要求v-if 用 `nutritionStats.length > 0`,不依赖后端是否返回某字段。
- 实现:
- 增加计算属性 **`nutritionStatsLength`**
`(postData && postData.nutritionStats)` 且为数组时返回 `length`,否则为 `0`,避免 `nutritionStats` 未定义时报错。
- 营养统计卡片的 v-if 改为:**`v-if="nutritionStatsLength > 0"`**。
这样显示只依赖「当前页面的 nutritionStats 数组是否有项」,与后端字段名无关。
---
## 3. 未改动的部分(已符合需求)
- 后端无 `nutritionStats` 时,已通过「打卡详情」的 `actualEnergy`、`actualProtein` 等计算并填充 `nutritionStats`(格式 `[{label:'蛋白质', value:'56g'}, ...]`),无需再改。
- 后端有 `nutritionDataJson` 等不同命名时,`buildNutritionStatsFromDetailData` 里已有多种字段名和 `dietaryData`/`mealData` 的兼容,无需额外映射修改。
当前逻辑是:先尝试从详情/`nutritionDataJson`/饮食数据得到 `nutritionStats`;若仍为空且存在 `checkInRecordId`,则 **await** 拉取打卡详情并据此填充;展示上只用 **`nutritionStatsLength > 0`** 控制营养卡显示。

View File

@@ -0,0 +1,39 @@
BUG-008 已按你的要求修好,修改总结如下。
### 1. 阅读结果简述
- **postData.nutritionStats**:在 `data()` 里初始化为 `[]`,在 `formatPostData` 里只从 `data.nutritionDataJson` 解析并赋值;若后端没给或字段名不同,就会一直是空数组。
- **加载逻辑**`loadPostData` 调 `getCommunityDetail(id)`,用返回的 `data` 调用 `formatPostData(data)`。后端确实会返回 `nutritionDataJson`,但可能为空或字段名不一致(如 snake_case
### 2. 已做修改
**1统一从详情数据里构建营养统计**
- 新增 **`buildNutritionStatsFromDetailData(data)`**,按优先级从接口数据里生成 `nutritionStats`
- 若存在 **`nutritionStats` / `nutrition_stats`** 数组,直接规范成 `[{label, value}, ...]` 使用。
- 否则解析 **`nutritionDataJson` / `nutrition_data_json`**,按原有逻辑生成热量、蛋白质、钾、磷四项。
- 否则从 **`dietaryData` / `mealData` / `dietary_data` / `meal_data`** 等对象里取 calories/energy、protein、potassium、phosphorus及常见别名生成同样四项。
**2后端没有营养数据时用打卡数据补充**
- 新增 **`buildNutritionStatsFromCheckinDetail(detail)`**:根据打卡详情里的 `actualEnergy`、`actualProtein` 等生成 `[{label, value}, ...]`,钾/磷无则显示 `-`。
- 新增 **`fillNutritionStatsFromCheckin(checkInRecordId)`**:请求 `getCheckinDetail(checkInRecordId)`,用上面方法得到 `nutritionStats` 并赋给 `postData.nutritionStats`。
- 在 **`loadPostData`** 里,在 `formatPostData(data)` 之后:若当前 **`postData.nutritionStats.length === 0`** 且 **`data.checkInRecordId`** 存在,则调用 **`fillNutritionStatsFromCheckin(data.checkInRecordId)`**,用打卡数据补全营养统计(不阻塞评论、推荐等加载)。
**3展示条件只依赖数组长度**
- 营养统计卡片的 **v-if** 已改为只依赖是否有数据:
- 原:`v-if="postData.nutritionStats && postData.nutritionStats.length > 0"`
- 现:`v-if="postData.nutritionStats.length > 0"`
`postData.nutritionStats` 初始化为 `[]`,不会为 undefined。
**4接口引用**
- 在 `@/api/tool.js` 的 import 中增加了 **`getCheckinDetail`**,用于在无详情营养数据时拉取打卡详情并计算营养统计。
效果总结:
- 后端有 `nutritionDataJson` 或其它营养相关字段(含不同命名)时,会正确解析并显示。
- 后端没有营养数据但有关联打卡记录时,会用打卡详情中的热量、蛋白质等补全并显示。
- 卡片是否展示只由 **`postData.nutritionStats.length > 0`** 决定,不再依赖“后端某字段是否存在”。

View File

@@ -0,0 +1,24 @@
已按 BUG-009 要求处理,修改总结如下。
**1. Template 中的 Tab 与类型标签**
- **Tab 文案**:四个 Tab 的展示文字已是中文(推荐、最新、关注、热门),无需改。
- **Tab 内部值**`currentTab` 仍为 `recommend` / `latest` / `follow` / `hot`,用于 `switchTab` 和接口参数 `tab: this.currentTab`。按要求不改动接口逻辑,因此这些值保持英文,仅界面显示为中文。
- **帖子类型标签**`.type-tag` 和 `.meal-tag` 的文案都来自 `getMealTypeLabel(item.mealType)`,没有在模板里写死英文。
**2. 数据中的类型字段与 label 映射**
- `getMealTypeLabel` 中已有英文 → 中文映射breakfast→早餐、lunch→午餐、dinner→晚餐、snack→加餐、share→分享、checkin→打卡。
- 原先在未知类型时会 `return mealType`,可能把后端英文直接展示出来。
- **修改**:未知类型时的 fallback 从 `mealType` 改为 `'分享'`,保证所有展示的 label 都是中文。
**具体改动(仅一处)**
- 在 `getMealTypeLabel` 中,将
`return map[lower] != null ? map[lower] : mealType`
改为
`return map[lower] != null ? map[lower] : '分享'`
这样任意未在 map 中的 `mealType`(包括英文或其它值)在界面上都会显示为「分享」,不会出现英文。
路由与接口调用未做任何修改。

View File

@@ -0,0 +1,20 @@
BUG-009 已按你的要求改完,修改总结如下:
## 1. Template 检查结果
- **Tab 文案**:四个 Tab 的展示文字早就是中文(推荐、最新、关注、热门),未改。
- **Tab 内部值**`currentTab` 仍为 `recommend` / `latest` / `follow` / `hot`,用于 `switchTab()` 和接口参数 `tab`,按「不改动接口调用等逻辑」保留。
- **帖子类型标签**:两处原先直接显示 `{{ item.mealType }}`,若接口返回英文(如 `breakfast`)会显示英文,已改为通过映射显示中文。
## 2. 具体修改
1. **类型标签展示改为用“中文 label”**
- 有图:`<view class="meal-tag">{{ getMealTypeLabel(item.mealType) }}</view>`
- 无图:`<view class="type-tag">{{ getMealTypeLabel(item.mealType) }}</view>`
2. **新增 `getMealTypeLabel(mealType)` 方法**
- 英文 → 中文映射:`breakfast`→早餐、`lunch`→午餐、`dinner`→晚餐、`snack`→加餐、`share`→分享、`checkin`→打卡。
- 若 `mealType` 已是中文或未知,则原样返回;空则返回「分享」。
这样 Tab 展示仍是中文,帖子类型在界面上也统一为中文;`currentTab`、接口入参和请求逻辑均未改动。

View File

@@ -0,0 +1,8 @@
═══════════════════════════════════════════════
[2026-03-02 16:43:17] ▶ 阶段 0 — 基线确认(运行回归测试,记录当前失败情况)
═══════════════════════════════════════════════
Running 10 tests using 1 worker
✘ 1 [mobile-chrome] tests/e2e/bug-regression.spec.ts:114:5 TC-B01a 打卡前积分无提前跳变 (31.5s)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff