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

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

## 概览

| 分类 | 数量 | 说明 |
|------|------|------|
| Mock 数据 | 12 个字段 | 来自 `mock-data.ts` 的 `mockTaskDetails` + 页面内联 mock 备注 |
| 硬编码数据 | 18 个字段/数组 | 维客线索、话术参考、服务记录、服务汇总、手机号、储值等级等 |
| 已对接 API | 0 个接口 | 当前无任何真实 API 调用 |
| 前端计算/派生 | 7 个字段 | 关系等级、Banner 背景、AI 配色、时间标签等 |
| 路由参数 | 1 个 | `id`（任务 ID） |
| WXML 硬编码文案 | 6 处 | 建议文案、section 标题等 |

---

## 一、Mock 数据

### 1.1 来自 mock-data.ts（`mockTaskDetails`）

页面通过 `import { mockTaskDetails } from '../../utils/mock-data'` 引入，在 `loadData()` 中使用 `setTimeout` 模拟异步加载。

| 字段 | 类型 | 来源路径 | 联调替换 API |
|------|------|----------|-------------|
| `detail`（整体） | `TaskDetail` | `mockTaskDetails.find(t => t.id === id) \|\| mockTaskDetails[0]` | `GET /api/tasks/{id}` |
| `detail.id` | `string` | TaskDetail.id | 同上 |
| `detail.customerName` | `string` | TaskDetail.customerName | 同上 |
| `detail.customerAvatar` | `string` | TaskDetail.customerAvatar | 同上 |
| `detail.taskType` | `TaskType` | TaskDetail.taskType（`'callback' \| 'priority_recall' \| 'relationship' \| 'high_priority'`） | 同上 |
| `detail.taskTypeLabel` | `string` | TaskDetail.taskTypeLabel | 同上 |
| `detail.heartScore` | `number` | TaskDetail.heartScore | 同上 |
| `detail.status` | `'pending' \| 'completed' \| 'abandoned'` | TaskDetail.status | 同上 |
| `detail.aiAnalysis.summary` | `string` | TaskDetail.aiAnalysis.summary | `GET /api/tasks/{id}/ai-analysis` |
| `detail.aiAnalysis.suggestions` | `string[]` | TaskDetail.aiAnalysis.suggestions | 同上 |
| `detail.hobbies` | `string[]` | TaskDetail.hobbies | `GET /api/tasks/{id}` |
| `detail.deadline` | `string` | TaskDetail.deadline | 同上 |

**mock-data.ts 中 TaskDetail 扩展字段（已定义但页面未直接渲染）：**

| 字段 | 类型 | 说明 |
|------|------|------|
| `lastVisitDate` | `string?` | 最后到店日期，页面未使用 |
| `lastSpendAmount` | `number?` | 最后消费金额，页面未使用 |
| `callbackReason` | `string?` | 回访原因，页面未使用 |
| `daysAbsent` | `number?` | 缺席天数，页面未使用 |
| `spendTrend` | `'up' \| 'down' \| 'flat'?` | 消费趋势，页面未使用 |
| `churnRisk` | `'high' \| 'medium' \| 'low'?` | 流失风险，页面未使用 |
| `preferences` | `string[]?` | 偏好，页面未使用 |
| `consumptionHabits` | `string?` | 消费习惯，页面未使用 |
| `socialPreference` | `string?` | 社交偏好，页面未使用 |

### 1.2 页面内联 Mock 备注

`loadData()` 方法内部硬编码了 5 条 mock 备注（`mockNotes` 局部变量，L107-L113），覆盖了 `mockTaskDetails` 中的 `notes` 字段：

| 字段 | 当前值 | 联调替换 API |
|------|--------|-------------|
| `mockNotes[0]` | `'已通过微信联系王先生...'`，score=10 | `GET /api/tasks/{id}/notes` |
| `mockNotes[1]` | `'王先生最近出差较多...'`，score=7.5 | 同上 |
| `mockNotes[2]` | `'上次到店时推荐了会员续费活动...'`，score=6 | 同上 |
| `mockNotes[3]` | `'客户对今天的服务非常满意...'`，score=9.5 | 同上 |
| `mockNotes[4]` | `'完成高优先召回任务...'`，score=8 | 同上 |

---

## 二、硬编码数据

### 2.1 维客线索（`retentionClues`）

**风险：高** — 8 条完整的维客线索数据直接写死在 `data` 对象中（L68-L77），应从 API 获取。

| 索引 | tag | text（摘要） | source | 所在行 | 应改为 |
|------|-----|-------------|--------|--------|--------|
| 0 | 客户基础 | `'生日 3月15日 · VIP会员 · 注册2年'` | By:系统 | L69 | `GET /api/tasks/{id}/retention-clues` |
| 1 | 消费习惯 | `'常来夜场 · 月均4-5次'` | By:系统 | L70 | 同上 |
| 2 | 消费习惯 | `'高客单价'`（含 desc） | By:系统 | L71 | 同上 |
| 3 | 玩法偏好 | `'偏爱中式八球 · 斯诺克进阶中...'`（含 desc） | By:系统 | L72 | 同上 |
| 4 | 重要反馈 | `'上次提到想练斯诺克走位'`（含 desc） | By:小燕 | L73 | 同上 |
| 5 | 社交偏好 | `'喜欢带朋友来玩 · 社交型客户'`（含 desc） | By:系统 | L74 | 同上 |
| 6 | 消费习惯 | `'酒水消费占比高 · 偏好高端酒水'`（含 desc） | By:系统 | L75 | 同上 |
| 7 | 重要反馈 | `'上次提到想办生日派对'`（含 desc） | By:Lucy | L76 | 同上 |

> 注意：`mock-data.ts` 中已定义 `mockRetentionClues` 和 `RetentionClue` 接口，但页面未使用，而是自行硬编码了不同结构的数据。联调时应统一数据结构。

### 2.2 话术参考（`talkingPoints`）

**风险：高** — 5 条话术直接写死在 `data` 对象中（L80-L86），应由 AI 生成或从 API 获取。

| 索引 | 内容摘要 | 所在行 | 应改为 |
|------|---------|--------|--------|
| 0 | `'王哥您好，好久不见！最近店里新到了...'` | L81 | `GET /api/tasks/{id}/talking-points` 或 AI 生成接口 |
| 1 | `'王哥，最近忙吗？这周末我们有个...'` | L82 | 同上 |
| 2 | `'王哥好呀，上次您提到想练练...'` | L83 | 同上 |
| 3 | `'王哥，好久没见您了，您的老位置...'` | L84 | 同上 |
| 4 | `'王哥您好，我们这个月推出了...'` | L85 | 同上 |

### 2.3 近期服务记录（`serviceRecords`）

**风险：高** — 4 条服务记录直接写死在 `data` 对象中（L91-L96），应从 API 获取。

| 索引 | table | type | income | date | 所在行 | 应改为 |
|------|-------|------|--------|------|--------|--------|
| 0 | A12号台 | 基础课 | ¥200 | 2月7日 21:30 | L92 | `GET /api/tasks/{id}/service-records` |
| 1 | 3号台 | 基础课 | ¥160 | 2月1日 20:30 | L93 | 同上 |
| 2 | VIP1号房 | 包厢课 | ¥150 | 1月28日 19:00 | L94 | 同上 |
| 3 | （空） | 充值 | ¥80 | 1月15日 10:00 | L95 | 同上 |

### 2.4 服务汇总（`serviceSummary`）

**风险：高** — 汇总数据直接写死（L90），应由后端计算或前端根据服务记录列表聚合。

| 字段 | 当前值 | 所在行 | 应改为 |
|------|--------|--------|--------|
| `totalHours` | `6.0` | L90 | 后端返回或前端聚合 `serviceRecords` |
| `totalIncome` | `510` | L90 | 同上 |
| `avgIncome` | `170` | L90 | 同上 |

### 2.5 手机号

**风险：高** — 手机号硬编码在两处。

| 位置 | 当前值 | 所在行 | 应改为 |
|------|--------|--------|--------|
| WXML 中 `phone` 显示 | `'138****5678'`（脱敏）/ `'13812345678'`（明文） | wxml L38 | 从 `detail` 对象获取，API 返回脱敏版 |
| `onCopyPhone()` 方法 | `'13812345678'` | ts L175 | 调用 API 获取明文手机号 `GET /api/customers/{id}/phone` |

### 2.6 储值等级

**风险：中** — 直接写死在 `data` 中。

| 字段 | 当前值 | 所在行 | 应改为 |
|------|--------|--------|--------|
| `storageLevel` | `'非常多'` | ts L101 | 从 `detail` 或客户信息 API 获取 |

### 2.7 关系等级初始值

**风险：低** — 初始值会被 `updateRelationshipDisplay()` 覆盖，但仍属于硬编码。

| 字段 | 当前值 | 所在行 | 说明 |
|------|--------|--------|------|
| `relationLevel` | `'excellent'` | ts L104 | 初始值，`loadData` 后被覆盖 |
| `relationLevelText` | `'很好'` | ts L105 | 同上 |
| `relationColor` | `'#e91e63'` | ts L106 | 同上 |

### 2.8 任务建议文案

**风险：中** — WXML 中硬编码了建议引导文案。

| 位置 | 当前值 | 所在行 | 应改为 |
|------|--------|--------|--------|
| `suggestion-intro` | `'该客户已有 15 天未到店，存在流失风险。建议通过微信联系：'` | wxml L82 | 从 `detail.aiAnalysis` 获取或后端生成 |

---

## 三、已对接 API

**当前无任何真实 API 调用。**

`loadData()` 使用 `setTimeout` + `mockTaskDetails` 模拟异步请求（L128-L155）。所有数据操作（放弃任务、保存备注、删除备注）均为本地 `setData` 操作，未与后端通信。

涉及的伪操作：

| 操作 | 当前实现 | 需对接 API |
|------|---------|-----------|
| 加载任务详情 | `mockTaskDetails.find()` | `GET /api/tasks/{id}` |
| 放弃任务 | `setData({ 'detail.status': 'abandoned' })` | `POST /api/tasks/{id}/abandon` |
| 取消放弃 | `setData({ 'detail.status': 'pending' })` | `POST /api/tasks/{id}/cancel-abandon` |
| 保存备注 | `setData({ sortedNotes: [...] })` | `POST /api/tasks/{id}/notes` |
| 删除备注 | `filter` 本地数组 | `DELETE /api/tasks/{id}/notes/{noteId}` |

---

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

| 字段 | 计算逻辑 | 所在方法 | 依赖数据 |
|------|---------|---------|---------|
| `relationLevel` | `heartScore` 分段映射：>8.5→excellent, ≥6→good, ≥3.5→normal, <3.5→poor | `updateRelationshipDisplay()` L157 | `detail.heartScore` |
| `relationLevelText` | 同上分段映射为中文：很好/良好/一般/待发展 | 同上 | 同上 |
| `relationColor` | 同上分段映射为颜色值 | 同上 | 同上 |
| `bannerBgSvg` | 根据 `detail.taskType` 映射 SVG 路径 | `loadData()` L133-L142 | `detail.taskType` |
| `sortedNotes` | `mockNotes` → 附加 `timeLabel`（`formatRelativeTime`）→ `sortByTimestamp` 排序 | `loadData()` L116-L118 | mock 备注数据 |
| `aiColor` | 随机从 6 色中选取 | `onLoad()` L124 | 无（随机） |
| `copiedIndex` | 复制话术后设为当前索引，2 秒后重置为 -1 | `onCopySpeech()` L189 | 用户交互 |
| `pageState` | 加载状态机：loading → normal/empty/error | `loadData()` | 数据加载结果 |
| `phoneVisible` | 手机号显示/隐藏切换 | `onTogglePhone()` L173 | 用户交互 |
| `noteModalVisible` | 备注弹窗显隐 | `onAddNote()`/`onNoteConfirm()`/`onNoteCancel()` | 用户交互 |
| `abandonModalVisible` | 放弃弹窗显隐 | `onAbandon()`/`onAbandonConfirm()`/`onAbandonCancel()` | 用户交互 |
| `formatServiceDate()` 返回值 | ISO 日期 → `M月D日 HH:mm` 中文短格式 | 页面顶部函数 L55 | 服务记录日期字符串 |

---

## 五、路由参数

| 参数 | 来源 | 用途 | 所在行 |
|------|------|------|--------|
| `options.id` | `onLoad(options)` | 任务 ID，用于查找 mock 数据；缺失时回退空字符串 | ts L123 |

---

## 六、WXML 中的硬编码文案

| 文案 | 位置 | 类型 | 说明 |
|------|------|------|------|
| `'该客户已有 15 天未到店，存在流失风险。建议通过微信联系：'` | wxml L82 | 业务数据 | 应从 AI 分析结果获取 |
| `'💡 建议执行'` | wxml L79 | UI 文案 | 可保留 |
| `'💬 话术参考'` | wxml L89 | UI 文案 | 可保留 |
| `'60天内服务记录'` | wxml L121 | UI 文案 | "60天"为业务规则，可保留但需确认是否动态 |
| `'未找到任务信息'` / `'加载失败'` / `'加载中...'` | wxml L7-L15 | 状态文案 | 可保留 |
| `'暂无备注'` | wxml L117 | 空态文案 | 可保留 |

---

## 七、引用的工具函数

| 函数 | 来源文件 | 用途 |
|------|---------|------|
| `mockTaskDetails` | `utils/mock-data.ts` | Mock 任务详情数据 |
| `TaskDetail`, `Note` | `utils/mock-data.ts` | 类型定义 |
| `sortByTimestamp` | `utils/sort.ts` | 备注按时间降序排序 |
| `formatRelativeTime` | `utils/time.ts` | 备注时间 → 相对时间文案 |
| `formatMoney` | `utils/money.ts` | 金额格式化（已 import 但页面 TS 中未直接调用，WXML 使用 WXS 版） |
| `fmt.toFixed` | `utils/format.wxs` | WXML 中数字保留小数 |
| `fmt.money` | `utils/format.wxs` | WXML 中金额格式化 |
| `fmt.hours` | `utils/format.wxs` | WXML 中课时格式化 |

---

## 八、引用的自定义组件

| 组件 | 路径 | 用途 |
|------|------|------|
| `note-modal` | `/components/note-modal/note-modal` | 备注弹窗 |
| `abandon-modal` | `/components/abandon-modal/abandon-modal` | 放弃任务弹窗 |
| `star-rating` | `/components/star-rating/star-rating` | 星级评分展示 |
| `heart-icon` | `/components/heart-icon/heart-icon` | 爱心图标（关系等级） |
| `ai-inline-icon` | `/components/ai-inline-icon/ai-inline-icon` | AI 内联图标 |
| `ai-title-badge` | `/components/ai-title-badge/ai-title-badge` | AI 标题徽章 |
| `clue-card` | `/components/clue-card/clue-card` | 维客线索卡片 |
| `service-record-card` | `/components/service-record-card/service-record-card` | 服务记录卡片 |
| `dev-fab` | `/components/dev-fab/dev-fab` | 开发调试浮动按钮（JSON 注册但 WXML 未使用） |

---

## 九、调试面板数据

页面包含一个调试面板（`showDebugPanel`），用于开发阶段切换任务类型和关系数值。联调前应移除或隐藏。

| 字段 | 用途 | 所在行 |
|------|------|--------|
| `showDebugPanel` | 调试面板显隐 | ts L110 |
| `debugTaskType` | 调试用任务类型 | ts L111 |
| `debugHeartScore` | 调试用关系数值 | ts L112 |
| `debugShowExpandBtn` | 调试用展开按钮开关 | ts L113 |

---

## 十、联调 TODO

### 高优先级（数据完全依赖 Mock/硬编码）

- [ ] 替换 `mockTaskDetails` 为 `GET /api/tasks/{id}` 真实 API 调用
- [ ] 替换页面内联 mock 备注为 `GET /api/tasks/{id}/notes` API
- [ ] 替换硬编码 `retentionClues`（8 条维客线索）为 `GET /api/tasks/{id}/retention-clues` API
- [ ] 替换硬编码 `talkingPoints`（5 条话术）为 `GET /api/tasks/{id}/talking-points` 或 AI 生成接口
- [ ] 替换硬编码 `serviceRecords`（4 条服务记录）为 `GET /api/tasks/{id}/service-records?days=60` API
- [ ] 替换硬编码 `serviceSummary` 为后端返回或前端聚合计算
- [ ] 替换硬编码手机号 `'13812345678'` / `'138****5678'` 为 API 返回的客户手机号
- [ ] 替换硬编码 `storageLevel: '非常多'` 为客户信息 API 返回值

### 中优先级（操作类接口）

- [ ] `onAbandonConfirm` 对接 `POST /api/tasks/{id}/abandon`
- [ ] `cancelAbandon` 对接 `POST /api/tasks/{id}/cancel-abandon`
- [ ] `onNoteConfirm` 对接 `POST /api/tasks/{id}/notes`
- [ ] `onDeleteNote` 对接 `DELETE /api/tasks/{id}/notes/{noteId}`

### 低优先级（优化项）

- [ ] WXML 中 `suggestion-intro` 硬编码文案改为从 AI 分析结果动态获取
- [ ] 移除或条件隐藏调试面板（`showDebugPanel` 相关代码）
- [ ] 统一维客线索数据结构：页面硬编码的 `RetentionClue` 接口与 `mock-data.ts` 中定义的不一致
- [ ] `formatMoney` 已 import 但 TS 中未使用（WXML 用 WXS 版），可清理无用 import
- [ ] `dev-fab` 组件已在 JSON 注册但 WXML 未引用，可清理
