2023 lines
80 KiB
Markdown
2023 lines
80 KiB
Markdown
# 小程序接口契约文档
|
||
|
||
> 创建时间:2026-03-18
|
||
> 状态:联调前草案,后端开发时以此为契约基准
|
||
> 基础路径:`/api/xcx`
|
||
|
||
---
|
||
|
||
## 〇、约定
|
||
|
||
- 所有接口需认证(`Authorization: Bearer <token>`),除 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(💖 = 高亲密度 ≥ 0.7,💛 = 中亲密度 < 0.7) | 后端根据 `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 | 💖 = 高亲密度(关系指数 ≥ 0.7),💛 = 中亲密度(关系指数 < 0.7) |
|
||
|
||
#### 数据源映射表
|
||
|
||
| 模块 | 主数据源 | 辅助数据源 |
|
||
|------|---------|-----------|
|
||
| 基础信息 | `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 前缀)
|
||
* 示例:['💖 王先生', '💖 李女士', '💛 赵总']
|
||
* 💖 = 高亲密度,💛 = 中亲密度
|
||
*/
|
||
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 规则 | 💖 = 高亲密度(关系指数 ≥ 0.7),💛 = 中亲密度(关系指数 < 0.7) |
|
||
|
||
### 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。
|
||
|
||
---
|
||
|
||
## 八、对话模块
|
||
|
||
> 数据源:`zqyy_app.chat_sessions`(对话会话)、`zqyy_app.chat_messages`(消息)
|
||
> 时间字段统一使用 `createdAt`(camelCase),替代前端 `timestamp` 和旧契约 `created_at`
|
||
|
||
### CHAT-1: 对话历史列表
|
||
|
||
```
|
||
GET /api/xcx/chat/history?page=1&pageSize=20
|
||
```
|
||
|
||
#### 响应结构
|
||
|
||
```typescript
|
||
interface ChatHistoryResponse {
|
||
items: Array<{
|
||
id: string // 对话 ID
|
||
title: string // 对话标题(新增,需求 5.8.2)
|
||
customerName?: string // 关联客户姓名(可选)
|
||
lastMessage: string // 最后一条消息摘要
|
||
timestamp: string // 最后消息时间(ISO 8601)
|
||
unreadCount: number // 未读消息数
|
||
}>
|
||
total: number
|
||
page: number
|
||
pageSize: number
|
||
}
|
||
```
|
||
|
||
### CHAT-2: 对话消息
|
||
|
||
> 支持 `customerId` 查询参数(需求 5.8.5):后端根据 `customerId` 自动查找或创建对话,返回对应的 `chatId` 和消息列表
|
||
|
||
```
|
||
GET /api/xcx/chat/{chatId}/messages?page=1&pageSize=50
|
||
GET /api/xcx/chat/messages?customerId={customerId}&page=1&pageSize=50
|
||
```
|
||
|
||
#### 响应结构
|
||
|
||
```typescript
|
||
interface ChatMessagesResponse {
|
||
chatId: string // 对话 ID(customerId 入口时由后端返回)
|
||
items: Array<{
|
||
id: string
|
||
role: 'user' | 'assistant'
|
||
content: string
|
||
createdAt: string // 统一时间字段名(需求 5.8.1),ISO 8601
|
||
|
||
// 引用卡片(可选,需求 5.8.3)
|
||
referenceCard?: {
|
||
type: 'customer' | 'record' // 引用类型
|
||
title: string // 卡片标题,如 '张伟 — 消费概览'
|
||
summary: string // 摘要文字
|
||
data: Record<string, string> // 键值对详情,如 { '近30天消费': '¥2,380', '到店次数': '8次' }
|
||
}
|
||
}>
|
||
total: number
|
||
page: number
|
||
pageSize: number
|
||
}
|
||
```
|
||
|
||
### CHAT-3: 发送消息
|
||
|
||
```
|
||
POST /api/xcx/chat/{chatId}/messages
|
||
Body: { content: string }
|
||
```
|
||
|
||
#### 响应结构
|
||
|
||
```typescript
|
||
interface SendMessageResponse {
|
||
userMessage: {
|
||
id: string
|
||
content: string
|
||
createdAt: string
|
||
}
|
||
aiReply: {
|
||
id: string
|
||
content: string
|
||
createdAt: string
|
||
}
|
||
}
|
||
```
|
||
|
||
### CHAT-4: SSE 流式端点(需求 5.8.4)
|
||
|
||
> 用于 AI 流式回复,前端通过 SSE 接收逐 token 输出
|
||
|
||
```
|
||
POST /api/xcx/chat/stream
|
||
Content-Type: application/json
|
||
Body: { chatId: string, content: string }
|
||
Response: text/event-stream
|
||
```
|
||
|
||
#### SSE 事件格式
|
||
|
||
```
|
||
event: message
|
||
data: {"token": "根据"}
|
||
|
||
event: message
|
||
data: {"token": "数据分析"}
|
||
|
||
event: done
|
||
data: {"messageId": "msg-xxx", "createdAt": "2026-03-05T14:30:00+08:00"}
|
||
```
|
||
|
||
- `event: message` — 逐 token 输出,`data.token` 为文本片段
|
||
- `event: done` — 流结束,`data.messageId` 为完整消息 ID
|
||
- `event: error` — 错误,`data.message` 为错误描述
|
||
|
||
> 注意:SSE 端点的响应不经过 ResponseWrapperMiddleware 包装(`text/event-stream` 自动跳过)
|
||
|
||
#### 字段说明
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `CHAT-1.items[].title` | string | 对话标题,新增字段 |
|
||
| `CHAT-2.items[].createdAt` | string | 统一时间字段名,替代 `created_at` 和 `timestamp` |
|
||
| `CHAT-2.items[].referenceCard` | object? | 引用卡片,从其他页面跳转时附带的上下文信息 |
|
||
| `CHAT-2` `customerId` 参数 | query | 支持通过客户 ID 查找/创建对话 |
|
||
| `CHAT-4` | SSE | 流式端点,`text/event-stream` 格式,不经过响应包装 |
|
||
|
||
---
|
||
|
||
## 九、配置模块
|
||
|
||
### 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 | P2 |
|
||
| chat | CHAT-2, CHAT-3 | P2 |
|
||
| my-profile | AUTH-3 | ✅ 已实现(读 globalData) |
|