feat: batch update - gift card breakdown spec, backend APIs, miniprogram pages, ETL finance recharge, docs & migrations

This commit is contained in:
Neo
2026-03-20 01:43:48 +08:00
parent 075caf067f
commit 79f9a0e1da
437 changed files with 118603 additions and 976 deletions

View File

@@ -0,0 +1,225 @@
# performance-records 页面数据来源排查
> 排查日期2026-03-18
> 页面路径pages/performance-records/performance-records
## 概览
| 分类 | 数量 | 说明 |
|------|------|------|
| A. Mock 数据 | 1 | 从 `mock-data.ts` 导入但实际未使用于渲染 |
| B. 硬编码数据TS 内联) | 42 | `loadData()` 中手写的 dateGroups + 统计汇总,占页面数据主体 |
| C. 已对接 API | 0 | 无任何真实 API 调用 |
| D. 前端计算/派生数据 | 12 | `nameToAvatarColor()``formatMoney()``formatHours()``formatCount()`、月份切换逻辑 |
| E. 路由参数 | 0 | `onLoad()` 未读取 options |
| F. WXML 硬编码文案 | 14 | 标签文字、提示语、状态文案 |
---
## 一、Mock 数据(来自 mock-data.ts
| # | 字段/变量 | 来源 | 说明 |
|---|-----------|------|------|
| 1 | `allRecords` | `mockPerformanceRecords`mock-data.ts | `import { mockPerformanceRecords }` 导入,赋值给 `allRecords`,但 **未参与任何渲染**;页面实际展示的 `dateGroups` 是 TS 内联硬编码,与此 mock 数据结构完全不同 |
> **注意**`mockPerformanceRecords` 的类型 `PerformanceRecord`(含 `id/customerName/amount/date/type/category`)与页面实际渲染的 `RecordItem` 接口(含 `hours/timeRange/courseType/location/income` 等)字段不匹配,说明 mock 数据是早期占位,后续内联硬编码覆盖了实际展示逻辑。
---
## 二、硬编码数据TS 内联)
### 2.1 Banner 区域Page data 初始值)
| # | 字段 | 硬编码值 | 位置 |
|---|------|----------|------|
| 1 | `coachName` | `'小燕'` | data 初始化 |
| 2 | `coachLevel` | `'星级'` | data 初始化 |
| 3 | `storeName` | `'球会名称店'` | data 初始化 |
### 2.2 月份切换Page data 初始值)
| # | 字段 | 硬编码值 | 位置 |
|---|------|----------|------|
| 4 | `currentYear` | `2026` | data 初始化 |
| 5 | `currentMonth` | `2` | data 初始化 |
| 6 | `monthLabel` | `'2026年2月'` | data 初始化 |
| 7 | `canGoPrev` | `true` | data 初始化 |
| 8 | `canGoNext` | `false` | data 初始化 |
### 2.3 统计概览loadData 中 setData
| # | 字段 | 硬编码值 | 位置 |
|---|------|----------|------|
| 9 | `totalCount` | `32` | loadData → setData |
| 10 | `totalHours` | `59.0` | loadData → setData |
| 11 | `totalIncome` | `4720` | loadData → setData |
| 12 | `hasMore` | `false` | loadData → setData |
### 2.4 dateGroups 内联数据loadData 中12 个日期分组,共 30 条记录)
每条记录包含以下硬编码字段:
| 字段 | 类型 | 示例值 | 说明 |
|------|------|--------|------|
| `id` | string | `'r1'` ~ `'r30'` | 记录唯一标识 |
| `customerName` | string | `'王先生'``'李女士'` 等 | 客户姓名 |
| `avatarChar` | string | `'王'``'李'` 等 | 头像首字 |
| `timeRange` | string | `'20:00-22:00'` | 服务时间段 |
| `hours` | number | `2.0``1.5``1.0` | 折算后课时 |
| `hoursRaw` | number? | `2.5`(部分记录有) | 折算前课时 |
| `courseType` | string | `'基础课'`/`'包厢课'`/`'打赏课'` | 课程类型文案 |
| `courseTypeClass` | string | `'tag-basic'`/`'tag-vip'`/`'tag-tip'` | 课程类型样式类 |
| `location` | string | `'3号台'`/`'VIP1号房'`/`'打赏'` | 服务位置 |
| `income` | number | `160`/`190`/`120`/`80` | 预估收入(元) |
日期分组级硬编码字段:
| 字段 | 类型 | 示例值 | 说明 |
|------|------|--------|------|
| `date` | string | `'2月7日'` ~ `'1月27日'` | 日期标签 |
| `totalHours` | number | `6.0`/`3.5`/`4.0` 等 | 当日总课时 |
| `totalIncome` | number | `510`/`280`/`320`/`350`/`470` | 当日总收入 |
**完整日期分组清单**12 组30 条记录):
| # | date | 记录数 | totalHours | totalIncome | 记录 ID |
|---|------|--------|------------|-------------|---------|
| 13 | 2月7日 | 3 | 6.0 | 510 | r1, r2, r3 |
| 14 | 2月6日 | 2 | 3.5 | 280 | r4, r5 |
| 15 | 2月5日 | 2 | 4.0 | 320 | r6, r7 |
| 16 | 2月4日 | 2 | 4.0 | 350 | r8, r9 |
| 17 | 2月3日 | 2 | 3.5 | 280 | r10, r11 |
| 18 | 2月2日 | 2 | 4.0 | 350 | r12, r13 |
| 19 | 2月1日 | 6 | 6.0 | 510 | r14~r19 |
| 20 | 1月31日 | 3 | 5.5 | 470 | r20~r22 |
| 21 | 1月30日 | 2 | 3.5 | 280 | r23, r24 |
| 22 | 1月29日 | 2 | 4.0 | 320 | r25, r26 |
| 23 | 1月28日 | 2 | 4.0 | 350 | r27, r28 |
| 24 | 1月27日 | 2 | 4.0 | 350 | r29, r30 |
---
## 三、已对接 API
**无。** 页面当前 0 个 API 调用。
`loadData()` 使用 `setTimeout(500ms)` 模拟异步,内部注释标注:
```typescript
// TODO: 替换为真实 API按月份请求
const allRecords = mockPerformanceRecords
```
---
## 四、前端计算/派生数据
| # | 字段 | 计算方式 | 依赖 |
|---|------|----------|------|
| 1 | `rec.avatarColor` | `nameToAvatarColor(name)` — 基于姓名首字 charCode 取模映射到 24 色板 | `utils/avatar-color.ts` |
| 2 | `totalCountLabel` | `formatCount(32, '笔')``'32笔'` | `utils/money.ts` |
| 3 | `totalHoursLabel` | `formatHours(59.0)``'59h'` | `utils/time.ts` |
| 4 | `totalHoursRawLabel` | `formatHours(63.5)``'63.5h'` | `utils/time.ts` |
| 5 | `totalIncomeLabel` | `formatMoney(4720)``'¥4,720'` | `utils/money.ts` |
| 6 | `group.totalHoursLabel` | `formatHours(n)` — 每个日期分组的课时格式化 | `utils/time.ts` |
| 7 | `group.totalIncomeLabel` | `formatMoney(n)` — 每个日期分组的收入格式化 | `utils/money.ts` |
| 8 | `fmt.hours(rec.hours)` | WXS `hours()` — WXML 中课时展示 | `utils/format.wxs` |
| 9 | `fmt.hours(rec.hoursRaw)` | WXS `hours()` — WXML 中折前课时展示 | `utils/format.wxs` |
| 10 | `fmt.money(rec.income)` | WXS `money()` — WXML 中收入展示 | `utils/format.wxs` |
| 11 | `monthLabel` | `switchMonth()` 中拼接 `` `${currentYear}年${currentMonth}月` `` | 月份切换逻辑 |
| 12 | `canGoNext` | `switchMonth()` 中与当前日期比较,不能超过当月 | 月份切换逻辑 |
---
## 五、路由参数
**无。** `onLoad()` 未接收任何 `options` 参数。
当前页面不知道:
- 当前登录助教是谁(`coachName`/`coachLevel` 硬编码)
- 所属门店(`storeName` 硬编码)
- 初始展示月份(硬编码 2026 年 2 月)
---
## 六、WXML 硬编码文案
| # | 文案 | 位置 | 说明 |
|---|------|------|------|
| 1 | `加载中...` | toast 加载浮层 | 加载状态提示 |
| 2 | `加载失败,请点击重试` | 错误态 | 错误提示文案 |
| 3 | `重试` | 错误态按钮 | 按钮文案 |
| 4 | `总记录` | 统计概览 stat-label | 统计标签 |
| 5 | `总业绩时长` | 统计概览 stat-label | 统计标签 |
| 6 | `预估`×2 处) | 统计概览 stat-hint | 时长和收入后的提示 |
| 7 | `折前` | 统计概览 stat-hours-raw | 折前课时前缀 |
| 8 | `收入` | 统计概览 stat-label | 统计标签 |
| 9 | `暂无数据` | 空态 | 空状态提示 |
| 10 | `` | 日期分隔线 dd-date | 日期后分隔符 |
| 11 | `·` | 日期分隔线 dd-stats | 课时与收入间分隔符 |
| 12 | `预估` | 日期分隔线 dd-stats | 日分组收入前缀 |
| 13 | `我的预估收入` | record-income | 记录行收入前缀 |
| 14 | `— 已加载全部记录 —` | list-end-hint | 列表底部提示 |
---
## 七、联调 TODO
### 7.1 需要对接的 API按优先级
| 优先级 | API | 用途 | 替换目标 |
|--------|-----|------|----------|
| P0 | 获取当前助教信息 | Banner 区域展示 | `coachName`、`coachLevel`、`storeName` 硬编码 |
| P0 | 业绩明细列表(按月分页) | 核心数据 | `loadData()` 中全部 `dateGroups` 内联硬编码 |
| P0 | 月度统计汇总 | 统计概览 | `totalCount`/`totalHours`/`totalIncome` 等硬编码 |
### 7.2 需要确认的数据结构
| 问题 | 说明 |
|------|------|
| `PerformanceRecord` vs `RecordItem` 接口不匹配 | mock-data.ts 中 `PerformanceRecord` 含 `amount/date/type/category`,页面实际渲染的 `RecordItem` 含 `hours/timeRange/courseType/location/income`,需与后端确认最终字段 |
| `hoursRaw`(折前课时)是否由后端返回 | 当前仅部分记录有此字段,需确认业务规则 |
| `courseType` 枚举值 | 当前有 `基础课`/`包厢课`/`打赏课` 三种,需确认是否完整 |
| `courseTypeClass` 样式映射 | 是前端根据 `courseType` 派生,还是后端直接返回 |
| 日期分组逻辑 | 由后端返回已分组数据,还是前端按日期聚合 |
| 分页机制 | 当前 `page`/`pageSize`/`hasMore` 已定义但未实际使用 |
| 月份切换 | 切换月份后应重新请求 API当前 `loadData()` 未传入年月参数 |
### 7.3 需要清理的代码
| 项目 | 说明 |
|------|------|
| 删除 `import { mockPerformanceRecords }` | mock 数据导入 |
| 删除 `import type { PerformanceRecord }` | mock 类型导入 |
| 删除 `loadData()` 中 `setTimeout` 模拟 | 替换为真实 API 调用 |
| 删除 `loadData()` 中 12 个 dateGroups 内联数据 | 约 120 行硬编码 |
| 删除 `allRecords` 字段 | 当前赋值了 mock 但未使用 |
| 确认 `onLoad` 是否需要接收路由参数 | 如助教 ID、初始月份等 |
### 7.4 数据一致性问题
| 问题 | 详情 |
|------|------|
| 2月4日 `totalHours=4.0` 但 `totalIncome=350` | `totalIncomeLabel` 用 `formatMoney(320)` 格式化,但 `totalIncome` 赋值 `350`,数值不一致 |
| 2月2日 同上 | `totalIncomeLabel=formatMoney(320)` 但 `totalIncome=350` |
| 1月28日 同上 | `totalIncomeLabel=formatMoney(320)` 但 `totalIncome=350` |
| 1月27日 同上 | `totalIncomeLabel=formatMoney(320)` 但 `totalIncome=350` |
| 统计汇总与明细不匹配 | `totalCount=32` 但 dateGroups 中实际只有 30 条记录r1~r30 |
| `totalHours=59.0` 未验证 | 各日期 totalHours 之和 = 6+3.5+4+4+3.5+4+6+5.5+3.5+4+4+4 = 52h ≠ 59h |
| `totalIncome=4720` 未验证 | 各日期 totalIncome 之和 = 510+280+320+350+280+350+510+470+280+320+350+350 = 4370 ≠ 4720 |
### 7.5 组件依赖
| 组件 | 来源 | 说明 |
|------|------|------|
| `coach-level-tag` | 自定义组件 `/components/coach-level-tag/` | 助教等级标签 |
| `ai-float-button` | 自定义组件 `/components/ai-float-button/` | AI 悬浮按钮 |
| `dev-fab` | 自定义组件 `/components/dev-fab/` | 开发调试按钮 |
| `t-icon` | TDesign `tdesign-miniprogram/icon` | 图标 |
| `t-loading` | TDesign `tdesign-miniprogram/loading` | 加载动画 |
### 7.6 静态资源
| 资源 | 路径 | 说明 |
|------|------|------|
| Banner 背景图 | `/assets/images/banner-bg-coral-aurora.svg` | 珊瑚极光渐变背景 |
| 助教头像 | `/assets/images/avatar-coach.png` | 默认助教头像 |