# 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 | ❌ 待确认 | 会话创建、历史会话列表 |
