feat: batch update - gift card breakdown spec, backend APIs, miniprogram pages, ETL finance recharge, docs & migrations

This commit is contained in:
Neo
2026-03-20 01:43:48 +08:00
parent 075caf067f
commit 79f9a0e1da
437 changed files with 118603 additions and 976 deletions

View File

@@ -0,0 +1,217 @@
# 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 | ❌ 待确认 | 会话创建、历史会话列表 |