Files
Neo-ZQYY/docs/miniprogram-dev/api-audit/customer-service-records.md

179 lines
9.8 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.
# customer-service-records 页面数据来源排查
> 排查日期2026-03-18
> 页面路径pages/customer-service-records/customer-service-records
## 概览
| 分类 | 数量 | 说明 |
|------|------|------|
| A. Mock 数据 | 5 | 来自 `mock-data.ts``mockCustomers` + `mockCustomerDetail` |
| B. 硬编码数据 | 8 | 页面 data 初始值、辅助函数内联常量 |
| C. 已对接 API | 0 | 全部数据均为 Mock无真实 API 调用 |
| D. 前端计算/派生 | 12 | 月份筛选、统计汇总、格式转换等 |
| E. 路由参数 | 1 | `customerId` / `id` |
| F. WXML 硬编码文案 | 10 | 状态提示、标签文字、按钮文案等 |
---
## 一、Mock 数据(来自 mock-data.ts
所有业务数据均通过 `loadData()` 中的 `setTimeout` 模拟异步获取,数据源为 `mock-data.ts`
| # | 字段 / 数据 | Mock 来源 | 说明 |
|---|------------|-----------|------|
| A1 | `mockCustomers` 列表 | `mock-data.ts → mockCustomers` | 用于按 `id` 查找客户基本信息(`id`, `name` |
| A2 | `mockCustomerDetail` | `mock-data.ts → mockCustomerDetail` | 客户详情,提供 `name``consumptionRecords` |
| A3 | `consumptionRecords` 数组 | `mockCustomerDetail.consumptionRecords` | 5 条消费记录,字段:`id`, `date`, `project`, `duration`, `amount`, `coachName` |
| A4 | `customerName` / `customerInitial` | 从 `mockCustomerDetail.name``mockCustomers[].name` 取值 | 客户姓名及首字 |
| A5 | `allRecords` | `detail.consumptionRecords``sortByTimestamp` 排序 | 全量消费记录(降序) |
### Mock 数据结构ConsumptionRecord
```typescript
interface ConsumptionRecord {
id: string // 如 'cr-001'
date: string // 如 '2026-03-05'
project: string // 如 '中式台球 1v1'、'会员充值'
duration: number // 分钟数,如 90
amount: number // 金额,如 380
coachName: string // 助教名,如 '王芳'
}
```
---
## 二、硬编码数据
| # | 字段 / 数据 | 位置 | 硬编码值 | 说明 |
|---|------------|------|----------|------|
| B1 | `customerPhone` | `data` 初始值 | `'139****5678'` | 脱敏手机号,应从 API 获取 |
| B2 | `customerPhoneFull` | `data` 初始值 | `'13900005678'` | 完整手机号,应从 API 获取 |
| B3 | `relationIndex` | `data` 初始值 | `'0.85'` | 关系指数,应从 API 获取 |
| B4 | `monthRelation` | `data` 初始值 | `'0.85'` | 月度关系指数,应从 API 获取WXML 中绑定展示) |
| B5 | `minYearMonth` | `data` 初始值 | `202601` | 数据起始年月,应从 API 返回的最早记录推算 |
| B6 | `maxYearMonth` | `data` 初始值 | `202602` | 数据截止年月,应取当前月份动态计算 |
| B7 | `currentYear` / `currentMonth` | `data` 初始值 | `2026` / `2` | 当前年月,应取系统时间动态计算 |
| B8 | `tables` 数组 | `getTableNo()` 方法 | `['A12号台', '3号台', 'VIP1号房', '5号台', 'VIP2号房', '8号台']` | 模拟台号,应从消费记录 API 返回 |
---
## 三、已对接 API
| # | 接口 | 状态 |
|---|------|------|
| — | — | **无。页面当前 0 个真实 API 调用。** |
`loadData()` 方法中有明确 TODO 注释:
```typescript
// TODO: 替换为真实 API 调用
```
---
## 四、前端计算/派生数据
| # | 字段 / 数据 | 计算逻辑 | 来源依赖 |
|---|------------|----------|----------|
| D1 | `customerInitial` | `name[0] \|\| '?'` | Mock → `detail.name` |
| D2 | `totalServiceCount` | `allRecords.length` | Mock → `consumptionRecords` |
| D3 | `monthLabel` | `` `${currentYear}年${currentMonth}月` `` | 硬编码 `currentYear` / `currentMonth` |
| D4 | `records`(当月记录) | `allRecords.filter(r => r.date.startsWith(monthPrefix))` 后 `.map()` 转换 | Mock → `allRecords` + 硬编码年月 |
| D5 | `monthCount` | `monthRecords.length + '次'` | D4 筛选结果 |
| D6 | `monthHours` | `(totalMinutes / 60).toFixed(1) + 'h'``totalMinutes = reduce(sum + r.duration)` | D4 筛选结果 |
| D7 | `canPrev` / `canNext` | `yearMonth > minYearMonth` / `yearMonth < maxYearMonth` | 硬编码边界 |
| D8 | `pageState` | 根据 `records.length === 0 && allRecords.length === 0` 判断 `'empty'` / `'normal'` | D4 + A5 |
| D9 | `ServiceRecord.table` | `getTableNo(r.id)` — 按 id 数字取模从硬编码数组选取 | 硬编码 B8 |
| D10 | `ServiceRecord.type` / `typeClass` | `getTypeLabel(r.project)` / `getTypeClass(r.project)` — 按 `project` 字符串 `includes` 匹配 | Mock → `project` 字段 |
| D11 | `ServiceRecord.duration` | `parseFloat((r.duration / 60).toFixed(1))`(充值记录为 0 | Mock → `duration`(分钟) |
| D12 | `ServiceRecord.date`(展示用) | 格式化为 `"X月X日 HH:MM - HH:MM"`,时间段由 `generateTimeRange()` 随机生成 | Mock → `date` + 随机数 |
### 辅助函数详情
| 函数 | 逻辑 | 数据来源 |
|------|------|----------|
| `generateTimeRange(durationMin)` | 随机起始小时 14-19计算结束时间 | 入参 `duration`Mock + `Math.random()` |
| `getTypeLabel(project)` | `includes('小组')→'小组课'`、`includes('1v1')→'基础课'`、`includes('充值')→'充值'`、`includes('斯诺克')→'斯诺克'`、默认 `'基础课'` | 硬编码映射规则 |
| `getTypeClass(project)` | `includes('充值')→'recharge'`、`includes('小组'/'斯诺克')→'vip'`、默认 `'basic'` | 硬编码映射规则 |
| `getTableNo(id)` | `id` 提取数字 → 取模 → 从 6 元素数组选取 | 硬编码台号数组 |
| `sortByTimestamp(list, field)` | 按指定字段降序排序(`utils/sort.ts` | 纯工具函数 |
### 固定为默认值的 ServiceRecord 字段
| 字段 | 固定值 | 说明 |
|------|--------|------|
| `durationRaw` | `0` | 折算前小时数,未实现 |
| `isEstimate` | `false` | 是否预估金额,未实现 |
| `drinks` | `''` | 商品/饮品描述,未实现 |
| `income` | `r.amount`(直接透传) | 到手金额 = 消费金额,未做提成计算 |
| `recordType` | `project.includes('充值') ? 'recharge' : 'course'` | 仅按项目名判断 |
---
## 五、路由参数
| # | 参数 | 取值逻辑 | 说明 |
|---|------|----------|------|
| E1 | `customerId` | `options?.customerId \|\| options?.id \|\| ''` | 从上级页面传入,支持两种参数名 |
---
## 六、WXML 硬编码文案
| # | 文案 | 位置 | 说明 |
|---|------|------|------|
| F1 | `"加载中..."` | 加载态 toast | 状态提示 |
| F2 | `"暂无服务记录"` | `<t-empty>` description | 空态提示 |
| F3 | `"加载失败,请点击重试"` | 错误态文案 | 错误提示 |
| F4 | `"重试"` | 错误态按钮 | 按钮文案 |
| F5 | `"服务 {{totalServiceCount}} 次"` | Banner 徽章 | 模板 + 动态数据 |
| F6 | `"查看"` / `"复制"` | 手机号操作按钮 | 根据 `phoneVisible` 切换 |
| F7 | `"本月服务"` / `"服务时长"` / `"关系指数"` | 月度统计标签 | 固定标签文案 |
| F8 | `"本月暂无服务记录"` | 当月无数据提示 | 空月提示 |
| F9 | `"— 已加载全部记录 —"` | 列表底部 | 底部提示 |
| F10 | `"手机号码已复制"` | `onCopyPhone()` Toast | JS 中的提示文案 |
---
## 七、引用组件
| 组件 | 路径 | 用途 |
|------|------|------|
| `service-record-card` | `/components/service-record-card/` | 服务记录卡片,接收 `ServiceRecord` 各字段 |
| `ai-float-button` | `/components/ai-float-button/` | AI 悬浮按钮,传入 `customerId` |
| `dev-fab` | `/components/dev-fab/` | 开发调试浮动按钮 |
| `t-loading` | TDesign | 加载动画 |
| `t-icon` | TDesign | 图标 |
| `t-empty` | TDesign | 空态组件 |
---
## 八、联调 TODO
### 需要对接的 API 清单
| 优先级 | API | 用途 | 替换目标 |
|--------|-----|------|----------|
| P0 | `GET /api/customers/{id}/service-records` | 获取客户服务记录列表(支持月份筛选、分页) | `loadData()` 中的 `mockCustomerDetail.consumptionRecords` |
| P0 | `GET /api/customers/{id}` | 获取客户基本信息(姓名、手机号、关系指数) | `mockCustomers.find()` + `mockCustomerDetail` + 硬编码手机号 |
| P1 | `GET /api/customers/{id}/monthly-stats` | 获取月度统计(服务次数、时长、关系指数) | 前端 `reduce` 计算的 `monthCount`、`monthHours` + 硬编码 `monthRelation` |
### 联调时需处理的改动点
| # | 改动点 | 当前状态 | 联调要求 |
|---|--------|----------|----------|
| T1 | 移除 `mock-data.ts` 导入 | `import { mockCustomerDetail, mockCustomers }` | 替换为 API 请求模块 |
| T2 | `loadData()` 改为真实请求 | `setTimeout` + Mock 查找 | `wx.request` 或封装的 HTTP 客户端 |
| T3 | 手机号从 API 获取 | 硬编码 `'139****5678'` / `'13900005678'` | API 返回脱敏 + 完整手机号(或按需请求完整号) |
| T4 | 关系指数从 API 获取 | 硬编码 `'0.85'` | API 返回 `relationIndex` / `monthRelation` |
| T5 | 台桌号从 API 获取 | `getTableNo()` 硬编码数组取模 | 消费记录 API 返回 `tableNo` 字段 |
| T6 | 时间段从 API 获取 | `generateTimeRange()` 随机生成 | 消费记录 API 返回 `startTime` / `endTime` |
| T7 | 年月边界动态计算 | 硬编码 `minYearMonth=202601` / `maxYearMonth=202602` | 从 API 返回的最早记录推算 `min``max` 取当前月 |
| T8 | `currentYear` / `currentMonth` 动态化 | 硬编码 `2026` / `2` | 取 `new Date()` 当前年月 |
| T9 | 分页加载 | `onReachBottom` 空实现 | 对接分页 API`page` / `pageSize` 参数) |
| T10 | `durationRaw` 字段 | 固定 `0` | API 返回折算前时长 |
| T11 | `drinks` 字段 | 固定 `''` | API 返回商品/饮品信息 |
| T12 | `isEstimate` 字段 | 固定 `false` | API 返回是否预估标记 |
| T13 | `income` 字段 | 直接透传 `amount` | API 返回助教到手金额(需提成计算) |
| T14 | 错误处理 | 无 `pageState='error'` 触发路径 | API 失败时 `setData({ pageState: 'error' })` |