296 lines
14 KiB
Markdown
296 lines
14 KiB
Markdown
# board-customer 页面数据来源排查
|
||
|
||
> 排查日期:2026-03-18
|
||
> 页面路径:`pages/board-customer/board-customer`
|
||
> 引用文件:`board-customer.ts` / `.wxml` / `.wxss` / `.json`
|
||
> 外部依赖:`utils/ai-color-manager.ts`(`initPageAiColor`,已导入但未调用)
|
||
|
||
## 概览
|
||
|
||
| 分类 | 数量 | 说明 |
|
||
|------|------|------|
|
||
| A. Mock 数据 | 1 组(3 条记录,~30 个字段/条) | 页面内联 `MOCK_CUSTOMERS` 常量,未引用 `utils/mock-data.ts` |
|
||
| B. 硬编码数据 | 5 处 | 维度选项、项目选项、Tab 列表、列表标题、加载延迟 |
|
||
| C. 已对接 API | 0 | 无任何 `wx.request` / API 调用 |
|
||
| D. 前端计算/派生 | 3 处 | `dimType` 映射、滚动隐藏筛选栏、空/错误态判断 |
|
||
| E. 路由参数 | 0 | `onLoad` 无参数读取 |
|
||
| F. WXML 硬编码文案 | 12 处 | Tab 文案、列表标题、网格标签、状态提示等 |
|
||
|
||
---
|
||
|
||
## 一、Mock 数据
|
||
|
||
### 1.1 `MOCK_CUSTOMERS`(页面内联,第 72–178 行)
|
||
|
||
页面顶部定义的 `const MOCK_CUSTOMERS: CustomerItem[]`,包含 3 条客户记录(王先生 / 李女士 / 张先生),在 `loadData()` 中通过 `setTimeout(400ms)` 模拟异步加载。
|
||
|
||
**未引用 `utils/mock-data.ts`**:该文件中有 `mockCustomers: CustomerCard[]`,但 board-customer 页面使用了自己定义的 `CustomerItem` 接口(字段更丰富,支持 8 维度卡片)。
|
||
|
||
#### 字段清单(每条 CustomerItem)
|
||
|
||
| 字段 | 示例值(王先生) | 数据类型 | 联调时对应 API 字段 |
|
||
|------|------------------|----------|---------------------|
|
||
| `id` | `'u1'` | string | 客户 ID(`member_id`) |
|
||
| `name` | `'王先生'` | string | 客户姓名(`dim_member.nickname`,注意 DQ-6 断档) |
|
||
| `initial` | `'王'` | string | 前端从 `name` 截取首字 |
|
||
| `avatarCls` | `'avatar--amber'` | string | 前端根据规则分配头像色 |
|
||
| `idealDays` | `7` | number | 理想到店间隔天数(DWS 计算) |
|
||
| `elapsedDays` | `15` | number | 距上次到店已过天数(DWS 计算) |
|
||
| `overdueDays` | `8` | number | 超期天数 = `elapsedDays - idealDays` |
|
||
| `visits30d` | `3` | number | 近 30 天到店次数 |
|
||
| `balance` | `'¥2,680'` | string | 当前余额(储值卡) |
|
||
| `recallIndex` | `'9.2'` | string | 召回指数(DWS 综合评分) |
|
||
| `potentialTags` | `[{text:'高频',theme:'primary'}]` | array | 消费潜力标签(后端计算) |
|
||
| `spend30d` | `'¥4,200'` | string | 近 30 天消费金额 |
|
||
| `avgVisits` | `'6.2次'` | string | 月均到店次数 |
|
||
| `avgSpend` | `'¥680'` | string | 次均消费金额 |
|
||
| `lastVisit` | `'3天前'` | string | 最近到店(相对时间) |
|
||
| `monthlyConsume` | `'¥3,500'` | string | 月均消耗金额 |
|
||
| `availableMonths` | `'约0.8个月'` | string | 余额可用月数 = `balance / monthlyConsume` |
|
||
| `lastRecharge` | `'2月15日'` | string | 最后充值日期 |
|
||
| `rechargeAmount` | `'¥5,000'` | string | 最后充值金额 |
|
||
| `recharges60d` | `'2次'` | string | 近 60 天充值次数 |
|
||
| `currentBalance` | `'¥2,680'` | string | 当前余额(与 `balance` 重复) |
|
||
| `spend60d` | `'¥8,400'` | string | 近 60 天消费金额 |
|
||
| `visits60d` | `'12'` | string | 近 60 天到店次数 |
|
||
| `highSpendTag` | `true` | boolean | 是否高消费标签 |
|
||
| `avgInterval` | `'5.0天'` | string | 平均到店间隔 |
|
||
| `intimacy` | `'92'` | string | 专一度指数 |
|
||
| `topCoachName` | `'小燕'` | string | 最亲密助教姓名 |
|
||
| `topCoachHeart` | `9.2` | number | 最亲密助教关系指数 |
|
||
| `topCoachScore` | `'9.2'` | string | 同上(字符串版) |
|
||
| `coachDetails` | 2 条明细 | array | 助教服务明细表 |
|
||
| `weeklyVisits` | 8 周数据 | array | 8 周到店频率(迷你柱状图) |
|
||
| `coachName` | `'小燕'` | string | 主助教姓名 |
|
||
| `coachRatio` | `'78%'` | string | 主助教服务占比 |
|
||
| `visitFreq` | `'6.2次/月'` | string | 到店频率 |
|
||
| `daysAgo` | `3` | number | 距上次到店天数 |
|
||
| `assistants` | 2 条 | array | 助教列表(底部行) |
|
||
|
||
#### `coachDetails` 子字段
|
||
|
||
| 字段 | 示例值 | 说明 |
|
||
|------|--------|------|
|
||
| `name` | `'小燕'` | 助教姓名 |
|
||
| `cls` | `'assistant--assignee'` | 样式类(跟/弃/普通) |
|
||
| `heartScore` | `9.2` | 关系指数 |
|
||
| `badge` | `'跟'` | 跟/弃标签 |
|
||
| `avgDuration` | `'2.3h'` | 次均服务时长 |
|
||
| `serviceCount` | `'14'` | 服务次数 |
|
||
| `coachSpend` | `'¥4,200'` | 助教消费金额 |
|
||
| `relationIdx` | `9.2` | 关系指数(数值版) |
|
||
|
||
#### `assistants` 子字段
|
||
|
||
| 字段 | 示例值 | 说明 |
|
||
|------|--------|------|
|
||
| `name` | `'小燕'` | 助教姓名 |
|
||
| `cls` | `'assistant--assignee'` | 样式类 |
|
||
| `heartScore` | `9.2` | 关系指数 |
|
||
| `badge` | `'跟'` | 跟/弃标签(可选) |
|
||
| `badgeCls` | `'assistant-badge--follow'` | badge 样式类(可选) |
|
||
|
||
|
||
---
|
||
|
||
## 二、硬编码数据
|
||
|
||
### 2.1 `DIMENSION_OPTIONS`(维度下拉选项)
|
||
|
||
| 风险 | 字段 | 当前值 | 应从何处获取 |
|
||
|------|------|--------|-------------|
|
||
| 🟡 低 | `DIMENSION_OPTIONS` | 8 个维度选项(recall/potential/balance/recharge/recent/spend60/freq60/loyal) | 前端枚举,可保留硬编码;若后端支持动态维度则需 API |
|
||
|
||
```ts
|
||
const DIMENSION_OPTIONS = [
|
||
{ value: 'recall', text: '最应召回' },
|
||
{ value: 'potential', text: '最大消费潜力' },
|
||
{ value: 'balance', text: '最高余额' },
|
||
{ value: 'recharge', text: '最近充值' },
|
||
{ value: 'recent', text: '最近到店' },
|
||
{ value: 'spend60', text: '最高消费 近60天' },
|
||
{ value: 'freq60', text: '最频繁 近60天' },
|
||
{ value: 'loyal', text: '最专一 近60天' },
|
||
]
|
||
```
|
||
|
||
### 2.2 `PROJECT_OPTIONS`(项目筛选选项)
|
||
|
||
| 风险 | 字段 | 当前值 | 应从何处获取 |
|
||
|------|------|--------|-------------|
|
||
| 🟡 低 | `PROJECT_OPTIONS` | 5 个项目选项(all/chinese/snooker/mahjong/karaoke) | 前端枚举,可保留;若门店项目可配置则需 API |
|
||
|
||
```ts
|
||
const PROJECT_OPTIONS = [
|
||
{ value: 'all', text: '全部' },
|
||
{ value: 'chinese', text: '🎱 中式/追分' },
|
||
{ value: 'snooker', text: '斯诺克' },
|
||
{ value: 'mahjong', text: '🀄 麻将/棋牌' },
|
||
{ value: 'karaoke', text: '🎤 团建/K歌' },
|
||
]
|
||
```
|
||
|
||
### 2.3 `DIMENSION_TO_DIM` 映射表
|
||
|
||
| 风险 | 字段 | 说明 |
|
||
|------|------|------|
|
||
| 🟢 无 | `DIMENSION_TO_DIM` | 纯前端映射,维度 value → DimType,无需替换 |
|
||
|
||
### 2.4 `loadData()` 中的 `setTimeout(400ms)`
|
||
|
||
| 风险 | 位置 | 当前值 | 说明 |
|
||
|------|------|--------|------|
|
||
| 🔴 高 | `loadData()` | `setTimeout(() => {...}, 400)` | 模拟网络延迟,联调时必须替换为真实 `wx.request` |
|
||
|
||
### 2.5 `onPullDownRefresh()` 中的 `setTimeout(500ms)`
|
||
|
||
| 风险 | 位置 | 当前值 | 说明 |
|
||
|------|------|--------|------|
|
||
| 🟡 中 | `onPullDownRefresh()` | `setTimeout(() => wx.stopPullDownRefresh(), 500)` | 联调时应在 API 回调中调用 `wx.stopPullDownRefresh()` |
|
||
|
||
---
|
||
|
||
## 三、已对接 API
|
||
|
||
**无。** 页面中没有任何 `wx.request`、`wx.cloud.callFunction` 或封装的 API 调用。
|
||
|
||
`loadData()` 当前实现:
|
||
```ts
|
||
loadData() {
|
||
this.setData({ pageState: 'loading' })
|
||
setTimeout(() => {
|
||
const data = MOCK_CUSTOMERS // ← 直接引用内联 mock
|
||
if (!data || data.length === 0) {
|
||
this.setData({ pageState: 'empty' })
|
||
return
|
||
}
|
||
this.setData({
|
||
allCustomers: data,
|
||
customers: data,
|
||
totalCount: data.length,
|
||
pageState: 'normal',
|
||
})
|
||
}, 400)
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 四、前端计算/派生数据
|
||
|
||
| 字段/逻辑 | 位置 | 说明 |
|
||
|-----------|------|------|
|
||
| `dimType` | `onDimensionChange()` | 由 `DIMENSION_TO_DIM[selectedDimension]` 映射得出,控制 WXML 卡片模板切换 |
|
||
| `filterBarVisible` | `onPageScroll()` | 滚动方向 + 累积距离判断,纯 UI 交互逻辑 |
|
||
| `pageState` | `loadData()` | 根据数据加载结果设置 `'loading'` / `'empty'` / `'normal'` / `'error'` |
|
||
| `totalCount` | `loadData()` | `data.length`,由返回数据长度计算 |
|
||
| `overdueDays > 7` | WXML | 控制超期标签颜色(danger / warn),纯前端判断 |
|
||
| `item.highSpendTag` | WXML | 控制是否显示"高消费"标签,值来自 mock 数据 |
|
||
| 柱状图 `opacity` | WXML | `0.2 + wIdx * 0.057`,渐变透明度,纯 UI 计算 |
|
||
| `cd.relationIdx >= 8` | WXML | 控制关系指数颜色(primary / gray),纯前端判断 |
|
||
|
||
---
|
||
|
||
## 五、路由参数
|
||
|
||
**无。** `onLoad()` 不接收任何参数,直接调用 `loadData()`。
|
||
|
||
页面作为导航目标时的入口:
|
||
- `board-finance.ts` → `wx.navigateTo({ url: '/pages/board-customer/board-customer' })`
|
||
- `board-coach.ts` → `wx.navigateTo({ url: '/pages/board-customer/board-customer' })`
|
||
- `dev-tools.ts` → 开发工具页面列表
|
||
|
||
---
|
||
|
||
## 六、WXML 中的硬编码文案
|
||
|
||
| 位置 | 硬编码文案 | 风险 | 说明 |
|
||
|------|-----------|------|------|
|
||
| 加载态 | `加载中...` | 🟢 无 | 通用 UI 文案 |
|
||
| 空状态 | `暂无客户数据` | 🟢 无 | 通用 UI 文案 |
|
||
| 错误态 | `加载失败` | 🟢 无 | 通用 UI 文案 |
|
||
| 错误态按钮 | `点击重试` | 🟢 无 | 通用 UI 文案 |
|
||
| Tab 栏 | `财务` / `客户` / `助教` | 🟢 无 | 固定导航文案 |
|
||
| 列表标题 | `客户列表` | 🟢 无 | 固定标题 |
|
||
| 列表副标题 | `· 前100名` | 🟡 低 | 若后端支持分页/动态 TopN,需改为动态值 |
|
||
| 列表计数 | `共{{totalCount}}名客户` | 🟢 无 | 动态绑定 |
|
||
| 召回维度标签 | `理想` / `已过` / `超期` / `天` | 🟢 无 | 固定维度标签 |
|
||
| 网格标签 | `30天消费` / `月均到店` / `余额` / `次均消费` / `月均消耗` / `可用` / `最后充值` / `充值` / `60天充值` / `当前余额` / `近60天消费` / `到店次数` | 🟢 无 | 固定维度标签 |
|
||
| 频率维度 | `60天到店` / `次` / `8周前` / `本周` | 🟢 无 | 固定维度标签 |
|
||
| 专一维度表头 | `助教` / `次均时长` / `服务次数` / `助教消费` / `关系指数` | 🟢 无 | 固定表头 |
|
||
| 助教标签 | `助教:` / `跟` / `弃` | 🟢 无 | 固定 UI 文案 |
|
||
| 最近到店 | `天前到店` / `理想间隔` / `60天到店` / `次均消费` | 🟢 无 | 固定维度标签 |
|
||
| 频率中间行 | `平均间隔` / `60天消费` | 🟢 无 | 固定维度标签 |
|
||
| 召回中间行 | `30天到店` / `余额` / `召回指数` | 🟢 无 | 固定维度标签 |
|
||
| 最近到店右上角 | `天前到店` | 🟢 无 | 固定维度标签 |
|
||
|
||
---
|
||
|
||
## 七、引用组件清单
|
||
|
||
| 组件 | 路径 | 数据来源 | 说明 |
|
||
|------|------|----------|------|
|
||
| `filter-dropdown` | `/components/filter-dropdown/` | 父组件传入 `options` / `value` | 纯展示组件,无自有数据 |
|
||
| `heart-icon` | `/components/heart-icon/` | 父组件传入 `score` | 纯展示组件,根据分数渲染心形图标 |
|
||
| `ai-float-button` | `/components/ai-float-button/` | 自有逻辑 | AI 悬浮按钮,独立组件 |
|
||
| `board-tab-bar` | `/custom-tab-bar/index` | `active="board"` | 底部导航栏,当前 mock 3 按钮 |
|
||
| `dev-fab` | `/components/dev-fab/` | — | 开发调试按钮,`wx:if="{{false}}"` 已隐藏 |
|
||
| `t-loading` / `t-empty` / `t-icon` / `t-tag` | TDesign | — | 第三方 UI 组件 |
|
||
|
||
---
|
||
|
||
## 八、联调 TODO
|
||
|
||
### 🔴 P0 — 必须替换
|
||
|
||
| # | 任务 | 当前状态 | 目标 |
|
||
|---|------|----------|------|
|
||
| 1 | 替换 `MOCK_CUSTOMERS` 为 API 调用 | 页面内联 3 条 mock 数据 | 调用后端客户看板 API,传入 `dimension` + `project` 参数 |
|
||
| 2 | 实现 `loadData()` 真实请求 | `setTimeout(400ms)` 模拟 | `wx.request` → 后端 API,处理 loading/error/empty 三态 |
|
||
| 3 | 下拉刷新对接 | `setTimeout(500ms)` 后停止 | API 回调中 `wx.stopPullDownRefresh()` |
|
||
| 4 | 维度切换重新请求 | `onDimensionChange` 仅切换 `dimType` | 切换维度后重新调用 API(不同维度返回不同排序/字段) |
|
||
| 5 | 项目筛选重新请求 | `onProjectChange` 仅更新 `selectedProject` | 切换项目后重新调用 API(按项目类型过滤) |
|
||
|
||
### 🟡 P1 — 建议优化
|
||
|
||
| # | 任务 | 说明 |
|
||
|---|------|------|
|
||
| 6 | 分页/滚动加载 | 当前一次性加载全部,"前 100 名"硬编码;若数据量大需分页 |
|
||
| 7 | `initPageAiColor` 调用 | 已导入但未在 `onLoad` 中调用,需补充 |
|
||
| 8 | 金额字段格式化 | Mock 中金额为预格式化字符串(`'¥2,680'`),联调后需前端格式化 |
|
||
| 9 | 相对时间计算 | `lastVisit: '3天前'`、`daysAgo: 3` 等,联调后需从时间戳计算 |
|
||
| 10 | `balance` 与 `currentBalance` 去重 | Mock 中两字段值相同,API 设计时应统一 |
|
||
|
||
### 🟢 P2 — 可保留
|
||
|
||
| # | 项目 | 说明 |
|
||
|---|------|------|
|
||
| 11 | `DIMENSION_OPTIONS` | 前端枚举,维度列表固定,可保留硬编码 |
|
||
| 12 | `PROJECT_OPTIONS` | 前端枚举,若门店项目固定可保留 |
|
||
| 13 | `DIMENSION_TO_DIM` 映射 | 纯前端逻辑,保留 |
|
||
| 14 | WXML 固定文案 | 维度标签、表头等,保留 |
|
||
|
||
---
|
||
|
||
## 九、待确认 API 设计
|
||
|
||
联调前需与后端确认以下接口设计:
|
||
|
||
```
|
||
GET /api/v1/board/customers
|
||
Query:
|
||
dimension: string — 排序维度(recall/potential/balance/...)
|
||
project: string — 项目筛选(all/chinese/snooker/...)
|
||
limit: number — 返回条数(默认 100)
|
||
site_id: string — 门店 ID(从登录态获取)
|
||
Response:
|
||
total: number
|
||
items: CustomerItem[]
|
||
```
|
||
|
||
**关键字段口径注意**:
|
||
- 金额字段(`balance`、`spend30d`、`spend60d` 等):后端返回 `numeric(2)` 原始数值,前端负责 `¥` 前缀 + 千分位格式化
|
||
- 会员姓名:通过 `member_id` JOIN `dim_member.nickname`(DQ-6 断档,禁止直接用 `member_name`)
|
||
- 储值卡余额:DWS 层 `balance_pay`(总额)= `recharge_card_pay` + `gift_card_pay`
|
||
- 消费金额:使用 `items_sum` 口径,禁止直接使用 `consume_money`(三种历史口径混合)
|