# 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` 等),前端并行请求 + 分段渲染