9.8 KiB
9.8 KiB
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)
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 注释:
// 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' }) |