# coach-detail 页面数据来源排查

> 排查日期：2026-03-18
> 页面路径：pages/coach-detail/coach-detail

## 概览

| 分类 | 数量 | 说明 |
|------|------|------|
| A. Mock 数据（页面内联） | 7 组 | 页面 .ts 文件顶部定义的大量 mock 常量 |
| A. Mock 数据（mock-data.ts） | 1 处 | 仅引用 `mockCoaches` 做 ID→名称匹配 |
| B. 硬编码数据 | 12 处 | perfCards 文案/sub、tierNodes、动画参数、avatar 路径等 |
| C. 已对接 API | 0 | 全部数据均为 mock，无真实 API 调用 |
| D. 前端计算/派生 | 8 处 | perfCards 组装、incomeTotal、perfPercent、进度条参数等 |
| E. 路由参数 | 1 个 | `options.id` → `coachId` |
| F. WXML 硬编码文案 | 22 处 | 各 section 标题、按钮文案、空态提示等 |

---

## 一、Mock 数据

### A1. 页面内联 Mock（coach-detail.ts 顶部常量）

| 变量名 | 类型 | 字段数 | 用途 | 联调替换目标 |
|--------|------|--------|------|-------------|
| `mockCoachDetail` | `CoachDetail` | 12 个顶层字段 | 助教基本信息 + 绩效 + 收入 + 备注 | `GET /api/xcx/coaches/:id` |
| `mockVisibleTasks` | `TaskItem[]` | 6 条 | 当前可见任务列表 | `GET /api/xcx/coaches/:id/tasks` |
| `mockHiddenTasks` | `TaskItem[]` | 3 条 | 折叠区任务列表 | 同上（分页/分组） |
| `mockAbandonedTasks` | `AbandonedTask[]` | 2 条 | 已放弃任务 | 同上（status=abandoned） |
| `mockTopCustomers` | `TopCustomer[]` | 20 条 | 客户关系 TOP20 | `GET /api/xcx/coaches/:id/top-customers` |
| `mockServiceRecords` | `ServiceRecord[]` | 4 条 | 近期服务明细 | `GET /api/xcx/coaches/:id/service-records` |
| `mockHistoryMonths` | `HistoryMonth[]` | 5 条 | 历史月度汇总 | `GET /api/xcx/coaches/:id/history` |

#### mockCoachDetail 字段明细

| 字段路径 | Mock 值 | 说明 |
|----------|---------|------|
| `id` | `'coach-001'` | 助教 ID |
| `name` | `'小燕'` | 助教姓名 |
| `avatar` | `'/assets/images/avatar-coach.png'` | 头像（硬编码路径） |
| `level` | `'星级'` | 助教等级 |
| `skills` | `['中🎱', '🎯斯诺克']` | 技能标签 |
| `workYears` | `3` | 工龄 |
| `customerCount` | `68` | 客户数 |
| `hireDate` | `'2023-03-15'` | 入职日期 |
| `performance.monthlyHours` | `87.5` | 本月定档业绩（小时） |
| `performance.monthlySalary` | `6950` | 本月工资（预估） |
| `performance.customerBalance` | `86200` | 客源储值余额 |
| `performance.tasksCompleted` | `38` | 本月任务完成数 |
| `performance.perfCurrent` | `80` | 绩效当前值 |
| `performance.perfTarget` | `100` | 绩效目标值 |
| `income.thisMonth[]` | 4 条 | 本月收入明细（基础课时费/激励课时费/充值提成/酒水提成） |
| `income.lastMonth[]` | 4 条 | 上月收入明细 |
| `notes[]` | 3 条 | 备注记录 |

#### mockVisibleTasks / mockHiddenTasks 字段明细

| 字段 | 说明 |
|------|------|
| `typeLabel` | 任务类型文案（高优先召回/优先召回/关系构建/客户回访） |
| `typeClass` | 样式类名 |
| `customerName` | 客户姓名 |
| `noteCount` | 备注数量 |
| `pinned` | 是否置顶 |
| `notes[]` | 备注列表（pinned/text/date） |

#### mockAbandonedTasks 字段明细

| 字段 | 说明 |
|------|------|
| `customerName` | 客户姓名 |
| `reason` | 放弃原因（客户拒绝/超时未响应） |

#### mockTopCustomers 字段明细（20 条）

| 字段 | 说明 |
|------|------|
| `id` | 客户 ID |
| `name` | 客户姓名 |
| `initial` | 姓氏首字（头像占位） |
| `avatarGradient` | 头像渐变色 key |
| `heartEmoji` | 爱心 emoji（💖/🧡/💛/💙） |
| `score` | 亲密度评分 |
| `scoreColor` | 评分颜色 key |
| `serviceCount` | 服务次数 |
| `balance` | 储值余额 |
| `consume` | 消费金额 |

#### mockServiceRecords 字段明细（4 条）

| 字段 | 说明 |
|------|------|
| `customerId` | 客户 ID（可选） |
| `customerName` | 客户姓名 |
| `initial` | 姓氏首字 |
| `avatarGradient` | 头像渐变色 key |
| `type` | 服务类型（基础课/激励课） |
| `typeClass` | 样式类名 |
| `table` | 台号 |
| `duration` | 时长 |
| `income` | 收入 |
| `date` | 日期 |
| `perfHours` | 定档绩效时长（可选） |

#### mockHistoryMonths 字段明细（5 条）

| 字段 | 说明 |
|------|------|
| `month` | 月份标签（本月/上月/4月/3月/2月） |
| `estimated` | 是否预估 |
| `customers` | 服务客户数 |
| `hours` | 业绩时长 |
| `salary` | 工资 |
| `callbackDone` | 回访完成数 |
| `recallDone` | 召回完成数 |

### A2. 外部 Mock 引用（mock-data.ts）

| 引用 | 用途 | 代码位置 |
|------|------|----------|
| `mockCoaches` | `loadData()` 中通过 `mockCoaches.find(c => c.id === id)` 匹配助教 ID，取 `id` 和 `name` 覆盖 mockCoachDetail | `coach-detail.ts` L197-198 |

> `mockCoaches` 来自 `utils/mock-data.ts`，类型为 `CoachCard[]`，含 3 条记录（coach-001/002/003）。

---

## 二、硬编码数据

| 位置 | 硬编码内容 | 说明 |
|------|-----------|------|
| `loadData()` perfCards[0].sub | `'折算前 89.0h'` | 应由 API 返回折算前时长 |
| `loadData()` perfCards[1].sub | `'含预估部分'` | 固定文案 |
| `loadData()` perfCards[2].sub | `` `${detail.customerCount}位客户合计` `` | 模板拼接，customerCount 来自 mock |
| `loadData()` perfCards[3].sub | `'覆盖 22 位客户'` | 应由 API 返回覆盖客户数 |
| `loadData()` tierNodes | `[0, 100, 130, 160, 190, 220]` | 绩效档位节点，注释标注"Mock，实际由接口返回" |
| `loadData()` maxHours | `220` | 进度条最大值，应与 tierNodes 联动 |
| `data.taskStats` | `{ recall: 24, callback: 14 }` | 任务统计，应由 API 返回 |
| 动画常量 SHINE_SPEED | `70` | 进度条动画速度 |
| 动画常量 SPARK_DELAY_MS | `-150` | 火花延迟 |
| 动画常量 SPARK_DUR_MS | `1400` | 火花持续时间 |
| 动画常量 NEXT_LOOP_DELAY_MS | `400` | 下一轮延迟 |
| 动画常量 SHINE_WIDTH_RPX / TRACK_WIDTH_RPX | `120 / 634` | 进度条尺寸参数（UI 常量，非业务数据） |

---

## 三、已对接 API

**无。** 当前页面 0 个真实 API 调用。

`loadData()` 使用 `setTimeout(500ms)` 模拟异步加载，内部全部使用 mock 数据。

代码中有两处 TODO 注释标记了待对接接口：
1. `// TODO: 替换为真实 API 调用 GET /api/coaches/:id` — `loadData()` 内
2. `// TODO: 替换为真实 API 调用 POST /api/xcx/notes` — `onNoteConfirm()` 内

---

## 四、前端计算/派生数据

| 字段 | 计算逻辑 | 依赖数据 |
|------|----------|----------|
| `perfCards[]` | 由 `detail.performance` 各字段组装为展示卡片数组 | `performance.*` (mock) |
| `perfGap` | `perfTarget - perfCurrent` | `performance.perfTarget/perfCurrent` (mock) |
| `perfPercent` | `Math.min(Math.round(perfCurrent/perfTarget*100), 100)` | 同上 |
| `pbFilledPct` | `Math.min(100, Math.round(totalHours/maxHours*1000)/10)` | `performance.monthlyHours` + 硬编码 `maxHours=220` |
| `pbCurrentTier` | 遍历 tierNodes 找到当前所在档位 | `performance.monthlyHours` + 硬编码 `tierNodes` |
| `pbTicks[]` | `buildTicks(tierNodes, maxHours)` — 计算每个档位的 left 百分比 | 硬编码 `tierNodes` + `maxHours` |
| `incomeTotal` | `items.reduce()` 累加当前 tab 的收入金额 | `detail.income.thisMonth/lastMonth` (mock) |
| `currentIncome` | 根据 `incomeTab` 切换本月/上月数据 | `detail.income.*` (mock) |
| `sortedNotes` | `sortByTimestamp(detail.notes, 'timestamp')` 按时间降序排列 | `detail.notes` (mock) |
| `pbShineDurMs` | `calcShineDur(filledPct)` — 根据填充百分比计算动画时长 | `pbFilledPct` (派生) |
| `pbClampedSparkPct` | `Math.max(0, Math.min(100, pbFilledPct))` | `pbFilledPct` (派生) |
| 新备注 `newNote` | `onNoteConfirm()` 中用 `Date.now()` 生成 ID 和时间 | 用户输入 `content` + 当前时间 |

---

## 五、路由参数

| 参数 | 来源 | 用途 |
|------|------|------|
| `options.id` | `onLoad(options)` 从页面路由 query 获取 | 赋值给 `coachId`，传入 `loadData(id)` 用于匹配 mockCoaches |

---

## 六、WXML 硬编码文案

| 文案 | 位置 | 说明 |
|------|------|------|
| `加载中...` | 加载态 toast | 状态提示 |
| `未找到助教信息` | 空态 | 状态提示 |
| `加载失败` | 错误态 | 状态提示 |
| `点击重试` | 错误态按钮 | 操作文案 |
| `绩效概览` | section 标题 | 固定文案 |
| `绩效档位进度` | 进度条标题 | 固定文案 |
| `距下一档还差 {{perfGap}}h` | 进度条提示 | 模板 + 派生数据 |
| `收入明细` | section 标题 | 固定文案 |
| `本月` / `上月` | 收入 Tab | 固定文案 |
| `预估` | 收入 Tab 标签 | 固定文案 |
| `合计（预估）` / `合计` | 收入合计行 | 条件文案 |
| `任务执行` | section 标题 | 固定文案 |
| `本月完成` | 任务统计标签 | 固定文案 |
| `回访` / `召回` + `个` | 任务统计 | 固定文案 |
| `收起 ↑` / `展开全部 ↓` | 任务折叠按钮 | 条件文案 |
| `客户关系 TOP20` | section 标题 | 固定文案 |
| `近60天` | 客户关系副标题 | 固定文案（应由 API 返回时间范围） |
| `近期服务明细` | section 标题 | 固定文案 |
| `查看更多服务记录 →` | 服务明细底部 | 操作文案 |
| `更多信息` | section 标题 | 固定文案 |
| `入职日期` | 更多信息标签 | 固定文案 |
| `备注记录` + `共 {{sortedNotes.length}} 条` | section 标题 | 固定 + 动态 |
| `暂无备注` | 备注空态 | 状态提示 |
| `问问助手` | 底部按钮 | 操作文案 |
| `备注` | 底部按钮 | 操作文案 |
| `备注已保存` | Toast 提示 | 操作反馈 |
| `页面跳转失败` | 多处 navigateTo fail | 错误提示 |
| 历史表头：`月份` / `服务客户` / `访/召完成` / `业绩时长` / `工资` | 历史月度表格 | 固定文案 |

---

## 七、引用组件

| 组件 | 路径 | 用途 |
|------|------|------|
| `coach-level-tag` | `/components/coach-level-tag/` | 助教等级标签（接收 `level` prop） |
| `perf-progress-bar` | `/components/perf-progress-bar/` | 绩效进度条（接收多个动画参数 prop） |
| `note-modal` | `/components/note-modal/` | 备注弹窗（接收 `visible`/`customerName`，emit `confirm`/`cancel`） |
| `dev-fab` | `/components/dev-fab/` | 开发调试浮动按钮 |
| `t-loading` | TDesign | 加载动画 |
| `t-icon` | TDesign | 图标 |
| `t-tag` | TDesign | 标签（已注册但 WXML 中未使用） |

---

## 八、引用工具函数

| 函数 | 来源 | 用途 |
|------|------|------|
| `sortByTimestamp` | `utils/sort.ts` | 对备注列表按 `timestamp` 字段降序排序 |

---

## 九、联调 TODO 清单

### 优先级 P0 — 核心数据

| # | 待对接接口 | 替换目标 | 涉及字段 |
|---|-----------|----------|----------|
| 1 | `GET /api/xcx/coaches/:id` | `mockCoachDetail` 全部字段 | 基本信息、绩效、收入、备注 |
| 2 | `GET /api/xcx/coaches/:id/tasks` | `mockVisibleTasks` + `mockHiddenTasks` + `mockAbandonedTasks` | 任务列表（含备注） |
| 3 | `GET /api/xcx/coaches/:id/top-customers` | `mockTopCustomers` | 客户关系 TOP20 |
| 4 | `GET /api/xcx/coaches/:id/service-records` | `mockServiceRecords` | 近期服务明细 |
| 5 | `GET /api/xcx/coaches/:id/history` | `mockHistoryMonths` | 历史月度汇总 |
| 6 | `POST /api/xcx/notes` | `onNoteConfirm()` 内联构造 | 新增备注 |

### 优先级 P1 — 硬编码需接口化

| # | 硬编码项 | 当前值 | 建议 |
|---|---------|--------|------|
| 7 | `tierNodes` | `[0,100,130,160,190,220]` | 由接口 #1 返回绩效档位配置 |
| 8 | `taskStats` | `{recall:24, callback:14}` | 由接口 #2 返回任务统计汇总 |
| 9 | perfCards[0].sub `'折算前 89.0h'` | 硬编码 | 由接口 #1 返回折算前时长 |
| 10 | perfCards[3].sub `'覆盖 22 位客户'` | 硬编码 | 由接口 #1 返回覆盖客户数 |
| 11 | WXML `近60天` | 硬编码 | 由接口 #3 返回统计时间范围 |

### 优先级 P2 — 清理项

| # | 项目 | 说明 |
|---|------|------|
| 12 | 删除页面内联 mock 常量 | 7 个 mock 变量 + 接口类型定义迁移到 types |
| 13 | 移除 `import { mockCoaches }` | 联调后不再需要 mock-data.ts 依赖 |
| 14 | 移除 `setTimeout(500ms)` 模拟延迟 | 替换为真实异步请求 |
| 15 | `t-tag` 组件注册但未使用 | 从 JSON 中移除或确认是否需要 |
