# 小程序接口契约文档 > 创建时间:2026-03-18 > 状态:联调前草案,后端开发时以此为契约基准 > 基础路径:`/api/xcx` --- ## 〇、约定 - 所有接口需认证(`Authorization: Bearer `),除 login/dev-login 外 - 响应统一 JSON,成功 `{ code: 0, data: ... }`,失败 `{ code: number, message: string }` - 分页统一 `{ items: T[], total: number, page: number, pageSize: number }` - 金额单位:元(number,保留 2 位小数) - 时间格式:ISO 8601(`2026-03-18T10:30:00`) - 排序参数:`sort=field:asc|desc` --- ## 一、认证模块(已实现 ✅) ### AUTH-1: 微信登录 ``` POST /api/xcx/login Body: { code: string } Response: { access_token, refresh_token, user: { user_id, status, nickname } } ``` ### AUTH-2: 开发模式登录 ``` POST /api/xcx/dev-login Body: { openid: string } Response: 同 AUTH-1 ⚠️ 生产环境必须禁用此接口(REQ-3) ``` ### AUTH-3: 查询当前用户 ``` GET /api/xcx/me Response: { user_id: number, status: 'new' | 'pending' | 'approved' | 'rejected' | 'disabled', nickname: string, role: string, // 新增(REQ-4) store_name: string, // 新增(REQ-4) coach_level?: string, // 新增(REQ-4),仅助教角色 avatar?: string // 新增(REQ-4) } ``` ### AUTH-4: 刷新 Token ``` POST /api/xcx/refresh Body: { refresh_token: string } Response: { access_token, refresh_token } ``` ### AUTH-5: 提交入驻申请 ``` POST /api/xcx/apply Body: { name: string, phone: string, store_id: string } Response: { user_id: number, status: 'pending' } ``` --- ## 二、任务模块 ### TASK-1: 任务列表 > 数据源:`zqyy_app.coach_tasks`(任务)、`etl_feiqiu.dws.*`(绩效聚合,via FDW) > 金额口径:收入金额使用 `items_sum`(DWD-DOC 强制规则 1) ``` GET /api/xcx/tasks?status={pending|completed|abandoned}&page=1&pageSize=20 ``` #### 响应结构 ```typescript interface TaskListResponse { items: TaskItem[] total: number page: number pageSize: number // 绩效概览(附带在任务列表响应中,避免额外请求) performance: PerformanceSummary } ``` #### 子类型定义 ```typescript /** 任务项 */ interface TaskItem { id: string customerName: string customerAvatar: string taskType: 'callback' | 'priority_recall' | 'relationship' | 'high_priority' taskTypeLabel: string deadline: string // ISO 8601 heartScore: number // 0-10 hobbies: string[] isPinned: boolean hasNote: boolean status: 'pending' | 'completed' | 'abandoned' // ── 可选扩展字段(需求 5.7.2) ── lastVisitDays?: number // 距上次到店天数 balance?: number // 客户余额(元) aiSuggestion?: string // AI 建议摘要 } /** 绩效概览(扩展版,需求 5.7.1) * 从原 4 字段扩展为 15+ 字段,供任务列表页 Banner 进度条组件使用 */ interface PerformanceSummary { // ── 原有字段 ── totalHours: number // 本月总工时(折算后) totalIncome: number // 本月总收入 totalCustomers: number // 本月服务客户数 monthLabel: string // 月份标签,如 '3月' // ── 扩展字段 ── tierNodes: number[] // 档位节点数组,如 [0, 100, 130, 160, 190, 220] basicHours: number // 基础课时 bonusHours: number // 激励课时 currentTier: number // 当前档位索引(0-based,对应 tierNodes) nextTierHours: number // 下一档位工时阈值 tierCompleted: boolean // 是否已达最高档 bonusMoney: number // 升档奖金(元) incomeTrend: string // 收入趋势描述,如 '↓368' incomeTrendDir: 'up' | 'down' // 趋势方向 prevMonth: string // 上月标签,如 '1月' currentTierLabel: string // 当前档位名称,如 '初级' } ``` #### 字段说明 | 字段 | 类型 | 说明 | |------|------|------| | `performance.tierNodes` | number[] | 档位节点数组,前端用于绘制进度条刻度 | | `performance.basicHours` | number | 基础课工时 | | `performance.bonusHours` | number | 激励课工时 | | `performance.currentTier` | number | 当前所在档位索引(0=未入档,1=初级...) | | `performance.nextTierHours` | number | 下一档位的工时阈值 | | `performance.tierCompleted` | boolean | 是否已达最高档位 | | `performance.bonusMoney` | number | 达到下一档位可获得的奖金 | | `performance.incomeTrend` | string | 与上月对比的收入变化,含方向符号 | | `performance.incomeTrendDir` | string | `'up'` 或 `'down'`,前端用于控制颜色 | | `performance.prevMonth` | string | 上月标签,用于对比展示 | | `items[].lastVisitDays` | number? | 距上次到店天数,可选 | | `items[].balance` | number? | 客户余额,可选 | | `items[].aiSuggestion` | string? | AI 建议摘要,可选 | ### TASK-2: 任务详情 ``` GET /api/xcx/tasks/{taskId} Response: { // 基础信息(同 TASK-1 item) id, customer_name, customer_avatar, task_type, task_type_label, deadline, heart_score, hobbies, is_pinned, has_note, status, // 扩展信息 last_visit_date?: string, last_spend_amount?: number, days_absent?: number, spend_trend?: 'up' | 'down' | 'flat', churn_risk?: 'high' | 'medium' | 'low', callback_reason?: string, preferences?: string[], consumption_habits?: string, social_preference?: string, // 维客线索 retention_clues: Array<{ tag: string, tag_color: 'primary' | 'success' | 'purple' | 'error', emoji: string, text: string, source: string, desc?: string }>, // 话术参考 talking_points: string[], // 近期服务记录摘要 service_summary: { total_hours: number, total_income: number, avg_income: number }, service_records: Array<{ table: string, type: string, type_class: 'basic' | 'vip' | 'tip' | 'recharge' | 'incentive', record_type?: 'course' | 'recharge', duration: number, duration_raw?: number, income: number, is_estimate?: boolean, drinks: string, date: string }>, // AI 分析 ai_analysis: { summary: string, suggestions: string[] }, // 备注(最近 N 条) notes: Array<{ id: string, content: string, tag_type: 'customer' | 'coach' | 'system', tag_label: string, created_at: string, ai_score?: number, manual_score?: number }> } ``` ### TASK-3: 放弃任务 ``` POST /api/xcx/tasks/{taskId}/abandon Body: { reason: string } Response: { status: 'abandoned' } ``` ### TASK-4: 取消放弃 ``` POST /api/xcx/tasks/{taskId}/restore Response: { status: 'pending' } ``` --- ## 三、备注模块 ### NOTE-1: 备注列表 ``` GET /api/xcx/notes?page=1&pageSize=20 Response: { items: Array<{ id: string, content: string, tag_type: 'customer' | 'coach' | 'system', tag_label: string, created_at: string, ai_score?: number, manual_score?: number }>, total, page, pageSize } ``` ### NOTE-2: 新增备注 ``` POST /api/xcx/notes Body: { content: string, task_id?: string, customer_id?: string } Response: { id: string, created_at: string } ``` ### NOTE-3: 删除备注 ``` DELETE /api/xcx/notes/{noteId} Response: { success: true } ``` --- ## 四、绩效模块 ### PERF-1: 绩效概览 > 数据源:`etl_feiqiu.dws.*`(绩效/收入聚合,via FDW)、`zqyy_app.coaches`(助教信息) > 金额口径:所有收入金额使用 `items_sum`(DWD-DOC 强制规则 1);助教费用使用 `assistant_pd_money` + `assistant_cx_money` 拆分(DWD-DOC 强制规则 2) ``` GET /api/xcx/performance?year={year}&month={month} ``` #### 响应结构 ```typescript interface PerformanceOverviewResponse { // ── Banner 数据 ── coachName: string // 助教姓名 coachRole: string // 角色,如 '助教' storeName: string // 门店名 monthlyIncome: string // 本月收入(格式化),如 '¥6,206' lastMonthIncome: string // 上月收入(格式化),如 '¥16,880' // ── 收入档位 ── currentTier: { basicRate: number // 当前档基础课时费率(元/h) incentiveRate: number // 当前档激励课时费率(元/h) } nextTier: { basicRate: number // 下一档基础课时费率 incentiveRate: number // 下一档激励课时费率 } upgradeHoursNeeded: number // 距升档所需工时 upgradeBonus: number // 升档奖金(元) // ── 本月业绩明细(incomeItems) ── incomeItems: IncomeItem[] monthlyTotal: string // 本月收入合计(格式化),如 '¥6,950.5' // ── 本月服务记录(thisMonthRecords) ── // 按日期分组的 DateGroup 结构 thisMonthRecords: DateGroup[] // ── 新客列表(newCustomers) ── newCustomers: Array<{ name: string avatarChar: string // 姓氏首字 avatarColor: string // 头像渐变色 lastService: string // 最后服务日期,如 '2月7日' count: number // 服务次数 }> // ── 常客列表(regularCustomers) ── regularCustomers: Array<{ name: string avatarChar: string avatarColor: string hours: number // 总工时 income: string // 总收入(格式化),如 '¥960' count: number // 服务次数 }> } ``` #### 子类型定义 ```typescript /** 业绩明细项 */ interface IncomeItem { icon: string // emoji 图标,如 '🎱'、'⭐'、'💰'、'🏆' label: string // 标签,如 '基础课'、'激励课'、'充值激励'、'TOP3 销冠奖' desc: string // 费率×工时描述,如 '80元/h × 75h' value: string // 金额(格式化),如 '¥6,000';或文字如 '继续努力' } /** 按日期分组的服务记录 */ interface DateGroup { date: string // 日期标签,如 '2月7日' totalHours: string // 当日总工时,如 '4.0h' totalIncome: string // 当日总收入,如 '¥350' records: Array<{ customerName: string avatarChar: string // 姓氏首字 avatarColor: string // 头像渐变色 timeRange: string // 时间段,如 '20:00-22:00' hours: string // 工时,如 '2.0h' courseType: string // 课程类型,如 '基础课'、'包厢课' courseTypeClass: string // 样式类:tag-basic / tag-vip / tag-tip location: string // 台桌/包厢,如 '3号台'、'VIP1号房' income: string // 收入(格式化),如 '¥160' }> } ``` #### 字段说明 | 字段 | 类型 | 说明 | |------|------|------| | `lastMonthIncome` | string | 上月收入,用于 Banner 对比展示 | | `currentTier` / `nextTier` | object | 当前/下一档位的基础课和激励课费率 | | `upgradeHoursNeeded` | number | 距升档还需多少工时 | | `upgradeBonus` | number | 升档后可获得的奖金 | | `incomeItems[].desc` | string | 费率×工时的拆分描述,如 `"80元/h × 75h"` | | `newCustomers[].lastService` | string | 最后服务日期 | | `newCustomers[].count` | number | 服务次数 | | `regularCustomers[].hours` | number | 总工时 | | `regularCustomers[].income` | string | 总收入(`items_sum` 口径) | ### PERF-2: 绩效明细(按月) ``` GET /api/xcx/performance/records?year={year}&month={month}&page=1&pageSize=20 Response: { // 月度汇总 summary: { total_count: number, total_hours: number, total_hours_raw: number, total_income: number }, // 按日期分组 date_groups: Array<{ date: string, // "3月18日" total_hours: number, total_income: number, records: Array<{ id: string, customer_name: string, time_range: string, // "20:00-22:00" hours: number, hours_raw?: number, course_type: string, course_type_class: string, location: string, income: number }> }>, has_more: boolean } ``` --- ## 五、客户模块(已实现 ✅) ### CUST-1: 客户详情 > 客户详情页完整数据,包含 Banner 概览、AI 洞察、关联助教任务、最亲密助教、消费记录(嵌套结构)和备注列表。 > 数据源:`biz.members`(会员基础信息)、`fdw_etl.dwd.dim_member`(手机号/昵称,DQ-6)、`fdw_etl.dwd.dim_member_card_account`(会员卡,DQ-7)、`fdw_etl.dwd.dwd_settlement_head`(消费记录)、`fdw_etl.dws.*`(聚合指标)、`biz.ai_cache`(AI 洞察)、`biz.tasks`(助教任务)、`biz.customer_notes`(备注) > DWD-DOC 强制规则:金额字段使用 `items_sum` 口径(规则 1);助教费用使用 `assistant_pd_money` + `assistant_cx_money` 拆分(规则 2);会员信息通过 `member_id` JOIN `dim_member` 获取(规则 DQ-6),禁止直接使用 `settlement_head.member_phone`;会员卡信息通过 `member_id` JOIN `dim_member_card_account` 获取(规则 DQ-7) ``` GET /api/xcx/customers/{customerId} ``` #### 请求参数 | 参数 | 类型 | 必填 | 说明 | |------|------|:----:|------| | `customerId` | string (path) | 是 | 客户唯一 ID | #### 响应结构(TypeScript 类型定义) ```typescript /** 顶层响应 */ interface CustomerDetailResponse { // ─── 基础信息 ─── /** 客户唯一 ID */ id: string /** 客户姓名(⚠️ 通过 member_id JOIN dim_member.nickname 获取,DQ-6) */ name: string /** 脱敏手机号,如 "139****5678"(⚠️ 通过 member_id JOIN dim_member.mobile 获取,DQ-6) */ phone: string /** 完整手机号(点击查看时用)(⚠️ 同上,DQ-6) */ phoneFull: string /** 头像 URL */ avatar: string /** 会员等级(⚠️ 通过 member_id JOIN dim_member_card_account 获取,DQ-7) */ memberLevel: string /** 关系指数,如 "0.85" */ relationIndex: string /** 客户标签列表 */ tags: string[] // ─── Banner 概览字段 ─── /** 客户余额(元),⚠️ 使用 `items_sum` 口径 */ balance: number /** 近 60 天消费金额(元),⚠️ 使用 `items_sum` 口径 */ consumption60d: number /** 理想到店间隔(天) */ idealInterval: number /** 距上次到店天数 */ daysSinceVisit: number // ─── AI 洞察模块 ─── /** AI 分析洞察,数据源:biz.ai_cache(cache_type=app4_analysis) */ aiInsight: AiInsight // ─── 关联助教任务列表 ─── /** 关联助教任务 */ coachTasks: CoachTask[] // ─── 最亲密助教 ─── /** 最亲密助教列表 */ favoriteCoaches: FavoriteCoach[] // ─── 维客线索 ─── /** 维客线索列表(同 TASK-2 格式) */ retentionClues: RetentionClue[] // ─── 消费记录(嵌套结构) ─── /** 消费记录列表,⚠️ 所有金额使用 `items_sum` 口径 */ consumptionRecords: ConsumptionRecord[] // ─── 备注列表 ─── /** 客户备注 */ notes: CustomerNote[] } // ─── AI 洞察 ─── interface AiInsight { /** AI 分析摘要文本 */ summary: string /** 策略建议列表 */ strategies: Array<{ /** 标签颜色(CSS 类或色值) */ color: string /** 策略建议文本 */ text: string }> } // ─── 关联助教任务 ─── interface CoachTask { /** 助教姓名 */ name: string /** 助教等级:star / senior / middle / junior */ level: string /** 等级对应颜色(前端样式用) */ levelColor: string /** 任务类型,如 "回访"、"召回" */ taskType: string /** 任务类型对应颜色 */ taskColor: string /** 背景样式类 */ bgClass: string /** 任务状态,如 "进行中"、"已完成" */ status: string /** 最后服务日期,如 "2026-03-01" */ lastService: string /** * 指标列表(近 60 天统计) * 含:近60天次数 / 总时长 / 次均时长 */ metrics: MetricItem[] } // ─── 最亲密助教 ─── interface FavoriteCoach { /** 亲密度 emoji,如 "💖"、"🧡"、"💛"、"💙" */ emoji: string /** 助教姓名 */ name: string /** 关系指数,如 "0.92" */ relationIndex: string /** 关系指数对应颜色 */ indexColor: string /** 背景样式类 */ bgClass: string /** * 统计指标列表 * 含:基础课时(assistant_pd_money 对应)/ 激励课时(assistant_cx_money 对应)/ 上课次数 / 充值金额 * ⚠️ 课时费用使用 assistant_pd_money + assistant_cx_money 拆分(DWD-DOC 规则 2) */ stats: MetricItem[] } // ─── 通用指标项 ─── interface MetricItem { /** 指标标签,如 "近60天次数"、"基础课时" */ label: string /** 指标值,如 "12次"、"45.5h"、"¥3,200" */ value: string /** 可选颜色标记(高亮/警告等) */ color?: string } // ─── 维客线索(同 TASK-2 格式) ─── interface RetentionClue { /** 线索类型 */ type: string /** 线索描述 */ text: string } // ─── 消费记录(嵌套结构) ─── interface ConsumptionRecord { /** 记录唯一 ID */ id: string /** 消费类型:table(台桌消费)/ shop(酒水/商品消费)/ recharge(充值) */ type: 'table' | 'shop' | 'recharge' /** 消费日期,如 "2026-03-01" */ date: string /** 台桌名称(type=table 时有值) */ tableName?: string /** 开始时间,如 "14:00"(type=table 时有值) */ startTime?: string /** 结束时间,如 "16:30"(type=table 时有值) */ endTime?: string /** 时长(分钟)(type=table 时有值) */ duration?: number /** * 台费金额(元),⚠️ 使用 `items_sum` 口径中的 `table_charge_money` * type=table 时有值 */ tableFee?: number /** 台费原价(元,优惠前),type=table 时有值 */ tableOrigPrice?: number /** * 助教服务列表 * ⚠️ fee 使用 assistant_pd_money(基础/陪打)+ assistant_cx_money(激励/超休)拆分(DWD-DOC 规则 2) */ coaches: CoachServiceItem[] /** 酒水/商品金额(元),⚠️ 使用 `items_sum` 口径中的 `goods_money` */ foodAmount?: number /** 酒水/商品原价(元,优惠前) */ foodOrigPrice?: number /** * 消费总金额(元) * ⚠️ 使用 `items_sum` 口径 = table_charge_money + goods_money + assistant_pd_money + assistant_cx_money + electricity_money */ totalAmount: number /** 消费总原价(元,优惠前) */ totalOrigPrice: number /** 支付方式,如 "储值卡"、"微信支付"、"现金" */ payMethod: string /** 充值金额(元),type=recharge 时有值 */ rechargeAmount?: number } /** 助教服务明细(嵌套在消费记录中) */ interface CoachServiceItem { /** 助教姓名 */ name: string /** 助教等级:star / senior / middle / junior */ level: string /** 等级对应颜色 */ levelColor: string /** 课程类型,如 "基础课"、"激励课" */ courseType: string /** 服务时长(小时) */ hours: number /** * 折算工时(小时),可选 * 激励课按系数折算后的工时 */ perfHours?: number /** * 服务费用(元) * ⚠️ 基础课 = assistant_pd_money,激励课 = assistant_cx_money(DWD-DOC 规则 2) */ fee: number } // ─── 客户备注 ─── interface CustomerNote { /** 备注唯一 ID */ id: string /** 标签文本,如 "跟进"、"重要" */ tagLabel: string /** 创建时间,ISO 8601 格式 */ createdAt: string /** 备注内容 */ content: string } ``` #### 字段说明表 ##### 基础信息字段(8 个) | 字段 | 类型 | 说明 | 数据源 | |------|------|------|--------| | `id` | string | 客户唯一 ID | `biz.members.id` | | `name` | string | 客户姓名 | ⚠️ `member_id` JOIN `fdw_etl.dwd.dim_member.nickname`(`scd2_is_current=1`),DQ-6 | | `phone` | string | 脱敏手机号,如 `"139****5678"` | ⚠️ `member_id` JOIN `fdw_etl.dwd.dim_member.mobile`(`scd2_is_current=1`),DQ-6,后端脱敏 | | `phoneFull` | string | 完整手机号(点击查看时用) | 同上,不脱敏 | | `avatar` | string | 头像 URL | `biz.members.avatar` | | `memberLevel` | string | 会员等级 | ⚠️ `member_id` JOIN `fdw_etl.dwd.dim_member_card_account`(`tenant_member_id=member_id`,`scd2_is_current=1`),DQ-7 | | `relationIndex` | string | 关系指数,如 `"0.85"` | 后端根据服务频率/消费金额/亲密度综合计算 | | `tags` | `string[]` | 客户标签列表 | `biz.customer_tags` | ##### Banner 概览字段(4 个) | 字段 | 类型 | 说明 | 数据源 | |------|------|------|--------| | `balance` | number | 客户余额(元),⚠️ `items_sum` 口径 | `fdw_etl.dws.*` 聚合,储值卡余额 | | `consumption60d` | number | 近 60 天消费金额(元),⚠️ `items_sum` 口径 | `fdw_etl.dwd.dwd_settlement_head`,按 `settle_time` 过滤近 60 天,`SUM(items_sum)` | | `idealInterval` | number | 理想到店间隔(天) | 后端根据历史到店频率计算 | | `daysSinceVisit` | number | 距上次到店天数 | `fdw_etl.dwd.dwd_settlement_head`,`MAX(settle_time)` 与当前日期差值 | ##### aiInsight 模块(2 个字段) | 字段 | 类型 | 说明 | 数据源 | |------|------|------|--------| | `summary` | string | AI 分析摘要文本 | `biz.ai_cache`,`cache_type='app4_analysis'`,`target_id=customerId` | | `strategies` | `Array<{ color, text }>` | 策略建议列表,`color` 为标签颜色,`text` 为建议文本 | 同上,JSON 解析 `cache_value` | ##### coachTasks 模块(每项 9 个字段) | 字段 | 类型 | 说明 | 数据源 | |------|------|------|--------| | `name` | string | 助教姓名 | `biz.users.nickname` | | `level` | string | 助教等级:`star`/`senior`/`middle`/`junior` | `fdw_etl.v_dws_assistant_salary_calc.coach_level` | | `levelColor` | string | 等级对应颜色 | 后端根据 `level` 映射 | | `taskType` | string | 任务类型,如 `"回访"`、`"召回"` | `biz.tasks.task_type` | | `taskColor` | string | 任务类型对应颜色 | 后端根据 `taskType` 映射 | | `bgClass` | string | 背景样式类 | 后端根据 `level` 映射 | | `status` | string | 任务状态,如 `"进行中"`、`"已完成"` | `biz.tasks.status` | | `lastService` | string | 最后服务日期 | `fdw_etl.dwd.dwd_settlement_head`,该助教对该客户的 `MAX(settle_time)` | | `metrics` | `MetricItem[]` | 近 60 天指标:次数/总时长/次均时长 | `fdw_etl.dwd.dwd_settlement_head` + `dwd_assistant_service_log_ex`,按助教+客户聚合近 60 天 | ##### favoriteCoaches 模块(每项 6 个字段) | 字段 | 类型 | 说明 | 数据源 | |------|------|------|--------| | `emoji` | string | 亲密度 emoji(💖(>8.5) / 🧡(>7) / 💛(>5) / 💙(≤5),后端 compute_heart_icon() 根据 rs_display 映射) | 后端根据 `relationIndex` 映射 | | `name` | string | 助教姓名 | `biz.users.nickname` | | `relationIndex` | string | 关系指数,如 `"0.92"` | 后端根据服务频率/消费金额综合计算 | | `indexColor` | string | 关系指数对应颜色 | 后端根据 `relationIndex` 阈值映射 | | `bgClass` | string | 背景样式类 | 后端根据排名映射 | | `stats` | `MetricItem[]` | 统计指标:基础课时(`assistant_pd_money`)/ 激励课时(`assistant_cx_money`)/ 上课次数 / 充值金额 | `fdw_etl.dwd.dwd_settlement_head` + `dwd_assistant_service_log_ex`,⚠️ 课时费用使用 `assistant_pd_money` + `assistant_cx_money` 拆分(DWD-DOC 规则 2) | ##### consumptionRecords 模块(每项 16 个字段) | 字段 | 类型 | 说明 | 数据源 | |------|------|------|--------| | `id` | string | 记录唯一 ID | `fdw_etl.dwd.dwd_settlement_head.id` | | `type` | enum | 消费类型:`table`/`shop`/`recharge` | 后端根据 `settle_type` 映射(`IN (1,3)` 为正向交易) | | `date` | string | 消费日期 | `dwd_settlement_head.settle_time`,格式化为日期 | | `tableName` | string? | 台桌名称 | `dwd_settlement_head.table_name` | | `startTime` | string? | 开始时间 | `dwd_settlement_head.start_time`,格式化为 `"HH:mm"` | | `endTime` | string? | 结束时间 | `dwd_settlement_head.end_time`,格式化为 `"HH:mm"` | | `duration` | number? | 时长(分钟) | 后端根据 `startTime`/`endTime` 计算 | | `tableFee` | number? | 台费金额(元),⚠️ `table_charge_money` | `dwd_settlement_head.table_charge_money` | | `tableOrigPrice` | number? | 台费原价(元,优惠前) | `dwd_settlement_head.table_charge_money` + 对应折扣 | | `coaches` | `CoachServiceItem[]` | 助教服务列表 | `fdw_etl.dwd.dwd_assistant_service_log_ex`,关联 `settlement_id` | | `foodAmount` | number? | 酒水/商品金额(元),⚠️ `goods_money` | `dwd_settlement_head.goods_money` | | `foodOrigPrice` | number? | 酒水/商品原价(元,优惠前) | `dwd_settlement_head.goods_money` + 对应折扣 | | `totalAmount` | number | 消费总金额(元),⚠️ `items_sum` 口径 | `dwd_settlement_head.items_sum` | | `totalOrigPrice` | number | 消费总原价(元,优惠前) | `items_sum` + `adjust_amount` | | `payMethod` | string | 支付方式 | 后端根据 `pay_type` / `balance_amount` 等字段映射 | | `rechargeAmount` | number? | 充值金额(元),`type=recharge` 时有值 | `biz.recharge_records.amount` | ##### coaches 子数组(CoachServiceItem,每项 7 个字段) | 字段 | 类型 | 说明 | 数据源 | |------|------|------|--------| | `name` | string | 助教姓名 | `biz.users.nickname` | | `level` | string | 助教等级 | `fdw_etl.v_dws_assistant_salary_calc.coach_level` | | `levelColor` | string | 等级对应颜色 | 后端根据 `level` 映射 | | `courseType` | string | 课程类型:`"基础课"` / `"激励课"` | 后端根据 `dwd_assistant_service_log_ex` 服务类型映射 | | `hours` | number | 服务时长(小时) | `dwd_assistant_service_log_ex.service_hours` | | `perfHours` | number? | 折算工时(小时),激励课按系数折算 | `dwd_assistant_service_log_ex`,后端按激励系数计算 | | `fee` | number | 服务费用(元),⚠️ 基础课 = `assistant_pd_money`,激励课 = `assistant_cx_money`(DWD-DOC 规则 2) | `dwd_settlement_head.assistant_pd_money` / `assistant_cx_money` | ##### notes 模块(每项 4 个字段) | 字段 | 类型 | 说明 | 数据源 | |------|------|------|--------| | `id` | string | 备注唯一 ID | `biz.customer_notes.id` | | `tagLabel` | string | 标签文本,如 `"跟进"`、`"重要"` | `biz.customer_notes.tag` | | `createdAt` | string | 创建时间,ISO 8601 格式 | `biz.customer_notes.created_at` | | `content` | string | 备注内容 | `biz.customer_notes.content` | #### 业务规则 | 规则 | 说明 | |------|------| | `items_sum` 口径(DWD-DOC 规则 1) | 所有金额字段(`balance`/`consumption60d`/`tableFee`/`foodAmount`/`totalAmount` 等)使用 `items_sum = table_charge_money + goods_money + assistant_pd_money + assistant_cx_money + electricity_money`,禁止使用 `consume_money` | | 助教费用拆分(DWD-DOC 规则 2) | `coaches[].fee` 中基础课 = `assistant_pd_money`(陪打),激励课 = `assistant_cx_money`(超休);`favoriteCoaches[].stats` 中基础课时/激励课时同理。禁止使用 `service_fee` | | 会员信息 JOIN(DWD-DOC 规则 DQ-6) | `name`/`phone`/`phoneFull` 必须通过 `member_id` LEFT JOIN `dwd.dim_member`(取 `scd2_is_current=1`)获取,禁止直接使用 `settlement_head.member_phone`/`member_name`(自 2025-12 起全为 NULL) | | 会员卡 JOIN(DWD-DOC 规则 DQ-7) | `memberLevel` 必须通过 `member_id` LEFT JOIN `dwd.dim_member_card_account`(`tenant_member_id=member_id`,`scd2_is_current=1`)获取,禁止直接使用 `member_card_type_name`(自 2025-07-21 起全为 NULL) | | `settle_type` 过滤 | 消费记录取正向交易 `settle_type IN (1, 3)`,本表无 `is_delete` 字段 | | 废单排除 | 使用 `dwd_assistant_service_log_ex.is_trash` 排除废单,`dwd_assistant_trash_event` 已废弃 | | `type` 枚举映射 | `table`(台桌消费)/ `shop`(酒水/商品消费)/ `recharge`(充值),后端根据 `settle_type` 和业务逻辑映射 | | 亲密度 emoji | 💖(>8.5) / 🧡(>7) / 💛(>5) / 💙(≤5) | #### 数据源映射表 | 模块 | 主数据源 | 辅助数据源 | |------|---------|-----------| | 基础信息 | `biz.members` | `fdw_etl.dwd.dim_member`(DQ-6)、`fdw_etl.dwd.dim_member_card_account`(DQ-7) | | Banner 概览 | `fdw_etl.dwd.dwd_settlement_head` | `fdw_etl.dws.*`(余额聚合) | | AI 洞察 | `biz.ai_cache`(`cache_type='app4_analysis'`) | — | | 关联助教任务 | `biz.tasks` | `biz.users`、`fdw_etl.dwd.dwd_settlement_head`、`dwd_assistant_service_log_ex` | | 最亲密助教 | `fdw_etl.dwd.dwd_assistant_service_log_ex` | `biz.users`、`fdw_etl.dwd.dwd_settlement_head` | | 消费记录 | `fdw_etl.dwd.dwd_settlement_head` | `fdw_etl.dwd.dwd_assistant_service_log_ex`、`biz.recharge_records` | | 备注 | `biz.customer_notes` | — | ### CUST-2: 客户服务记录 ``` GET /api/xcx/customers/{customerId}/records?year={year}&month={month}&table={tableId} Response: { customer_name: string, customer_phone: string, relation_index: string, tables: Array<{ id: string, name: string }>, records: Array<{ id: string, date: string, time_range: string, table: string, type: string, type_class: string, duration: number, duration_raw?: number, income: number, drinks: string }>, has_more: boolean } ``` ### 客户模块错误码(CUST-1 / CUST-2 共用) | HTTP 状态码 | 场景 | 响应示例 | |:-----------:|------|---------| | 403 | 用户未通过审核(`require_approved()` 拦截) | `{ "code": 403, "message": "用户未通过审核,无法访问此资源" }` | | 404 | 客户不存在(`customerId` 无对应记录) | `{ "code": 404, "message": "客户不存在" }` | | 422 | 请求参数校验失败(year/month/page/pageSize 不合法) | Pydantic `HTTPValidationError` | > CUST-1 扩展模块(aiInsight/coachTasks/favoriteCoaches/consumptionRecords)查询失败时不返回错误码,而是对该模块返回空默认值(空数组或空对象),整体响应仍为 HTTP 200。 --- ## 六、看板模块 ### BOARD-1: 助教看板 > 助教排行看板,支持排序×技能×时间三重筛选,4 种维度卡片(定档业绩/工资/客源储值/任务)。 > 数据源:`fdw_etl.v_dws_assistant_salary_calc`(工资/定档)、`fdw_etl.v_dws_recharge_summary`(储值)、`biz.tasks`(任务) > DWD-DOC 强制规则:金额字段使用 `items_sum` 口径(规则 1);助教费用使用 `assistant_pd_money` + `assistant_cx_money` 拆分(规则 2) ``` GET /api/xcx/board/coaches?sort={sort}&skill={skill}&time={time} ``` #### 请求参数 | 参数 | 类型 | 必填 | 说明 | |------|------|:----:|------| | `sort` | enum | 是 | 排序维度,6 种枚举:`perf_desc`(定档业绩最高)/ `perf_asc`(定档业绩最低)/ `salary_desc`(工资最高)/ `salary_asc`(工资最低)/ `sv_desc`(客源储值最高)/ `task_desc`(任务完成最多) | | `skill` | enum | 是 | 技能筛选,5 种枚举:`all`(不限)/ `chinese`(🎱 中式/追分)/ `snooker`(斯诺克)/ `mahjong`(🀄 麻将/棋牌)/ `karaoke`(🎤 团建/K歌) | | `time` | enum | 是 | 时间范围,6 种枚举:`month`(本月)/ `quarter`(本季度)/ `last_month`(上月)/ `last_3m`(前3个月不含本月)/ `last_quarter`(上季度)/ `last_6m`(最近6个月不含本月) | #### 交叉约束 | 约束 | 说明 | |------|------| | `time=last_6m` + `sort=sv_desc` | ⛔ 不兼容,后端返回 HTTP 400 `{ code: 400, message: "最近6个月不支持客源储值排序" }` | > 原因:储值数据跨 6 个月聚合开销过大,且业务意义有限。前端 `TIME_OPTIONS` 中已在 `last_6m` 选项文案标注"不支持客源储值最高"。 #### 响应结构(TypeScript 类型定义) ```typescript /** 顶层响应 */ interface BoardCoachesResponse { items: CoachBoardItem[] } /** 助教卡片 — 基础字段 + 4 维度专属字段 */ interface CoachBoardItem { // ─── 基础字段(所有维度共享) ─── /** 助教 ID */ id: string /** 助教姓名 */ name: string /** 姓名首字(用于头像占位符) */ initial: string /** 头像渐变色标识(blue/green/pink/amber/violet/cyan 等) */ avatarGradient: string /** 等级英文 key:star / senior / middle / junior */ level: string /** * 技能标签列表 * - text: 技能 emoji 或文字(如 '🎱'、'斯'、'🀄'、'🎤') * - cls: 前端样式类(如 'skill--chinese'、'skill--snooker') */ skills: Array<{ text: string; cls: string }> /** * Top 客户列表(含亲密度 emoji 前缀) * 示例:['💖 王先生', '🧡 李女士', '💛 赵总', '💙 新客户'] * 💖(>8.5) / 🧡(>7) / 💛(>5) / 💙(≤5) */ topCustomers: string[] // ─── perf 维度专属(sort=perf_desc / perf_asc 时展示) ─── /** 当期定档工时(小时) */ perfHours: number /** 上期定档工时(小时),无上期数据时不返回 */ perfHoursBefore?: number /** 距升档差距描述,如 '距升档 13.8h';已达标时不返回 */ perfGap?: string /** 是否已达标当前档位 */ perfReached: boolean // ─── salary 维度专属(sort=salary_desc / salary_asc 时展示) ─── /** 预估工资总额(元) */ salary: number /** 工资维度-定档工时(小时) */ salaryPerfHours: number /** 工资维度-上期定档工时(小时),无上期数据时不返回 */ salaryPerfBefore?: number // ─── sv 维度专属(sort=sv_desc 时展示) ─── /** 客源储值总额(元) */ svAmount: number /** 储值客户数 */ svCustomerCount: number /** 储值消耗额(元) */ svConsume: number // ─── task 维度专属(sort=task_desc 时展示) ─── /** 召回任务完成数 */ taskRecall: number /** 回访任务完成数 */ taskCallback: number } ``` #### 字段说明表 ##### 基础字段(7 个,所有维度共享) | 字段 | 类型 | 说明 | 数据源 | |------|------|------|--------| | `id` | string | 助教唯一 ID | `biz.users.id` | | `name` | string | 助教姓名 | `biz.users.nickname` | | `initial` | string | 姓名首字,用于头像占位符渲染 | 后端从 `name` 提取 | | `avatarGradient` | string | 头像渐变色标识(`blue`/`green`/`pink`/`amber`/`violet`/`cyan`) | 后端根据 ID 哈希分配 | | `level` | string | 等级英文 key:`star`/`senior`/`middle`/`junior` | `fdw_etl.v_dws_assistant_salary_calc.coach_level` | | `skills` | `Array<{ text: string, cls: string }>` | 技能标签,`text` 为 emoji 或文字,`cls` 为前端样式类 | `biz.coach_skills` 或配置表 | | `topCustomers` | `string[]` | Top 客户列表,含亲密度 emoji 前缀(💖/🧡/💛/💙) | 后端按亲密度排序取 Top 3,拼接 emoji + 姓名 | ##### perf 维度专属字段(4 个) | 字段 | 类型 | 说明 | 数据源 | |------|------|------|--------| | `perfHours` | number | 当期定档工时(小时) | `fdw_etl.v_dws_assistant_salary_calc`,按 `time` 参数聚合 | | `perfHoursBefore` | number? | 上期定档工时(小时),无上期数据时不返回 | 同上,取上一周期 | | `perfGap` | string? | 距升档差距描述,如 `'距升档 13.8h'`;已达标时不返回 | 后端根据档位阈值计算 | | `perfReached` | boolean | 是否已达标当前档位 | 后端根据档位阈值判断 | ##### salary 维度专属字段(3 个) | 字段 | 类型 | 说明 | 数据源 | |------|------|------|--------| | `salary` | number | 预估工资总额(元),⚠️ 使用 `items_sum` 口径 | `fdw_etl.v_dws_assistant_salary_calc` | | `salaryPerfHours` | number | 工资维度-定档工时(小时) | 同上 | | `salaryPerfBefore` | number? | 工资维度-上期定档工时(小时),无上期数据时不返回 | 同上,取上一周期 | ##### sv 维度专属字段(3 个) | 字段 | 类型 | 说明 | 数据源 | |------|------|------|--------| | `svAmount` | number | 客源储值总额(元) | `fdw_etl.v_dws_recharge_summary`,按助教关联客户聚合 | | `svCustomerCount` | number | 储值客户数 | 同上,`COUNT(DISTINCT customer_id)` | | `svConsume` | number | 储值消耗额(元) | 同上,消耗金额聚合 | ##### task 维度专属字段(2 个) | 字段 | 类型 | 说明 | 数据源 | |------|------|------|--------| | `taskRecall` | number | 召回任务完成数 | `biz.tasks`,`task_type='priority_recall' AND status='completed'` | | `taskCallback` | number | 回访任务完成数 | `biz.tasks`,`task_type='callback' AND status='completed'` | #### 维度与排序的关系 前端根据 `sort` 参数决定展示哪组维度字段(卡片模板切换): | sort 值 | 维度类型 | 展示字段 | |---------|---------|---------| | `perf_desc` / `perf_asc` | perf | `perfHours` / `perfHoursBefore` / `perfGap` / `perfReached` | | `salary_desc` / `salary_asc` | salary | `salary` / `salaryPerfHours` / `salaryPerfBefore` | | `sv_desc` | sv | `svAmount` / `svCustomerCount` / `svConsume` | | `task_desc` | task | `taskRecall` / `taskCallback` | > 后端始终返回所有维度字段(避免多次请求),前端根据 `sort` 选择性渲染对应卡片模板。 #### 业务规则 | 规则 | 说明 | |------|------| | `time=last_6m` + `sort=sv_desc` → HTTP 400 | 储值数据跨 6 个月聚合开销过大,后端拒绝此组合 | | `items_sum` 口径(DWD-DOC 规则 1) | `salary` 等金额字段使用 `items_sum` 口径,禁止使用 `consume_money` | | 助教费用拆分(DWD-DOC 规则 2) | 工资计算中基础课 = `assistant_pd_money`,激励课 = `assistant_cx_money` | | `levelClass` 前端计算 | 后端仅返回 `level`(英文 key),前端通过 `LEVEL_CLASS` 映射生成样式类 | | `perfGap` 后端计算 | 后端根据档位阈值计算差距描述字符串,已达标时不返回该字段 | | `topCustomers` emoji 规则 | 💖(>8.5) / 🧡(>7) / 💛(>5) / 💙(≤5),基于 rs_display 值域 0-10 | ### BOARD-2: 客户看板 > 客户排行看板,支持 8 个维度查看前 100 名客户,每个维度展示不同的专属字段卡片。 > 数据源:`fdw_etl.v_dws_customer_*` 系列视图(召回/潜力/余额/充值/到店/消费/频率/专一度)、`biz.tasks`(任务关联)、`fdw_etl.dwd.dim_member`(会员信息) > DWD-DOC 强制规则:金额字段使用 `items_sum` 口径(规则 1);会员信息通过 `member_id` JOIN `dim_member` 获取(规则 DQ-6/DQ-7) ``` GET /api/xcx/board/customers?dimension={dim}&project={proj}&page=1&pageSize=20 ``` #### 请求参数 | 参数 | 类型 | 必填 | 说明 | |------|------|:----:|------| | `dimension` | enum | 是 | 维度,8 种枚举:`recall`(最应召回)/ `potential`(最大消费潜力)/ `balance`(最高余额)/ `recharge`(最近充值)/ `recent`(最近到店)/ `spend60`(最高消费近60天)/ `freq60`(最频繁近60天)/ `loyal`(最专一近60天) | | `project` | enum | 是 | 项目筛选,5 种枚举:`all`(全部)/ `chinese`(🎱 中式/追分)/ `snooker`(斯诺克)/ `mahjong`(🀄 麻将/棋牌)/ `karaoke`(🎤 团建/K歌) | | `page` | number | 否 | 页码,默认 `1` | | `pageSize` | number | 否 | 每页条数,默认 `20` | #### 响应结构(TypeScript 类型定义) ```typescript /** 顶层响应 */ interface BoardCustomersResponse { items: CustomerBoardItem[] total: number page: number pageSize: number } // ─── 助教信息(嵌套在客户卡片中) ─── interface AssistantInfo { /** 助教姓名 */ name: string /** * 前端样式类 * - 'assistant--assignee':当前跟进助教 * - 'assistant--abandoned':已放弃的助教 * - 'assistant--normal':普通关联助教 */ cls: string /** 亲密度分数 0-10,供 heart-icon 组件渲染 */ heartScore: number /** 角标文字,如 '跟'(跟进中)/ '弃'(已放弃) */ badge?: string /** 角标样式类,如 'assistant-badge--follow' / 'assistant-badge--drop' */ badgeCls?: string } /** 客户卡片 — 基础字段 + 8 维度专属字段 */ interface CustomerBoardItem { // ─── 基础字段(所有维度共享,5 个) ─── /** 客户唯一 ID */ id: string /** 客户姓名 */ name: string /** 姓名首字(用于头像占位符) */ initial: string /** 头像样式类(avatar--amber / avatar--pink / avatar--blue 等) */ avatarCls: string /** * 关联助教列表 * 按亲密度降序排列,含跟进状态角标 */ assistants: AssistantInfo[] // ─── recall 维度专属(dimension=recall 时展示) ─── /** 理想到店间隔(天) */ idealDays?: number /** 已过天数(距上次到店) */ elapsedDays?: number /** 超期天数(elapsedDays - idealDays,> 0 表示超期) */ overdueDays?: number /** 近 30 天到店次数 */ visits30d?: number /** 客户余额(格式化字符串,如 '¥2,680') */ balance?: string /** 召回指数(0-10 评分,如 '9.2') */ recallIndex?: string // ─── potential 维度专属(dimension=potential 时展示) ─── /** * 潜力标签列表 * - text: 标签文字(如 '高频'、'高客单'、'高余额'、'低频') * - theme: 标签主题色('primary' / 'warning' / 'success' / 'gray') */ potentialTags?: Array<{ text: string; theme: string }> /** 近 30 天消费(格式化字符串,如 '¥4,200') */ spend30d?: string /** 平均到店频率(如 '6.2次') */ avgVisits?: string /** 平均客单价(格式化字符串,如 '¥680') */ avgSpend?: string // ─── balance 维度专属(dimension=balance 时展示) ─── // balance 字段复用上方 recall 维度的 balance(格式化字符串) /** 最后到店时间(如 '3天前'、'12天前') */ lastVisit?: string /** 月均消费(格式化字符串,如 '¥3,500') */ monthlyConsume?: string /** 可用月数(如 '约0.8个月'、'约4.6个月') */ availableMonths?: string // ─── recharge 维度专属(dimension=recharge 时展示) ─── /** 最近充值日期(如 '2月15日') */ lastRecharge?: string /** 充值金额(格式化字符串,如 '¥5,000') */ rechargeAmount?: string /** 近 60 天充值次数(如 '2次') */ recharges60d?: string /** 当前余额(格式化字符串,如 '¥2,680') */ currentBalance?: string // ─── recent 维度专属(dimension=recent 时展示) ─── /** 距上次到店天数 */ daysAgo?: number /** 到店频率(如 '6.2次/月') */ visitFreq?: string // idealDays 复用 recall 维度字段 // visits30d 复用 recall 维度字段 // avgSpend 复用 potential 维度字段 // ─── spend60 维度专属(dimension=spend60 时展示) ─── /** 近 60 天消费(格式化字符串,如 '¥8,400') */ spend60d?: string /** 近 60 天到店次数(如 '12') */ visits60d?: string /** 是否高消费标记(true 时前端显示高消费标签) */ highSpendTag?: boolean // avgSpend 复用 potential 维度字段 // ─── freq60 维度专属(dimension=freq60 时展示) ─── // visits60d 复用 spend60 维度字段 /** 平均到店间隔(天,如 '5.0天') */ avgInterval?: string /** * 最近 8 周到店柱状图数据 * - val: 该周到店次数 * - pct: 百分比高度(0-100,相对于最大值) * 数组长度固定为 8,从最早一周到最近一周排列 */ weeklyVisits?: Array<{ val: number; pct: number }> // spend60d 复用 spend60 维度字段 // ─── loyal 维度专属(dimension=loyal 时展示) ─── /** 专一度指数(0-100,如 '92') */ intimacy?: string /** Top 1 助教姓名 */ topCoachName?: string /** Top 1 助教亲密度(heart-icon 分数,0-10) */ topCoachHeart?: number /** Top 1 助教关系指数(如 '9.2') */ topCoachScore?: string /** 主助教姓名(占比最高的助教) */ coachName?: string /** 主助教服务占比(如 '78%') */ coachRatio?: string /** * 助教明细列表(按服务占比降序) * 展示每位助教的服务统计和关系指数 */ coachDetails?: Array<{ /** 助教姓名 */ name: string /** 前端样式类(同 AssistantInfo.cls) */ cls: string /** 亲密度分数 0-10 */ heartScore: number /** 角标文字('跟' / '弃'),无角标时不返回 */ badge?: string /** 平均服务时长(如 '2.3h') */ avgDuration: string /** 服务次数(如 '14') */ serviceCount: string /** 该助教关联消费金额(格式化字符串,如 '¥4,200') */ coachSpend: string /** 关系指数(0-10) */ relationIdx: number }> } ``` #### 字段说明表 ##### 基础字段(5 个,所有维度共享) | 字段 | 类型 | 说明 | 数据源 | |------|------|------|--------| | `id` | string | 客户唯一 ID | `fdw_etl.dwd.dim_member.member_id` | | `name` | string | 客户姓名 | `fdw_etl.dwd.dim_member.nickname`(通过 `member_id` JOIN,DQ-6) | | `initial` | string | 姓名首字,用于头像占位符渲染 | 后端从 `name` 提取 | | `avatarCls` | string | 头像样式类(`avatar--amber`/`avatar--pink`/`avatar--blue` 等) | 后端根据 ID 哈希分配 | | `assistants` | `AssistantInfo[]` | 关联助教列表,含亲密度和跟进状态 | `biz.tasks` + `fdw_etl.dws` 亲密度计算 | ##### recall 维度专属字段(6 个) | 字段 | 类型 | 说明 | 数据源 | |------|------|------|--------| | `idealDays` | number | 理想到店间隔(天),基于历史到店频率计算 | `fdw_etl.v_dws_customer_recall` | | `elapsedDays` | number | 距上次到店已过天数 | 同上,`CURRENT_DATE - last_visit_date` | | `overdueDays` | number | 超期天数(`elapsedDays - idealDays`) | 后端计算 | | `visits30d` | number | 近 30 天到店次数 | `fdw_etl.v_dws_customer_recall` | | `balance` | string | 客户余额(格式化),⚠️ 使用 `items_sum` 口径 | `fdw_etl.dwd.dim_member_card_account` | | `recallIndex` | string | 召回指数(0-10),综合超期天数、余额、频率加权 | 后端算法计算 | ##### potential 维度专属字段(4 个) | 字段 | 类型 | 说明 | 数据源 | |------|------|------|--------| | `potentialTags` | `Array<{ text, theme }>` | 潜力标签(高频/高客单/高余额/低频等),`theme` 取值 `primary`/`warning`/`success`/`gray` | 后端根据消费特征生成 | | `spend30d` | string | 近 30 天消费(格式化),⚠️ 使用 `items_sum` 口径 | `fdw_etl.v_dws_customer_potential` | | `avgVisits` | string | 平均到店频率(如 '6.2次') | 同上 | | `avgSpend` | string | 平均客单价(格式化),⚠️ 使用 `items_sum` 口径 | 同上,`spend / visits` | ##### balance 维度专属字段(4 个) | 字段 | 类型 | 说明 | 数据源 | |------|------|------|--------| | `balance` | string | 客户余额(格式化),与 recall 维度共用字段 | `fdw_etl.dwd.dim_member_card_account` | | `lastVisit` | string | 最后到店时间描述(如 '3天前') | `fdw_etl.v_dws_customer_balance` | | `monthlyConsume` | string | 月均消费(格式化),⚠️ 使用 `items_sum` 口径 | 同上 | | `availableMonths` | string | 可用月数(`balance / monthlyConsume`,如 '约0.8个月') | 后端计算 | ##### recharge 维度专属字段(4 个) | 字段 | 类型 | 说明 | 数据源 | |------|------|------|--------| | `lastRecharge` | string | 最近充值日期(如 '2月15日') | `fdw_etl.v_dws_customer_recharge` | | `rechargeAmount` | string | 最近充值金额(格式化) | 同上 | | `recharges60d` | string | 近 60 天充值次数(如 '2次') | 同上 | | `currentBalance` | string | 当前余额(格式化) | `fdw_etl.dwd.dim_member_card_account` | ##### recent 维度专属字段(5 个) | 字段 | 类型 | 说明 | 数据源 | |------|------|------|--------| | `daysAgo` | number | 距上次到店天数 | `fdw_etl.v_dws_customer_recent` | | `visitFreq` | string | 到店频率(如 '6.2次/月') | 同上 | | `idealDays` | number | 理想到店间隔(天),与 recall 维度共用字段 | `fdw_etl.v_dws_customer_recall` | | `visits30d` | number | 近 30 天到店次数,与 recall 维度共用字段 | 同上 | | `avgSpend` | string | 平均客单价,与 potential 维度共用字段 | `fdw_etl.v_dws_customer_potential` | ##### spend60 维度专属字段(4 个) | 字段 | 类型 | 说明 | 数据源 | |------|------|------|--------| | `spend60d` | string | 近 60 天消费(格式化),⚠️ 使用 `items_sum` 口径 | `fdw_etl.v_dws_customer_spend60` | | `visits60d` | string | 近 60 天到店次数 | 同上 | | `highSpendTag` | boolean | 是否高消费标记(`true` 时前端显示高消费标签) | 后端根据阈值判断 | | `avgSpend` | string | 平均客单价,与 potential 维度共用字段 | `fdw_etl.v_dws_customer_potential` | ##### freq60 维度专属字段(4 个) | 字段 | 类型 | 说明 | 数据源 | |------|------|------|--------| | `visits60d` | string | 近 60 天到店次数,与 spend60 维度共用字段 | `fdw_etl.v_dws_customer_spend60` | | `avgInterval` | string | 平均到店间隔(如 '5.0天') | `fdw_etl.v_dws_customer_freq60` | | `weeklyVisits` | `Array<{ val, pct }>` | 最近 8 周到店柱状图,`val` 为次数,`pct` 为百分比高度(0-100) | 同上,按周聚合 | | `spend60d` | string | 近 60 天消费,与 spend60 维度共用字段 | `fdw_etl.v_dws_customer_spend60` | ##### loyal 维度专属字段(7 个) | 字段 | 类型 | 说明 | 数据源 | |------|------|------|--------| | `intimacy` | string | 专一度指数(0-100) | `fdw_etl.v_dws_customer_loyal` | | `topCoachName` | string | Top 1 助教姓名 | 同上,按服务占比排序取第一 | | `topCoachHeart` | number | Top 1 助教亲密度(0-10,heart-icon 用) | 同上 | | `topCoachScore` | string | Top 1 助教关系指数(如 '9.2') | 同上 | | `coachName` | string | 主助教姓名(占比最高) | 同上 | | `coachRatio` | string | 主助教服务占比(如 '78%') | 同上 | | `coachDetails` | `Array<{ name, cls, heartScore, badge?, avgDuration, serviceCount, coachSpend, relationIdx }>` | 助教明细列表,按服务占比降序 | 同上,逐助教聚合 | #### 维度与展示字段的关系 前端根据 `dimension` 参数决定展示哪组维度字段(卡片模板切换): | dimension 值 | 维度类型 | 展示字段 | |-------------|---------|---------| | `recall` | 召回 | `idealDays` / `elapsedDays` / `overdueDays` / `visits30d` / `balance` / `recallIndex` | | `potential` | 潜力 | `potentialTags` / `spend30d` / `avgVisits` / `avgSpend` | | `balance` | 余额 | `balance` / `lastVisit` / `monthlyConsume` / `availableMonths` | | `recharge` | 充值 | `lastRecharge` / `rechargeAmount` / `recharges60d` / `currentBalance` | | `recent` | 到店 | `daysAgo` / `visitFreq` / `idealDays` / `visits30d` / `avgSpend` | | `spend60` | 消费60天 | `spend60d` / `visits60d` / `highSpendTag` / `avgSpend` | | `freq60` | 频率60天 | `visits60d` / `avgInterval` / `weeklyVisits` / `spend60d` | | `loyal` | 专一度 | `intimacy` / `topCoachName` / `topCoachHeart` / `topCoachScore` / `coachName` / `coachRatio` / `coachDetails` | > 后端按 `dimension` 参数仅返回对应维度的专属字段(减少传输量),前端根据 `dimension` 选择性渲染对应卡片模板。 #### 业务规则 | 规则 | 说明 | |------|------| | `items_sum` 口径(DWD-DOC 规则 1) | `balance`、`spend30d`、`avgSpend`、`monthlyConsume`、`spend60d`、`coachSpend` 等金额字段使用 `items_sum` 口径,禁止使用 `consume_money` | | 会员信息 JOIN(DWD-DOC 规则 DQ-6) | 客户姓名通过 `member_id` JOIN `dim_member`(`nickname`,`scd2_is_current=1`),禁止直接使用 `member_phone`/`member_name` | | 会员卡余额 JOIN(DWD-DOC 规则 DQ-7) | 余额通过 `member_id` JOIN `dim_member_card_account`(`scd2_is_current=1`),禁止直接使用 `member_card_type_name` | | `assistants` 排序规则 | 按亲密度(`heartScore`)降序排列;当前跟进助教(`cls='assistant--assignee'`)置顶 | | `weeklyVisits` 固定 8 周 | 数组长度固定为 8,从最早一周到最近一周排列;`pct` 为相对于 8 周最大值的百分比 | | `coachDetails` 排序规则 | 按服务占比降序排列;`badge` 仅在跟进('跟')或放弃('弃')状态时返回 | | 分页上限 | 单次最多返回 100 条(`pageSize` 上限 100),超出截断 | #### 数据源映射 | 维度 | FDW 视图 | 说明 | |------|---------|------| | recall | `fdw_etl.v_dws_customer_recall` | 召回指数、理想间隔、超期天数 | | potential | `fdw_etl.v_dws_customer_potential` | 潜力标签、消费频率、客单价 | | balance | `fdw_etl.v_dws_customer_balance` | 余额、月均消费、可用月数 | | recharge | `fdw_etl.v_dws_customer_recharge` | 充值记录、充值频率 | | recent | `fdw_etl.v_dws_customer_recent` | 到店天数、到店频率 | | spend60 | `fdw_etl.v_dws_customer_spend60` | 60 天消费、到店次数 | | freq60 | `fdw_etl.v_dws_customer_freq60` | 到店间隔、周柱状图 | | loyal | `fdw_etl.v_dws_customer_loyal` | 专一度、助教明细 | | 会员基础 | `fdw_etl.dwd.dim_member` | 姓名、手机号(DQ-6) | | 会员卡 | `fdw_etl.dwd.dim_member_card_account` | 余额(DQ-7) | | 助教关联 | `biz.tasks` + `biz.users` | 跟进状态、角标 | ### BOARD-3: 财务看板 > ⚠️ 全项目最复杂的单个接口,6 个独立板块嵌套结构。 > 数据源:`fdw_etl.v_dws_finance_*` 系列视图 + `fdw_etl.v_dws_assistant_salary_calc` > DWD-DOC 强制规则:所有金额字段使用 `items_sum` 口径(规则 1);助教费用使用 `assistant_pd_money` + `assistant_cx_money` 拆分(规则 2) ``` GET /api/xcx/board/finance?time={time}&area={area}&compare={0|1} ``` #### 请求参数 | 参数 | 类型 | 必填 | 说明 | |------|------|:----:|------| | `time` | enum | 是 | 时间范围,8 种枚举:`month`(本月)/ `lastMonth`(上月)/ `week`(本周)/ `lastWeek`(上周)/ `quarter3`(前3个月不含本月)/ `quarter`(本季度)/ `lastQuarter`(上季度)/ `half6`(最近6个月不含本月) | | `area` | enum | 是 | 区域筛选,7 种枚举:`all`(全部区域)/ `hall`(大厅)/ `hallA`(A区)/ `hallB`(B区)/ `hallC`(C区)/ `mahjong`(麻将房)/ `teamBuilding`(团建房) | | `compare` | `0` \| `1` | 否 | 是否返回环比数据,默认 `0`。`compare=0` 时所有 `xxxCompare` / `isDown` / `isFlat` 字段不返回(减少查询开销) | #### 响应结构(TypeScript 类型定义) ```typescript /** 顶层响应 */ interface BoardFinanceResponse { overview: OverviewSection recharge: RechargeSection | null // area ≠ all 时为 null(储值卡数据不按区域拆分) revenue: RevenueSection cashflow: CashflowSection expense: ExpenseSection coachAnalysis: CoachAnalysisSection } // ─── 环比字段通用模式 ─── // 每个核心指标附带 3 个环比字段: // xxxCompare: string — 环比百分比,如 "12.5%"、"持平" // isDown: boolean — 是否下降(true = 负向变化) // isFlat: boolean — 是否持平(true 时 compare 显示 "持平") // compare=0 时这 3 个字段不返回 // ─── 1. 经营一览 overview ─── interface OverviewSection { /** 发生额/正价 — 所有消费项目按标价计算的总金额 */ occurrence: string // 格式化金额,如 "¥823,456" occurrenceCompare?: string occurrenceDown?: boolean occurrenceFlat?: boolean /** 总优惠 — 会员折扣 + 赠送卡抵扣 + 团购差价等 */ discount: string // 负值,如 "-¥113,336" discountCompare?: string discountDown?: boolean discountFlat?: boolean /** 折扣率 — discount / occurrence */ discountRate: string // 百分比,如 "13.8%" discountRateCompare?: string discountRateDown?: boolean discountRateFlat?: boolean /** 成交/确认收入 — occurrence - discount */ confirmedRevenue: string confirmedCompare?: string confirmedDown?: boolean confirmedFlat?: boolean /** 实收/现金流入 — 实际到账现金(消费直接支付 + 储值充值) */ cashIn: string cashInCompare?: string cashInDown?: boolean cashInFlat?: boolean /** 现金支出 — 人工 + 房租 + 水电 + 进货等 */ cashOut: string cashOutCompare?: string cashOutDown?: boolean cashOutFlat?: boolean /** 现金结余 — cashIn - cashOut */ cashBalance: string cashBalanceCompare?: string cashBalanceDown?: boolean cashBalanceFlat?: boolean /** 结余率 — cashBalance / cashIn */ balanceRate: string // 百分比,如 "32.4%" balanceRateCompare?: string balanceRateDown?: boolean balanceRateFlat?: boolean } // ─── 2. 预收资产 recharge ─── // ⚠️ area ≠ all 时整个板块返回 null(储值卡数据不按区域拆分) interface RechargeSection { /** 储值卡充值实收 */ actualIncome: string actualCompare?: string actualDown?: boolean actualFlat?: boolean /** 首充 */ firstCharge: string firstChargeCompare?: string firstChargeDown?: boolean firstChargeFlat?: boolean /** 续费 */ renewCharge: string renewChargeCompare?: string renewChargeDown?: boolean renewChargeFlat?: boolean /** 消耗 — 会员使用储值卡消费的金额 */ consumed: string consumedCompare?: string consumedDown?: boolean consumedFlat?: boolean /** 储值卡总余额 */ cardBalance: string cardBalanceCompare?: string cardBalanceDown?: boolean cardBalanceFlat?: boolean /** * 赠送卡 3×4 矩阵 * 行:新增 / 消费 / 余额 * 列:合计 / 酒水卡 / 台费卡 / 抵用券 * 每个单元格含值和环比字段 */ giftRows: GiftRow[] /** 全类别会员卡余额合计 — 储值卡 + 赠送卡(酒水卡 + 台费卡 + 抵用券) */ allCardBalance: string allCardBalanceCompare?: string allCardBalanceDown?: boolean allCardBalanceFlat?: boolean } interface GiftRow { label: string // "新增" | "消费" | "余额" total: string // 合计金额 totalCompare?: string wine: string // 酒水卡 wineCompare?: string table: string // 台费卡 tableCompare?: string coupon: string // 抵用券 couponCompare?: string } // ─── 3. 应计收入确认 revenue ─── interface RevenueSection { /** * 收入结构表 — 9 行含子行标记 * 主行:开台与包厢 / 助教(基础课) / 助教(激励课) / 食品酒水 * 子行(isSub=true):A区 / B区 / C区 / 团建区 / 麻将区(属于"开台与包厢"的子行) * * ⚠️ DWD-DOC 规则 2:助教行金额来自 assistant_pd_money(基础课/陪打)和 assistant_cx_money(激励课/超休) */ structureRows: RevenueStructureRow[] /** 正价明细 — 4 项 */ priceItems: RevenueDetailItem[] /** 发生额合计 */ totalOccurrence: string totalOccurrenceCompare?: string /** 优惠明细 — 4 项 */ discountItems: RevenueDetailItem[] /** 确认收入合计 — occurrence - discount */ confirmedTotal: string confirmedTotalCompare?: string confirmedTotalDown?: boolean confirmedTotalFlat?: boolean /** 渠道明细 — 3 项(储值卡结算冲销 / 现金线上支付 / 团购核销确认收入) */ channelItems: RevenueDetailItem[] } interface RevenueStructureRow { id: string // 行标识,如 "table" / "area-a" / "coach-basic" / "food" name: string // 行名称,如 "开台与包厢" / "A区" / "助教" / "食品酒水" desc?: string // 行描述,如 "基础课" / "激励课" amount: string // 发生额 discount: string // 优惠金额(无优惠时为 "-") booked: string // 入账金额 bookedCompare?: string // 入账环比 isSub?: boolean // 是否为子行(缩进展示) } interface RevenueDetailItem { name: string // 项目名称 desc?: string // 补充说明,如 "台桌卡+酒水卡+抵用券" value: string // 金额 compare?: string // 环比 } // ─── 4. 现金流入 cashflow ─── interface CashflowSection { /** 消费收款 — 3 项(纸币现金 / 线上收款 / 团购平台) */ consumeItems: CashflowItem[] /** 充值收款 — 1 项(会员充值到账) */ rechargeItems: CashflowItem[] /** 合计 */ total: string totalCompare?: string totalDown?: boolean totalFlat?: boolean } interface CashflowItem { name: string // 项目名称 desc: string // 补充说明,如 "柜台现金收款" value: string // 金额 compare?: string // 环比 isDown?: boolean // 是否下降 } // ─── 5. 现金流出 expense ─── interface ExpenseSection { /** 经营支出 — 3 项(食品饮料 / 耗材 / 报销) */ operationItems: ExpenseItem[] /** 固定支出 — 4 项(房租 / 水电 / 物业 / 人员工资) */ fixedItems: ExpenseItem[] /** * 助教分成 — 4 项(基础课分成 / 激励课分成 / 充值提成 / 额外奖金) * ⚠️ DWD-DOC 规则 2:基础课分成来自 assistant_pd_money,激励课分成来自 assistant_cx_money */ coachItems: ExpenseItem[] /** 平台服务费 — 3 项(汇来米 / 美团 / 抖音) */ platformItems: ExpenseItem[] /** 合计 */ total: string totalCompare?: string totalDown?: boolean totalFlat?: boolean } interface ExpenseItem { name: string // 项目名称 value: string // 金额 compare?: string // 环比,如 "4.5%" 或 "持平" isDown?: boolean // 是否下降 isFlat?: boolean // 是否持平 } // ─── 6. 助教分析 coachAnalysis ─── interface CoachAnalysisSection { /** 基础课(陪打)— assistant_pd_money */ basic: CoachAnalysisTable /** 激励课(超休)— assistant_cx_money */ incentive: CoachAnalysisTable } interface CoachAnalysisTable { /** 汇总行 */ totalPay: string // 总课时费 totalPayCompare?: string totalPayDown?: boolean totalShare: string // 总分成 totalShareCompare?: string totalShareDown?: boolean avgHourly: string // 平均时薪,如 "¥25/h" avgHourlyCompare?: string avgHourlyFlat?: boolean /** 按等级分行 — 初级 / 中级 / 高级 / 星级 */ rows: CoachAnalysisRow[] } interface CoachAnalysisRow { level: string // "初级" | "中级" | "高级" | "星级" pay: string // 课时费 payCompare?: string payDown?: boolean share: string // 分成 shareCompare?: string shareDown?: boolean hourly: string // 时薪,如 "¥20/h" hourlyCompare?: string // 如 "持平" hourlyFlat?: boolean // 是否持平 } ``` #### 业务规则 | 规则 | 说明 | |------|------| | `area ≠ all` 时 `recharge = null` | 储值卡数据不按区域拆分,非全部区域时预收资产板块不返回 | | `items_sum` 口径(DWD-DOC 规则 1) | 所有金额字段使用 `items_sum = table_charge_money + goods_money + assistant_pd_money + assistant_cx_money + electricity_money`,禁止使用 `consume_money` | | 助教费用拆分(DWD-DOC 规则 2) | 基础课 = `assistant_pd_money`(陪打),激励课 = `assistant_cx_money`(超休),禁止使用 `service_fee` | | 支付渠道恒等式(DWD-DOC 规则 3) | `balance_amount = recharge_card_amount + gift_card_amount`,三者不可重复计算 | | 折扣互斥(DWD-DOC 规则 6) | `discount_manual`(大客户优惠)与 `discount_other` 互斥,两者之和 = `adjust_amount` | | 现金流互斥(DWD-DOC 规则 7) | `platform_settlement_amount` 和 `groupbuy_pay_amount` 互斥 | | 环比计算 | 后端根据 `time` 参数计算当期和上期日期范围,分别查询后计算百分比;`compare=0` 时不计算 | #### 数据源映射 | 板块 | FDW 视图 | 说明 | |------|---------|------| | overview | `fdw_etl.v_dws_finance_overview` | 8 指标 + 8 环比 | | recharge | `fdw_etl.v_dws_finance_recharge` | 储值卡 + 赠送卡矩阵 | | revenue | `fdw_etl.v_dws_finance_revenue` | 收入结构表 + 明细 | | cashflow | `fdw_etl.v_dws_finance_cashflow` | 消费收款 + 充值收款 | | expense | `fdw_etl.v_dws_finance_expense` | 4 子分组 | | coachAnalysis | `fdw_etl.v_dws_assistant_salary_calc` | 按等级分行聚合 | #### 字段说明表 ##### overview 经营一览(8 指标) | 字段 | 类型 | 说明 | 环比字段 | |------|------|------|---------| | `occurrence` | string | 发生额/正价 | `occurrenceCompare` + `occurrenceDown` + `occurrenceFlat` | | `discount` | string | 总优惠(负值) | `discountCompare` + `discountDown` + `discountFlat` | | `discountRate` | string | 折扣率(百分比) | `discountRateCompare` + `discountRateDown` + `discountRateFlat` | | `confirmedRevenue` | string | 成交/确认收入 | `confirmedCompare` + `confirmedDown` + `confirmedFlat` | | `cashIn` | string | 实收/现金流入 | `cashInCompare` + `cashInDown` + `cashInFlat` | | `cashOut` | string | 现金支出 | `cashOutCompare` + `cashOutDown` + `cashOutFlat` | | `cashBalance` | string | 现金结余 | `cashBalanceCompare` + `cashBalanceDown` + `cashBalanceFlat` | | `balanceRate` | string | 结余率(百分比) | `balanceRateCompare` + `balanceRateDown` + `balanceRateFlat` | ##### recharge 预收资产 | 字段 | 类型 | 说明 | |------|------|------| | `actualIncome` | string | 储值卡充值实收 | | `firstCharge` | string | 首充 | | `renewCharge` | string | 续费 | | `consumed` | string | 消耗 | | `cardBalance` | string | 储值卡总余额 | | `giftRows` | GiftRow[] | 赠送卡 3×4 矩阵(新增/消费/余额 × 合计/酒水卡/台费卡/抵用券) | | `allCardBalance` | string | 全类别会员卡余额合计 | ##### revenue 应计收入确认 | 字段 | 类型 | 说明 | |------|------|------| | `structureRows` | RevenueStructureRow[] | 收入结构表(9 行含子行) | | `priceItems` | RevenueDetailItem[] | 正价明细(4 项:开台消费/酒水商品/包厢费用/助教服务) | | `totalOccurrence` | string | 发生额合计 | | `discountItems` | RevenueDetailItem[] | 优惠明细(4 项:团购优惠/手动调整+大客户优惠/赠送卡抵扣/其他优惠) | | `confirmedTotal` | string | 确认收入合计 | | `channelItems` | RevenueDetailItem[] | 渠道明细(3 项:储值卡结算冲销/现金线上支付/团购核销确认收入) | ##### coachAnalysis 助教分析 | 子表 | 数据源字段 | 说明 | |------|-----------|------| | `basic` | `assistant_pd_money` | 基础课(陪打)按初级/中级/高级/星级分行 | | `incentive` | `assistant_cx_money` | 激励课(超休)按初级/中级/高级/星级分行 | --- ## 七、助教模块(已实现 ✅) ### COACH-1: 助教详情 > 数据源:`zqyy_app.coaches`(基本信息)、`etl_feiqiu.dws.*`(绩效/收入,via FDW)、`zqyy_app.coach_tasks`(任务)、`zqyy_app.coach_notes`(备注) > 金额口径:所有消费金额使用 `items_sum`(DWD-DOC 强制规则 1);助教费用使用 `assistant_pd_money` + `assistant_cx_money` 拆分(DWD-DOC 强制规则 2) > 会员信息:通过 `member_id` JOIN `dim_member` 获取(DWD-DOC 强制规则 DQ-6),禁止直接使用 `member_phone` ``` GET /api/xcx/coaches/{coachId} ``` #### 响应结构 ```typescript interface CoachDetailResponse { // ── 基本信息 ── id: string name: string avatar: string level: string // 初级/中级/高级/星级 skills: string[] // 技能标签,如 ['中🎱', '🎯斯诺克'] workYears: number // 工龄(年) customerCount: number // 客户数 hireDate: string // 入职日期,如 '2023-03-15' // ── 绩效指标(performance) ── performance: { monthlyHours: number // 本月定档工时 monthlySalary: number // 本月工资(预估) customerBalance: number // 客源储值余额合计 tasksCompleted: number // 本月任务完成数 perfCurrent: number // 当前绩效值 perfTarget: number // 绩效目标值 } // ── 收入明细(income) ── // thisMonth / lastMonth 各含 4 项收入分类 income: { thisMonth: IncomeItem[] lastMonth: IncomeItem[] } // ── 档位节点(tierNodes) ── // 供前端绩效进度条组件使用,如 [0, 100, 130, 160, 190, 220] tierNodes: number[] // ── 任务分组 ── visibleTasks: TaskItem[] // 可见任务 hiddenTasks: TaskItem[] // 隐藏任务 abandonedTasks: AbandonedTask[] // 已放弃任务 // ── Top 客户(topCustomers) ── topCustomers: TopCustomer[] // ── 近期服务记录(serviceRecords) ── serviceRecords: ServiceRecord[] // ── 历史月份统计(historyMonths) ── // 最近 5+ 个月的汇总数据 historyMonths: HistoryMonth[] // ── 备注(notes) ── notes: NoteItem[] } ``` #### 子类型定义 ```typescript /** 收入分项 */ interface IncomeItem { label: string // 如 '基础课时费'、'激励课时费'、'充值提成'、'酒水提成' amount: string // 格式化金额,如 '¥3,500' color: string // 主题色:primary / success / warning / purple } /** 任务项(visibleTasks / hiddenTasks) */ interface TaskItem { typeLabel: string // 任务类型标签,如 '高优先召回'、'客户回访' typeClass: string // 样式类:high-priority / priority / callback / relationship customerName: string // 客户姓名 customerId?: string // 客户 ID(用于跳转 customer-detail) noteCount: number // 备注数量 pinned: boolean // 是否置顶 notes?: Array<{ // 备注列表(可选) pinned?: boolean // 是否置顶备注 text: string // 备注内容 date: string // 日期,如 '2026-02-06' }> } /** 已放弃任务 */ interface AbandonedTask { customerName: string reason: string // 放弃原因,如 '客户拒绝'、'超时未响应' } /** Top 客户(扩展版,含关系指数和余额) */ interface TopCustomer { id: string name: string initial: string // 姓氏首字,如 '王' avatarGradient: string // 头像渐变色:pink / amber / green / blue / violet / teal heartEmoji: string // 关系 emoji(P6 AC3 四级映射):💖 / 🧡 / 💛 / 💙 relationScore: string // 关系指数(0-10),如 '9.5' scoreColor: string // 分数颜色:#FF6B6B / #FF8C00 / #FFA726 / #5B9BD5 serviceCount: number // 服务次数 balance: string // 余额(格式化),如 '¥8,600' consume: string // 消费总额(格式化,items_sum 口径),如 '¥12,800' } /** 近期服务记录 */ interface ServiceRecord { customerId?: string // 客户 ID(用于跳转 customer-detail) customerName: string initial: string avatarGradient: string type: string // 课程类型:'基础课' / '激励课' typeClass: string // 样式类:basic / incentive table: string // 台桌名,如 'A12号台' duration: string // 时长,如 '2.5h' income: string // 收入(格式化),如 '¥200' date: string // 日期时间,如 '2026-02-07 21:30' perfHours?: string // 折算工时(可选),如 '2h' } /** 历史月份统计 */ interface HistoryMonth { month: string // 月份标签:'本月' / '上月' / '4月' 等 estimated: boolean // 是否为预估数据 customers: string // 客户数(格式化),如 '22人' hours: string // 工时(格式化),如 '87.5h' salary: string // 工资(格式化),如 '¥6,950' callbackDone: number // 回访任务完成数 recallDone: number // 召回任务完成数 } /** 备注项 */ interface NoteItem { id: string content: string // 备注内容 timestamp: string // ISO 时间戳,用于排序 aiScore?: number // AI 应用 6 评分(1-10,展示用,星数 = aiScore ÷ 2) manualScore?: number // 用户手动评分(1-5 星,输入用) customerName: string // 关联客户/操作人 tagLabel: string // 标签 createdAt: string // 格式化时间,如 '2026-03-05 14:30' } ``` #### 字段说明 | 字段 | 类型 | 说明 | |------|------|------| | `performance.monthlyHours` | number | 本月定档工时(折算后) | | `performance.monthlySalary` | number | 本月工资预估(含基础+激励+提成) | | `performance.customerBalance` | number | 该助教所有客户的储值余额合计 | | `performance.tasksCompleted` | number | 本月已完成任务数 | | `performance.perfCurrent` | number | 当前绩效进度值 | | `performance.perfTarget` | number | 绩效目标值 | | `tierNodes` | number[] | 档位节点数组,如 `[0, 100, 130, 160, 190, 220]`,前端用于绘制进度条刻度 | | `income.thisMonth` / `lastMonth` | IncomeItem[] | 各含 4 项:基础课时费 / 激励课时费 / 充值提成 / 酒水提成 | | `topCustomers[].consume` | string | 消费总额,使用 `items_sum` 口径 | | `serviceRecords[].perfHours` | string? | 折算工时,仅激励课有值 | | `serviceRecords[].customerId` | string? | 客户 ID,用于从服务记录跳转到客户详情 | | `visibleTasks[].customerId` | string? | 客户 ID,用于从任务项跳转到客户详情 | | `historyMonths` | HistoryMonth[] | 最近 5+ 个月,第一条为本月(`estimated: true`) | ### 助教模块错误码(COACH-1) | HTTP 状态码 | 场景 | 响应示例 | |:-----------:|------|---------| | 403 | 用户未通过审核(`require_approved()` 拦截) | `{ "code": 403, "message": "用户未通过审核,无法访问此资源" }` | | 404 | 助教不存在(`coachId` 无对应记录) | `{ "code": 404, "message": "助教不存在" }` | | 422 | 请求参数校验失败(coachId 类型不合法) | Pydantic `HTTPValidationError` | > COACH-1 扩展模块(income/topCustomers/serviceRecords/historyMonths/notes)查询失败时不返回错误码,而是对该模块返回空默认值,整体响应仍为 HTTP 200。 --- ## 八、对话模块(已实现 ✅ — RNS1.4) > 路由前缀:`/api/xcx/chat`(RNS1.4 从 `/api/ai/*` 迁移,旧路径已移除) > 数据源:`zqyy_app.biz.ai_conversations`(对话会话)、`zqyy_app.biz.ai_messages`(消息) > 时间字段统一使用 `createdAt`(camelCase),替代前端 `timestamp` 和旧契约 `created_at` > 所有端点需 JWT(approved 状态),使用 `require_approved()` 权限检查 ### CHAT-1: 对话历史列表 ``` GET /api/xcx/chat/history?page=1&pageSize=20 ``` #### 请求参数 | 参数 | 类型 | 必填 | 说明 | |------|------|:----:|------| | `page` | number (query) | 否 | 页码,默认 1 | | `pageSize` | number (query) | 否 | 每页条数,默认 20,最大 100 | #### 响应结构 ```typescript interface ChatHistoryResponse { items: Array<{ id: number // 对话 ID(即 chatId) title: string // 对话标题(自定义 > 客户姓名 > 首条消息前20字) customerName?: string // 关联客户姓名(仅 contextType=customer 时有值) lastMessage?: string // 最后一条消息摘要 timestamp: string // 最后消息时间(ISO 8601) unreadCount: number // 未读消息数 }> total: number page: number pageSize: number } ``` #### 排序规则 按 `last_message_at` 倒序(最新对话在前)。 ### CHAT-2a: 通过 chatId 查询消息 ``` GET /api/xcx/chat/{chatId}/messages?page=1&pageSize=50 ``` #### 请求参数 | 参数 | 类型 | 必填 | 说明 | |------|------|:----:|------| | `chatId` | number (path) | 是 | 对话 ID | | `page` | number (query) | 否 | 页码,默认 1 | | `pageSize` | number (query) | 否 | 每页条数,默认 50,最大 100 | ### CHAT-2b: 通过上下文查询消息 > 后端根据 `contextType` + `contextId` 自动查找或创建对话,返回对应的 `chatId` 和消息列表。 > 对话复用规则:`task` 入口同一 taskId 始终复用(无时限);`customer`/`coach` 入口 ≤ 3 天复用、> 3 天新建;`general` 入口始终新建。 ``` GET /api/xcx/chat/messages?contextType={type}&contextId={id}&page=1&pageSize=50 ``` #### 请求参数 | 参数 | 类型 | 必填 | 说明 | |------|------|:----:|------| | `contextType` | string (query) | 是 | 上下文类型:`task` / `customer` / `coach` / `general` | | `contextId` | string (query) | 是 | 上下文 ID(taskId / customerId / coachId,general 时为空) | | `page` | number (query) | 否 | 页码,默认 1 | | `pageSize` | number (query) | 否 | 每页条数,默认 50,最大 100 | #### 响应结构(CHAT-2a / CHAT-2b 共用) ```typescript interface ChatMessagesResponse { chatId: number // 对话 ID(上下文入口时由后端返回,供后续发送消息使用) items: Array<{ id: number role: 'user' | 'assistant' content: string createdAt: string // 统一时间字段名,ISO 8601 // 引用卡片(可选,AI 回复涉及特定客户时附带) referenceCard?: ReferenceCard }> total: number page: number pageSize: number } ``` #### 排序规则 消息按 `created_at` 正序(最早的消息在前)。 #### referenceCard 结构定义 ```typescript interface ReferenceCard { type: 'customer' | 'record' // 引用类型 title: string // 卡片标题,如 '张伟 — 消费概览' summary: string // 摘要文字,如 '余额 ¥5,200,近30天消费 ¥2,380' data: Record // 键值对详情 } ``` 示例: ```json { "type": "customer", "title": "张伟 — 消费概览", "summary": "余额 ¥5,200,近30天消费 ¥2,380", "data": { "余额": "¥5,200", "近30天消费": "¥2,380", "到店次数": "8次", "最近到店": "3天前" } } ``` > referenceCard 中金额使用 `items_sum` 口径(DWD-DOC 强制规则 1),会员信息通过 `member_id` JOIN `dim_member` 获取(DWD-DOC 规则 DQ-6)。 ### CHAT-3: 发送消息(同步回复) ``` POST /api/xcx/chat/{chatId}/messages Content-Type: application/json Body: { content: string } ``` #### 请求参数 | 参数 | 类型 | 必填 | 说明 | |------|------|:----:|------| | `chatId` | number (path) | 是 | 对话 ID(归属验证:不属于当前用户返回 403) | | `content` | string (body) | 是 | 消息内容(不能为空) | #### 响应结构 ```typescript interface SendMessageResponse { userMessage: { id: number content: string createdAt: string // ISO 8601 } aiReply: { id: number content: string createdAt: string // ISO 8601 } } ``` #### AI 失败降级 AI 服务调用失败时,用户消息仍保存,`aiReply.content` 返回错误提示消息(如 `"抱歉,AI 助手暂时无法回复,请稍后重试"`),HTTP 状态码保持 200。 ### CHAT-4: SSE 流式端点 > 用于 AI 流式回复,前端通过 SSE 接收逐 token 输出。 > chatId 归属验证在 SSE 流开始前完成,失败时返回普通 HTTP 403(非 SSE)。 ``` POST /api/xcx/chat/stream Content-Type: application/json Body: { chatId: number, content: string } Response: text/event-stream ``` #### SSE 事件类型定义 三种事件类型:`message`(逐 token)、`done`(完成)、`error`(错误)。 ``` event: message data: {"token": "根据"} event: message data: {"token": "数据分析"} event: done data: {"messageId": 123, "createdAt": "2026-03-20T14:30:00+08:00"} event: error data: {"message": "AI 服务暂时不可用"} ``` | 事件类型 | data 结构 | 说明 | |----------|----------|------| | `message` | `{ "token": string }` | 逐 token 输出,`token` 为文本片段 | | `done` | `{ "messageId": number, "createdAt": string }` | 流结束,返回完整消息 ID 和创建时间 | | `error` | `{ "message": string }` | 错误,返回错误描述 | > 注意:SSE 端点的响应不经过 ResponseWrapperMiddleware 包装(`text/event-stream` 自动跳过,RNS1.0 已实现)。 #### 对话模块错误码 | HTTP 状态码 | 场景 | 响应示例 | |:-----------:|------|---------| | 403 | 用户未通过审核 | `{ "code": 403, "message": "用户未通过审核,无法访问此资源" }` | | 403 | chatId 不属于当前用户 | `{ "code": 403, "message": "无权访问此对话" }` | | 404 | 对话不存在 | `{ "code": 404, "message": "对话不存在" }` | | 422 | 消息内容为空 | `{ "code": 422, "message": "消息内容不能为空" }` | #### 字段说明 | 字段 | 类型 | 说明 | |------|------|------| | `CHAT-1.items[].title` | string | 对话标题(自定义 > 客户姓名 > 首条消息前20字) | | `CHAT-1.items[].timestamp` | string | 最后消息时间(ISO 8601) | | `CHAT-2.items[].createdAt` | string | 统一时间字段名,替代 `created_at` 和 `timestamp` | | `CHAT-2.items[].referenceCard` | ReferenceCard? | 引用卡片,AI 回复涉及客户时附带结构化上下文数据 | | `CHAT-2b` `contextType` 参数 | query | 上下文类型:`task` / `customer` / `coach` / `general` | | `CHAT-2b` `contextId` 参数 | query | 上下文 ID(taskId / customerId / coachId) | | `CHAT-4` SSE | text/event-stream | 三种事件:`message`(token)/ `done`(完成)/ `error`(错误) | --- ## 九、配置模块 ### CONFIG-1: 技能类型列表(REQ-1) ``` GET /api/xcx/config/skill-types Response: { skills: Array<{ value: string, text: string, icon?: string }> } ``` --- ## 附:接口与页面映射 | 页面 | 依赖接口 | 优先级 | |------|----------|--------| | login | AUTH-1, AUTH-2 | ✅ 已实现 | | app.ts | AUTH-3, AUTH-4 | ✅ 已实现 | | apply | AUTH-5 | ✅ 已实现 | | reviewing | AUTH-3 | ✅ 已实现 | | no-permission | AUTH-3 | ✅ 已实现 | | task-list | TASK-1 | P0 | | task-detail | TASK-2, TASK-3, TASK-4, NOTE-2, NOTE-3 | P0 | | notes | NOTE-1, NOTE-3 | P1 | | performance | PERF-1 | P1 | | performance-records | PERF-2 | P1 | | customer-detail | CUST-1 | ✅ 后端已实现 | | customer-service-records | CUST-2 | ✅ 后端已实现 | | coach-detail | COACH-1 | ✅ 后端已实现 | | board-coach | BOARD-1, CONFIG-1 | P2 | | board-customer | BOARD-2 | P2 | | board-finance | BOARD-3 | P2 | | chat-history | CHAT-1 | ✅ 后端已实现 | | chat | CHAT-2a, CHAT-2b, CHAT-3, CHAT-4 | ✅ 后端已实现 | | my-profile | AUTH-3 | ✅ 已实现(读 globalData) |