# 变更审计记录：助教详情页设计稿对齐 + 数据格式化修复

| 字段 | 值 |
|------|-----|
| 日期 | 2026-03-29 09:16:43 |
| Prompt-ID | P20260329-090937 |
| Session-ID | 96ce2a05 |
| Session 路径 | docs/audit/session_logs/2026-03/29/29_abf3b322_085456 |

## 操作摘要
修复助教详情页与设计稿的多项显示差异：后端金额/数量字段从预格式化字符串改为返回原始数字（避免前端 WXS 格式化 NaN），收入 color 从 hex 值改为 CSS 类名，服务记录 table 从 table_id 改为 table_name，WXML 模板统一使用 `fmt.safe()` / `fmt.money()` / `fmt.hours()` WXS 函数格式化。同时将 coach-detail 和 customer-detail 页面的自定义加载组件替换为小程序原生 `wx.showLoading`。

## 根因分析
后端 `_format_currency()` 预格式化金额为字符串（如 "¥25,447"），前端 WXML 中 WXS `money()` 函数对字符串输入返回 NaN。违反了"TS 与 WXS 格式化互斥"规范（`frontend-backend-integration.md`）。

## 本次对话文件变更

### 新增文件
- `docs/audit/prompt_logs/prompt_log_20260329_090937.md`
- `docs/audit/session_logs/2026-03/29/29_abf3b322_085456/main_01_96ce2a05.md`

## 改动注解

### `apps/backend/app/schemas/xcx_coaches.py`
- 变更类型：修改
- 原始原因：前端 WXS `money()` 函数对已格式化的字符串（如 "¥25,447"）调用数值方法返回 NaN，需要后端返回原始数字
- 思路分析：将 `TopCustomer.balance/consume` 从 `str` → `float`，`CoachServiceRecord.income` 从 `str` → `float`，`HistoryMonth.customers` 从 `str` → `int`、`hours/salary` 从 `str` → `float`。遵循"TS 与 WXS 格式化互斥"规范，后端只返回原始数值，格式化交给前端 WXS
- 修改结果：Schema 类型与 service 层返回值对齐，Pydantic 验证通过，前端 WXS 可正确格式化数值

### `apps/backend/app/services/coach_service.py`
- 变更类型：修改
- 原始原因：多处与设计稿不一致：color 用 hex 值而非 CSS 类名、金额预格式化导致前端 NaN、table 显示 id 而非名称、TASK_TYPE_MAP 缺少 relationship_building
- 思路分析：(1) `_build_income` 中 color 从 hex（`#42A5F5` 等）改为 CSS 类名（`primary/success/warning/purple`），符合"后端值拼接 WXSS 类名时必须是合法 CSS 标识符"规范；(2) `_build_top_customers` 中 `balance/consume` 从 `_format_currency()` 改为返回原始 float；(3) `_build_service_records` 中 `income` 改为原始 float，`table` 从 `table_id` 改为 `table_name`；(4) `_build_history_months` 中 `customers/hours/salary` 改为原始数字；(5) TASK_TYPE_MAP 新增 `relationship_building` → "关系维护"；(6) `_build_notes` 中 `ai_score` → `score` 对齐 Schema；(7) 添加 `@trace_service` 装饰器
- 修改结果：所有数值字段返回原始类型，前端 WXS 可正确格式化；color 值为合法 CSS 类名；table 显示人类可读名称

### `apps/backend/app/services/fdw_queries.py`
- 变更类型：修改
- 原始原因：`get_assistant_info` 的 hire_date 带时区后缀、`get_coach_top_customers` 未过滤散客、`get_coach_service_records` 缺少 table_name
- 思路分析：(1) `get_assistant_info` 中 hire_date 从 `str(row[3])` 改为 `.strftime("%Y-%m-%d")`，去除时区信息；(2) `get_coach_top_customers` 添加 `tenant_member_id > 0` 过滤散客（散客 member_id ≤ 0，符合飞球数据规范）；(3) `get_coach_service_records` LEFT JOIN `v_dim_table` 获取 `table_name`，返回 `table_name` 字段替代 `table_id`
- 修改结果：hire_date 格式统一为 YYYY-MM-DD；TOP 客户列表不再包含散客；服务记录显示台桌名称而非 ID

### `apps/miniprogram/miniprogram/pages/coach-detail/coach-detail.wxml`
- 变更类型：修改
- 原始原因：(1) 使用自定义加载组件（`g-toast-loading` + `t-loading`），需改为原生 `wx.showLoading`；(2) 数据展示未使用 WXS 格式化函数；(3) 更多信息表格 hours 格式化从 `fmt.hours` 改为 `fmt.hoursH`（"小时"→"h"）
- 思路分析：移除自定义加载 DOM，引入 `format.wxs`，所有数据绑定统一使用 `fmt.safe()` / `fmt.money()` / `fmt.hours()` WXS 函数，确保 null/undefined 值安全显示
- 修改结果：页面加载使用原生遮罩，数据展示统一通过 WXS 格式化，避免 NaN 和 undefined 显示

### `apps/miniprogram/miniprogram/pages/customer-detail/customer-detail.wxml`
- 变更类型：修改
- 原始原因：使用自定义加载组件，需改为原生 `wx.showLoading`；数据展示未统一使用 WXS 格式化
- 思路分析：与 coach-detail 同步改造，移除自定义加载 DOM，统一使用 `fmt.safe()` / `fmt.money()` / `fmt.hours()` WXS 函数格式化所有数据绑定
- 修改结果：页面加载使用原生遮罩，金额/时长/文本统一通过 WXS 格式化

### `apps/miniprogram/miniprogram/pages/customer-records/customer-records.wxml`
- 变更类型：修改
- 原始原因：消费记录页面数据展示未统一使用 WXS 格式化，金额直接拼接 `¥` 前缀
- 思路分析：所有金额字段改用 `fmt.money()` 格式化，文本字段用 `fmt.safe()` 防护，时长用 `fmt.hours()` 格式化
- 修改结果：消费记录页面数据展示统一通过 WXS 格式化，与其他页面保持一致

## 合规检查

| 检查项 | 状态 |
|--------|------|
| 新增迁移 SQL | 无 |
| DDL 基线 | N/A |
| BD 手册 | N/A（无 DB schema 变更） |
| ⚠️ OpenAPI spec | 接口返回类型已变更但 spec 未同步。自动导出失败（`dashscope` 模块缺失），需手动在后端 venv 中执行 `python scripts/ops/_export_openapi.py` |

## 风险评估
- 影响范围：助教详情页（COACH-1）、客户详情页、消费记录页
- 前后端契约变更：`TopCustomer.balance/consume`、`CoachServiceRecord.income`、`HistoryMonth.customers/hours/salary` 类型从 str 变为数值类型，前端已同步适配 WXS 格式化
- 向后兼容：前端已同步修改，无兼容性问题
