# 看板 & 详情页差距分析与实施指南 > 调研日期:2026-03-28 > 范围:小程序看板(助教/客户列表)、助教详情页、客户详情页 > 目的:全面梳理前后端完成度差距,为后续联调实施提供完整依据 --- ## 一、总体完成度 | 模块 | 后端接口 | 前端页面 | 真实数据对接 | 完成度 | 主要差距 | |------|---------|---------|-------------|--------|---------| | 助教看板 BOARD-1 | ✅ | ✅ | ❌ Mock | ~60% | 前端用 Mock;skills 为空;环比字段为 None | | 客户看板 BOARD-2 | ✅ | ✅ | ❌ Mock | ~60% | 前端用 Mock;loyal 缺 coachDetails;排序规则需修正 | | 助教详情 COACH-1 | ✅ | ✅ | ⚠️ 部分 | ~40% | API 已调用但 Mock 覆盖大部分字段 | | 客户详情 CUST-1 | ✅ | ✅ | ⚠️ 部分 | ~35% | API 已调用但只映射了 id/name/phone | | 客户服务记录 CUST-2 | ✅ | ✅ | ✅ 真实 | ~90% | 已完成联调 | | 看板 Tab 切换 | N/A | ✅ | N/A | 需重构 | 当前是页面跳转,需改为同页切换 | --- ## 二、问题清单(按优先级排列) ### P1:看板 Tab 切换改为同页切换 **现状**:三个看板页面(board-finance / board-customer / board-coach)各自独立,通过 `wx.navigateTo()` 或 `wx.switchTab()` 跳转。 **问题**: - `board-coach.ts` 第 245 行 `onTabChange()`:`wx.navigateTo` / `wx.switchTab` - `board-customer.ts` 第 277 行 `onTabChange()`:同上 - `board-finance.ts` 第 473 行 `onTabChange()`:同上 **目标**:改为同一页面内的 tab 切换(无页面跳转感),类似 `wx:if` 或 `hidden` 控制显示/隐藏。 **实施方案选项**: 1. **方案 A:合并为单页**:将三个看板合并到一个页面(如 `board/board`),用 `wx:if="{{activeTab === 'finance'}}"` 切换内容区。优点:切换无延迟;缺点:单页代码量大,首次加载慢。 2. **方案 B:`wx.redirectTo` 替代**:用 `wx.redirectTo` 替代 `wx.navigateTo`,避免页面栈堆积。优点:改动最小;缺点:仍有页面切换闪烁。 3. **方案 C:组件化**:将三个看板内容抽为自定义组件,在一个容器页面中按 tab 切换。优点:代码隔离好、切换流畅;缺点:需要重构。 **建议**:方案 C(组件化),兼顾代码隔离和切换体验。 **影响范围**: - 前端:`pages/board-finance/`、`pages/board-customer/`、`pages/board-coach/` → 抽为组件 - 路由:`app.json` 页面注册需调整 - 权限守卫:`checkPageAccess` 需适配新路由 - custom-tab-bar:如果 board-finance 是 tabBar 页面,需要调整 --- ### P2:助教详情页 Mock → 真实 API 映射 **现状**(`coach-detail.ts` 第 297-370 行 `loadData()`): - 已调用 `fetchCoachDetail(id)` 获取后端数据 - 但只映射了 `id` 和 `name`,其余全部用 `mockCoachDetail` 覆盖 - 档位节点硬编码为 `[0, 100, 130, 160, 190, 220]`(实际应为 `[0, 120, 150, 180, 210]`) **后端 Schema**(`CoachDetailResponse`)已返回的完整字段: ``` id, name, avatar, level, skills, work_years, customer_count, hire_date, performance { monthly_hours, monthly_salary, customer_balance, tasks_completed, perf_current, perf_target }, income { this_month[], last_month[] }, tier_nodes[], visible_tasks[], hidden_tasks[], abandoned_tasks[], top_customers[], service_records[], history_months[], notes[] ``` **前端需要映射但当前被 Mock 覆盖的字段**: | 字段 | Mock 变量 | 说明 | |------|----------|------| | `tier_nodes` | 硬编码 `[0, 100, 130, 160, 190, 220]` | 档位节点,后端从 `cfg_performance_tier` 读取 | | `visible_tasks` | `mockVisibleTasks` | 可见任务列表 | | `hidden_tasks` | `mockHiddenTasks` | 隐藏任务列表 | | `abandoned_tasks` | `mockAbandonedTasks` | 已放弃任务列表 | | `top_customers` | `mockTopCustomers` | TOP 客户列表 | | `service_records` | `mockServiceRecords` | 近期服务记录 | | `history_months` | `mockHistoryMonths` | 历史月份数据 | | `notes` | Mock 内嵌 | 备注列表(已部分映射) | | `income` | Mock 内嵌 | 收入明细(本月/上月) | | `performance` 全部子字段 | `mockCoachDetail.performance` | 绩效数据 | **实施要点**: 1. 移除 `mockCoachDetail` 及所有 `mock*` 变量 2. 直接使用 `fetchCoachDetail(id)` 返回的完整对象 3. 注意 camelCase 转换(后端 snake_case → 前端 camelCase 自动转换) 4. `tier_nodes` 使用后端返回值,fallback 用 `_FALLBACK_TIER_NODES = [0, 120, 150, 180, 210]` 5. 数值字段传原始数字给 `setData`,WXML 中用 WXS 格式化(TS 与 WXS 格式化互斥规则) 6. null 值清洗:`?? 0` / `?? ''`(组件 property 收到 null 不走默认值) --- ### P3:客户详情页 Mock → 真实 API 映射 **现状**(`customer-detail.ts` 第 97-120 行 `loadDetail()`): - 已调用 `fetchCustomerDetail(id)` 获取后端数据 - 只映射了 `id`、`name`、`phone` 三个字段 - 其余模块(Banner、AI 洞察、维客线索、助教任务、最亲密助教、消费记录、备注)全部使用 `data` 中的初始空值 **后端 Schema**(`CustomerDetailResponse`)已返回的完整字段: ``` id, name, phone, phone_full, avatar, member_level, relation_index, tags[], balance, consumption_60d, ideal_interval, days_since_visit, ai_insight { summary, strategies[] }, coach_tasks[], favorite_coaches[], retention_clues[], consumption_records[], notes[] ``` **前端需要映射但当前未映射的字段**: | 字段 | 前端 data key | 说明 | |------|-------------|------| | `phone_full` | `detail.phone` | 完整手机号(用于复制) | | `avatar` | 未使用 | 头像 URL | | `member_level` | 未使用 | 会员等级 | | `relation_index` | 未使用 | 关系指数 | | `tags` | 未使用 | 标签数组 | | `balance` | `detail.balance` | 卡余额 | | `consumption_60d` | `detail.consumption60d` | 60天消费 | | `ideal_interval` | `detail.idealInterval` | 理想到店间隔 | | `days_since_visit` | `detail.daysSinceVisit` | 距上次到店天数 | | `ai_insight` | `aiInsight` | AI 洞察(暂不处理) | | `coach_tasks` | `coachTasks` | 关联助教任务 | | `favorite_coaches` | `favoriteCoaches` | 最亲密助教 | | `retention_clues` | `clues` | 维客线索 | | `consumption_records` | `consumptionRecords` | 消费记录 | | `notes` | `sortedNotes` | 备注 | **实施要点**: 1. `loadDetail` 中将 `fetchCustomerDetail(id)` 返回的完整对象映射到 `data` 2. AI 相关字段(`ai_insight`)暂不处理,保持空值 3. Banner 四项指标(balance / consumption60d / idealInterval / daysSinceVisit)直接映射 4. 子模块(coachTasks / favoriteCoaches / clues / consumptionRecords / notes)直接映射 5. null 值清洗规则同 P2 --- ### P4:助教看板 Mock → 真实 API **现状**(`board-coach.ts` 第 195-215 行 `loadData()`): - 使用 `setTimeout` + `MOCK_COACHES` 模拟加载 - 未调用任何 API **后端接口**:`GET /api/xcx/board/coaches?sort=perf_desc&skill=ALL&time=month` **后端返回结构**: ```json { "items": [ { "id": 123, "name": "张三", "initial": "张", "avatar_gradient": "", "level": "senior", "skills": [], "top_customers": ["李四", "王五"], "perf_hours": 156.5, "perf_hours_before": null, "perf_gap": null, "perf_reached": false, "salary": 18500.0, "salary_perf_hours": 156.5, "salary_perf_before": null, "sv_amount": 45000.0, "sv_customer_count": 12, "sv_consume": 8500.0, "task_recall": 5, "task_callback": 8 } ], "dim_type": "perf" } ``` **实施要点**: 1. 替换 `setTimeout` + `MOCK_COACHES` 为真实 API 调用 2. 筛选参数(sort / skill / time)传给 API 3. 筛选变更时重新请求(当前只在前端过滤 Mock 数据) 4. 注意 `skills` 字段当前为空数组(见 P6) 5. `perf_hours_before` / `salary_perf_before` 当前为 null(见 P7) --- ### P5:客户看板 Mock → 真实 API **现状**(`board-customer.ts` 第 218-235 行 `loadData()`): - 使用 `setTimeout` + `MOCK_CUSTOMERS` 模拟加载 - 未调用任何 API **后端接口**:`GET /api/xcx/board/customers?dimension=recall&project=ALL&page=1&page_size=20` **实施要点**: 1. 替换 Mock 为真实 API 调用 2. 维度切换时重新请求(不同维度调用不同 FDW 查询函数) 3. 项目筛选传给 API 4. 分页支持(page / page_size) 5. 注意前端字段名与后端返回的映射(camelCase 自动转换) **客户看板前端字段 → 后端字段映射**: | 前端字段 | 后端字段 | 维度 | |---------|---------|------| | `idealDays` | `ideal_days` | recall | | `elapsedDays` | `elapsed_days` | recall | | `overdueDays` | `overdue_days` | recall | | `visits30d` | `visits_30d` | recall | | `balance` | `balance` | recall / balance | | `recallIndex` | `recall_index` | recall | | `spend30d` | `spend_30d` | potential | | `avgVisits` | `avg_visits` | potential | | `avgSpend` | `avg_spend` | potential | | `lastVisit` | `last_visit` | balance / recent | | `monthlyConsume` | `monthly_consume` | balance | | `availableMonths` | `available_months` | balance | | `lastRecharge` | `last_recharge` | recharge | | `rechargeAmount` | `recharge_amount` | recharge | | `recharges60d` | `recharges_60d` | recharge | | `currentBalance` | `current_balance` | recharge | | `spend60d` | `spend_60d` | spend60 | | `visits60d` | `visits_60d` | spend60 / freq60 | | `avgInterval` | `avg_interval_days` | freq60 | | `weeklyVisits` | `weekly_visits` | freq60 | | `intimacy` | `intimacy` | loyal | | `topCoachName` | `top_coach_name` | loyal | | `topCoachHeart` | `top_coach_heart` | loyal | | `coachDetails` | ❌ 后端未返回 | loyal | | `daysAgo` | `days_ago` | recent | | `assistants` | `assistants` | 所有维度 | --- ### P6:助教 Skills 字段为空 **现状**: - 后端 `get_coach_board()` 返回 `"skills": []`(第 316 行注释:`v_dim_assistant 无 skill 列,暂返回空`) - 后端 `get_coach_detail()` 同样返回空 skills - 前端 WXML 已有 skills 标签渲染逻辑 **调研发现——"技能"的两层含义**: 1. **课程类型(skill_id)**:指助教能教的课程类型 - 基础课(陪打/PD):`skill_id = 2791903611396869` - 附加课(超休/CX):`skill_id = 2807440316432197` - 包厢课:归入基础课口径 - 配置表:`cfg_skill_type`(`skill_id → course_type_code: BASE/BONUS/ROOM`) - 这不是看板需要展示的"技能" 2. **项目类型(area_category)**:指助教擅长的运动项目 - BILLIARD(🎱 中式/追分)、SNOOKER(斯诺克)、MAHJONG(🀄 麻将/棋牌)、KTV(🎤 团建/K歌) - 配置表:`cfg_area_category`(台桌 → 区域 → 项目类型映射) - 视图:`app.v_cfg_area_category`(去重到 category 级别) - **这才是看板需要展示的"技能标签"** **看板中 skills 的业务含义**: - 前端 `SKILL_CLASS` 映射:`'🎱' → 'skill--chinese'`、`'斯' → 'skill--snooker'`、`'🀄' → 'skill--mahjong'`、`'🎤' → 'skill--karaoke'` - 即:助教擅长哪些项目类型(台球/斯诺克/麻将/KTV) **数据来源方案**: - 从 `dwd_assistant_service_log` 按助教聚合历史服务的 `area_category_code`,取去重后的项目类型列表 - SQL 示例: ```sql SELECT DISTINCT asl.area_category_code FROM dwd.dwd_assistant_service_log asl WHERE asl.assistant_id = %s AND asl.is_delete = 0 AND asl.area_category_code IS NOT NULL ``` - 或者:在 `v_dim_assistant` 视图中新增 `skills` 列(从服务记录聚合) **实施建议**: 1. 在 `fdw_queries` 中新增 `get_assistant_skills_batch(conn, site_id, assistant_ids)` 函数 2. 从 `v_dwd_assistant_service_log` 按助教聚合 `area_category_code` 3. 映射为前端需要的格式:`["BILLIARD", "SNOOKER"]` 4. 在 `get_coach_board()` 和 `get_coach_detail()` 中调用并填充 `skills` 字段 --- ### P7:助教看板"折前"字段(perf_hours_before / salary_perf_before) **现状**: - 后端 `get_coach_board()` 返回 `perf_hours_before: None`、`salary_perf_before: None` - 前端 WXML 已有条件渲染:`wx:if="{{item.perfHoursBeforeLabel}}"` 显示"折前 XX.Xh" **业务含义**: - `perf_hours` = 折算后的定档业绩课时(effective_hours) - `perf_hours_before` = 折算前的原始课时(base_hours + bonus_hours + room_hours) - 折算规则:同一台桌同一时段 >2 名助教重叠挂台时,计算 `per_hour_contribution = base_ledger_amount / base_hours / overlap_count`,若 < 24 元/小时则按比例扣减 `penalty_minutes` - 豁免条件:`is_exempt = true`(客人真实需求,前台核实并绑定客人信息) - 只要 `effective_hours != raw_hours` 就显示折前课时,与新入职无关 **注意**:这不是"环比",而是"折算前 vs 折算后"的对比。前端 WXML 中的 `perfHoursBeforeLabel` 显示的是"折前 XX.Xh",不是"上期 XX.Xh"。 **数据来源**: - `dws_assistant_salary_calc` 表中应有 `raw_hours`(折算前)和 `effective_hours`(折算后) - 需确认 DWS 表是否已有 `raw_hours` 字段 - 如果没有,需要在 DWS 任务中补充计算 --- ### P8:客户看板 loyal 维度缺少 coachDetails 子数组 **现状**: - 前端 WXML 已有完整的助教服务明细表渲染(`board-customer.wxml` 第 254-277 行) - 表头:助教 | 次均时长 | 服务次数 | 助教消费 | 关系指数 - 数据绑定:`wx:for="{{item.coachDetails}}"` - 但后端 `get_customer_board_loyal()` 只返回了 `top_assistant_id` 和 `top_coach_name`,没有返回 `coach_details` 数组 **用户明确的排列规则**: - 客户-助教对,RSI 从高到低排列 - 客户不重复,排重规则是放在最高对的位置 - 每个卡片展示客户信息 + 该客户对应所有助教 RSI 值从高到低排列 - 右上位置展示最高 RSI 值助教 **当前后端实现**(`fdw_queries.py` 第 2272-2340 行): ```sql WITH member_top AS ( SELECT ri.member_id, MAX(ri.rs_display) AS max_rs, (ARRAY_AGG(ri.assistant_id ORDER BY ri.rs_display DESC))[1] AS top_assistant_id, (ARRAY_AGG(ri.rs_display ORDER BY ri.rs_display DESC))[1] AS top_rs FROM app.v_dws_member_assistant_relation_index ri GROUP BY ri.member_id ORDER BY MAX(ri.rs_display) DESC LIMIT %s OFFSET %s ) ``` - ✅ 排序正确:按 `max_rs DESC`(最高 RSI 降序) - ✅ 客户不重复:`GROUP BY ri.member_id` - ✅ 右上角最高 RSI 助教:`top_assistant_id` + `top_coach_name` - ❌ 缺少:每个客户对应的所有助教明细 **需要补充的后端逻辑**: 在 `get_customer_board_loyal()` 返回 items 后,批量查询每个客户的所有助教 RSI 明细: ```sql SELECT ri.member_id, ri.assistant_id, COALESCE(da.real_name, da.nickname, '') AS name, ri.rs_display, ri.service_count, ri.total_hours, ri.total_income FROM app.v_dws_member_assistant_relation_index ri LEFT JOIN app.v_dim_assistant da ON ri.assistant_id = da.assistant_id AND da.scd2_is_current = 1 WHERE ri.member_id = ANY(%s) ORDER BY ri.member_id, ri.rs_display DESC ``` **前端 coachDetails 字段结构**: ```typescript interface CoachDetail { name: string // 助教姓名 cls: string // 样式类 heartScore: number // RSI 值(0-10) badge?: string // "跟" / "弃" avgDuration: string // 次均时长 serviceCount: string // 服务次数 coachSpend: string // 助教消费金额 relationIdx: number // 关系指数 } ``` **计算规则**: - `avgDuration` = `total_hours / service_count`(次均时长) - `serviceCount` = `service_count`(服务次数) - `coachSpend` = `total_income`(助教消费金额,即该客户在该助教处的消费) - `relationIdx` = `rs_display`(RSI 值) - `heartScore` = `rs_display`(用于 heart-icon 组件) - `badge`:跟/弃标记来源待确认(可能来自 `coach_tasks` 表的任务状态) --- ### P9:客户看板项目筛选枚举不一致 **现状**: - `board-customer.ts` 的 `PROJECT_OPTIONS` 使用旧枚举值: ```typescript { value: 'all', text: '全部' }, { value: 'chinese', text: '🎱 中式/追分' }, { value: 'snooker', text: '斯诺克' }, { value: 'mahjong', text: '🀄 麻将/棋牌' }, { value: 'karaoke', text: '🎤 团建/K歌' }, ``` - `board-coach.ts` 的 `SKILL_OPTIONS` 已修正为数据库枚举值(2026-03-20 修复): ```typescript { value: 'ALL', text: '不限' }, { value: 'BILLIARD', text: '🎱 中式/追分' }, { value: 'SNOOKER', text: '斯诺克' }, { value: 'MAHJONG', text: '🀄 麻将/棋牌' }, { value: 'KTV', text: '🎤 团建/K歌' }, ``` **问题**:客户看板的 `project` 参数值(`all/chinese/snooker/mahjong/karaoke`)与后端枚举(`ALL/BILLIARD/SNOOKER/MAHJONG/KTV`)不一致,API 调用会失败。 **修复**:将 `PROJECT_OPTIONS` 的 value 改为 `ALL/BILLIARD/SNOOKER/MAHJONG/KTV`。 --- ## 三、环比字段分布 根据调研,环比字段的分布如下: ### 3.1 财务看板(BOARD-3)— 已实现环比 后端 `get_finance_board()` 接受 `compare` 参数(0/1),当 `compare=1` 时计算环比。 环比字段分布(`xcx_board.py` Schema): - `OverviewPanel`:8 项核心指标各有 `*_compare` / `*_down` / `*_flat` 三元组 - `RechargePanel`:储值卡各项 + 全类别余额合计 - `RevenuePanel`:总发生额 / 优惠总计 / 确认收入 - `CashflowPanel`:充值合计 - `ExpensePanel`:支出合计 - `CoachAnalysisPanel`:pay / share / hourly 各有环比 前端 WXML 使用 `fmt.compareText()` + `fmt.compareClass()` WXS 函数渲染。 ### 3.2 助教看板(BOARD-1)— 非环比字段 `perf_hours_before` / `salary_perf_before` 不是环比,而是"折算前课时": - 前端 WXML 显示为"折前 XX.Xh" - 只有新入职助教才有折算值 - 数据来源:DWS `dws_assistant_salary_calc` 的 `raw_hours` 字段 ### 3.3 客户看板(BOARD-2)— 无环比 当前无环比字段设计。 ### 3.4 助教详情页(COACH-1)— 无环比 当前无环比字段设计。 ### 3.5 客户详情页(CUST-1)— 无环比 当前无环比字段设计。 --- ## 四、统计规则详解 ### 4.1 助教看板四维度 #### 定档业绩维度(perf) - 排序字段:`effective_hours`(折算后工时) - 升序/降序:`perf_desc` / `perf_asc` - 卡片展示:定档课时、折前课时(新入职才有)、距升档差距、是否达标 - 数据来源:`dws_assistant_salary_calc.effective_hours` #### 工资维度(salary) - 排序字段:`gross_salary` - 计算公式:`assistant_pd_money_total + assistant_cx_money_total + bonus_money + room_income` - 卡片展示:工资金额、定档课时、折前课时 - 数据来源:`dws_assistant_salary_calc` #### 客源储值维度(sv) - 排序字段:`sv_amount`(客户余额合计) - 卡片展示:储值金额、储值客户数、储值消耗 - 数据来源:`fdw_queries.get_coach_sv_data()` - 互斥规则:`time=last_6m` 时不支持此维度(HTTP 400) #### 任务维度(task) - 排序字段:`task_total`(recall + callback) - 卡片展示:回访完成数、召回完成数 - 数据来源:业务库 `biz.coach_tasks` 按 `task_type` 分类统计 ### 4.2 客户看板八维度 #### 最应召回(recall) - 排序:自定义排序(综合理想间隔、已过天数、余额等因素) - 展示:理想间隔天数、已过天数、逾期天数、30天到店次数、余额、召回指数 - 数据来源:`v_dws_member_consumption_summary` + `v_dws_member_winback_index` #### 最大消费潜力(potential) - 排序:自定义评分 - 展示:潜力标签、30天消费、平均到店频率、平均客单价 - 数据来源:`v_dws_member_consumption_summary` #### 最高余额(balance) - 排序:`total_card_balance DESC`(卡余额快照值) - 展示:最后到店日期、月消费、可用月数 - 数据来源:`v_dim_member_card_account`(快照值,取最后一天) - ⚠️ 余额是快照值,禁止 SUM #### 最近充值(recharge) - 排序:`recharge_amount DESC` - 展示:最后充值日期、充值金额、60天充值次数、当前余额 - 数据来源:`v_dws_member_consumption_summary` #### 最近到店(recent) - 排序:`last_visit_date DESC` - 展示:距今天数、60天到店次数 - 数据来源:`v_dws_member_consumption_summary` #### 最高消费 近60天(spend60) - 排序:`consume_amount_60d DESC` - 展示:60天消费金额、60天到店次数、高消费标签 - 数据来源:`v_dws_member_consumption_summary` #### 最频繁 近60天(freq60) - 排序:`visit_count_60d DESC` - 展示:平均间隔天数、8周柱状图 - 数据来源:`v_dws_member_consumption_summary`(汇总)+ `v_dwd_assistant_service_log`(周粒度) **8 周柱状图统计规则**(已实现,`_get_weekly_visits_batch()`): - 时间范围:最近 56 天(8 个自然周) - 分组:按 ISO 周(`DATE_TRUNC('week', create_time::date)`) - 每周统计到店次数(`COUNT(*)`) - `val`:该周到店次数 - `pct`:相对于 8 周中最高值的百分比(`val / max_val * 100`) - 固定返回 8 个元素,无数据的周 `val=0, pct=0` #### 最专一 近60天(loyal) - 排序:`max_rs DESC`(最高 RSI 降序) - 展示:最高 RSI 助教(右上角)、助教服务明细表 - 数据来源:`v_dws_member_assistant_relation_index` **排列规则**(用户明确): 1. 客户-助教对,RSI 从高到低排列 2. 客户不重复,排重规则是放在最高对的位置 3. 每个卡片展示客户信息 + 该客户对应所有助教 RSI 值从高到低排列 4. 右上位置展示最高 RSI 值助教 **RSI(关系指数)**: - 存储:`dws_member_assistant_relation_index` 表 - 展示值:`rs_display`(0-10 刻度) - Emoji 四级映射:`>8.5→💖` / `>7→🧡` / `>5→💛` / `≤5→💙` - 计算:由 DWS 层 `DWS_RELATION_INDEX` 任务产出(RS/OS/MS/ML 四个子指数) ### 4.3 薪酬计算规则(DWS 需求文档 3.2 节) **现行方案(2026-03-01 起)**: | 档位 | 总业绩小时数阈值 | 专业课抽成(元/h) | 打赏课抽成 | 次月休假 | |------|-----------------|-------------------|-----------|---------| | 0档 淘汰压力 | H < 120 | 28 | 50% | 3天 | | 1档 及格档 | 120 ≤ H < 150 | 18 | 40% | 4天 | | 2档 良好档 | 150 ≤ H < 180 | 13 | 35% | 5天 | | 3档 优秀档 | 180 ≤ H < 210 | 10 | 30% | 6天 | | 4档 销冠竞争 | H ≥ 210 | 8 | 25% | 休假自由 | - 过档后所有时长按新档位计算 - 新入职助教:按日均 × 30 折算定档(25日后入职最高定档至 2档) - 折算仅用于定档,不适用于 Top3 奖 - 档位节点:`[0, 120, 150, 180, 210]`(从 `cfg_performance_tier` 配置表读取) --- ## 五、数据层依赖 ### 5.1 DWS 表 | 表名 | 用途 | 使用页面 | |------|------|---------| | `dws_assistant_salary_calc` | 助教绩效/工资 | BOARD-1、COACH-1 | | `dws_member_consumption_summary` | 客户消费汇总 | BOARD-2(6个维度) | | `dws_member_assistant_relation_index` | 客户-助教关系指数 | BOARD-2(loyal)、CUST-1 | | `dws_member_winback_index` | 客户召回指数 | BOARD-2(recall) | ### 5.2 DWD 表 | 表名 | 用途 | 使用页面 | |------|------|---------| | `dwd_assistant_service_log` | 服务记录明细 | BOARD-2(freq60 周粒度)、COACH-1 | | `dim_member` | 会员维度表 | 所有页面(JOIN 取姓名) | | `dim_assistant` | 助教维度表 | 所有页面(JOIN 取姓名) | | `dim_member_card_account` | 会员卡账户 | BOARD-2(balance)、CUST-1 | ### 5.3 配置表 | 表名 | 用途 | |------|------| | `cfg_performance_tier` | 绩效档位节点 | | `cfg_bonus_rules` | 奖金规则 | | `cfg_skill_type` | 课程类型映射(skill_id → BASE/BONUS/ROOM) | | `cfg_area_category` | 区域-项目类型映射(BILLIARD/SNOOKER/MAHJONG/KTV) | ### 5.4 业务库表 | 表名 | 用途 | 使用页面 | |------|------|---------| | `biz.coach_tasks` | 助教任务 | BOARD-1(任务维度)、COACH-1、CUST-1 | | `biz.notes` | 备注 | COACH-1、CUST-1 | | `biz.ai_cache` | AI 洞察缓存 | CUST-1(暂不处理) | | `public.member_retention_clue` | 维客线索 | CUST-1 | --- ## 六、实施顺序建议 ``` Phase 1 — 基础联调(前端 Mock → 真实 API) ├── P9: 修复客户看板项目筛选枚举(5 分钟) ├── P4: 助教看板 Mock → 真实 API ├── P5: 客户看板 Mock → 真实 API ├── P2: 助教详情页 Mock → 真实 API └── P3: 客户详情页 Mock → 真实 API(AI 相关暂跳过) Phase 2 — 数据补全 ├── P6: 助教 skills 字段填充 ├── P7: 助教看板折前课时字段 └── P8: 客户看板 loyal 维度 coachDetails 子数组 Phase 3 — 交互优化 └── P1: 看板 Tab 切换改为同页切换 ``` --- ## 七、风险点 1. **TS 与 WXS 格式化互斥**:替换 Mock 时,`setData` 必须传原始数字,禁止用 `formatMoney()` 预格式化 2. **null 值清洗**:后端返回 null 的字段,前端必须 `?? 0` / `?? ''` 清洗后再传给组件 3. **Pydantic 静默丢弃**:后端 service 新增返回字段时,必须同步更新 Schema,否则数据被静默丢弃 4. **余额快照值**:`balance` 是日末快照,禁止 SUM 聚合 5. **档位节点**:前端硬编码的 `[0, 100, 130, 160, 190, 220]` 与实际 `[0, 120, 150, 180, 210]` 不一致 6. **看板 Tab 重构**:如果 board-finance 是 tabBar 页面,合并后需要调整 `app.json` 和 custom-tab-bar 7. **助教姓名**:所有助教必须显示昵称/花名(`nickname`),禁止显示真实姓名(`real_name`)。SQL 统一用 `COALESCE(nickname, real_name, '')`