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

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

## 概览

| 分类 | 数量 | 说明 |
|------|------|------|
| A. Mock 数据 | 1 处 import + 1 处页面内联 | `mockCustomerDetail` 已 import 但未使用；`mockRecords` 内联定义 |
| B. 硬编码数据 | 7 个数据块 | `detail`、`aiInsight`、`clues`、`coachTasks`、`favoriteCoaches`、`sortedNotes`、`consumptionRecords` |
| C. 已对接 API | 0 | 页面无任何 `request()` 调用 |
| D. 前端计算/派生 | 3 处 | `aiColor` 随机、`phoneVisible` 切换、`pageState` 状态机 |
| E. 路由参数 | 0 | `onLoad(options)` 未读取任何参数 |
| F. WXML 硬编码文案 | 14 处 | 标题、标签、按钮文字等 |

---

## 一、Mock 数据

### 1.1 `mockCustomerDetail`（已 import 未使用）

| 项目 | 说明 |
|------|------|
| 来源 | `import { mockCustomerDetail } from "../../utils/mock-data"` |
| 状态 | **已 import 但页面内未引用**，属于死代码 |
| mock-data.ts 中的字段 | `id`, `name`, `avatar`, `tags`, `heartScore`, `phone`, `spiIndex`, `consumptionRecords[]` |
| 联调动作 | 删除此 import；页面 `detail` 对象改为从 API 获取 |

### 1.2 `mockRecords`（页面内联 mock）

| 项目 | 说明 |
|------|------|
| 来源 | 页面顶部 `const mockRecords: ConsumptionRecord[]` 硬编码 3 条记录 |
| 赋值位置 | `data.consumptionRecords: mockRecords` |
| 字段清单 | 每条记录含：`id`, `type`, `date`, `tableName`, `startTime`, `endTime`, `duration`, `tableFee`, `tableOrigPrice`, `coaches[]`（`name`, `level`, `levelColor`, `courseType`, `hours`, `perfHours`, `fee`）, `foodAmount`, `foodOrigPrice`, `totalAmount`, `totalOrigPrice`, `payMethod`, `rechargeAmount` |
| 联调 API | `GET /api/xcx/customer/{member_id}/consumption-records`（待开发） |
| 风险 | 🔴 高 — 金额字段涉及 `tableFee`/`totalAmount` 等财务数据，需严格对齐 DWD-DOC 口径 |

---

## 二、硬编码数据

### 2.1 `detail` 对象（客户基础信息）

| 字段 | 硬编码值 | 应对接来源 | 风险 |
|------|----------|-----------|------|
| `id` | `"cust_001"` | API 返回 `member_id` | 🔴 高 |
| `name` | `"王先生"` | API `dim_member.nickname` | 🔴 高 |
| `avatarChar` | `"王"` | 前端从 `name` 截取首字 | 🟡 中 |
| `phone` | `"13812345678"` | API `dim_member.mobile`（注意 DQ-6：需通过 `member_id` JOIN `dim_member`） | 🔴 高 |
| `balance` | `"8,600"` | API 储值余额（`balance_pay`） | 🔴 高 |
| `consumption60d` | `"2,800"` | API 近60天消费汇总 | 🔴 高 |
| `idealInterval` | `"7天"` | API / AI 计算 | 🟡 中 |
| `daysSinceVisit` | `"12天"` | API 最后到店距今天数 | 🔴 高 |

**联调 API**：`GET /api/xcx/customer/{member_id}/profile`（待开发）

### 2.2 `aiInsight` 对象（AI 智能洞察）

| 字段 | 硬编码值 | 应对接来源 | 风险 |
|------|----------|-----------|------|
| `summary` | 长文本（"高价值 VIP 客户…"） | AI 缓存 API `GET /api/xcx/ai-cache?cache_type=customer_insight&target_id={member_id}` | 🔴 高 |
| `strategies[]` | 3 条策略，含 `color` + `text` | 同上，AI 缓存返回 | 🔴 高 |

**联调 API**：`GET /api/xcx/ai-cache`（已有路由 `xcx_ai_cache.py`，需确认 `cache_type` 枚举是否包含 `customer_insight`）

### 2.3 `clues` 数组（维客线索）

共 7 条硬编码线索：

| 索引 | category | text 摘要 | source | 应对接来源 | 风险 |
|------|----------|----------|--------|-----------|------|
| 0 | 客户基础 | 🎂 生日 3月15日 · VIP会员 · 注册2年 | 系统 | API 维客线索 | 🔴 高 |
| 1 | 消费习惯 | 🌙 常来夜场 · 月均4-5次 | 系统 | API 维客线索 | 🔴 高 |
| 2 | 消费习惯 | 💰 高客单价（含 detail） | 系统 | API 维客线索 | 🔴 高 |
| 3 | 玩法偏好 | 🎱 偏爱中式 · 斯诺克进阶中 | 系统 | API 维客线索 | 🔴 高 |
| 4 | 促销接受 | 🍷 爱点酒水套餐（含 detail） | 系统 | API 维客线索 | 🔴 高 |
| 5 | 社交关系 | 👥 常带朋友（含 detail） | 系统 | API 维客线索 | 🔴 高 |
| 6 | 重要反馈 | ⚠️ 想练斯诺克走位… | 小燕 | API 维客线索 | 🔴 高 |

**联调 API**：`GET /api/retention-clue/{member_id}?site_id=X`（已有路由 `member_retention_clue.py`）

### 2.4 `coachTasks` 数组（助教任务分配）

共 4 条硬编码任务卡片：

| 索引 | name | taskType | 字段清单 | 风险 |
|------|------|----------|---------|------|
| 0 | 小燕 | 高优先召回 | `level`, `levelColor`, `taskColor`, `bgClass`, `status`, `lastService`, `metrics[]`（近60天次数/总时长/次均时长） | 🔴 高 |
| 1 | 泡芙 | 优先召回 | 同上 + `status: "pinned"` | 🔴 高 |
| 2 | Amy | 关系构建 | 同上 | 🔴 高 |
| 3 | Lucy | 客户回访 | 同上 + `status: "abandoned"` | 🔴 高 |

**联调 API**：`GET /api/xcx/customer/{member_id}/coach-tasks`（待开发；后端 `biz.coach_tasks` 表已存在）

### 2.5 `favoriteCoaches` 数组（最喜欢的助教）

共 2 条硬编码：

| 索引 | name | 字段清单 | 风险 |
|------|------|---------|------|
| 0 | 小燕 | `emoji`, `relationIndex: "9.2"`, `indexColor`, `bgClass`, `stats[]`（基础/激励/上课/充值） | 🔴 高 |
| 1 | 泡芙 | 同上，`relationIndex: "7.8"` | 🔴 高 |

**联调 API**：`GET /api/xcx/customer/{member_id}/favorite-coaches`（待开发；需 DWS 层助教-客户关系聚合）

### 2.6 `sortedNotes` 数组（备注记录）

共 3 条硬编码备注：

| 索引 | tagLabel | createdAt | content 摘要 | 风险 |
|------|----------|-----------|-------------|------|
| 0 | 管理员 | 2026-03-05 14:30 | 对斯诺克课程感兴趣… | 🔴 高 |
| 1 | 小燕 | 2026-02-20 16:45 | 客户反馈服务态度很好… | 🔴 高 |
| 2 | 管理员 | 2026-02-10 10:00 | 储值活动当天即充值… | 🔴 高 |

**联调 API**：`GET /api/xcx/notes?target_type=member&target_id={member_id}`（已有路由 `xcx_notes.py`）

### 2.7 其他硬编码值

| 位置 | 字段 | 硬编码值 | 说明 | 风险 |
|------|------|----------|------|------|
| `data` | `pageState` | `"loading"` | 初始状态，正常 | 🟢 低 |
| `data` | `phoneVisible` | `false` | UI 状态，正常 | 🟢 低 |
| `data` | `loadingMore` | `false` | UI 状态，正常 | 🟢 低 |
| `data` | `noteModalVisible` | `false` | UI 状态，正常 | 🟢 低 |
| `loadDetail()` | `pageState` | 直接设为 `"normal"` | ⚠️ 无 API 调用，直接跳过 loading | 🔴 高 |
| WXML | 手机号掩码 | `'138****5678'` | 硬编码掩码，应从 `detail.phone` 动态生成 | 🟡 中 |

---

## 三、已对接 API

| API | 状态 |
|-----|------|
| （无） | 页面当前 **零 API 调用**，`loadDetail()` 为空壳 |

---

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

| 字段 | 计算逻辑 | 说明 |
|------|----------|------|
| `aiColor` | `onLoad` 中随机从 6 色数组取值 | 用于 AI 洞察卡片配色，纯 UI 装饰 |
| `phoneVisible` | `onTogglePhone()` 切换布尔值 | 控制手机号显示/隐藏 |
| `pageState` | `loadDetail()` 中设为 `"normal"` | 应改为：loading → API 成功 normal / 失败 error / 空 empty |

---

## 五、路由参数

| 参数 | 状态 | 说明 |
|------|------|------|
| `options.id` / `options.memberId` | ❌ 未读取 | `onLoad(options)` 接收了 `options` 但未使用任何参数 |

**联调时必须**：从 `options` 中读取 `memberId`（或 `id`），作为所有 API 请求的入参。

---

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

| 位置 | 文案 | 是否需要动态化 | 说明 |
|------|------|---------------|------|
| 导航栏 | `"客户详情"` | ❌ 保持 | JSON 配置 `navigationBarTitleText` |
| loading 态 | `"加载中..."` | ❌ 保持 | 通用文案 |
| empty 态 | `"未找到客户信息"` | ❌ 保持 | 通用文案 |
| error 态 | `"加载失败"` | ❌ 保持 | 通用文案 |
| error 按钮 | `"点击重试"` | ❌ 保持 | 通用文案 |
| banner 统计标签 | `"储值余额"` / `"60天消费"` / `"理想间隔"` / `"距今到店"` | ❌ 保持 | 固定标签 |
| AI 卡片标题 | `"AI 智能洞察"` | ❌ 保持 | 固定标题 |
| 策略标题 | `"当前推荐策略"` | ❌ 保持 | 固定标题 |
| 维客线索标题 | `"维客线索"` | ❌ 保持 | 固定标题 |
| 助教任务标题 | `"助教任务分配"` / `"当前进行中"` | ❌ 保持 | 固定标题 |
| 最喜欢助教标题 | `"最喜欢的助教"` / `"近60天"` | ❌ 保持 | 固定标题 |
| 消费记录 | `"消费记录"` / `"商城订单"` / `"总金额"` / `"🍷 食品酒水"` | ❌ 保持 | 固定标签 |
| 底部按钮 | `"问问助手"` / `"备注"` | ❌ 保持 | 固定文案 |
| 空态提示 | `"暂无消费记录"` / `"暂无备注"` | ❌ 保持 | 通用文案 |

---

## 七、联调 TODO

### 优先级 P0（页面核心数据，阻塞上线）

| # | 数据块 | 当前状态 | 需要的 API | 后端现状 |
|---|--------|---------|-----------|---------|
| 1 | 客户基础信息 `detail` | 全部硬编码 | `GET /api/xcx/customer/{member_id}/profile` | ❌ 待开发 |
| 2 | 消费记录 `consumptionRecords` | 内联 mock | `GET /api/xcx/customer/{member_id}/consumption-records` | ❌ 待开发 |
| 3 | 路由参数 `memberId` | 未读取 | 从 `onLoad(options)` 读取 | — |
| 4 | `loadDetail()` 空壳 | 直接设 normal | 改为真实 API 调用 + 状态管理 | — |

### 优先级 P1（重要业务数据）

| # | 数据块 | 当前状态 | 需要的 API | 后端现状 |
|---|--------|---------|-----------|---------|
| 5 | 维客线索 `clues` | 全部硬编码 | `GET /api/retention-clue/{member_id}` | ✅ 已有路由 |
| 6 | 备注记录 `sortedNotes` | 全部硬编码 | `GET /api/xcx/notes?target_type=member&target_id={member_id}` | ✅ 已有路由 |
| 7 | AI 智能洞察 `aiInsight` | 全部硬编码 | `GET /api/xcx/ai-cache?cache_type=customer_insight&target_id={member_id}` | ⚠️ 路由已有，需确认 cache_type |

### 优先级 P2（增强功能）

| # | 数据块 | 当前状态 | 需要的 API | 后端现状 |
|---|--------|---------|-----------|---------|
| 8 | 助教任务 `coachTasks` | 全部硬编码 | `GET /api/xcx/customer/{member_id}/coach-tasks` | ❌ 待开发（`biz.coach_tasks` 表已有） |
| 9 | 最喜欢助教 `favoriteCoaches` | 全部硬编码 | `GET /api/xcx/customer/{member_id}/favorite-coaches` | ❌ 待开发（需 DWS 聚合） |

### 前端清理项

| # | 项目 | 说明 |
|---|------|------|
| 10 | 删除 `import { mockCustomerDetail }` | 死代码，import 后未使用 |
| 11 | 删除 `const mockRecords` 内联 mock | 替换为 API 数据 |
| 12 | 手机号掩码硬编码 `'138****5678'` | 改为从 `detail.phone` 动态生成掩码 |
| 13 | `ConsumptionRecord` interface | 页面内重复定义，应移至 `utils/types.ts` 或复用 `mock-data.ts` 中的类型 |

### 注意事项

- **DQ-6 会员手机号断档**：`settlement_head.member_phone` 自 2025-12 起全为 NULL，后端 API 必须通过 `member_id` JOIN `dim_member.mobile` 获取
- **金额口径**：消费记录中的金额字段需严格对齐 DWD-DOC 标杆文档，禁止直接使用 `consume_money`
- **助教费用拆分**：消费记录中的 `coaches[].fee` 需区分陪打（`assistant_pd_money`）和超休（`assistant_cx_money`）
