Files
Neo-ZQYY/docs/miniprogram-dev/api-audit/board-customer.md

296 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`(页面内联,第 72178 行)
页面顶部定义的 `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`(三种历史口径混合)