Files
msh-system/客户反馈分析报告.md
msh-agent b164d8ba11 feat(ai-chat): 新增豆包API + AI模型配置项支持动态切换
- 后端新增豆包(火山引擎Ark)API集成:DoubaoController、ToolDoubaoServiceImpl,
  使用OkHttp3 SSE流式对话,兼容OpenAI Chat Completions格式
- 新增DoubaoConfig配置类,读取doubao.api.*配置
- 在eb_system_config表新增ai_chat_model配置项,支持doubao/coze/gemini三种模型切换
- 新增GET /api/front/doubao/ai-model-config接口供前端读取当前模型配置
- 前端ai-nutritionist.vue的sendToAI按系统配置分发到_sendViaDoubao/_sendViaCoze/_sendViaGemini
- 前端models-api.js新增doubaoChatStream/doubaoChat/getAiModelConfig函数
- 附带豆包API测试脚本和数据库初始化SQL

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 18:03:21 +08:00

323 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 测试问题与优化建议 — 修改解决方案
**分析日期:** 2026年4月11日
**项目:** msh_single_uniappUniApp 小程序)
**涉及页面:** calculator-result / ai-nutritionist / food-encyclopedia
---
## 一、总览:需处理项 vs 暂不处理项
| # | 问题 | 决策 | 涉及文件 |
|---|------|------|----------|
| 1 | 食物份数 → 克数 | **采纳** | `pages/tool/calculator-result.vue` |
| 2 | AI对话交互按钮 | **部分采纳** | `pages/tool/ai-nutritionist.vue` |
| 3 | AI回复速度慢 | **已优化切换Coze流式API** | `ai-nutritionist.vue` + `models-api.js` |
| 4 | 食物百科图片缺失 | **不处理**(后台可改) | — |
| 5 | 百科卡片误导点击 | **采纳**(跳转详情页) | `pages/tool/food-encyclopedia.vue` |
| 6 | 角色权限与分销 | **暂不处理**(后台人工配置) | — |
| 7 | 百科点击报错 Bug | **必须修复** | `pages/tool/food-encyclopedia.vue` |
---
## 二、需修改的问题及代码级解决方案
---
### 问题1食物份数建议改为克数
**页面:** `pages/tool/calculator-result.vue`
**现状分析:**
当前"食物份数建议"卡片约第94-113行食物列表渲染逻辑为
```html
<text class="food-portion">{{ item.portion }} 份</text>
```
数据结构 `foodList` 中每个 item 包含 `{ number, name, portion }``portion` 为份数值。
**修改方案:**
1. **后端接口改造**(推荐):`getCalculatorResult` 接口返回数据中增加 `gram` 字段,或将 `portion` 的含义改为克数。
2. **前端模板修改**`calculator-result.vue` 约第94-113行
```html
<!-- 修改前 -->
<text class="card-title">食物份数建议</text>
...
<text class="food-portion">{{ item.portion }} 份</text>
<!-- 修改后 -->
<text class="card-title">每日食物建议</text>
...
<text class="food-portion">{{ item.gram || item.portion }} 克</text>
```
3. **数据层适配**`applyResult` 方法约第320-350行解析接口返回时确保 `foodList` 中的 item 携带 `gram` 字段。如后端暂未改造,可前端用换算公式临时过渡:
```javascript
// 在 applyResult 方法中
this.foodList = (res.data.foodList || []).map(item => ({
...item,
gram: item.gram || Math.round(item.portion * item.gramPerServing) || item.portion
}))
```
**工作量估计:** 前端 0.5天后端如需改接口0.5天
---
### 问题2AI营养师对话页面新增交互按钮
**页面:** `pages/tool/ai-nutritionist.vue`
**现状分析:**
当前每条 AI 消息仅有 **1个操作按钮**TTS 语音朗读约第84-90行。消息数据结构为 `{ role, content, type, loading, streaming }`。AI 回复通过 `api.kieaiGeminiChat()` 调用(实际走 `/api/front/kieai/gemini/chat``stream: false` 非流式),整体响应一次性返回。
**部分采纳后的需求:** 新增"复制"、"重新生成"、"删除"按钮(语音朗读已有)。
**修改方案:**
在消息气泡下方的操作区域约第84-90行 TTS 按钮位置),扩展为按钮组:
```html
<!-- 修改前:仅 TTS 按钮 -->
<view class="tts-play-btn" v-if="msg.role === 'ai' && !msg.loading && !msg.streaming && msg.content && msg.type !== 'image'" @click="...">
<text>{{ ttsPlayingIndex === index ? '⏹' : '▶' }}</text>
</view>
<!-- 修改后:操作按钮组 -->
<view class="msg-actions" v-if="msg.role === 'ai' && !msg.loading && !msg.streaming && msg.content && msg.type !== 'image'">
<!-- 复制 -->
<view class="action-btn" @click="copyMessage(index)">
<text class="action-icon">📋</text>
</view>
<!-- 重新生成仅最后一条AI消息显示 -->
<view class="action-btn" v-if="isLastAiMessage(index)" @click="regenerateMessage(index)">
<text class="action-icon">🔄</text>
</view>
<!-- 语音朗读(已有) -->
<view class="action-btn" @click="ttsPlayingIndex === index ? stopTTS() : playTTS(index)">
<text class="action-icon">{{ ttsPlayingIndex === index ? '⏹' : '▶' }}</text>
</view>
<!-- 删除 -->
<view class="action-btn" @click="deleteMessage(index)">
<text class="action-icon">🗑️</text>
</view>
</view>
```
**新增 methods**
```javascript
// 复制消息
copyMessage(index) {
const msg = this.messageList[index]
uni.setClipboardData({
data: msg.content,
success: () => uni.showToast({ title: '已复制', icon: 'success' })
})
},
// 删除单条消息
deleteMessage(index) {
uni.showModal({
title: '提示',
content: '确定删除这条消息吗?',
success: (res) => {
if (res.confirm) {
this.messageList.splice(index, 1)
}
}
})
},
// 重新生成(重发上一条用户消息)
regenerateMessage(index) {
// 找到该AI消息对应的上一条用户消息
let userMsgIndex = index - 1
while (userMsgIndex >= 0 && this.messageList[userMsgIndex].role !== 'user') {
userMsgIndex--
}
if (userMsgIndex < 0) return
// 移除当前AI回复
this.messageList.splice(index, 1)
// 重新发送
this.sendToAI()
},
// 判断是否为最后一条AI消息
isLastAiMessage(index) {
for (let i = this.messageList.length - 1; i >= 0; i--) {
if (this.messageList[i].role === 'ai') return i === index
}
return false
}
```
**新增样式:**
```scss
.msg-actions {
display: flex;
gap: 16rpx;
margin-top: 8rpx;
justify-content: flex-start;
}
.action-btn {
width: 56rpx;
height: 56rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: #f4f5f7;
}
.action-icon {
font-size: 24rpx;
}
```
**工作量估计:** 1天
---
### 问题3AI回复速度慢已优化 — 切换至 Coze API
**优化前调用链路:**
```
小程序 → POST /api/front/kieai/gemini/chat/stream
→ Spring KieAIController (SseEmitter)
→ OkHttp3 异步 POST https://api.kie.ai/gemini-2.5-flash/v1/chat/completions
→ KieAI 代理网关 → Gemini 2.5 Flash 模型
```
**优化后调用链路:**
```
小程序 → POST /api/front/coze/chat/stream
→ Spring CozeController (SseEmitter, 120s timeout, 15s heartbeat)
→ Coze 官方 SDK (client.chat().stream())
→ https://api.coze.cn → Coze Bot (7591133240535449654)
```
**已完成的优化:**
1. **前端切换至 Coze 流式 API** `sendToAI()` 改为调用 `api.cozeChatStream()`,直接使用 Coze 的流式事件(`conversation.message.delta`),逐字渲染 AI 回复。首字可见时间大幅缩短。
2. **Coze 事件解析:** 正确解析 Coze SSE 事件格式 `{ event, conversation_id, chat_id, content, role, type }`,仅累积 `event=conversation.message.delta``type=answer` 的文本增量。
3. **多轮对话支持:** 自动保存 `conversation_id`后续消息在同一会话中进行Coze Bot 可获取完整上下文。
4. **消息格式适配:** 新增 `buildCozeMessages()` / `buildCozeRequest()` 方法,将用户输入(文本/图片/多模态)转换为 Coze 格式的 `additionalMessages`
5. **降级策略:** 流式失败时自动降级为非流式 `api.cozeChat()`,并通过 `pollCozeResult()` 轮询获取最终回复。
**Coze 相比 KieAI 代理的优势:**
- 去掉一层 KieAI 代理网关,减少一跳网络延迟
- Coze 后端已配置 `X-Accel-Buffering: no` + `Cache-Control: no-cache`SSE 不会被 nginx 缓冲
- Coze SDK 内置连接池管理,不再每次请求 new OkHttpClient
- 内置 15s heartbeat 防止连接超时断开
- 支持通过 `conversation_id` 进行多轮上下文对话
---
### 问题4食物百科图片缺失
**决策:不处理。** 图片数据通过商城 PC 管理后台维护,属运营侧工作。
---
### 问题5百科卡片点击 → 跳转食物详情页
**页面:** `pages/tool/food-encyclopedia.vue`
**现状分析:**
当前卡片已绑定了 `@click="goToFoodDetail(item)"`约第103行`goToFoodDetail` 方法约第1012-1030行会尝试提取 item 的 id 并跳转到 `/pages/tool/food-detail`。项目中已存在 `pages/tool/food-detail.vue` 页面。
**问题:** 跳转逻辑本身已实现,但存在 Bug见问题7导致点击报错而非正常跳转用户看到的效果就是"点击无反应"或"点击后闪退"。
**修改方案:** 修复问题7的 Bug 后,卡片点击跳转详情页的功能即可正常工作。需确认:
1. `food-detail.vue` 详情页内容是否完整可用
2. `pages.json` 中是否已注册 `/pages/tool/food-detail` 路由
**工作量估计:** 与问题7合并处理0.5天
---
### 问题6角色权限与分销系统
**决策:暂不处理。** 临时方案为运营人员在后台手动为指定用户账号配置角色标签(医生/护士/普通用户)。
---
### 问题7Bug食物百科点击报错 TypeError
**页面:** `pages/tool/food-encyclopedia.vue`
**错误信息:**
```
TypeError: Cannot read property 'id' of undefined
at pickNumericIdStr (food-encyclopedia.vue:1015)
at VueComponent.goToFoodDetail (food-encyclopedia.vue:1022)
```
**根因分析:**
`goToFoodDetail(item)` 方法第1012行内部闭包 `pickNumericIdStr` 直接访问 `item.id`第1015行但未做空值校验。当 `filteredFoodList` 中的某个 item 为 `undefined``null` 时(可能因 API 返回脏数据、`normalizeFoodItem` 边界情况),点击即触发 TypeError。
**修复方案:**
`goToFoodDetail` 方法开头增加防御性校验约第1012行
```javascript
goToFoodDetail(item) {
// 防御性校验:避免 item 为空时报错
if (!item || typeof item !== 'object') {
console.warn('[food-encyclopedia] goToFoodDetail: item 为空,跳过跳转')
return
}
const pickNumericIdStr = () => {
const cands = [item.id, item.foodId, item.food_id, item.v2FoodId, item.v2_food_id, item.foodID]
// ... 原有逻辑不变
}
// ... 后续逻辑不变
}
```
同时建议加固 `filteredFoodList` 计算属性约第261行确保过滤掉无效项
```javascript
filteredFoodList() {
const list = (this.foodList || []).filter(item => item != null && typeof item === 'object' && item.name)
// ... 后续过滤逻辑不变
}
```
**工作量估计:** 0.5天(含测试验证)
---
## 三、执行优先级排序
| 优先级 | 问题 | 预估工时 | 负责方 |
|--------|------|----------|--------|
| **P0** | #7 百科点击报错 Bug | 0.5天 | 前端 |
| **P1** | #1 份数→克数 | 0.5-1天 | 前端+后端 |
| **P1** | #5 百科卡片跳转详情页 | 与#7合并 | 前端 |
| **P2** | #3 AI响应速度已切换Coze流式API | ✅ 已完成 | 前端 |
| **P2** | #2 AI对话新增操作按钮 | 1天 | 前端 |
| **—** | #4 百科图片 | 不处理 | 运营 |
| **—** | #6 角色权限 | 暂不处理 | 运营(后台手动) |
**总预估:** 前端约 3-4天后端约 2-3天
---
*基于 msh_single_uniapp 项目代码分析生成,所有代码引用均已核对源文件。*