# 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`(三种历史口径混合)