# P9→NS1/RNS1 缺失项 #1：客户详情页分段加载策略

## 简要结论
- 状态：⚠️ 部分解决
- 风险等级：🔴 高
- 后端采用单 API 返回全部数据（无分段端点），但各扩展模块有独立 try/except 优雅降级；前端无 skeleton 占位，仅有全局 loading toast。

## 详细审查

### 审查范围
- `apps/backend/app/routers/xcx_customers.py` — CUST-1 路由
- `apps/backend/app/services/customer_service.py` — `get_customer_detail()` 实现
- `apps/miniprogram/miniprogram/pages/customer-detail/customer-detail.ts` — 前端加载逻辑
- `apps/miniprogram/miniprogram/pages/customer-detail/customer-detail.wxml` — 前端模板

### 发现

1. **后端：单 API，无分段端点**
   - `GET /api/xcx/customers/{customer_id}` 一次性返回全部数据（基本信息 + Banner + AI 洞察 + 关联助教 + 最亲密助教 + 消费记录 + 备注）
   - 无独立的 `/ai-insight`、`/notes`、`/records` 等子端点
   - P9 定义的"基本信息→消费汇总→AI 洞察→消费记录→备注"分段加载策略未实现

2. **后端：优雅降级已实现**
   - 各扩展模块（`ai_insight`、`retention_clues`、`notes`、`consumption_records`、`coach_tasks`、`favorite_coaches`）均有独立 try/except，失败时降级为空默认值
   - 核心字段（member_info）失败直接 500，符合预期

3. **前端：无 skeleton 占位**
   - 加载态为全局 `g-toast-loading`（圆形 loading + "加载中..."文字），非 P9 定义的分段 skeleton
   - `loadDetail()` 调用单个 `fetchCustomerDetail(id)` 后一次性 setData
   - 无分段渲染逻辑（先展示基本信息，再逐步加载扩展模块）

### 证据

后端 `get_customer_detail()` 一次性返回所有模块：
```python
return {
    "id": customer_id, "name": name, "phone": phone, ...
    "balance": balance, "consumption_60d": consumption_60d, ...
    "ai_insight": ai_insight,
    "coach_tasks": coach_tasks,
    "favorite_coaches": favorite_coaches,
    "retention_clues": retention_clues,
    "consumption_records": consumption_records,
    "notes": notes,
}
```

前端加载逻辑（无分段）：
```typescript
async loadDetail(id?: string) {
    this.setData({ pageState: 'loading' })
    try {
      if (id) {
        const detail = await fetchCustomerDetail(id)
        // 一次性 setData
      }
      this.setData({ pageState: 'normal' })
    } catch { ... }
}
```

### 建议（如未完全解决）
1. **短期**：前端可在单 API 返回后，先渲染 Banner 区域，再用 `nextTick` 或 `setTimeout` 分批 setData 扩展模块，减少首屏白屏时间
2. **中期**：为各扩展模块添加 skeleton 占位组件（参考 TDesign `t-skeleton`）
3. **长期**：后端拆分为多个子端点（`/basic`、`/ai-insight`、`/records` 等），前端并行请求 + 分段渲染
