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

> 排查日期：2026-03-18
> 页面路径：pages/task-list/task-list
> 引用文件：`utils/mock-data.ts`、`utils/money.ts`、`utils/time.ts`、`utils/request.ts`（未使用）

## 概览

| 分类 | 数量 | 说明 |
|------|------|------|
| Mock 数据 | 28 个字段 | 全部任务列表 + 业绩进度卡片均来自 mock |
| 硬编码数据 | 12 个字段 | 用户信息、头像、动画参数、AI 建议文案等 |
| 已对接 API | 0 个接口 | 页面当前无任何真实 API 调用 |
| 前端计算/派生 | 14 个字段 | enrichTask 派生字段 + 进度条动画计算 |
| 路由参数 | 0 个 | onLoad 未读取 options |
| WXML 硬编码文案 | 5 处 | 标题、分组标签等 UI 文案 |

---

## 一、Mock 数据

### 1.1 来自 mock-data.ts

#### 1.1.1 `mockTasks` — 任务列表主数据

| 字段 | 类型 | 说明 | 联调替换 API |
|------|------|------|-------------|
| `id` | `string` | 任务 ID | `GET /api/xcx/tasks` |
| `customerName` | `string` | 客户姓名 | 同上 |
| `customerAvatar` | `string` | 客户头像 URL | 同上 |
| `taskType` | `TaskType` | 任务类型枚举：`callback` / `priority_recall` / `relationship` / `high_priority` | 同上 |
| `taskTypeLabel` | `string` | 任务类型中文标签 | 同上（或前端根据 taskType 映射） |
| `deadline` | `string` | 截止日期 ISO 字符串 | 同上 |
| `heartScore` | `number` | 爱心评分 0-10 | 同上 |
| `hobbies` | `string[]` | 客户爱好标签 | 同上 |
| `isPinned` | `boolean` | 是否置顶 | 同上 |
| `hasNote` | `boolean` | 是否有备注 | 同上 |
| `status` | `'pending' \| 'completed' \| 'abandoned'` | 任务状态 | 同上 |

- 导入方式：`import { mockTasks } from '../../utils/mock-data'`（task-list.ts L2）
- `loadData()` 中直接展开 `mockTasks` 并追加了一条内联 mock 任务 `task-007`（见 1.2 节）

#### 1.1.2 `mockPerformance` — 绩效概览数据

| 字段 | 类型 | 当前 Mock 值 | 联调替换 API |
|------|------|-------------|-------------|
| `monthlyIncome` | `number` | `12680` | `GET /api/xcx/performance/summary` |
| `incomeChange` | `number` | `15.3` | 同上 |
| `currentTier` | `string` | `'银牌助教'` | 同上 |
| `nextTierGap` | `number` | `3320` | 同上 |
| `todayServiceCount` | `number` | `4` | 同上 |
| `weekServiceCount` | `number` | `18` | 同上 |
| `monthServiceCount` | `number` | `67` | 同上 |

- 导入方式：`import { mockPerformance } from '../../utils/mock-data'`（task-list.ts L2）
- 当前仅用 `mockPerformance.currentTier` 赋值给 `bannerTitle`（L233）

### 1.2 页面内联 Mock

#### 1.2.1 `task-007` 内联任务（loadData 内，约 L213-L225）

```typescript
{
  id: 'task-007',
  customerName: '孙丽',
  customerAvatar: '/assets/images/avatar-default.png',
  taskType: 'callback',
  taskTypeLabel: '客户回访',
  deadline: '2026-03-06',
  heartScore: 3.5,
  hobbies: [],
  isPinned: false,
  hasNote: false,
  status: 'abandoned',
}
```

- 联调时需删除，改为 API 返回的完整任务列表

#### 1.2.2 `buildPerfData()` — 业绩进度卡片构造函数（约 L130-L160）

| 字段 | Mock 值 | 说明 | 联调替换 API |
|------|---------|------|-------------|
| `nextTierHours` | `100` | 下一档位小时数 | `GET /api/xcx/performance/progress` |
| `remainHours` | `12.5` | 距下一档剩余小时 | 同上 |
| `currentTier` | `1` | 当前档位序号 | 同上 |
| `tierProgress` | `58` | 当前段内进度% | 同上（或前端计算） |
| `filledPct` | `39.8`（87.5/220×100） | 总进度条百分比 | 同上（或前端计算） |
| `ticks` | `buildTicks([0,100,130,160,190,220], 220)` | 刻度数组 | 同上（档位节点由接口返回） |
| `basicHours` | `'77.5'` | 基础课时 | 同上 |
| `bonusHours` | `'12'` | 激励课时 | 同上 |
| `totalHours` | `'87.5'` | 总课时 | 同上 |
| `tierCompleted` | `true` | 是否达标 | 同上 |
| `bonusMoney` | `'800'` | 达标奖金（元） | 同上 |
| `incomeMonth` | `'2月'` | 收入月份 | 同上 |
| `prevMonth` | `'1月'` | 对比月份 | 同上 |
| `incomeFormatted` | `'6,206'` | 预计收入格式化 | 同上 |
| `incomeTrend` | `'↓368'` | 收入趋势文案 | 同上 |
| `incomeTrendDir` | `'down'` | 趋势方向 | 同上 |

#### 1.2.3 `enrichTask()` 内联 Mock 逻辑（约 L100-L125）

| 派生字段 | Mock 算法 | 说明 |
|----------|-----------|------|
| `lastVisitDays` | `(id.charCodeAt(last) % 15) + 1` | 伪随机天数，联调时应由 API 返回 |
| `balanceLabel` | `formatMoney((id.charCodeAt(last) * 137) % 5000 + 200)` | 伪随机余额，联调时应由 API 返回 |
| `aiSuggestion` | 从 5 条硬编码文案中按 id 取模选取 | 联调时应由 AI 接口返回 |

5 条硬编码 AI 建议文案：
1. `'建议推荐斯诺克进阶课程，提升客户粘性'`
2. `'客户近期消费下降，建议电话关怀了解原因'`
3. `'适合推荐周末球友赛活动，增强社交体验'`
4. `'高价值客户，建议维护关系并推荐VIP权益'`
5. `'新客户首次体验后未续费，建议跟进意向'`

#### 1.2.4 `loadData()` 中的 600ms 模拟延迟

```typescript
setTimeout(() => { /* 全部数据构造逻辑 */ }, 600)
```

- 联调时替换为真实 API 请求

---

## 二、硬编码数据

| 字段 | 当前值 | 应改为 | 风险 | 所在位置 |
|------|--------|--------|------|----------|
| `userName` | `'小燕'` | `globalData.userInfo.name` 或登录 API | 高 | data 初始化 |
| `userRole` | `'助教'` | `globalData.userInfo.role` 或登录 API | 高 | data 初始化 |
| `storeName` | `'广州朗朗桌球'` | `globalData.storeInfo.name` 或登录 API | 高 | data 初始化 |
| `avatarUrl` | `'/assets/images/avatar-coach.png'` | `globalData.userInfo.avatar` 或登录 API | 中 | data 初始化 |
| `DETAIL_ROUTE` | `'/pages/task-detail/task-detail'` | 保持硬编码（路由常量） | 低 | 常量定义 |
| `tierNodes` | `[0, 100, 130, 160, 190, 220]` | 业绩进度 API 返回 | 高 | `buildPerfData()` + `_applyDebugHours()` |
| `maxHours` | `220` | 业绩进度 API 返回 | 高 | `buildPerfData()` |
| `total` | `87.5` | 业绩进度 API 返回 | 高 | `buildPerfData()` |
| `aiColor` | 随机选取 `['red','orange','yellow','blue','indigo','purple']` | 可保持前端随机（纯 UI） | 低 | `onLoad()` / `onShow()` |
| `abandonReason` | `'客户已转至其他门店'` | API 返回（放弃原因由用户输入） | 中 | `enrichTask()` |
| `suggestions[]` | 5 条固定文案 | AI 建议 API 返回 | 高 | `enrichTask()` |
| `hasMore` | `true` → 触底后 `false` | 分页 API 返回 `has_next` | 中 | `loadData()` / `onReachBottom()` |

---

## 三、已对接 API

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

- `utils/request.ts` 已导出 `request()` 函数（支持 token 自动附加、401 刷新重试）
- task-list.ts 未 import `request`，所有数据均来自 mock

---

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

### 4.1 任务分组（loadData 内）

| 派生字段 | 计算逻辑 | 依赖源数据 |
|----------|----------|-----------|
| `pinnedTasks` | `enriched.filter(t => t.isPinned && !t.isAbandoned)` | `allTasks` |
| `normalTasks` | `enriched.filter(t => !t.isPinned && !t.isAbandoned && t.status === 'pending')` | `allTasks` |
| `abandonedTasks` | `enriched.filter(t => t.isAbandoned)` | `allTasks` |
| `taskCount` | `pinnedTasks.length + normalTasks.length + abandonedTasks.length` | 三组任务 |
| `pageState` | `totalCount > 0 ? 'normal' : 'empty'` | `taskCount` |

### 4.2 enrichTask 派生字段

| 派生字段 | 计算逻辑 | 依赖 |
|----------|----------|------|
| `isAbandoned` | `task.status === 'abandoned'` | `task.status` |
| `deadlineLabel` | `formatDeadline(task.deadline).text` | `task.deadline` + `utils/time.ts` |
| `deadlineStyle` | `formatDeadline(task.deadline).style` | `task.deadline` + `utils/time.ts` |
| `balanceLabel` | `formatMoney(balanceSeedNum)` | Mock 种子值 + `utils/money.ts`（联调后改为 API 余额） |

### 4.3 进度条动画计算

| 派生字段 | 计算逻辑 | 依赖 |
|----------|----------|------|
| `filledPct` | `Math.min(100, (total / 220) * 100)` | `total`、`maxHours` |
| `clampedSparkPct` | `Math.max(0, Math.min(100, filledPct))` | `filledPct` |
| `shineDurMs` | `calcShineDur(filledPct)` — 基于 `SHINE_SPEED` 和进度百分比 | `filledPct`、`SHINE_SPEED` |
| `ticks[]` | `buildTicks(tierNodes, maxHours)` — 计算每个刻度的 `left` 百分比位置 | `tierNodes`、`maxHours` |

### 4.4 调试面板计算（`_applyDebugHours`）

| 派生字段 | 说明 |
|----------|------|
| `currentTier` | 遍历 tiers 数组确定当前档位 |
| `tierProgress` | 当前段内进度百分比 |
| `remainHours` | `nextTierHours - total` |
| `tierCompleted` | `total >= 220` |

> 调试面板仅开发阶段使用，联调时可保留或移除。

---

## 五、路由参数

`onLoad()` 未读取任何 `options` 参数。页面作为 TabBar 页面，不接收路由参数。

---

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

| 文案 | 位置 | 类型 | 说明 |
|------|------|------|------|
| `'今日 客户维护'` | section-header | UI 文案 | 可保持硬编码，或由 API 返回标题 |
| `'📌 置顶'` | group-label--pinned | UI 文案 | 保持硬编码 |
| `'正常任务'` | group-label--normal | UI 文案 | 保持硬编码 |
| `'已放弃'` | group-label--abandoned | UI 文案 | 保持硬编码 |
| `'暂无待办任务'` | state-empty | UI 文案 | 保持硬编码 |
| `'加载失败，请重试'` | state-error | UI 文案 | 保持硬编码 |
| `'没有更多了'` | load-more | UI 文案 | 保持硬编码 |
| `'最近到店：{{...}}天前 · 余额：{{...}}'` | card-row-2 | 模板文案 | 保持硬编码（数据部分由变量填充） |
| `'放弃原因：{{...}}'` | card-row-abandon | 模板文案 | 保持硬编码 |
| `'基础课 \| 激励课 \| 全部'` | hours-label | UI 文案 | 保持硬编码 |

> 以上均为 UI 展示文案，非业务数据硬编码，联调时无需替换。

---

## 七、联调 TODO

### 高优先（P0 — 页面核心功能）

- [ ] 替换 `mockTasks` + 内联 `task-007` 为 `GET /api/xcx/tasks` API 调用
- [ ] 替换 `buildPerfData()` 为 `GET /api/xcx/performance/progress` API 调用
- [ ] 从 `globalData` 或登录 API 获取 `userName`、`userRole`、`storeName`、`avatarUrl`
- [ ] 替换 `enrichTask()` 中的 mock 派生字段（`lastVisitDays`、`balanceLabel`）为 API 返回字段
- [ ] 替换 `enrichTask()` 中的 `aiSuggestion` 硬编码文案为 AI 建议 API
- [ ] 替换 `mockPerformance` 引用为真实绩效 API
- [ ] 档位节点 `tierNodes` 和 `maxHours` 改为 API 返回

### 中优先（P1 — 交互完整性）

- [ ] 实现分页加载：`onReachBottom` 对接分页 API（`page` / `cursor` 参数）
- [ ] `onCtxPin` 置顶操作对接 `POST /api/xcx/tasks/{id}/pin` API
- [ ] `onCtxAbandon` / `onAbandonConfirm` 放弃操作对接 `POST /api/xcx/tasks/{id}/abandon` API
- [ ] `onCtxCancelAbandon` 取消放弃对接 `POST /api/xcx/tasks/{id}/cancel-abandon` API
- [ ] `onNoteConfirm` 备注保存对接 `POST /api/xcx/tasks/{id}/notes` API
- [ ] `loadData` 中移除 `setTimeout 600ms` 模拟延迟

### 低优先（P2 — 可后续处理）

- [ ] 移除 `enrichTask()` 函数（字段由 API 直接返回）
- [ ] 移除 `buildPerfData()` 函数（数据由 API 直接返回）
- [ ] 移除 `buildTicks()` 函数（或保留为前端工具函数，接收 API 返回的 tierNodes）
- [ ] 评估是否保留调试面板（`showDebugPanel` 相关逻辑）
- [ ] 清理 `mock-data.ts` 中 task-list 不再使用的导出

---

## 八、依赖组件清单

| 组件 | 路径 | 数据来源 |
|------|------|----------|
| `perf-progress-bar` | `/components/perf-progress-bar/` | 接收 `perfData.*` 属性（全部 mock） |
| `heart-icon` | `/components/heart-icon/` | 接收 `item.heartScore`（mock） |
| `ai-inline-icon` | `/components/ai-inline-icon/` | 接收 `aiColor`（前端随机） |
| `ai-float-button` | `/components/ai-float-button/` | 无数据依赖 |
| `note-modal` | `/components/note-modal/` | 接收 `noteTarget.customerName`（mock） |
| `abandon-modal` | `/components/abandon-modal/` | 接收 `abandonTarget.customerName`（mock） |
| `dev-fab` | `/components/dev-fab/` | 无数据依赖（开发工具） |
| `t-icon` | TDesign 组件 | 无业务数据依赖 |

---

## 九、风险总结

| 风险项 | 等级 | 说明 |
|--------|------|------|
| 全页面零 API 调用 | 🔴 高 | 联调时需一次性替换所有数据源，工作量集中 |
| 用户信息硬编码 | 🔴 高 | `userName`/`userRole`/`storeName` 写死，多用户场景必崩 |
| AI 建议文案固定 | 🔴 高 | 5 条文案轮转，联调时需对接 AI 服务 |
| 业绩进度全量 mock | 🔴 高 | 档位、课时、收入全部硬编码，数值与真实数据无关 |
| 分页未实现 | 🟡 中 | `onReachBottom` 仅显示"没有更多了"，无真实分页 |
| 操作无持久化 | 🟡 中 | 置顶/放弃/备注仅修改本地 data，刷新即丢失 |
| `enrichTask` 伪随机 | 🟡 中 | `lastVisitDays`/`balanceLabel` 由 id 字符码计算，非真实数据 |
