# 小程序多页面联调改造任务书 > 创建日期:2026-03-27 > 来源:本轮对话排查 customer-service-records 页面加载问题后,用户提出的 4 页面改造需求 > 状态:第 1 步后端改造已完成,第 2-4 步待执行 --- ## 一、背景与前提 ### 1.1 已完成的工作(本轮对话) 1. **权限守卫修复**:`auth-guard.ts` 的 `PAGE_ROLES` 中 `customer-detail`、`customer-service-records`、`coach-detail` 三个页面加入了 `coach` 角色,解决了 coach 进入后被 `reLaunch` 踢回首页的问题。 2. **ETL 连接复用**:`fdw_queries.py` 的 `_fdw_context` 新增 `etl_conn` 可选参数,`customer_service.py` 的 `get_customer_records` 中只创建一个 ETL 连接传给所有子查询,连接开销从 ~10s 降到 ~2.6s。 3. **后端 assistant_id 过滤**: - `customer_service.py` 新增 `_get_assistant_id()` 从 `auth.user_assistant_binding` 获取 assistant_id - `get_customer_records` 签名新增 `user_id` 参数,内部获取 assistant_id 后传给所有 fdw_queries 调用 - `fdw_queries.get_customer_service_records` 和 `get_total_service_count` 加入可选 `assistant_id` 过滤(`site_assistant_id = %s`) - `_get_month_aggregation` 同步加入 `assistant_id` 过滤 - 路由层 `xcx_customers.py` 传入 `user.user_id` 4. **后端到手收入计算**: - `get_customer_records` 中查询 `v_dws_assistant_salary_calc` 获取当月费率 - 基础课到手 = hours × (base_course_price - base_deduction) - 激励课到手 = hours × bonus_course_price × (1 - bonus_deduction_ratio) - 充值提成到手 = ledger_amount(直接取) - Schema 新增 `month_income: float` 字段 5. **BACKLOG 更新**:`docs/roadmap/BACKLOG.md` 新增 AI BudgetTracker 和 admin_db_health UnicodeDecodeError 两条待办。 6. **联调规范更新**:`docs/guides/FRONTEND-BACKEND-INTEGRATION.md` 和 `.kiro/steering/frontend-backend-integration.md` 新增 ETL 连接复用规则。 ### 1.2 已识别但未修复的数据问题 通过开发者工具读取页面 data 发现以下问题: | # | 问题 | 原因 | 影响 | |---|------|------|------| | 1 | `customerName` 为空 | `loadCustomerInfo` 调的 `get_customer_detail` 还没做 ETL 连接复用优化,可能超时 | 顶部不显示客户名 | | 2 | `totalServiceCount: 0` | 后端 `get_total_service_count` 返回 0,需排查 assistant_id 是否正确传入 | 顶部"服务X次"显示 0 | | 3 | `monthHours: "0.0h"` | 后端返回 `duration` 是小时数(如 1.1),前端 `loadMonthRecords` 中 `rawRecords.reduce` 累加的是 `r.duration`,但后端字段名是 `duration`(CamelModel 输出 camelCase),需确认字段映射 | 月度时长显示 0 | | 4 | `income: 0` | 后端到手收入计算中 `salary_calc` 可能没查到数据(assistant_id 为 None 或费率为 0) | 列表收入全部显示 0 | | 5 | `displayDate` 格式错误 | 前端 `generateTimeRange` 把 `r.duration`(小时数)当分钟数处理,导致 `19:1.116...` | 时间显示乱码 | | 6 | `table` 显示 ID | 后端返回 `table_id`(数字 ID),前端直接显示,应该显示台桌名称 | 台桌号显示为长数字 | | 7 | `durationHours: 0` | 前端 `(r.duration \|\| 0) / 60` 把小时数再除以 60 变成了接近 0 的值 | 列表时长显示 0 | ### 1.3 开发环境 - Windows + PowerShell - 后端:`start-admin.bat` 启动(uvicorn --reload 端口 8000) - 前端:微信开发者工具(自动化端口 9420) - 数据库:ETL 库(PG_DSN)+ 业务库(APP_DB_DSN),ETL 连接耗时 ~2.6s - 可用调试工具:weixin-devtools MCP power(连接 `ws://127.0.0.1:9420`) ### 1.4 关键文件位置 | 文件 | 说明 | |------|------| | `apps/miniprogram/miniprogram/pages/customer-service-records/customer-service-records.ts` | 前端页面逻辑 | | `apps/miniprogram/miniprogram/pages/customer-service-records/customer-service-records.wxml` | 前端页面模板 | | `apps/miniprogram/miniprogram/pages/customer-service-records/customer-service-records.wxss` | 前端页面样式 | | `apps/miniprogram/miniprogram/services/api.ts` | 前端 API 调用层 | | `apps/miniprogram/miniprogram/utils/request.ts` | 前端请求封装(含 ResponseWrapper 解包) | | `apps/miniprogram/miniprogram/utils/auth-guard.ts` | 权限守卫 | | `apps/miniprogram/miniprogram/pages/task-detail/task-detail.ts` | 任务详情页(参考:60天服务记录列表格式) | | `apps/miniprogram/miniprogram/pages/task-detail/task-detail.wxml` | 任务详情页模板(参考:service-record-card) | | `apps/miniprogram/miniprogram/pages/performance/performance.ts` | 绩效页 | | `apps/miniprogram/miniprogram/pages/performance/performance.wxml` | 绩效页模板 | | `apps/miniprogram/miniprogram/pages/task-list/task-list.ts` | 任务列表页 | | `apps/miniprogram/miniprogram/pages/task-list/task-list.wxml` | 任务列表页模板 | | `apps/backend/app/services/customer_service.py` | 后端客户服务 | | `apps/backend/app/services/fdw_queries.py` | 后端 FDW 查询 | | `apps/backend/app/routers/xcx_customers.py` | 后端客户路由 | | `apps/backend/app/schemas/xcx_customers.py` | 后端响应 Schema | | `apps/backend/app/routers/xcx_auth.py` | 后端认证路由(/api/xcx/me) | | `apps/backend/app/auth/dependencies.py` | JWT 解析(CurrentUser) | ### 1.5 关键数据口径 - **到手收入**:基础课 = hours × (base_course_price - base_deduction);激励课 = hours × bonus_course_price × (1 - bonus_deduction_ratio);充值提成 = ledger_amount - **费率来源**:`v_dws_assistant_salary_calc`,按 `assistant_id + salary_month` 查询 - **DWD 层助教字段**:`site_assistant_id`(与 `auth.user_assistant_binding.assistant_id` 相同) - **废单排除**:`is_delete = 0` - **会员信息**:通过 `member_id` JOIN `v_dim_member (scd2_is_current=1)` - **CamelModel**:后端 Pydantic schema 自动将 snake_case 转 camelCase 输出 - **ResponseWrapper**:后端中间件将 2xx JSON 包装为 `{ code: 0, data: <原始body> }`,前端 `request.ts` 自动解包 --- ## 二、待执行任务 ### 任务 A:customer-service-records 前端改造(优先级最高) #### A1. 修复数据转换逻辑 前端 `loadMonthRecords` 中的字段映射需要对齐后端 CamelModel 输出: ``` 后端返回(camelCase) 前端当前读取 问题 ───────────────────────────────────────────────── duration (float, 小时) r.duration / 60 多除了一次 60 durationRaw (float, 小时) 未使用 应直接用 income (float, 到手元) r.amount 字段名不匹配 timeRange (string|null) 未使用 后端已返回,前端用 generateTimeRange 自己算 courseType (string) r.courseType ✓ 正确 table (string|null) r.table 后端返回 table_id 数字,需要改为台桌名 recordType (string) 前端自己判断 后端已返回,应直接用 isEstimate (bool) r.isEstimate ✓ 正确 drinks (string|null) r.drinks 后端当前返回 null,后续需补齐 ``` 修改要点: - `durationHours` 直接用 `r.duration`(已是小时数),不再 `/60` - `durationRaw` 直接用 `r.durationRaw` - `income` 用 `r.income`(后端已计算到手) - `displayDate` 优先用后端返回的 `r.timeRange`,无值时才用 `generateTimeRange` - `generateTimeRange` 的参数是小时数不是分钟数,需修正或移除 - `table` 字段:后端当前返回 `table_id`(数字),需要后端改为返回台桌名称,或前端做映射 #### A2. 修复月度统计 - `monthHours` 累加逻辑:`rawRecords.reduce((sum, r) => sum + (r.duration || 0), 0)` — 后端返回的 `duration` 已是小时数,不需要再 `/60` - 新增 `monthIncome`:从后端返回的 `monthIncome` 字段读取并展示 #### A3. 修复顶部信息 - `customerName`:`loadCustomerInfo` 调的 `fetchCustomerDetail` 超时问题 — 方案 1:给 `get_customer_detail` 也做 ETL 连接复用;方案 2:从 `get_customer_records` 的返回值中直接取 `customerName`(后端已返回) - `totalServiceCount`:从 `get_customer_records` 返回值中取(后端已返回 `totalServiceCount`) - 手机号查看/复制交互:对齐 task-detail 页面的实现 - 首字母头像:使用通用颜色规则(`nameToAvatarColor`) #### A4. 列表记录格式对齐 task-detail - 对齐 task-detail 的"60天内服务记录"列表的 `service-record-card` 组件格式 - 确认 `service-record-card` 组件是否已存在,还是内联在 task-detail 的 WXML 中 - 到手收入显示:`¥XXX` - 课程类型标签:基础课/激励课/充值 - 折前/折后时长:`durationRaw !== durationHours` 时才显示折前 #### A5. 加速加载 - `loadCustomerInfo` 可以去掉(改为从 `loadMonthRecords` 的返回值中取 `customerName` 和 `customerPhone`) - 减少一个 API 请求 = 减少一次 ETL 连接开销 --- ### 任务 B:task-detail 折前时长条件显示 文件:`apps/miniprogram/miniprogram/pages/task-detail/task-detail.wxml` - 找到服务记录列表中显示"折前X.Xh"的位置 - 加条件判断:`wx:if="{{item.durationRaw && item.durationRaw !== item.durationHours}}"` - 折前和折后相等时隐藏折前标签 --- ### 任务 C:performance 页面改造 文件:`apps/miniprogram/miniprogram/pages/performance/performance.ts` + `.wxml` #### C1. 未知客户点击拦截 - 找到客户卡片点击跳转逻辑 - 判断 `memberId <= 0`(散客/未知客户)时,`wx.showToast({ title: '未知客户不提供查看客户任务详情', icon: 'none' })` 并 return - 非散客正常跳转 #### C2. 角色标签对齐 task-list - 查看 task-list 页面的角色标签展示方式(WXML + WXSS) - 对齐 performance 页面顶部的角色标签样式 --- ### 任务 D:头像修复(performance + task-list) 文件:`performance.ts`、`task-list.ts`、对应 `.wxml` - 后端 `/api/xcx/me` 已返回 `avatarUrl` 字段(apply 时上传的头像) - 前端 `fetchMe()` 返回的 `data.avatarUrl` 需要绑定到页面 data - WXML 中头像 `` 的 `src` 应优先用 `avatarUrl`,无值时 fallback 到默认占位图或首字母头像 - 确认 task-list 和 performance 两个页面的 banner 区域头像都使用此逻辑 --- ### 任务 E:后端补充优化(可选) #### E1. get_customer_detail ETL 连接复用 - 与 `get_customer_records` 相同的模式:创建一个 ETL 连接传给所有 fdw_queries 调用 - 涉及的子查询:`get_member_info`、`get_member_balance`、`get_consumption_60d`、`get_last_visit_days`、`_build_consumption_records`、`_build_coach_tasks`、`_build_favorite_coaches` #### E2. 台桌名称返回 - 当前 `get_customer_service_records` 返回 `site_table_id`(数字 ID) - 需要 JOIN `v_dim_table`(或类似维度表)获取台桌名称 - 或者前端做映射(不推荐,增加前端复杂度) #### E3. 到手收入排查 - 确认 `_get_assistant_id` 返回值是否正确(可能 `user_assistant_binding` 中没有对应记录) - 确认 `get_salary_calc` 是否能查到当月数据(可能该月还没有 salary_calc 记录) - 费率为 0 时的兜底处理 --- ## 三、执行顺序建议 1. **E3**(排查到手收入为 0 的原因)→ 确认后端数据正确性 2. **A1 + A2 + A3**(前端数据转换修复)→ 页面能正确展示数据 3. **A4**(列表格式对齐 task-detail)→ UI 一致性 4. **A5**(加速加载)→ 去掉多余 API 请求 5. **B**(task-detail 折前时长)→ 小改动 6. **C**(performance 页面)→ 独立改动 7. **D**(头像修复)→ 独立改动 8. **E1 + E2**(后端补充优化)→ 可选 --- ## 四、验证方式 每步完成后: 1. 微信开发者工具编译(Ctrl+B) 2. 操作路径:任务列表 → 轩哥 → 任务详情 → 查看全部服务记录 3. 用 weixin-devtools MCP power 的 `evaluate_script` 读取页面 data 验证字段值 4. 用 `list_console_messages` 检查是否有错误 5. 后端改动通过 uvicorn `--reload` 自动加载,看后端窗口有无报错 --- ## 五、参考文档 - `docs/guides/FRONTEND-BACKEND-INTEGRATION.md` — 联调规范(字段命名、到手收入口径、ETL 连接复用等) - `.kiro/steering/frontend-backend-integration.md` — 联调规范摘要 - `.kiro/steering/feiqiu-data-rules.md` — 飞球数据规范索引 - `apps/backend/app/schemas/xcx_customers.py` — 后端响应 Schema(CamelModel 自动转 camelCase) - `apps/miniprogram/miniprogram/utils/request.ts` — 前端请求封装(ResponseWrapper 解包逻辑)