包含多个会话的累积代码变更: - backend: AI 聊天服务、触发器调度、认证增强、WebSocket、调度器最小间隔 - admin-web: ETL 状态页、任务管理、调度配置、登录优化 - miniprogram: 看板页面、聊天集成、UI 组件、导航更新 - etl: DWS 新任务(finance_area_daily/board_cache)、连接器增强 - tenant-admin: 项目初始化 - db: 19 个迁移脚本(etl_feiqiu 11 + zqyy_app 8) - packages/shared: 枚举和工具函数更新 - tools: 数据库工具、报表生成、健康检查 - docs: PRD/架构/部署/合约文档更新 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
12 KiB
小程序多页面联调改造任务书
创建日期:2026-03-27 来源:本轮对话排查 customer-service-records 页面加载问题后,用户提出的 4 页面改造需求 状态:第 1 步后端改造已完成,第 2-4 步待执行
一、背景与前提
1.1 已完成的工作(本轮对话)
-
权限守卫修复:
auth-guard.ts的PAGE_ROLES中customer-detail、customer-service-records、coach-detail三个页面加入了coach角色,解决了 coach 进入后被reLaunch踢回首页的问题。 -
ETL 连接复用:
fdw_queries.py的_fdw_context新增etl_conn可选参数,customer_service.py的get_customer_records中只创建一个 ETL 连接传给所有子查询,连接开销从 ~10s 降到 ~2.6s。 -
后端 assistant_id 过滤:
customer_service.py新增_get_assistant_id()从auth.user_assistant_binding获取 assistant_idget_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
-
后端到手收入计算:
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字段
-
BACKLOG 更新:
docs/roadmap/BACKLOG.md新增 AI BudgetTracker 和 admin_db_health UnicodeDecodeError 两条待办。 -
联调规范更新:
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_idJOINv_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(已是小时数),不再/60durationRaw直接用r.durationRawincome用r.income(后端已计算到手)displayDate优先用后端返回的r.timeRange,无值时才用generateTimeRangegenerateTimeRange的参数是小时数不是分钟数,需修正或移除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 中头像
<image>的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 时的兜底处理
三、执行顺序建议
- E3(排查到手收入为 0 的原因)→ 确认后端数据正确性
- A1 + A2 + A3(前端数据转换修复)→ 页面能正确展示数据
- A4(列表格式对齐 task-detail)→ UI 一致性
- A5(加速加载)→ 去掉多余 API 请求
- B(task-detail 折前时长)→ 小改动
- C(performance 页面)→ 独立改动
- D(头像修复)→ 独立改动
- E1 + E2(后端补充优化)→ 可选
四、验证方式
每步完成后:
- 微信开发者工具编译(Ctrl+B)
- 操作路径:任务列表 → 轩哥 → 任务详情 → 查看全部服务记录
- 用 weixin-devtools MCP power 的
evaluate_script读取页面 data 验证字段值 - 用
list_console_messages检查是否有错误 - 后端改动通过 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 解包逻辑)