218 lines
9.1 KiB
Markdown
218 lines
9.1 KiB
Markdown
# chat 页面数据来源排查
|
||
|
||
> 排查日期:2026-03-18
|
||
> 页面路径:pages/chat/chat
|
||
|
||
## 概览
|
||
|
||
| 分类 | 数量 | 说明 |
|
||
|------|------|------|
|
||
| A. Mock 数据 | 4 | 来自 `mock-data.ts` 或页面内联 mock,联调时需替换为 API |
|
||
| B. 硬编码数据 | 3 | 直接写死在 data 或方法中的值 |
|
||
| C. 已对接 API | 0 | 当前页面无任何真实 API 调用 |
|
||
| D. 前端计算/派生数据 | 6 | 由工具函数或页面逻辑计算得出 |
|
||
| E. 路由参数 | 1 | 从 `onLoad(options)` 获取 |
|
||
| F. WXML 硬编码文案 | 8 | 模板中直接写死的中文文案 |
|
||
|
||
---
|
||
|
||
## 一、Mock 数据
|
||
|
||
### A1. `mockChatMessages` — 历史消息列表
|
||
|
||
| 项目 | 内容 |
|
||
|------|------|
|
||
| 来源 | `import { mockChatMessages } from '../../utils/mock-data'` |
|
||
| 类型 | `ChatMessage[]` |
|
||
| 使用位置 | `loadMessages()` → `enrichMessages(mockChatMessages)` → `this.setData({ messages })` |
|
||
| 涉及字段 | `id`, `role`, `content`, `timestamp`, `referenceCard`(含 `type`, `title`, `summary`, `data`) |
|
||
| 联调替换 | 需替换为 **AI 对话历史 API**(如 `GET /api/chat/messages?customerId=xxx`),返回分页消息列表 |
|
||
| 当前 mock 条数 | 6 条(`msg-001` ~ `msg-006`) |
|
||
|
||
### A2. `mockAIReplies` — AI 回复模板(页面内联 mock)
|
||
|
||
| 项目 | 内容 |
|
||
|------|------|
|
||
| 来源 | `chat.ts` 顶部常量 `const mockAIReplies = [...]` |
|
||
| 类型 | `string[]`(3 条固定回复文案) |
|
||
| 使用位置 | `triggerAIReply()` → `mockAIReplies[this._msgCounter % mockAIReplies.length]` |
|
||
| 联调替换 | 需替换为 **AI 对话流式接口**(如 `POST /api/chat/send`,SSE/WebSocket 流式返回) |
|
||
| 当前值 | `['根据数据分析,这位客户近期消费频次有所下降...', '好的,我已经记录了你的需求...', '这位客户偏好中式台球...']` |
|
||
|
||
### A3. `simulateStreamOutput` — 模拟流式输出
|
||
|
||
| 项目 | 内容 |
|
||
|------|------|
|
||
| 来源 | `import { simulateStreamOutput } from '../../utils/chat'` |
|
||
| 类型 | 工具函数,逐字 `setTimeout(tick, 50)` 模拟打字效果 |
|
||
| 使用位置 | `triggerAIReply()` 中调用,将 mock 回复逐字输出 |
|
||
| 联调替换 | 替换为真实 SSE/WebSocket 流式读取逻辑,`callback` 接口可复用 |
|
||
|
||
### A4. `referenceCard` — 页面顶部引用卡片(条件构造)
|
||
|
||
| 项目 | 内容 |
|
||
|------|------|
|
||
| 来源 | `loadMessages()` 中根据 `customerId` 内联构造 |
|
||
| 代码 | `{ title: '客户详情', summary: \`正在查看客户 ${customerId} 的相关信息\` }` |
|
||
| 联调替换 | 需从 **客户详情 API** 获取客户名称等信息填充,或由跳转页面通过路由参数传入 |
|
||
|
||
---
|
||
|
||
## 二、硬编码数据
|
||
|
||
### B1. `statusBarHeight` 回退值
|
||
|
||
| 项目 | 内容 |
|
||
|------|------|
|
||
| 字段 | `statusBarHeight` |
|
||
| 位置 | `onLoad()` → `sysInfo.statusBarHeight \|\| 44` |
|
||
| 当前值 | `44`(回退默认值) |
|
||
| 应获取自 | `wx.getWindowInfo().statusBarHeight`(已获取,44 仅为兜底) |
|
||
| 风险等级 | **低** — 兜底值合理,不同机型差异可忽略 |
|
||
|
||
### B2. `loadMessages` 延迟时间
|
||
|
||
| 项目 | 内容 |
|
||
|------|------|
|
||
| 字段 | `setTimeout(() => { ... }, 500)` |
|
||
| 位置 | `loadMessages()` 方法 |
|
||
| 当前值 | `500`ms 模拟网络延迟 |
|
||
| 联调处理 | 替换为真实 API 调用后移除 `setTimeout`,改用 API 响应回调 |
|
||
| 风险等级 | **中** — 联调时必须移除,否则造成不必要的 500ms 延迟 |
|
||
|
||
### B3. `triggerAIReply` 延迟时间
|
||
|
||
| 项目 | 内容 |
|
||
|------|------|
|
||
| 字段 | `setTimeout(() => { this.triggerAIReply() }, 300)` |
|
||
| 位置 | `onSendMessage()` 末尾 |
|
||
| 当前值 | `300`ms 延迟触发 AI 回复 |
|
||
| 联调处理 | 替换为发送消息 API 成功回调后触发流式接收 |
|
||
| 风险等级 | **中** — 联调时需重构为 API 驱动的流程 |
|
||
|
||
---
|
||
|
||
## 三、已对接 API
|
||
|
||
**无。** 当前页面 0 个真实 API 调用。
|
||
|
||
> 注:项目中已存在 `utils/request.ts`(封装了 `wx.request`),联调时可直接使用。
|
||
|
||
---
|
||
|
||
## 四、前端计算/派生数据
|
||
|
||
### D1. `enrichMessages()` 派生字段
|
||
|
||
对每条消息计算以下展示字段:
|
||
|
||
| 派生字段 | 计算逻辑 | 来源函数 |
|
||
|----------|----------|----------|
|
||
| `timeLabel` | 相对时间文案(刚刚/N分钟前/MM-DD) | `formatRelativeTime(m.timestamp)` |
|
||
| `imTimeLabel` | IM 时间格式(HH:mm / MM-DD HH:mm) | `formatIMTime(m.timestamp)` |
|
||
| `showTimeDivider` | 是否显示时间分割线(首条必显示,间隔≥5分钟显示) | `shouldShowTimeDivider(prev, curr)` |
|
||
| `referenceCard.dataList` | 将 `Record<string,string>` 转为 `{key,value}[]` 供 `wx:for` | `toDataList(m.referenceCard.data)` |
|
||
|
||
### D2. 用户发送消息时的派生字段
|
||
|
||
| 派生字段 | 计算逻辑 |
|
||
|----------|----------|
|
||
| `userMsg.timeLabel` | 固定为 `'刚刚'` |
|
||
| `userMsg.imTimeLabel` | `formatIMTime(new Date().toISOString())` |
|
||
| `userMsg.showTimeDivider` | `shouldShowTimeDivider(prevTs, now)` |
|
||
| `userMsg.id` | `msg-user-${this._msgCounter}`(自增计数器) |
|
||
| `userMsg.timestamp` | `new Date().toISOString()`(客户端当前时间) |
|
||
|
||
### D3. AI 回复消息的派生字段
|
||
|
||
同 D2 逻辑,`id` 格式为 `msg-ai-${this._msgCounter}`。
|
||
|
||
### D4. `pageState` 状态机
|
||
|
||
| 状态值 | 触发条件 |
|
||
|--------|----------|
|
||
| `'loading'` | `loadMessages()` 开始时 |
|
||
| `'empty'` | 消息列表为空且无引用卡片 |
|
||
| `'normal'` | 消息加载成功且有内容 |
|
||
| `'error'` | `loadMessages()` 捕获异常 |
|
||
|
||
### D5. `isStreaming` / `streamingContent`
|
||
|
||
| 字段 | 说明 |
|
||
|------|------|
|
||
| `isStreaming` | AI 流式回复进行中标志,控制输入框禁用和打字指示器 |
|
||
| `streamingContent` | 流式输出的当前内容片段 |
|
||
|
||
### D6. `scrollToId`
|
||
|
||
滚动锚点,通过 `scrollToBottom()` 方法设置为 `'scroll-bottom'`,驱动 `scroll-view` 滚动到底部。
|
||
|
||
---
|
||
|
||
## 五、路由参数
|
||
|
||
### E1. `customerId`
|
||
|
||
| 项目 | 内容 |
|
||
|------|------|
|
||
| 获取方式 | `onLoad(options)` → `options?.customerId \|\| ''` |
|
||
| 用途 | 1. 传入 `loadMessages(customerId)` 加载对应客户的对话;2. 决定是否显示页面顶部引用卡片 |
|
||
| 当前状态 | 仅用于 mock 逻辑判断,未传入任何 API |
|
||
| 联调需求 | 作为 API 请求参数传入消息列表接口 |
|
||
|
||
---
|
||
|
||
## 六、WXML 硬编码文案
|
||
|
||
| 编号 | 文案 | 位置 | 说明 |
|
||
|------|------|------|------|
|
||
| F1 | `加载中...` | 加载态 `g-toast-loading-text` | 通用加载文案,可保留 |
|
||
| F2 | `加载失败,请重试` | 错误态 `error-text` | 通用错误文案,可保留 |
|
||
| F3 | `重新加载` | 错误态 `retry-btn-text` | 按钮文案,可保留 |
|
||
| F4 | `引用内容` | 引用卡片 `reference-tag` | 标签文案,可保留 |
|
||
| F5 | `你好,我是 AI 助手` | 空对话提示 `empty-text` | 欢迎语,建议后续从配置获取 |
|
||
| F6 | `有什么可以帮你的?` | 空对话提示 `empty-sub` | 副标题,建议后续从配置获取 |
|
||
| F7 | `输入消息...` | 输入框 `placeholder` | 占位文案,可保留 |
|
||
| F8 | `AI 助手` | `chat.json` → `navigationBarTitleText` | 导航栏标题,可保留 |
|
||
|
||
---
|
||
|
||
## 七、联调 TODO
|
||
|
||
### 优先级 P0(阻塞联调)
|
||
|
||
- [ ] **替换消息列表加载**:`loadMessages()` 中 `mockChatMessages` → 调用 `GET /api/chat/messages` 接口
|
||
- 入参:`customerId`(可选)、分页参数
|
||
- 出参:`ChatMessage[]`
|
||
- 移除 `setTimeout(..., 500)` 模拟延迟
|
||
|
||
- [ ] **替换 AI 回复流程**:`triggerAIReply()` 中 `mockAIReplies` + `simulateStreamOutput` → 调用 `POST /api/chat/send` 流式接口
|
||
- 入参:`content`(用户消息)、`customerId`、`conversationId`
|
||
- 出参:SSE/WebSocket 流式文本
|
||
- 保留 `isStreaming` 状态控制和逐字渲染逻辑
|
||
|
||
- [ ] **替换引用卡片数据**:`loadMessages()` 中内联构造的 `referenceCard` → 从客户详情 API 或路由参数获取真实客户信息
|
||
|
||
### 优先级 P1(联调后优化)
|
||
|
||
- [ ] **消息发送 API**:`onSendMessage()` 中用户消息需先通过 API 持久化,再触发 AI 回复
|
||
- [ ] **对话会话管理**:当前无 `conversationId` 概念,需增加会话创建/恢复逻辑
|
||
- [ ] **消息 ID 生成**:当前使用前端自增计数器 `_msgCounter`,联调后应使用服务端返回的 ID
|
||
|
||
### 优先级 P2(体验优化)
|
||
|
||
- [ ] **错误重试**:`onRetry()` 当前仅重新调用 `loadMessages()`,需增加 toast 提示
|
||
- [ ] **空对话欢迎语**:F5/F6 硬编码文案考虑从后端配置获取
|
||
- [ ] **移除 `simulateStreamOutput`**:联调完成后,`utils/chat.ts` 中的模拟函数可删除(保留 `simulateStreamOutputSync` 用于测试)
|
||
- [ ] **移除 `mockAIReplies`**:页面内联 mock 常量清理
|
||
- [ ] **移除 `mock-data.ts` 中 `mockChatMessages` 引用**:确认其他页面不再使用后,清理 `ChatMessage` 相关 mock
|
||
|
||
### 依赖确认
|
||
|
||
| 依赖项 | 状态 | 说明 |
|
||
|--------|------|------|
|
||
| `utils/request.ts` | ✅ 已存在 | 封装了 `wx.request`,可直接使用 |
|
||
| AI 对话 API(后端) | ❌ 待确认 | 需确认接口设计:REST vs WebSocket、流式协议 |
|
||
| 客户详情 API | ❌ 待确认 | 引用卡片需要客户基本信息 |
|
||
| 对话会话 API | ❌ 待确认 | 会话创建、历史会话列表 |
|