feat: batch update - gift card breakdown spec, backend APIs, miniprogram pages, ETL finance recharge, docs & migrations
This commit is contained in:
@@ -1,545 +0,0 @@
|
||||
# 小程序后端接口输出规范
|
||||
|
||||
> **文档编号**:DS-API-OUTPUT-001
|
||||
> **版本**:v1.0.0
|
||||
> **适用范围**:NeoZQYY 小程序所有后端接口(HTTP JSON 响应体)
|
||||
> **最后更新**:2026-03-18
|
||||
> **关联文档**:
|
||||
> - [DISPLAY-STANDARDS.md](./design-system/DISPLAY-STANDARDS.md) — 前端展示规范第 1~6 章
|
||||
> - [DISPLAY-STANDARDS-2.md](./design-system/DISPLAY-STANDARDS-2.md) — 第 7~9 章(截止日期 / 评分 / Mock)
|
||||
> - [DATETIME-DISPLAY-STANDARD.md](./design-system/DATETIME-DISPLAY-STANDARD.md) — 时间展示规范
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
1. [总体约定](#1-总体约定)
|
||||
2. [响应体结构](#2-响应体结构)
|
||||
3. [字段类型规范(核心规则)](#3-字段类型规范核心规则)
|
||||
4. [各业务字段细则](#4-各业务字段细则)
|
||||
5. [枚举值规范](#5-枚举值规范)
|
||||
6. [分页规范](#6-分页规范)
|
||||
7. [接口端点命名规范](#7-接口端点命名规范)
|
||||
8. [各页面接口字段对照表](#8-各页面接口字段对照表)
|
||||
9. [禁止事项速查](#9-禁止事项速查)
|
||||
|
||||
---
|
||||
|
||||
## 1. 总体约定
|
||||
|
||||
| 项目 | 规范 |
|
||||
|------|------|
|
||||
| 协议 | HTTPS REST JSON |
|
||||
| 编码 | UTF-8 |
|
||||
| Content-Type | `application/json` |
|
||||
| 时区 | **后端统一输出 UTC,前端负责本地化展示** |
|
||||
| 数值精度 | 金额为整数(元),课时为 0.5 步长浮点 |
|
||||
| 空值 | 字段不存在时输出 `null`,**不得省略字段** |
|
||||
| 布尔值 | `true` / `false`,不用 `0/1` |
|
||||
| 字符串编码格式 | 纯数据,**不含展示单位**(¥、h、%、笔等) |
|
||||
|
||||
---
|
||||
|
||||
## 2. 响应体结构
|
||||
|
||||
### 2.1 成功响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 失败响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 40001,
|
||||
"message": "参数错误:缺少 customerId",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 分页列表响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"list": [ ... ],
|
||||
"total": 32,
|
||||
"page": 1,
|
||||
"pageSize": 20,
|
||||
"hasMore": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 字段类型规范(核心规则)
|
||||
|
||||
> **核心原则:后端输出原始语义数值,前端负责格式化展示。**
|
||||
> 后端不拼接 `¥`、`h`、`%`、`笔` 等展示字符,也不做千分位格式化。
|
||||
|
||||
### 3.1 类型对照表
|
||||
|
||||
| 字段类型 | 后端输出类型 | 示例 | 前端处理函数 | 禁止输出 |
|
||||
|----------|-------------|------|-------------|----------|
|
||||
| **金额** | `number`(元,整数) | `12680` | `formatMoney()` | `"¥12,680"` `"12680.00"` |
|
||||
| **课时** | `number`(小时,0.5步长) | `2.5` | `formatHours()` | `"2.5h"` `"2h30min"` |
|
||||
| **计数** | `number`(整数) | `32` | `formatCount()` | `"32笔"` `"32次"` |
|
||||
| **百分比** | `number`(0~100,整数或1位小数) | `85.5` | `formatPercent()` | `"85.5%"` `"0.855"` |
|
||||
| **日期** | `string`(`YYYY-MM-DD`) | `"2026-03-18"` | `formatDeadline()` | `"2026/03/18"` `1742256000000` |
|
||||
| **时间戳** | `string`(ISO 8601,含时区) | `"2026-03-18T08:30:00+08:00"` | `formatRelativeTime()` | Unix 毫秒整数 |
|
||||
| **等级 key** | `string`(英文 key) | `"star"` | 查 `LEVEL_MAP` | `"星级"` `"gold"` |
|
||||
| **状态 key** | `string`(英文 key) | `"pending"` | 查枚举映射 | `"待处理"` |
|
||||
| **评分** | `number`(0~10,0.5步长) | `8.5` | `scoreToHalfStar()` | `"8.5分"` `4` (5分制) |
|
||||
| **布尔** | `boolean` | `true` | 直接使用 | `1` `"true"` |
|
||||
| **空值** | `null` | `null` | 展示 `--` | `""` `0`(有语义时除外) |
|
||||
|
||||
---
|
||||
|
||||
## 4. 各业务字段细则
|
||||
|
||||
### 4.1 金额字段
|
||||
|
||||
```json
|
||||
// 正确
|
||||
{ "balance": 2000, "income": 510, "salary": 12680 }
|
||||
|
||||
// 错误
|
||||
{ "balance": "¥2,000", "income": "¥510", "salary": "¥12,680" }
|
||||
```
|
||||
|
||||
- 统一以「元」为单位,**不含分**
|
||||
- 负数直接输出负整数:`-368`
|
||||
- 零值输出 `0`,不输出 `null`
|
||||
- 前端调用 `formatMoney(value)` 展示
|
||||
|
||||
### 4.2 课时字段
|
||||
|
||||
```json
|
||||
// 正确
|
||||
{ "hours": 2.5, "hoursRaw": 3.0 }
|
||||
|
||||
// 错误
|
||||
{ "hours": "2.5h", "deduct": "(折0.5h)" }
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `hours` | `number` | 折算后实际计入课时(小时) |
|
||||
| `hoursRaw` | `number \| null` | 折算前原始小时数;无折算时输出 `null` |
|
||||
|
||||
- 前端仅在 `hoursRaw !== null && hoursRaw !== hours` 时展示折算备注
|
||||
- 步长为 0.5,如 `1.0`、`1.5`、`2.0`、`2.5`
|
||||
|
||||
### 4.3 计数字段
|
||||
|
||||
```json
|
||||
// 正确
|
||||
{ "totalCount": 32, "serviceCount": 18 }
|
||||
|
||||
// 错误
|
||||
{ "totalCount": "32笔", "serviceCount": "18次" }
|
||||
```
|
||||
|
||||
- 前端调用 `formatCount(value, '笔')` 展示,单位由页面层决定
|
||||
|
||||
### 4.4 百分比字段
|
||||
|
||||
```json
|
||||
// 正确(进度/完成率)
|
||||
{ "filledPct": 85, "completionRate": 72.5 }
|
||||
|
||||
// 错误
|
||||
{ "filledPct": "85%", "completionRate": 0.725 }
|
||||
```
|
||||
|
||||
- 统一 0~100 整数或1位小数,**不用小数形式**
|
||||
- 允许超过 100(如完成率 120%)
|
||||
|
||||
### 4.5 日期与时间
|
||||
|
||||
| 场景 | 格式 | 示例 |
|
||||
|------|------|------|
|
||||
| 纯日期(截止日、生日等) | `YYYY-MM-DD` | `"2026-03-18"` |
|
||||
| 精确时间(备注时间、服务记录) | ISO 8601 含时区 | `"2026-03-18T08:30:00+08:00"` |
|
||||
| 时间区间(service timeRange) | 直接拼接字符串 | `"20:00-22:00"` |
|
||||
|
||||
- **禁止** 输出 Unix 毫秒时间戳整数
|
||||
- **禁止** 输出 `YYYY/MM/DD` 格式
|
||||
- 前端截止日期调用 `formatDeadline(date)` 语义化
|
||||
- 前端相对时间调用 `formatRelativeTime(iso)` 展示
|
||||
|
||||
### 4.6 评分字段
|
||||
|
||||
```json
|
||||
// 正确
|
||||
{ "heartScore": 8.5, "noteScore": 7.0 }
|
||||
|
||||
// 错误
|
||||
{ "heartScore": 4, "noteScore": "7.5分" }
|
||||
```
|
||||
|
||||
- 统一使用 **0~10 分制**,0.5 步长
|
||||
- 0 表示「未评分」,前端展示 `--`
|
||||
- 前端调用 `scoreToHalfStar(score)` 转换为 0~5 星展示
|
||||
|
||||
### 4.7 等级字段
|
||||
|
||||
```json
|
||||
// 正确
|
||||
{ "level": "star" }
|
||||
|
||||
// 错误
|
||||
{ "level": "星级" }
|
||||
```
|
||||
|
||||
| 后端 key | 前端展示 |
|
||||
|----------|----------|
|
||||
| `star` | 星级助教 |
|
||||
| `senior` | 高级助教 |
|
||||
| `middle` | 中级助教 |
|
||||
| `junior` | 初级助教 |
|
||||
|
||||
### 4.8 天数字段
|
||||
|
||||
```json
|
||||
// 正确
|
||||
{ "lastVisitDays": 23, "daysAbsent": 23 }
|
||||
|
||||
// 错误
|
||||
{ "lastVisitDays": "23天前", "daysAbsent": "23天" }
|
||||
```
|
||||
|
||||
- 统一输出整数天数,不含「天」「天前」等汉字
|
||||
- 前端拼接文案时自行添加单位
|
||||
|
||||
---
|
||||
|
||||
## 5. 枚举值规范
|
||||
|
||||
所有枚举字段统一使用英文下划线 key,前端维护映射表。
|
||||
|
||||
### 5.1 任务类型 `taskType`
|
||||
|
||||
| key | 展示文案 |
|
||||
|-----|----------|
|
||||
| `callback` | 回访 |
|
||||
| `priority_recall` | 优先召回 |
|
||||
| `high_priority` | 高优先召回 |
|
||||
| `relationship` | 关系构建 |
|
||||
|
||||
### 5.2 任务状态 `status`
|
||||
|
||||
| key | 展示文案 |
|
||||
|-----|----------|
|
||||
| `pending` | 待处理 |
|
||||
| `completed` | 已完成 |
|
||||
| `abandoned` | 已放弃 |
|
||||
|
||||
### 5.3 助教等级 `level`
|
||||
|
||||
| key | 展示文案 |
|
||||
|-----|----------|
|
||||
| `star` | 星级助教 |
|
||||
| `senior` | 高级助教 |
|
||||
| `middle` | 中级助教 |
|
||||
| `junior` | 初级助教 |
|
||||
|
||||
### 5.4 课程类型 `courseType`
|
||||
|
||||
| key | 展示文案 | 样式 key |
|
||||
|-----|----------|----------|
|
||||
| `basic` | 基础课 | `tag-basic` |
|
||||
| `vip` | 包厢课 | `tag-vip` |
|
||||
| `tip` | 打赏课 | `tag-tip` |
|
||||
| `recharge` | 充值 | `tag-recharge` |
|
||||
| `incentive` | 激励 | `tag-incentive` |
|
||||
|
||||
### 5.5 线索来源 `source`
|
||||
|
||||
| key | 展示文案 |
|
||||
|-----|----------|
|
||||
| `manual` | 助教手动录入 |
|
||||
| `ai_consumption` | 系统自动(消费分析) |
|
||||
| `ai_note` | 系统自动(备注分析) |
|
||||
|
||||
### 5.6 线索分类 `category`
|
||||
|
||||
| key | 展示文案 |
|
||||
|-----|----------|
|
||||
| `customer_basic` | 客户基础 |
|
||||
| `consumption` | 消费习惯 |
|
||||
| `play_pref` | 玩法偏好 |
|
||||
| `promo_pref` | 促销偏好 |
|
||||
| `social` | 社交偏好 |
|
||||
| `feedback` | 重要反馈 |
|
||||
|
||||
### 5.7 截止日期语义化(前端计算,非接口字段)
|
||||
|
||||
后端只输出 `deadline: "YYYY-MM-DD"`,前端调用 `formatDeadline()` 计算:
|
||||
|
||||
| diff | 展示文案 | 样式 |
|
||||
|------|----------|------|
|
||||
| < 0 | `逾期 N 天` | `danger`(红) |
|
||||
| = 0 | `今天到期` | `warning`(橙) |
|
||||
| 1~7 | `还剩 N 天` | `normal` |
|
||||
| > 7 | `MM-DD` | `muted`(灰) |
|
||||
| null | `--` | `muted` |
|
||||
|
||||
---
|
||||
|
||||
## 6. 分页规范
|
||||
|
||||
```json
|
||||
{
|
||||
"list": [],
|
||||
"total": 100,
|
||||
"page": 1,
|
||||
"pageSize": 20,
|
||||
"hasMore": true
|
||||
}
|
||||
```
|
||||
|
||||
- `page` 从 1 开始计数
|
||||
- `hasMore` 由后端根据 `total` 和当前 `page * pageSize` 计算
|
||||
- `total` 为全量数据总条数(不是当前页条数)
|
||||
|
||||
---
|
||||
|
||||
## 7. 接口端点命名规范
|
||||
|
||||
```
|
||||
GET /api/xcx/tasks 任务列表
|
||||
GET /api/xcx/tasks/:id 任务详情
|
||||
GET /api/xcx/tasks/:id/notes 任务备注列表
|
||||
POST /api/xcx/tasks/:id/notes 新增备注
|
||||
GET /api/xcx/tasks/:id/service-records 近期服务记录
|
||||
GET /api/xcx/coaches 助教看板列表
|
||||
GET /api/xcx/coaches/:id 助教详情
|
||||
GET /api/xcx/performance 绩效概览
|
||||
GET /api/xcx/performance/records 绩效记录列表(支持月份分页)
|
||||
GET /api/xcx/retention-clues/:customerId 维客线索
|
||||
```
|
||||
|
||||
- 资源名用复数小写连字符
|
||||
- 路径参数用 `:id`,不用查询参数代替
|
||||
- 月份筛选用 `?year=2026&month=2`
|
||||
|
||||
---
|
||||
|
||||
## 8. 各页面接口字段对照表
|
||||
|
||||
### 8.1 任务列表页 `GET /api/xcx/tasks`
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "task-001",
|
||||
"customerName": "王先生",
|
||||
"taskType": "high_priority",
|
||||
"taskTypeLabel": "高优先召回",
|
||||
"deadline": "2026-03-20",
|
||||
"heartScore": 8.5,
|
||||
"isPinned": true,
|
||||
"hasNote": true,
|
||||
"status": "pending",
|
||||
"lastVisitDays": 23,
|
||||
"balance": 2000,
|
||||
"filledPct": 85,
|
||||
"aiSuggestion": "建议本周内回访,余额即将到期"
|
||||
}
|
||||
```
|
||||
|
||||
**说明:**
|
||||
- `lastVisitDays`:integer,距上次到店天数,null 表示从未到店
|
||||
- `balance`:integer(元),余额;null 表示无储值
|
||||
- `filledPct`:integer(0~100),关系进度百分比
|
||||
- `deadline`:`YYYY-MM-DD`,前端调用 `formatDeadline()` 语义化
|
||||
- `aiSuggestion`:string,AI 摘要文案,后端生成,前端直接展示
|
||||
|
||||
### 8.2 任务详情页 `GET /api/xcx/tasks/:id`
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "task-001",
|
||||
"customerName": "王先生",
|
||||
"taskType": "high_priority",
|
||||
"taskTypeLabel": "高优先召回",
|
||||
"status": "pending",
|
||||
"deadline": "2026-03-20",
|
||||
"heartScore": 8.5,
|
||||
"phone": "138****5678",
|
||||
"lastVisitDate": "2026-03-01",
|
||||
"lastSpendAmount": 380,
|
||||
"daysAbsent": 23,
|
||||
"callbackReason": "上次体验课后未续费,需跟进意向",
|
||||
"churnRisk": "high",
|
||||
"spendTrend": "down",
|
||||
"aiAnalysis": {
|
||||
"summary": "最近 3 个月每周均有 1-2 次课程互动",
|
||||
"suggestions": ["询问近期是否有空", "告知会员专属活动"]
|
||||
},
|
||||
"talkingPoints": ["王哥好久不见..."],
|
||||
"notes": [
|
||||
{
|
||||
"id": "note-001",
|
||||
"content": "已通过微信联系王先生...",
|
||||
"tagType": "customer",
|
||||
"tagLabel": "客户:王先生",
|
||||
"createdAt": "2026-03-10T16:30:00+08:00",
|
||||
"score": 8.5
|
||||
}
|
||||
],
|
||||
"serviceRecords": [
|
||||
{
|
||||
"table": "A12号台",
|
||||
"type": "基础课",
|
||||
"typeClass": "basic",
|
||||
"recordType": "course",
|
||||
"hours": 2.5,
|
||||
"hoursRaw": 3.0,
|
||||
"income": 200,
|
||||
"isEstimate": true,
|
||||
"drinks": "百威x2 红牛x1",
|
||||
"date": "2026-02-07T21:30:00+08:00"
|
||||
}
|
||||
],
|
||||
"serviceSummary": {
|
||||
"totalHours": 6.0,
|
||||
"totalIncome": 510,
|
||||
"avgIncome": 170
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**说明:**
|
||||
- `phone`:后端已做脱敏处理(`138****5678`),或根据权限返回明文
|
||||
- `score`:0~10 分制,0 表示未评分
|
||||
- `serviceRecords.hours`、`hoursRaw`:number(小时),前端调用 `formatHours()`
|
||||
- `serviceRecords.income`、`serviceSummary.*`:integer(元),前端调用 `formatMoney()`
|
||||
- `isEstimate`:boolean,true 时前端展示「预估」标注
|
||||
|
||||
### 8.3 助教看板页 `GET /api/xcx/coaches`
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "coach-001",
|
||||
"name": "小燕",
|
||||
"level": "star",
|
||||
"storeName": "朗朗桌球",
|
||||
"customerCount": 28,
|
||||
"monthHours": 86.0,
|
||||
"monthIncome": 18600,
|
||||
"svAmount": 5000,
|
||||
"filledPct": 92,
|
||||
"taskCount": 6,
|
||||
"urgentTaskCount": 2,
|
||||
"aiSuggestion": "本月表现优秀,建议重点跟进 2 位高风险流失客户"
|
||||
}
|
||||
```
|
||||
|
||||
**说明:**
|
||||
- `level`:英文 key,前端查 `LEVEL_MAP` 展示中文
|
||||
- `monthHours`:number(小时),前端调用 `formatHours()`
|
||||
- `monthIncome`、`svAmount`:integer(元),前端调用 `formatMoney()`
|
||||
- `filledPct`:integer(0~100),前端调用 `formatPercent()` 或 `toProgressWidth()`
|
||||
- `customerCount`、`taskCount`、`urgentTaskCount`:integer,前端调用 `formatCount()`
|
||||
|
||||
### 8.4 绩效记录页 `GET /api/xcx/performance/records`
|
||||
|
||||
请求参数:`?year=2026&month=2&page=1&pageSize=20`
|
||||
|
||||
```json
|
||||
{
|
||||
"summary": {
|
||||
"totalCount": 32,
|
||||
"totalHours": 59.0,
|
||||
"totalIncome": 4720
|
||||
},
|
||||
"dateGroups": [
|
||||
{
|
||||
"date": "2026-02-07",
|
||||
"totalHours": 6.0,
|
||||
"totalIncome": 510,
|
||||
"records": [
|
||||
{
|
||||
"id": "r1",
|
||||
"customerName": "王先生",
|
||||
"timeRange": "20:00-22:00",
|
||||
"hours": 2.0,
|
||||
"hoursRaw": null,
|
||||
"courseType": "basic",
|
||||
"location": "3号台",
|
||||
"income": 160
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"hasMore": false,
|
||||
"page": 1,
|
||||
"pageSize": 20
|
||||
}
|
||||
```
|
||||
|
||||
**说明:**
|
||||
- `date`:`YYYY-MM-DD`,前端格式化为「2月7日」
|
||||
- `summary.*`、`dateGroups.totalIncome`、`records.income`:integer(元)
|
||||
- `summary.totalHours`、`dateGroups.totalHours`、`records.hours`:number(小时)
|
||||
- `hoursRaw`:null 表示无折算,有值时前端展示折算备注
|
||||
- `courseType`:英文 key(`basic`/`vip`/`tip`/`recharge`/`incentive`)
|
||||
|
||||
### 8.5 备注接口 `GET /api/xcx/tasks/:id/notes`
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "note-001",
|
||||
"content": "已通过微信联系王先生...",
|
||||
"tagType": "customer",
|
||||
"tagLabel": "客户:王先生",
|
||||
"createdAt": "2026-03-10T16:30:00+08:00",
|
||||
"score": 8.5
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
- `tagType`:`customer` / `coach` / `system`
|
||||
- `score`:0 表示未评分,0.5 步长,前端调用 `scoreToHalfStar()` 转 5 星制
|
||||
- `createdAt`:ISO 8601,前端调用 `formatRelativeTime()` 展示相对时间
|
||||
|
||||
---
|
||||
|
||||
## 9. 禁止事项速查
|
||||
|
||||
| 禁止行为 | 正确做法 |
|
||||
|----------|----------|
|
||||
| 输出 `"¥12,680"` | 输出 `12680` |
|
||||
| 输出 `"2.5h"` | 输出 `2.5` |
|
||||
| 输出 `"32笔"` | 输出 `32` |
|
||||
| 输出 `"85%"` | 输出 `85` |
|
||||
| 输出 `"星级"` 作为等级 | 输出 `"star"` |
|
||||
| 输出 `"待处理"` 作为状态 | 输出 `"pending"` |
|
||||
| 省略值为 null 的字段 | 显式输出 `null` |
|
||||
| 用 `0` 代替 `null` 表示「无」 | 用 `null`(0 保留给有意义的零值) |
|
||||
| 输出 Unix 毫秒时间戳 | 输出 ISO 8601 字符串 |
|
||||
| 输出 `YYYY/MM/DD` 日期格式 | 输出 `YYYY-MM-DD` |
|
||||
| 后端做 `toLocaleString()` 格式化 | 后端输出原始数值,前端格式化 |
|
||||
| `hoursRaw` 输出 `"(折0.5h)"` | 输出 `3.0`(原始小时数) |
|
||||
|
||||
---
|
||||
|
||||
## 关联文档
|
||||
|
||||
- [DISPLAY-STANDARDS.md](./design-system/DISPLAY-STANDARDS.md) — 前端格式化规范
|
||||
- [DISPLAY-STANDARDS-2.md](./design-system/DISPLAY-STANDARDS-2.md) — 截止日期 / 评分 / Mock 规范
|
||||
- [DATETIME-DISPLAY-STANDARD.md](./design-system/DATETIME-DISPLAY-STANDARD.md) — 时间展示规范
|
||||
- `utils/money.ts` — `formatMoney` / `formatCount` / `formatPercent` / `toProgressWidth`
|
||||
- `utils/time.ts` — `formatHours` / `formatDeadline` / `formatRelativeTime`
|
||||
- `utils/rating.ts` — `scoreToHalfStar` / `isUnrated`
|
||||
- `utils/mock-data.ts` — Mock 数据(字段结构与接口保持一致)
|
||||
2022
docs/miniprogram-dev/API-contract.md
Normal file
2022
docs/miniprogram-dev/API-contract.md
Normal file
File diff suppressed because it is too large
Load Diff
65
docs/miniprogram-dev/API-requirement.md
Normal file
65
docs/miniprogram-dev/API-requirement.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# 小程序前端 → 后端接口需求记录
|
||||
|
||||
> 记录小程序前端在硬编码审计中发现的、需要后端配合的接口需求。
|
||||
> 创建时间:2026-03-18
|
||||
|
||||
---
|
||||
|
||||
## REQ-1:技能类型配置接口
|
||||
|
||||
- 来源页面:`board-coach`
|
||||
- 当前状态:`SKILL_OPTIONS` 硬编码 5 项(全部/中🎱/🎯斯诺克/小组课/打赏课)
|
||||
- 需求:门店技能类型应可配置,需要后端提供配置接口
|
||||
- 建议接口:`GET /api/xcx/config/skill-types`
|
||||
- 返回格式:`{ skills: Array<{ value: string; text: string }> }`
|
||||
- 优先级:P2(联调阶段处理)
|
||||
|
||||
---
|
||||
|
||||
## REQ-2:对话图标颜色固定化
|
||||
|
||||
- 来源页面:`chat-history`
|
||||
- 当前状态:`iconGradient` 每次渲染随机分配颜色,刷新后颜色会变
|
||||
- 需求:颜色应按对话 ID 哈希固定,无需后端配合
|
||||
- 前端方案:已改为按 `id` 字符串哈希取模,从 6 色渐变数组中固定选取
|
||||
- 状态:✅ 已完成(前端自行处理)
|
||||
|
||||
---
|
||||
|
||||
## REQ-3:开发模式 openid 上线安全
|
||||
|
||||
- 来源页面:`login`
|
||||
- 当前状态:开发模式使用固定 `dev_test_openid`,通过 `API_BASE.startsWith("http://127.0.0.1")` 判断
|
||||
- 风险:如果 `isDevMode` 判断逻辑被绕过,可能存在安全隐患
|
||||
- 需求:上线前确保后端 `/api/xcx/dev-login` 接口在生产环境不可用
|
||||
- 建议:后端通过环境变量控制 dev-login 路由注册,生产环境不注册该路由
|
||||
- 优先级:P0(上线前必须处理)
|
||||
|
||||
---
|
||||
|
||||
## REQ-4:`authUser` 字段扩展
|
||||
|
||||
- 来源页面:`performance-records`、`my-profile`、`coach-detail`
|
||||
- 当前状态:`globalData.authUser` 仅有 `userId`、`status`、`nickname`
|
||||
- 需求:多个页面需要展示当前用户的角色、门店名、助教等级等信息
|
||||
- 建议:`GET /api/xcx/me` 返回扩展字段,前端存入 `authUser`
|
||||
- 新增字段:
|
||||
|
||||
| 字段 | 类型 | 说明 | 来源 |
|
||||
|------|------|------|------|
|
||||
| `role` | `string` | 用户角色(如"助教") | 后端用户表 |
|
||||
| `storeName` | `string` | 所属门店名称 | 后端门店表 |
|
||||
| `coachLevel` | `string?` | 助教等级(junior/middle/senior/star) | 后端助教表(仅助教角色有) |
|
||||
| `avatar` | `string?` | 用户头像 URL | 后端用户表 |
|
||||
|
||||
- 优先级:P1(联调阶段处理)
|
||||
|
||||
---
|
||||
|
||||
## REQ-5:筛选选项全平台共用
|
||||
|
||||
- 来源页面:`board-coach`、`board-customer`
|
||||
- 当前状态:`SORT_OPTIONS`(6 项排序维度)、`TIME_OPTIONS`(6 项时间范围)、维度选项等硬编码在前端
|
||||
- 决定:这些筛选选项全平台共用,暂保持前端硬编码
|
||||
- 后续:如果需要动态调整,再改为后端配置接口
|
||||
- 状态:🔒 暂不处理
|
||||
159
docs/miniprogram-dev/api-audit/_hardcode-summary.md
Normal file
159
docs/miniprogram-dev/api-audit/_hardcode-summary.md
Normal file
@@ -0,0 +1,159 @@
|
||||
# 小程序全页面硬编码汇总与处置方案
|
||||
|
||||
> 生成时间:2026-03-18 | 最后更新:2026-03-18
|
||||
> 覆盖页面:18 个(全部业务页面)
|
||||
|
||||
---
|
||||
|
||||
## 一、分类说明
|
||||
|
||||
| 类别 | 说明 | 行动 |
|
||||
|------|------|------|
|
||||
| ✅ 已修复 | 已完成修改 | — |
|
||||
| 📋 已决策-暂不动 | 用户已确认处置方式,当前不改 | 联调时处理 |
|
||||
| 🔒 保留 | 合理的 UI 文案/默认值/样式常量 | 不动 |
|
||||
|
||||
---
|
||||
|
||||
## 二、✅ 已修复的硬编码
|
||||
|
||||
### 2.1 时间/日期硬编码 → 动态计算 ✅
|
||||
|
||||
| # | 页面 | 字段 | 原值 | 修改结果 |
|
||||
|---|------|------|------|----------|
|
||||
| 1 | customer-service-records | `currentYear` | `2026` | `new Date().getFullYear()` |
|
||||
| 2 | customer-service-records | `currentMonth` | `2` | `new Date().getMonth() + 1` |
|
||||
| 3 | customer-service-records | `maxYearMonth` | `202602` | 动态计算当前年月 |
|
||||
| 4 | customer-service-records | `minYearMonth` | `202601` | 动态计算(当前年月 - 12 个月) |
|
||||
| 5 | performance-records | `currentYear` | `2026` | `new Date().getFullYear()` |
|
||||
| 6 | performance-records | `currentMonth` | `2` | `new Date().getMonth() + 1` |
|
||||
| 7 | performance-records | `monthLabel` | `'2026年2月'` | 动态拼接 |
|
||||
|
||||
### 2.2 用户信息 → 从 globalData.authUser 读取 ✅
|
||||
|
||||
| # | 页面 | 字段 | 原值 | 修改结果 |
|
||||
|---|------|------|------|----------|
|
||||
| 8 | performance-records | `coachName` | `'小燕'` | `onLoad()` 从 `app.globalData.authUser.nickname` 读取 |
|
||||
| 9 | performance-records | `coachLevel` | `'星级'` | `onLoad()` 从 `app.globalData.authUser.coachLevel` 读取 |
|
||||
| 10 | performance-records | `storeName` | `'球会名称店'` | `onLoad()` 从 `app.globalData.authUser.storeName` 读取 |
|
||||
| 11 | my-profile | `mockUserProfile` | `{name:'小燕',...}` | `onShow()` 从 `app.globalData.authUser` 读取,删除 mock import |
|
||||
|
||||
### 2.3 全局 UI 常量抽取 ✅
|
||||
|
||||
| # | 页面 | 原代码 | 修改结果 |
|
||||
|---|------|--------|----------|
|
||||
| 12 | my-profile | `confirmColor: '#e34d59'` | `CONFIRM_DANGER_COLOR`(from `ui-constants.ts`) |
|
||||
| 13 | task-detail | `confirmColor: '#e34d59'` | `CONFIRM_DANGER_COLOR`(from `ui-constants.ts`) |
|
||||
| 14 | notes | `confirmColor: '#e34d59'` | `CONFIRM_DANGER_COLOR`(from `ui-constants.ts`) |
|
||||
|
||||
> 新建 `utils/ui-constants.ts`,`CONFIRM_DANGER_COLOR` 引用 `vi-colors.ts` 的 `GLOBAL_COLORS.error`
|
||||
|
||||
### 2.4 死代码 / 无效路由清理 ✅
|
||||
|
||||
| # | 页面 | 问题 | 修改结果 |
|
||||
|---|------|------|----------|
|
||||
| 15 | customer-detail | 未使用的 `mockCustomerDetail` import | 已删除 |
|
||||
| 16 | apply | 无效路由 `/pages/mvp/mvp` | → `/pages/task-list/task-list` |
|
||||
| 17 | no-permission | 无效路由 `/pages/mvp/mvp` | → `/pages/task-list/task-list` |
|
||||
| 18 | reviewing | 无效路由 `/pages/mvp/mvp` | → `/pages/task-list/task-list` |
|
||||
| 19 | router | 死路由 `'settings': ''` | 已删除 |
|
||||
|
||||
### 2.5 交互优化 ✅
|
||||
|
||||
| # | 页面 | 问题 | 修改结果 |
|
||||
|---|------|------|----------|
|
||||
| 20 | chat-history | `iconGradient` 随机分配 | 改为按对话 ID 哈希固定(`hashIndex()` 函数) |
|
||||
|
||||
### 2.6 类型定义扩展 ✅
|
||||
|
||||
| # | 文件 | 修改 |
|
||||
|---|------|------|
|
||||
| 21 | `typings/index.d.ts` | `authUser` 接口新增 `role?`、`storeName?`、`coachLevel?`、`avatar?` 字段 |
|
||||
|
||||
---
|
||||
|
||||
## 三、📋 已决策-暂不动
|
||||
|
||||
### 3.1 Mock 数据(联调时替换)
|
||||
|
||||
| # | 页面 | 字段 | 用户决策 |
|
||||
|---|------|------|----------|
|
||||
| 1 | customer-service-records | `customerPhone`/`relationIndex` 等 | Mock 数据,联调时从接口取 |
|
||||
| 2 | performance-records | mock 数据不一致(totalCount/totalHours/totalIncome) | 联调时 API 替换,暂不修 |
|
||||
|
||||
### 3.2 筛选选项(全平台共用,暂前端硬编码)
|
||||
|
||||
| # | 页面 | 常量 | 用户决策 |
|
||||
|---|------|------|----------|
|
||||
| 3 | board-coach | `SKILL_OPTIONS` | 后端需配置接口(已记录 REQ-1) |
|
||||
| 4 | board-coach | `SORT_OPTIONS` | 全平台共用,暂保持 |
|
||||
| 5 | board-customer | 维度/项目选项 | 全平台共用,暂保持 |
|
||||
|
||||
### 3.3 其他已决策项
|
||||
|
||||
| # | 页面 | 项目 | 用户决策 |
|
||||
|---|------|------|----------|
|
||||
| 6 | no-permission | 管理员姓名 `厉超` | 保持硬编码(只有一个管理员) |
|
||||
| 7 | login | `dev_test_openid` | 上线前处理(已记录 REQ-3) |
|
||||
|
||||
> 后端接口需求已记录于 `docs/miniprogram-dev/API-requirement.md`(REQ-1 ~ REQ-5)
|
||||
|
||||
---
|
||||
|
||||
## 四、🔒 保留不动的硬编码
|
||||
|
||||
以下硬编码属于合理的 UI 文案、样式常量或默认值,无需修改:
|
||||
|
||||
### 4.1 UI 文案(各页面通用)
|
||||
|
||||
所有页面的以下文案保留不动:
|
||||
- 加载态:`加载中...`
|
||||
- 错误态:`加载失败,请重试` / `重新加载` / `重试`
|
||||
- 空态:`暂无数据` / `暂无备注记录` / `暂无对话记录` / `暂无助教数据` 等
|
||||
- 底部提示:`— 已加载全部记录 —`
|
||||
- 导航栏标题(各页面 `.json` 中的 `navigationBarTitleText`)
|
||||
- 业务术语标签:`定档` / `折前` / `预估` / `召回` / `回访` 等
|
||||
|
||||
### 4.2 `statusBarHeight` 回退值
|
||||
|
||||
所有页面的 `statusBarHeight` 默认值(`20` 或 `44`)作为 `wx.getWindowInfo()` 的兜底值,保留不动。
|
||||
|
||||
### 4.3 `setTimeout` 模拟延迟
|
||||
|
||||
所有页面的 `setTimeout(400~600ms)` 模拟网络延迟,联调时统一替换为真实 API 调用。当前阶段保留不动(改了也没意义,联调时整体重构 `loadData()`)。
|
||||
|
||||
### 4.4 路由路径
|
||||
|
||||
各页面中的 `wx.navigateTo` / `wx.reLaunch` / `wx.switchTab` 路径硬编码,属于小程序标准做法,保留不动。
|
||||
|
||||
### 4.5 样式映射常量
|
||||
|
||||
- `board-coach`: `LEVEL_CLASS` / `SKILL_CLASS` / `SORT_TO_DIM` / `TIME_OPTIONS`
|
||||
- `chat-history`: `ICON_GRADIENTS`(6 组 CSS 渐变色)
|
||||
- 各页面的 `courseTypeClass` / `avatarGradient` 等样式映射
|
||||
|
||||
### 4.6 表单校验规则
|
||||
|
||||
- `apply`: 手机号正则 `!/^\d{11}$/`、球房 ID `maxlength="5"`
|
||||
- `login`: 错误提示文案(`账号已被禁用` / `登录凭证无效` 等)
|
||||
|
||||
### 4.7 Mock 数据本体
|
||||
|
||||
所有 `mockXxx` 数据(包括页面内联和 `mock-data.ts` 中的)在联调前保留不动。联调时统一替换为 API 调用。
|
||||
|
||||
---
|
||||
|
||||
## 五、变更统计
|
||||
|
||||
本轮共修复 21 项硬编码问题,涉及 14 个文件:
|
||||
|
||||
| 类别 | 数量 |
|
||||
|------|------|
|
||||
| 时间/日期动态化 | 7 |
|
||||
| 用户信息 globalData 读取 | 4 |
|
||||
| UI 常量抽取 | 3 |
|
||||
| 死代码/无效路由清理 | 5 |
|
||||
| 交互优化(哈希固定) | 1 |
|
||||
| 类型定义扩展 | 1 |
|
||||
|
||||
涉及文件:`performance-records.ts`、`customer-service-records.ts`、`my-profile.ts`、`task-detail.ts`、`notes.ts`、`chat-history.ts`、`customer-detail.ts`、`apply.ts`、`no-permission.ts`、`reviewing.ts`、`router.ts`、`ui-constants.ts`(新建)、`vi-colors.ts`(已有)、`typings/index.d.ts`
|
||||
134
docs/miniprogram-dev/api-audit/apply.md
Normal file
134
docs/miniprogram-dev/api-audit/apply.md
Normal file
@@ -0,0 +1,134 @@
|
||||
# apply 页面数据来源排查
|
||||
|
||||
> 排查日期:2026-03-18
|
||||
> 页面路径:pages/apply/apply
|
||||
|
||||
## 概览
|
||||
|
||||
| 分类 | 数量 | 说明 |
|
||||
|------|------|------|
|
||||
| A. Mock 数据 | 0 | 无 mock 数据 |
|
||||
| B. 硬编码数据 | 5 | 表单校验规则、延时跳转、错误提示文案 |
|
||||
| C. 已对接 API | 2 | `/api/xcx/me`(GET)、`/api/xcx/apply`(POST) |
|
||||
| D. 前端计算/派生数据 | 2 | statusBarHeight、submitting 状态 |
|
||||
| E. 路由参数 | 0 | 无路由参数 |
|
||||
| F. WXML 硬编码文案 | 16 | 标题、欢迎语、步骤条、表单标签、placeholder、提示语 |
|
||||
|
||||
---
|
||||
|
||||
## 一、Mock 数据
|
||||
|
||||
无。页面未使用任何 mock 数据或本地假数据。
|
||||
|
||||
---
|
||||
|
||||
## 二、硬编码数据
|
||||
|
||||
| # | 位置(apply.ts 行号) | 内容 | 风险等级 | 说明 |
|
||||
|---|----------------------|------|----------|------|
|
||||
| B1 | L89 `!/^\d{11}$/.test(phone)` | 手机号正则:固定 11 位纯数字 | 🟡 低 | 仅适用于中国大陆手机号,若未来支持港澳台/海外号码需调整 |
|
||||
| B2 | L85 `maxlength="5"`(WXML) | 球房ID最大长度 5 | 🟡 低 | 与后端 site_code 长度约束需保持一致 |
|
||||
| B3 | L113 `setTimeout(() => {...}, 800)` | 提交成功后 800ms 延时跳转 | 🟢 无风险 | 纯 UX 延时,不影响业务逻辑 |
|
||||
| B4 | L117-123 错误码映射 | `409 → "您已有待审核的申请"`、`422 → "表单信息有误,请检查"` | 🟡 低 | 错误文案硬编码在前端,后端 detail 字段优先使用 |
|
||||
| B5 | L82-96 表单校验逻辑 | 4 条必填校验 + 手机号格式校验 | 🟢 无风险 | 标准前端校验,后端有独立校验 |
|
||||
|
||||
---
|
||||
|
||||
## 三、已对接 API
|
||||
|
||||
### API-1:获取当前用户状态
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|-----|
|
||||
| 端点 | `GET /api/xcx/me` |
|
||||
| 调用位置 | `apply.ts` → `_checkAccess()` (L30) |
|
||||
| 调用时机 | `onShow` 生命周期 |
|
||||
| 请求参数 | 无(仅 Authorization header) |
|
||||
| 认证 | `needAuth: true`(自动附加 Bearer token) |
|
||||
| 响应字段 | `user_id`、`status`(new/pending/approved/rejected/disabled)、`nickname` |
|
||||
| 响应处理 | 写入 `app.globalData.authUser` + `wx.setStorageSync("userId"/"userStatus")`;根据 status 路由分发 |
|
||||
| 错误处理 | catch 静默忽略(允许用户继续填表) |
|
||||
|
||||
**路由分发逻辑:**
|
||||
|
||||
| status | 行为 |
|
||||
|--------|------|
|
||||
| `new` | 留在当前页(允许填写申请表) |
|
||||
| `rejected` | `reLaunch → /pages/no-permission/no-permission` |
|
||||
| `approved` | `reLaunch → /pages/mvp/mvp` |
|
||||
| `pending` | `reLaunch → /pages/reviewing/reviewing` |
|
||||
| `disabled` | `reLaunch → /pages/no-permission/no-permission` |
|
||||
|
||||
### API-2:提交入驻申请
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|-----|
|
||||
| 端点 | `POST /api/xcx/apply` |
|
||||
| 调用位置 | `apply.ts` → `onSubmit()` (L99) |
|
||||
| 调用时机 | 用户点击"提交申请"按钮 |
|
||||
| 认证 | `needAuth: true` |
|
||||
| 请求参数 | 见下表 |
|
||||
| 成功处理 | Toast "申请已提交" → 800ms 后 `reLaunch → /pages/reviewing/reviewing` |
|
||||
| 错误处理 | 优先取 `err.data.detail`;409 → "您已有待审核的申请";422 → "表单信息有误";其他 → "提交失败,请稍后重试" |
|
||||
|
||||
**请求参数:**
|
||||
|
||||
| 字段 | 来源 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| `site_code` | 表单输入 `siteCode`(trim) | string | ✅ | 球房ID |
|
||||
| `applied_role_text` | 表单输入 `role`(trim) | string | ✅ | 申请身份(自由文本) |
|
||||
| `phone` | 表单输入 `phone` | string | ✅ | 11 位手机号 |
|
||||
| `employee_number` | 表单输入 `employeeNumber`(trim,空则 undefined) | string? | ❌ | 编号(选填) |
|
||||
| `nickname` | 表单输入 `nickname`(trim) | string | ✅ | 昵称 |
|
||||
|
||||
---
|
||||
|
||||
## 四、前端计算/派生数据
|
||||
|
||||
| # | 数据 | 来源 | 位置 |
|
||||
|---|------|------|------|
|
||||
| D1 | `statusBarHeight` | `wx.getWindowInfo().statusBarHeight` | `onLoad()` L14 |
|
||||
| D2 | `submitting` | 布尔状态锁,`onSubmit` 开始时设 true,finally 设 false | `onSubmit()` L98/L126 |
|
||||
|
||||
---
|
||||
|
||||
## 五、路由参数
|
||||
|
||||
无。`onLoad()` 未读取 `options` 参数。
|
||||
|
||||
---
|
||||
|
||||
## 六、WXML 硬编码文案
|
||||
|
||||
| # | 元素 | 文案内容 | 建议 |
|
||||
|---|------|----------|------|
|
||||
| F1 | navbar-title | `申请访问权限` | 可保留 |
|
||||
| F2 | welcome-title | `欢迎加入球房运营助手` | 可保留 |
|
||||
| F3 | welcome-desc | `请填写申请信息,等待管理员审核` | 可保留 |
|
||||
| F4 | step-label 1 | `提交申请` | 可保留 |
|
||||
| F5 | step-label 2 | `等待审核` | 可保留 |
|
||||
| F6 | step-label 3 | `审核通过` | 可保留 |
|
||||
| F7 | step-label 4 | `开始使用` | 可保留 |
|
||||
| F8 | form-label | `球房ID` | 可保留 |
|
||||
| F9 | form-label | `申请身份` | 可保留 |
|
||||
| F10 | form-label | `手机号` | 可保留 |
|
||||
| F11 | form-label | `编号` + `选填` | 可保留 |
|
||||
| F12 | form-label | `昵称` | 可保留 |
|
||||
| F13 | placeholder | `请输入球房ID` / `请输入申请身份(如:助教、店长等)` / `请输入手机号` / `请输入编号` / `请输入昵称` | 可保留 |
|
||||
| F14 | form-tip | `请认真填写,信息不完整可能导致审核不通过` | 可保留 |
|
||||
| F15 | submit-btn-text | `提交申请` | 可保留 |
|
||||
| F16 | bottom-tip | `审核通常需要 1-3 个工作日` | 🟡 若审核时效变化需同步更新 |
|
||||
|
||||
---
|
||||
|
||||
## 七、联调 TODO
|
||||
|
||||
| # | 类型 | 描述 | 优先级 |
|
||||
|---|------|------|--------|
|
||||
| T1 | 验证 | 确认 `GET /api/xcx/me` 返回字段与前端解构一致(`user_id`、`status`、`nickname`) | P0 |
|
||||
| T2 | 验证 | 确认 `POST /api/xcx/apply` 请求体字段名与后端 schema 一致(`site_code`、`applied_role_text`、`phone`、`employee_number`、`nickname`) | P0 |
|
||||
| T3 | 验证 | 确认后端 409 响应场景(重复申请)和 422 响应场景(参数校验失败)的 `detail` 字段格式 | P1 |
|
||||
| T4 | 验证 | 确认 `site_code` 最大长度(前端 maxlength=5)与后端约束一致 | P1 |
|
||||
| T5 | 优化 | `_checkAccess` 的 catch 静默忽略网络错误——考虑是否需要给用户提示网络异常 | P2 |
|
||||
| T6 | 优化 | `employee_number` 空值传 `undefined`(JSON 序列化时会被忽略)——确认后端是否接受缺省该字段 | P1 |
|
||||
| T7 | 确认 | `status` 枚举值完整性——前端处理了 `new/rejected/approved/pending/disabled`,确认后端不会返回其他值 | P1 |
|
||||
207
docs/miniprogram-dev/api-audit/board-coach.md
Normal file
207
docs/miniprogram-dev/api-audit/board-coach.md
Normal file
@@ -0,0 +1,207 @@
|
||||
# board-coach 页面数据来源排查
|
||||
|
||||
> 排查日期:2026-03-18
|
||||
> 页面路径:pages/board-coach/board-coach
|
||||
> 排查范围:board-coach.ts / board-coach.wxml / board-coach.wxss / board-coach.json + 引用工具函数
|
||||
|
||||
## 概览
|
||||
|
||||
| 分类 | 数量 | 说明 |
|
||||
|------|------|------|
|
||||
| A. Mock 数据 | 1 组(6 条记录,含 ~20 个字段/条) | 页面内联 `MOCK_COACHES` 数组,未引用 `utils/mock-data.ts` |
|
||||
| B. 硬编码数据 | 6 组常量 + 3 个默认值 | 排序/技能/时间选项、等级/技能样式映射、默认筛选值 |
|
||||
| C. 已对接 API | 0 | 全部数据来自 Mock,无任何 `wx.request` / service 调用 |
|
||||
| D. 前端计算/派生数据 | 10 个派生字段 | `loadData()` 中通过 `formatMoney`/`formatHours`/`formatCount` 生成展示标签 |
|
||||
| E. 路由参数 | 0 | `onLoad()` 未读取任何 query 参数 |
|
||||
| F. WXML 硬编码文案 | 14 处 | Tab 标签、状态提示、维度标签等 |
|
||||
|
||||
---
|
||||
|
||||
## 一、Mock 数据
|
||||
|
||||
### 1.1 页面内联 Mock:`MOCK_COACHES`(board-coach.ts L82-L161)
|
||||
|
||||
页面顶部定义了 `const MOCK_COACHES: CoachItem[]`,包含 6 位助教的完整数据。`loadData()` 通过 `setTimeout(400ms)` 模拟异步加载后直接使用该数组。
|
||||
|
||||
**未引用** `utils/mock-data.ts` 中的 `mockCoaches`(该文件有独立的 `CoachCard` 接口和 3 条记录,字段结构不同)。
|
||||
|
||||
| Mock 字段 | 类型 | 示例值 | 联调时应替换为 |
|
||||
|-----------|------|--------|---------------|
|
||||
| `id` | string | `'c1'` | API 返回的助教 ID |
|
||||
| `name` | string | `'小燕'` | 助教姓名 |
|
||||
| `initial` | string | `'小'` | 前端根据 name 首字计算,或 API 返回头像 URL |
|
||||
| `avatarGradient` | string | `'blue'` | API 返回头像 URL 后可移除,改用真实头像 |
|
||||
| `level` | string | `'star'` | 助教等级(API:`star/senior/middle/junior`) |
|
||||
| `skills` | Array | `[{text:'🎱', cls:'skill--chinese'}]` | API 返回技能列表,前端映射 cls |
|
||||
| `topCustomers` | string[] | `['💖 王先生','💖 李女士']` | API 返回 top 客户列表(含亲密度标记) |
|
||||
| `perfHours` | number | `86.2` | 定档业绩课时(API 聚合) |
|
||||
| `perfHoursBefore` | number? | `92.0` | 折前课时(API 聚合) |
|
||||
| `perfGap` | string? | `'距升档 13.8h'` | 可由前端计算或 API 直接返回 |
|
||||
| `perfReached` | boolean | `false` | 是否已达标(API 或前端判断) |
|
||||
| `salary` | number | `12680` | 预估工资(API 聚合) |
|
||||
| `salaryPerfHours` | number | `86.2` | 工资维度-定档课时 |
|
||||
| `salaryPerfBefore` | number? | `92.0` | 工资维度-折前课时 |
|
||||
| `svAmount` | number | `45200` | 客源储值金额(API 聚合) |
|
||||
| `svCustomerCount` | number | `18` | 储值客户数 |
|
||||
| `svConsume` | number | `8600` | 储值消耗金额 |
|
||||
| `taskRecall` | number | `18` | 召回任务完成数 |
|
||||
| `taskCallback` | number | `14` | 回访任务完成数 |
|
||||
|
||||
**联调替换目标 API**(待后端提供):
|
||||
|
||||
```
|
||||
GET /api/v1/board/coach/list
|
||||
Query: sort={selectedSort}&skill={selectedSkill}&time={selectedTime}
|
||||
Response: { coaches: CoachItem[] }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、硬编码数据
|
||||
|
||||
### 2.1 筛选选项常量(board-coach.ts L14-L50)
|
||||
|
||||
| 常量名 | 字段数 | 风险 | 说明 |
|
||||
|--------|--------|------|------|
|
||||
| `SORT_OPTIONS` | 6 项 | 🟡 中 | 排序维度选项,value/text 写死。若后端新增排序维度需同步修改 |
|
||||
| `SKILL_OPTIONS` | 5 项 | 🟡 中 | 技能筛选选项,含 emoji。若门店技能类型可配置则需改为 API 获取 |
|
||||
| `TIME_OPTIONS` | 6 项 | 🟢 低 | 时间范围选项,纯前端逻辑,变动概率低 |
|
||||
| `SORT_TO_DIM` | 6 项映射 | 🟢 低 | 排序值→维度类型映射,纯前端 UI 逻辑 |
|
||||
|
||||
### 2.2 样式映射常量(board-coach.ts L53-L68)
|
||||
|
||||
| 常量名 | 风险 | 说明 |
|
||||
|--------|------|------|
|
||||
| `LEVEL_CLASS` | 🟢 低 | 等级→CSS 类映射,含中英文 key 兼容。纯前端样式逻辑 |
|
||||
| `SKILL_CLASS` | 🟢 低 | 技能 emoji→CSS 类映射。纯前端样式逻辑 |
|
||||
|
||||
### 2.3 data 对象默认值(board-coach.ts L165-L180)
|
||||
|
||||
| 字段 | 硬编码值 | 风险 | 说明 |
|
||||
|------|----------|------|------|
|
||||
| `selectedSort` | `'perf_desc'` | 🟢 低 | 默认排序,合理默认值 |
|
||||
| `selectedSkill` | `'all'` | 🟢 低 | 默认不限技能 |
|
||||
| `selectedTime` | `'month'` | 🟢 低 | 默认本月 |
|
||||
| `dimType` | `'perf'` | 🟢 低 | 默认维度类型,与 selectedSort 联动 |
|
||||
| `filterBarVisible` | `true` | 🟢 低 | 筛选栏初始可见 |
|
||||
| `pageState` | `'loading'` | 🟢 低 | 页面初始状态 |
|
||||
| `activeTab` | `'coach'` | 🟢 低 | 当前 Tab 标识 |
|
||||
|
||||
### 2.4 `loadData()` 中的模拟延迟(board-coach.ts L195)
|
||||
|
||||
```typescript
|
||||
setTimeout(() => { ... }, 400) // 🔴 高风险:联调时必须替换为真实 API 调用
|
||||
```
|
||||
|
||||
### 2.5 `onPullDownRefresh` 中的延迟(board-coach.ts L192)
|
||||
|
||||
```typescript
|
||||
setTimeout(() => wx.stopPullDownRefresh(), 500) // 🟡 中风险:联调后应在 API 回调中停止
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、已对接 API
|
||||
|
||||
**无。** 当前页面 0 个 API 调用,全部数据来自页面内联 Mock。
|
||||
|
||||
页面中存在的导航跳转(非数据 API):
|
||||
- `wx.switchTab({ url: '/pages/board-finance/board-finance' })` — Tab 切换
|
||||
- `wx.navigateTo({ url: '/pages/board-customer/board-customer' })` — Tab 切换
|
||||
- `wx.navigateTo({ url: '/pages/coach-detail/coach-detail?id=' + id })` — 助教详情跳转
|
||||
|
||||
---
|
||||
|
||||
## 四、前端计算/派生数据
|
||||
|
||||
### 4.1 `loadData()` 中的格式化派生字段(board-coach.ts L197-L208)
|
||||
|
||||
| 派生字段 | 源字段 | 格式化函数 | 输出示例 |
|
||||
|----------|--------|-----------|----------|
|
||||
| `perfHoursLabel` | `perfHours` | `formatHours()` | `'86.2h'` |
|
||||
| `perfHoursBeforeLabel` | `perfHoursBefore` | `formatHours()` | `'92h'` |
|
||||
| `salaryLabel` | `salary` | `formatMoney()` | `'¥12,680'` |
|
||||
| `salaryPerfHoursLabel` | `salaryPerfHours` | `formatHours()` | `'86.2h'` |
|
||||
| `salaryPerfBeforeLabel` | `salaryPerfBefore` | `formatHours()` | `'92h'` |
|
||||
| `svAmountLabel` | `svAmount` | `formatMoney()` | `'¥45,200'` |
|
||||
| `svCustomerCountLabel` | `svCustomerCount` | `formatCount(n,'人')` | `'18人'` |
|
||||
| `svConsumeLabel` | `svConsume` | `formatMoney()` | `'¥8,600'` |
|
||||
| `taskRecallLabel` | `taskRecall` | `formatCount(n,'次')` | `'18次'` |
|
||||
| `taskCallbackLabel` | `taskCallback` | `formatCount(n,'次')` | `'14次'` |
|
||||
|
||||
### 4.2 维度类型切换(board-coach.ts L218-L222)
|
||||
|
||||
`onSortChange` 根据 `SORT_TO_DIM` 映射计算 `dimType`,控制 WXML 中四种卡片模板的条件渲染。
|
||||
|
||||
### 4.3 滚动隐藏/显示筛选栏(board-coach.ts L196-L215 `onPageScroll`)
|
||||
|
||||
纯前端交互逻辑:累积滚动距离超过阈值(上滑 12px / 下滑 24px)时切换 `filterBarVisible`。
|
||||
|
||||
---
|
||||
|
||||
## 五、路由参数
|
||||
|
||||
**无。** `onLoad()` 未接收任何 `options` 参数。
|
||||
|
||||
---
|
||||
|
||||
## 六、WXML 硬编码文案
|
||||
|
||||
| 位置 | 硬编码文案 | 风险 | 建议 |
|
||||
|------|-----------|------|------|
|
||||
| 加载态 | `加载中...` | 🟢 低 | 通用文案,可保留 |
|
||||
| 空状态 | `暂无助教数据` | 🟢 低 | 可保留 |
|
||||
| 错误态 | `加载失败` | 🟢 低 | 可保留 |
|
||||
| 重试按钮 | `点击重试` | 🟢 低 | 可保留 |
|
||||
| Tab-财务 | `财务` | 🟢 低 | 固定 Tab 文案 |
|
||||
| Tab-客户 | `客户` | 🟢 低 | 固定 Tab 文案 |
|
||||
| Tab-助教 | `助教` | 🟢 低 | 固定 Tab 文案 |
|
||||
| 定档维度标签 | `定档` / `折前` | 🟢 低 | 业务术语,可保留 |
|
||||
| 工资维度标签 | `预估` | 🟢 低 | 业务术语 |
|
||||
| 储值维度标签 | `储值` | 🟢 低 | 业务术语 |
|
||||
| 召回维度标签 | `召回` | 🟢 低 | 业务术语 |
|
||||
| 达标标记 | `✅ 已达标` | 🟢 低 | 可保留 |
|
||||
| 工资底部 | `定档` / `折前` | 🟢 低 | 同上 |
|
||||
| 储值底部 | `客户` / `消耗` / `\|` 分隔符 | 🟢 低 | 业务术语 |
|
||||
| 任务底部 | `回访` | 🟢 低 | 业务术语 |
|
||||
| 导航栏标题 | `助教看板`(board-coach.json) | 🟢 低 | 固定页面标题 |
|
||||
|
||||
---
|
||||
|
||||
## 七、引用组件清单
|
||||
|
||||
| 组件 | 来源 | 数据依赖 |
|
||||
|------|------|----------|
|
||||
| `coach-level-tag` | 自定义组件 | 接收 `level` 属性 |
|
||||
| `filter-dropdown` | 自定义组件 | 接收 `label`/`options`/`value`,触发 `change` 事件 |
|
||||
| `ai-float-button` | 自定义组件 | 无数据依赖 |
|
||||
| `board-tab-bar` | 自定义 Tab Bar | 接收 `active="board"` |
|
||||
| `t-loading` / `t-empty` | TDesign 组件 | 无业务数据依赖 |
|
||||
| `dev-fab` | 开发调试组件 | `wx:if="{{false}}"` 已禁用 |
|
||||
|
||||
---
|
||||
|
||||
## 八、联调 TODO
|
||||
|
||||
### 🔴 P0 — 必须完成
|
||||
|
||||
- [ ] **替换 `MOCK_COACHES` 为 API 调用**:移除页面内联 Mock 数组,`loadData()` 改为调用后端接口
|
||||
- 需后端提供:助教列表接口(含排序/技能/时间筛选参数)
|
||||
- 接口需返回:四种维度的全部字段(perf/salary/sv/task)
|
||||
- 错误处理:API 失败时 `setData({ pageState: 'error' })`
|
||||
- [ ] **移除 `setTimeout(400ms)` 模拟延迟**:替换为真实异步请求
|
||||
- [ ] **`onPullDownRefresh` 改为 API 回调中停止**:`wx.stopPullDownRefresh()` 应在请求完成后调用
|
||||
|
||||
### 🟡 P1 — 建议完成
|
||||
|
||||
- [ ] **筛选联动 API**:当前 `onSortChange`/`onSkillChange`/`onTimeChange` 仅更新本地状态,未触发重新请求。联调时需在筛选变更后重新调用 API
|
||||
- [ ] **头像方案确认**:当前使用 `initial`(首字)+ `avatarGradient`(渐变色)模拟头像。确认是否改为真实头像 URL
|
||||
- [ ] **`perfGap` 字段来源确认**:当前为 Mock 字符串(如 `'距升档 13.8h'`),确认由后端计算还是前端根据阈值计算
|
||||
- [ ] **`topCustomers` 格式确认**:当前含 emoji 亲密度标记(💖/💛),确认 API 返回格式
|
||||
|
||||
### 🟢 P2 — 可后续优化
|
||||
|
||||
- [ ] **`SKILL_OPTIONS` 是否需要动态获取**:若门店技能类型可配置,应改为 API 获取
|
||||
- [ ] **`SORT_OPTIONS` / `TIME_OPTIONS` 是否需要后端控制**:当前写死,若需动态调整需改为配置化
|
||||
- [ ] **清理 `utils/mock-data.ts` 中的 `CoachCard` 接口**:与页面内联 `CoachItem` 接口不一致,联调完成后统一或移除
|
||||
- [ ] **`levelClass` 字段**:当前在 Mock 中预计算,联调后应由前端根据 `level` 字段通过 `LEVEL_CLASS` 映射生成
|
||||
295
docs/miniprogram-dev/api-audit/board-customer.md
Normal file
295
docs/miniprogram-dev/api-audit/board-customer.md
Normal file
@@ -0,0 +1,295 @@
|
||||
# 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`(三种历史口径混合)
|
||||
352
docs/miniprogram-dev/api-audit/board-finance.md
Normal file
352
docs/miniprogram-dev/api-audit/board-finance.md
Normal file
@@ -0,0 +1,352 @@
|
||||
# board-finance 页面数据来源排查
|
||||
|
||||
> 排查日期:2026-03-18
|
||||
> 页面路径:pages/board-finance/board-finance
|
||||
> 排查人:Kiro AI
|
||||
> 状态:**全量内联 Mock,零 API 对接**
|
||||
|
||||
## 概览
|
||||
|
||||
| 分类 | 数量 | 说明 |
|
||||
|------|------|------|
|
||||
| Mock 数据(内联) | **120+ 个字段** | 全部财务指标写死在 `data` 对象中,无外部 mock 函数调用 |
|
||||
| 硬编码数据 | **19 个字段/配置** | 筛选选项、目录导航、指标解释、板块描述等 |
|
||||
| 已对接 API | **0 个接口** | 页面无任何 `request()` / `wx.request` 调用 |
|
||||
| 前端计算/派生 | **8 个字段** | UI 状态、滚动位置、配色等 |
|
||||
| 路由参数 | **0 个** | `onLoad` 未读取 `options` |
|
||||
| WXML 硬编码文案 | **3 处** | AI 洞察文案、平台服务费说明 |
|
||||
|
||||
---
|
||||
|
||||
## 一、Mock 数据(内联在 data 对象中)
|
||||
|
||||
> 本页面 **不引用** `utils/mock-data.ts`(该文件虽定义了 `BoardFinanceData` 接口,但页面未 import)。
|
||||
> 所有 mock 数据直接写死在 `Page({ data: { ... } })` 中,文件头注释标注 `TODO: 联调时替换 mock 数据为真实 API 调用`。
|
||||
|
||||
### 1.1 经营一览 `overview`
|
||||
|
||||
| 字段 | 类型 | 当前 Mock 值 | 联调替换 API | 备注 |
|
||||
|------|------|-------------|-------------|------|
|
||||
| `overview.occurrence` | string | `¥823,456` | 财务看板-经营一览 | 发生额/正价 |
|
||||
| `overview.occurrenceCompare` | string | `12.5%` | 同上(环比) | |
|
||||
| `overview.discount` | string | `-¥113,336` | 同上 | 总优惠 |
|
||||
| `overview.discountCompare` | string | `3.2%` | 同上(环比) | |
|
||||
| `overview.discountRate` | string | `13.8%` | 同上 | 优惠占比 |
|
||||
| `overview.discountRateCompare` | string | `1.5%` | 同上(环比) | |
|
||||
| `overview.confirmedRevenue` | string | `¥710,120` | 同上 | 成交/确认收入 |
|
||||
| `overview.confirmedCompare` | string | `8.7%` | 同上(环比) | |
|
||||
| `overview.cashIn` | string | `¥698,500` | 同上 | 实收/现金流入 |
|
||||
| `overview.cashInCompare` | string | `5.3%` | 同上(环比) | |
|
||||
| `overview.cashOut` | string | `¥472,300` | 同上 | 现金支出 |
|
||||
| `overview.cashOutCompare` | string | `2.1%` | 同上(环比) | |
|
||||
| `overview.cashBalance` | string | `¥226,200` | 同上 | 现金结余 |
|
||||
| `overview.cashBalanceCompare` | string | `15.2%` | 同上(环比) | |
|
||||
| `overview.balanceRate` | string | `32.4%` | 同上 | 结余率 |
|
||||
| `overview.balanceRateCompare` | string | `3.8%` | 同上(环比) | |
|
||||
|
||||
|
||||
### 1.2 预收资产 `recharge`
|
||||
|
||||
| 字段 | 类型 | 当前 Mock 值 | 联调替换 API | 备注 |
|
||||
|------|------|-------------|-------------|------|
|
||||
| `recharge.actualIncome` | string | `¥352,800` | 财务看板-预收资产 | 储值卡充值实收 |
|
||||
| `recharge.actualCompare` | string | `18.5%` | 同上(环比) | |
|
||||
| `recharge.firstCharge` | string | `¥188,500` | 同上 | 首充 |
|
||||
| `recharge.firstChargeCompare` | string | `12.3%` | 同上(环比) | |
|
||||
| `recharge.renewCharge` | string | `¥164,300` | 同上 | 续费 |
|
||||
| `recharge.renewChargeCompare` | string | `8.7%` | 同上(环比) | |
|
||||
| `recharge.consumed` | string | `¥238,200` | 同上 | 消耗 |
|
||||
| `recharge.consumedCompare` | string | `5.2%` | 同上(环比) | |
|
||||
| `recharge.cardBalance` | string | `¥642,600` | 同上 | 储值卡总余额 |
|
||||
| `recharge.cardBalanceCompare` | string | `11.4%` | 同上(环比) | |
|
||||
| `recharge.allCardBalance` | string | `¥586,500` | 同上 | 全类别会员卡余额合计 |
|
||||
| `recharge.allCardBalanceCompare` | string | `6.2%` | 同上(环比) | |
|
||||
|
||||
#### 赠送卡明细 `recharge.giftRows`(3 行 × 7 字段 = 21 个值)
|
||||
|
||||
| 行 | label | total | wine | table | coupon | 各字段均含 Compare |
|
||||
|----|-------|-------|------|-------|--------|-------------------|
|
||||
| 0 | 新增 | ¥108,600 | ¥43,200 | ¥54,100 | ¥11,300 | 9.8%/11.2%/8.5%/6.3% |
|
||||
| 1 | 消费 | ¥75,800 | ¥32,100 | ¥32,800 | ¥10,900 | 7.2%/8.1%/6.5%/5.8% |
|
||||
| 2 | 余额 | ¥243,900 | ¥118,500 | ¥109,200 | ¥16,200 | 4.5%/5.2%/3.8%/2.5% |
|
||||
|
||||
> 联调 API:财务看板-预收资产-赠送卡统计
|
||||
|
||||
### 1.3 应计收入确认 `revenue`
|
||||
|
||||
#### 收入结构 `revenue.structureRows`(9 行 × 5~6 字段)
|
||||
|
||||
| id | name | desc | amount | discount | booked | bookedCompare | isSub |
|
||||
|----|------|------|--------|----------|--------|---------------|-------|
|
||||
| `table` | 开台与包厢 | - | ¥358,600 | -¥45,200 | ¥313,400 | 9.2% | false |
|
||||
| `area-a` | A区 | - | ¥118,200 | -¥11,600 | ¥106,600 | 12.1% | true |
|
||||
| `area-b` | B区 | - | ¥95,800 | -¥11,200 | ¥84,600 | 8.5% | true |
|
||||
| `area-c` | C区 | - | ¥72,600 | -¥11,100 | ¥61,500 | 6.3% | true |
|
||||
| `team` | 团建区 | - | ¥48,200 | -¥6,800 | ¥41,400 | 5.8% | true |
|
||||
| `mahjong` | 麻将区 | - | ¥23,800 | -¥4,500 | ¥19,300 | -2.1% | true |
|
||||
| `coach-basic` | 助教 | 基础课 | ¥232,500 | - | ¥232,500 | 15.3% | false |
|
||||
| `coach-incentive` | 助教 | 激励课 | ¥112,800 | - | ¥112,800 | 8.2% | false |
|
||||
| `food` | 食品酒水 | - | ¥119,556 | -¥68,136 | ¥51,420 | 6.5% | false |
|
||||
|
||||
> 联调 API:财务看板-应计收入-收入结构
|
||||
|
||||
#### 项目正价 `revenue.priceItems`(4 项)
|
||||
|
||||
| name | value | compare |
|
||||
|------|-------|---------|
|
||||
| 开台消费 | ¥358,600 | 9.2% |
|
||||
| 酒水商品 | ¥186,420 | 18.5% |
|
||||
| 包厢费用 | ¥165,636 | 12.1% |
|
||||
| 助教服务 | ¥112,800 | 15.3% |
|
||||
|
||||
#### 发生额合计
|
||||
|
||||
| 字段 | 值 |
|
||||
|------|-----|
|
||||
| `revenue.totalOccurrence` | ¥823,456 |
|
||||
| `revenue.totalOccurrenceCompare` | 12.5% |
|
||||
|
||||
#### 优惠扣减 `revenue.discountItems`(4 项)
|
||||
|
||||
| name | desc | value | compare |
|
||||
|------|------|-------|---------|
|
||||
| 团购优惠 | - | -¥56,200 | 5.2% |
|
||||
| 手动调整 + 大客户优惠 | - | -¥34,800 | 3.1% |
|
||||
| 赠送卡抵扣 | 台桌卡+酒水卡+抵用券 | -¥22,336 | 8.6% |
|
||||
| 其他优惠 | 免单+抹零 | -¥0 | (空) |
|
||||
|
||||
#### 成交收入合计
|
||||
|
||||
| 字段 | 值 |
|
||||
|------|-----|
|
||||
| `revenue.confirmedTotal` | ¥710,120 |
|
||||
| `revenue.confirmedTotalCompare` | 8.7% |
|
||||
|
||||
#### 收款渠道 `revenue.channelItems`(3 项)
|
||||
|
||||
| name | desc | value | compare |
|
||||
|------|------|-------|---------|
|
||||
| 储值卡结算冲销 | - | ¥238,200 | 11.2% |
|
||||
| 现金/线上支付 | - | ¥345,800 | 7.8% |
|
||||
| 团购核销确认收入 | 团购成交价 | ¥126,120 | 5.3% |
|
||||
|
||||
> 联调 API:财务看板-应计收入-损益链
|
||||
|
||||
### 1.4 现金流入 `cashflow`
|
||||
|
||||
#### 消费收入 `cashflow.consumeItems`(3 项)
|
||||
|
||||
| name | desc | value | compare | isDown |
|
||||
|------|------|-------|---------|--------|
|
||||
| 纸币现金 | 柜台现金收款 | ¥85,600 | 12.3% | true |
|
||||
| 线上收款 | 微信/支付宝/刷卡 已扣除平台服务费 | ¥260,200 | 8.5% | false |
|
||||
| 团购平台 | 美团/抖音回款 已扣除平台服务费 | ¥126,120 | 15.2% | false |
|
||||
|
||||
#### 充值收入 `cashflow.rechargeItems`(1 项)
|
||||
|
||||
| name | desc | value | compare |
|
||||
|------|------|-------|---------|
|
||||
| 会员充值到账 | 首充/续费实收 | ¥352,800 | 18.5% |
|
||||
|
||||
#### 合计
|
||||
|
||||
| 字段 | 值 |
|
||||
|------|-----|
|
||||
| `cashflow.total` | ¥824,720 |
|
||||
| `cashflow.totalCompare` | 10.2% |
|
||||
|
||||
> 联调 API:财务看板-现金流入
|
||||
|
||||
### 1.5 现金流出 `expense`
|
||||
|
||||
#### 进货与运营 `expense.operationItems`(3 项)
|
||||
|
||||
| name | value | compare | isDown |
|
||||
|------|-------|---------|--------|
|
||||
| 食品饮料 | ¥108,200 | 4.5% | false |
|
||||
| 耗材 | ¥21,850 | 2.1% | true |
|
||||
| 报销 | ¥10,920 | 6.8% | false |
|
||||
|
||||
#### 固定支出 `expense.fixedItems`(4 项)
|
||||
|
||||
| name | value | compare | isFlat |
|
||||
|------|-------|---------|--------|
|
||||
| 房租 | ¥125,000 | 持平 | true |
|
||||
| 水电 | ¥24,200 | 3.2% | false |
|
||||
| 物业 | ¥11,500 | 持平 | true |
|
||||
| 人员工资 | ¥112,000 | 持平 | true |
|
||||
|
||||
#### 助教薪资 `expense.coachItems`(4 项)
|
||||
|
||||
| name | value | compare | isDown |
|
||||
|------|-------|---------|--------|
|
||||
| 基础课分成 | ¥116,250 | 8.2% | false |
|
||||
| 激励课分成 | ¥23,840 | 5.6% | false |
|
||||
| 充值提成 | ¥12,640 | 12.3% | false |
|
||||
| 额外奖金 | ¥11,500 | 3.1% | true |
|
||||
|
||||
#### 平台服务费 `expense.platformItems`(3 项)
|
||||
|
||||
| name | value | compare |
|
||||
|------|-------|---------|
|
||||
| 汇来米 | ¥10,680 | 1.5% |
|
||||
| 美团 | ¥11,240 | 2.8% |
|
||||
| 抖音 | ¥10,580 | 3.5% |
|
||||
|
||||
#### 合计
|
||||
|
||||
| 字段 | 值 |
|
||||
|------|-----|
|
||||
| `expense.total` | ¥600,400 |
|
||||
| `expense.totalCompare` | 2.1% |
|
||||
|
||||
> 联调 API:财务看板-现金流出
|
||||
|
||||
### 1.6 助教分析 `coachAnalysis`
|
||||
|
||||
#### 基础课 `coachAnalysis.basic`
|
||||
|
||||
| 字段 | 值 | 环比 |
|
||||
|------|-----|------|
|
||||
| totalPay | ¥232,500 | 15.3% |
|
||||
| totalShare | ¥116,250 | 15.3% |
|
||||
| avgHourly | ¥25/h | 4.2% |
|
||||
|
||||
明细行(4 行 × 7 字段):
|
||||
|
||||
| level | pay | payCompare | share | shareCompare | hourly | hourlyCompare | 特殊标记 |
|
||||
|-------|-----|------------|-------|--------------|--------|---------------|----------|
|
||||
| 初级 | ¥68,600 | 12.5% | ¥34,300 | 12.5% | ¥20/h | 持平 | hourlyFlat |
|
||||
| 中级 | ¥82,400 | 18.2% | ¥41,200 | 18.2% | ¥25/h | 8.7% | |
|
||||
| 高级 | ¥57,800 | 14.6% | ¥28,900 | 14.6% | ¥30/h | 持平 | hourlyFlat |
|
||||
| 星级 | ¥23,700 | 3.2% ↓ | ¥11,850 | 3.2% ↓ | ¥35/h | 持平 | payDown, shareDown, hourlyFlat |
|
||||
|
||||
#### 激励课 `coachAnalysis.incentive`
|
||||
|
||||
| 字段 | 值 | 环比 |
|
||||
|------|-----|------|
|
||||
| totalPay | ¥112,800 | 8.2% |
|
||||
| totalShare | ¥33,840 | 8.2% |
|
||||
| avgHourly | ¥15/h | 2.1% |
|
||||
|
||||
明细行(4 行 × 7 字段):
|
||||
|
||||
| level | pay | payCompare | share | shareCompare | hourly | hourlyCompare | 特殊标记 |
|
||||
|-------|-----|------------|-------|--------------|--------|---------------|----------|
|
||||
| 初级 | ¥32,400 | 6.8% | ¥9,720 | 6.8% | ¥12/h | 持平 | hourlyFlat |
|
||||
| 中级 | ¥38,600 | 10.5% | ¥11,580 | 10.5% | ¥15/h | 5.2% | |
|
||||
| 高级 | ¥28,200 | 7.3% | ¥8,460 | 7.3% | ¥18/h | 持平 | hourlyFlat |
|
||||
| 星级 | ¥13,600 | 2.1% ↓ | ¥4,080 | 2.1% ↓ | ¥22/h | 持平 | payDown, shareDown, hourlyFlat |
|
||||
|
||||
> 联调 API:财务看板-助教分析
|
||||
|
||||
---
|
||||
|
||||
## 二、硬编码数据
|
||||
|
||||
| 字段 | 当前值 | 应改为 | 风险 | 所在位置 |
|
||||
|------|--------|--------|------|----------|
|
||||
| `timeOptions` | 8 个固定选项(本月/上月/本周/上周/前3月/本季/上季/近6月) | 前端常量或后端配置 | **低** | data.timeOptions |
|
||||
| `areaOptions` | 7 个固定选项(全部/大厅/A区/B区/C区/麻将房/团建房) | **API 获取门店区域列表** | **高** | data.areaOptions |
|
||||
| `tocItems` | 6 个目录项(emoji + title + sectionId) | 前端常量(合理) | **低** | data.tocItems |
|
||||
| `tipContents` | 13 个指标解释(title + content) | 前端常量或后端配置 | **低** | 模块级常量 |
|
||||
| `_sectionDescs` | 6 条板块描述文案 | 前端常量(合理) | **低** | 实例属性 |
|
||||
| `selectedTime` | `'month'` | 默认值合理,但需与 API 参数对齐 | **中** | data.selectedTime |
|
||||
| `selectedTimeText` | `'本月'` | 同上 | **低** | data.selectedTimeText |
|
||||
| `selectedArea` | `'all'` | 默认值合理 | **低** | data.selectedArea |
|
||||
| `selectedAreaText` | `'全部区域'` | 同上 | **低** | data.selectedAreaText |
|
||||
| `compareEnabled` | `false` | 默认值合理 | **低** | data.compareEnabled |
|
||||
| `pageState` | `'normal'` | 联调后应初始为 `'loading'` | **高** | data.pageState |
|
||||
| Tab 导航 `data-tab` | `finance/customer/coach` 三个固定值 | 前端常量(合理) | **低** | WXML |
|
||||
| 导航路径 | `/pages/board-customer/board-customer` 等 | 前端常量(合理) | **低** | onTabChange |
|
||||
| `onPullDownRefresh` | `setTimeout 500ms` 后停止 | 联调后应触发数据刷新 | **高** | onPullDownRefresh |
|
||||
| AI 洞察文案 | 3 行硬编码文案(优惠率Top/差异最大/建议关注) | **API 返回或 AI 生成** | **高** | WXML 内联 |
|
||||
| 平台服务费说明 | "服务费在流水流入时,平台已经扣除。不产生支出流水。" | 前端常量(合理) | **低** | WXML 内联 |
|
||||
| 板块头描述文案 | 各板块 desc(如"快速了解收入与现金流的整体健康度") | 前端常量(合理) | **低** | WXML 内联 |
|
||||
| `filter-dropdown label` | `"本月"` / `"全部区域"` | 应绑定 `selectedTimeText` / `selectedAreaText` | **中** | WXML 属性 |
|
||||
| 激励课表格 | WXML 中只渲染了合计行,缺少明细行 `wx:for` | 需补齐明细行渲染 | **中** | WXML 板块6 |
|
||||
|
||||
---
|
||||
|
||||
## 三、已对接 API
|
||||
|
||||
**无。** 页面当前零 API 调用。
|
||||
|
||||
唯一的 `import` 是 `getRandomAiColor`(纯前端工具函数,与数据无关)。
|
||||
|
||||
文件头注释明确标注:
|
||||
```
|
||||
// TODO: 联调时替换 mock 数据为真实 API 调用
|
||||
```
|
||||
|
||||
`onShow` 中也有注释:
|
||||
```
|
||||
// TODO: 联调时在此刷新看板数据
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、前端计算/派生数据
|
||||
|
||||
| 字段 | 计算逻辑 | 说明 |
|
||||
|------|----------|------|
|
||||
| `aiColorClass` | `getRandomAiColor().className` | onLoad 时随机选取 AI 配色 |
|
||||
| `stickyHeaderVisible` | 滚动方向 + scrollTop 阈值判断 | 下滑 >80px 显示,上滑隐藏 |
|
||||
| `stickyHeaderEmoji` / `Title` / `Desc` | 从 `tocItems[currentIdx]` + `_sectionDescs[idx]` 映射 | 吸顶头内容 |
|
||||
| `currentSectionIndex` | 滚动位置与 `_sectionTops` 数组比较 | 当前所在板块索引 |
|
||||
| `_sectionTops` | `wx.createSelectorQuery` 获取各 section 的 top | 缓存的 DOM 位置 |
|
||||
| `selectedTimeText` | 从 `timeOptions` 中按 `value` 查找 `text` | 筛选文案 |
|
||||
| `selectedAreaText` | 从 `areaOptions` 中按 `value` 查找 `text` | 筛选文案 |
|
||||
| `tocVisible` / `tipVisible` | 用户交互切换 | UI 状态 |
|
||||
|
||||
---
|
||||
|
||||
## 五、路由参数
|
||||
|
||||
**无。** `onLoad()` 未读取 `options` 参数。
|
||||
|
||||
---
|
||||
|
||||
## 六、WXML 硬编码文案(业务数据)
|
||||
|
||||
| 位置 | 内容 | 类型 | 处理建议 |
|
||||
|------|------|------|----------|
|
||||
| AI 洞察区域 第1行 | `优惠率Top:团购(5.2%) / 大客户(3.1%) / 赠送卡(2.5%)` | **业务数据** | 应从 API 获取 |
|
||||
| AI 洞察区域 第2行 | `差异最大:酒水(+18%) / 台桌(-5%) / 包厢(+12%)` | **业务数据** | 应从 API 获取 |
|
||||
| AI 洞察区域 第3行 | `建议关注:充值高但消耗低,会员活跃度需提升` | **业务数据** | 应从 API/AI 生成 |
|
||||
| 平台服务费说明 | `服务费在流水流入时,平台已经扣除。不产生支出流水。` | UI 说明文案 | 可保留为前端常量 |
|
||||
|
||||
---
|
||||
|
||||
## 七、联调 TODO
|
||||
|
||||
### 高优先级(P0 — 阻塞联调)
|
||||
|
||||
- [ ] **设计并对接财务看板主 API**:一次请求返回全部板块数据(overview / recharge / revenue / cashflow / expense / coachAnalysis),或按板块拆分为 6 个接口
|
||||
- [ ] **替换 `data` 中全部内联 mock**:将 120+ 个字段从 API 响应填充
|
||||
- [ ] **`pageState` 初始值改为 `'loading'`**:onLoad/onShow 发起请求,成功→`'normal'`,失败→`'error'`,空数据→`'empty'`
|
||||
- [ ] **`onPullDownRefresh` 对接数据刷新**:替换当前的 `setTimeout 500ms` 空操作
|
||||
- [ ] **`areaOptions` 从 API 获取**:当前硬编码的区域列表(大厅/A区/B区/C区/麻将房/团建房)应从门店配置接口动态获取
|
||||
|
||||
### 中优先级(P1 — 影响数据准确性)
|
||||
|
||||
- [ ] **AI 洞察文案从 API 获取**:当前 WXML 中 3 行硬编码的洞察文案应由后端计算或 AI 生成
|
||||
- [ ] **`filter-dropdown` 的 `label` 属性绑定动态值**:当前硬编码 `"本月"` / `"全部区域"`,应改为 `"{{selectedTimeText}}"` / `"{{selectedAreaText}}"`
|
||||
- [ ] **激励课明细行补齐**:WXML 板块6 激励课部分只渲染了合计行,缺少 `wx:for="{{coachAnalysis.incentive.rows}}"` 的明细行循环
|
||||
- [ ] **时间/区域筛选参数传递给 API**:`onTimeChange` / `onAreaChange` 变更后需重新请求数据
|
||||
- [ ] **环比开关联动 API**:`toggleCompare` 切换后,若环比数据需要额外请求,需对接
|
||||
|
||||
### 低优先级(P2 — 优化项)
|
||||
|
||||
- [ ] **`tipContents` 指标解释考虑后端配置化**:当前 13 条解释硬编码在前端,如需动态更新可改为接口
|
||||
- [ ] **`timeOptions` 考虑后端配置化**:如需支持自定义时间范围
|
||||
- [ ] **加载态优化**:当前使用 toast 浮层,可考虑骨架屏
|
||||
- [ ] **错误态重试**:`onRetry` 当前只是重置 `pageState`,需对接真实重新请求
|
||||
|
||||
### 数据口径注意事项(参考 DWD-DOC 标杆文档)
|
||||
|
||||
- [ ] **助教费用拆分**:API 返回的助教数据需区分 `assistant_pd_money`(陪打/基础课)和 `assistant_cx_money`(超休/激励课),禁止使用笼统的 `service_fee`
|
||||
- [ ] **储值卡字段命名**:API 应使用 `balance_pay`(总额)、`recharge_card_pay`(现金充值卡)、`gift_card_pay`(赠送卡),与 DWS 层一致
|
||||
- [ ] **支付渠道恒等式**:`balance_amount = recharge_card_amount + gift_card_amount`,前端展示需校验
|
||||
- [ ] **折扣互斥**:`discount_manual`(大客户优惠)与 `discount_other` 互斥,两者之和 = `adjust_amount`
|
||||
- [ ] **consume_money 禁止直接用于计算**:前端展示的发生额应使用 `items_sum` 口径
|
||||
142
docs/miniprogram-dev/api-audit/chat-history.md
Normal file
142
docs/miniprogram-dev/api-audit/chat-history.md
Normal file
@@ -0,0 +1,142 @@
|
||||
# chat-history 页面数据来源排查
|
||||
|
||||
> 排查日期:2026-03-18
|
||||
> 页面路径:pages/chat-history/chat-history
|
||||
> 排查范围:chat-history.ts / .wxml / .wxss / .json + 引用的 mock-data.ts / sort.ts / time.ts
|
||||
|
||||
## 概览
|
||||
|
||||
| 分类 | 数量 | 说明 |
|
||||
|------|------|------|
|
||||
| A. Mock 数据 | 5 字段 | 全部列表数据来自 `mockChatHistory`,无真实 API |
|
||||
| B. 硬编码数据 | 5 处 | `ICON_GRADIENTS` 配色、延迟常量、回退值、跳转路径 |
|
||||
| C. 已对接 API | 0 | 页面尚未对接任何后端接口 |
|
||||
| D. 前端计算/派生 | 4 字段 | `timeLabel`、`iconGradient`、`pageState`、`statusBarHeight` |
|
||||
| E. 路由参数 | 0 | 本页无接收路由参数(跳转时传出 `historyId`) |
|
||||
| F. WXML 硬编码文案 | 6 处 | 加载/错误/空态/底部提示等 UI 文案 |
|
||||
|
||||
---
|
||||
|
||||
## 一、Mock 数据(来自 mock-data.ts)
|
||||
|
||||
数据源:`import { mockChatHistory } from '../../utils/mock-data'`
|
||||
类型定义:`ChatHistoryItem`
|
||||
|
||||
| # | 字段 | 类型 | Mock 值示例 | 说明 |
|
||||
|---|------|------|-------------|------|
|
||||
| 1 | `id` | `string` | `'chat-001'` | 对话记录唯一 ID |
|
||||
| 2 | `title` | `string` | `'张伟消费分析'` | 对话标题 |
|
||||
| 3 | `lastMessage` | `string` | `'已记录。下次回访张伟时...'` | 最后一条消息摘要 |
|
||||
| 4 | `timestamp` | `string` | `'2026-03-05T14:02:03+08:00'` | ISO 8601 时间戳 |
|
||||
| 5 | `customerName` | `string?` | `'张伟'` / `undefined` | 可选,关联客户名 |
|
||||
|
||||
Mock 数据条数:4 条(`mockChatHistory` 数组长度)
|
||||
|
||||
---
|
||||
|
||||
## 二、硬编码数据
|
||||
|
||||
| # | 位置 | 值 | 说明 |
|
||||
|---|------|-----|------|
|
||||
| 1 | `chat-history.ts` 顶部常量 `ICON_GRADIENTS` | 6 组 CSS 渐变色 | VI 规范 §6.2 AI 图标配色系统(indigo/purple/red/orange/yellow/blue) |
|
||||
| 2 | `chat-history.ts` → `loadData()` | `setTimeout(..., 400)` | 模拟网络延迟 400ms,联调时需替换为真实异步调用 |
|
||||
| 3 | `chat-history.ts` → `onPullDownRefresh()` | `setTimeout(() => wx.stopPullDownRefresh(), 600)` | 下拉刷新 600ms 后停止,联调时需与真实请求回调绑定 |
|
||||
| 4 | `chat-history.ts` → `onLoad()` | `sysInfo.statusBarHeight \|\| 44` | 状态栏高度回退值 44(当前 WXML 中未消费该值) |
|
||||
| 5 | `chat-history.ts` → `onItemTap()` | `'/pages/chat/chat?historyId=' + id` | 跳转路径硬编码 |
|
||||
|
||||
---
|
||||
|
||||
## 三、已对接 API
|
||||
|
||||
**无。** 页面当前 0 个真实 API 调用。
|
||||
|
||||
代码中有明确 TODO 注释:
|
||||
```typescript
|
||||
// TODO: 替换为真实 API 调用
|
||||
const sorted = sortByTimestamp(mockChatHistory)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、前端计算/派生数据
|
||||
|
||||
| # | 字段 | 计算逻辑 | 依赖 |
|
||||
|---|------|----------|------|
|
||||
| 1 | `timeLabel` | `formatRelativeTime(item.timestamp)` | `time.ts` → 将 ISO 时间戳转为「刚刚/N分钟前/N小时前/N天前/MM-DD/YYYY-MM-DD」 |
|
||||
| 2 | `iconGradient` | `ICON_GRADIENTS[Math.floor(Math.random() * ICON_GRADIENTS.length)]` | 从 6 色渐变数组中随机取一个(每次渲染结果不同) |
|
||||
| 3 | `pageState` | 状态机:`'loading'` → `'normal'` / `'empty'` / `'error'` | 根据 `list.length === 0` 判断空态,`catch` 进入错误态 |
|
||||
| 4 | `statusBarHeight` | `wx.getWindowInfo().statusBarHeight \|\| 44` | 系统 API 获取(当前 WXML 未消费此值) |
|
||||
|
||||
---
|
||||
|
||||
## 五、路由参数
|
||||
|
||||
**本页无接收路由参数。** `onLoad()` 未读取 `options` 参数。
|
||||
|
||||
传出参数(跳转时):
|
||||
|
||||
| 目标页 | 参数 | 来源 |
|
||||
|--------|------|------|
|
||||
| `/pages/chat/chat` | `historyId={item.id}` | 列表项的 `id` 字段(当前为 Mock) |
|
||||
|
||||
---
|
||||
|
||||
## 六、WXML 硬编码文案
|
||||
|
||||
| # | 位置 | 文案 | 说明 |
|
||||
|---|------|------|------|
|
||||
| 1 | 加载态 toast | `加载中...` | loading 浮层文案 |
|
||||
| 2 | 错误态 emoji | `😵` | 错误图标(emoji) |
|
||||
| 3 | 错误态文案 | `加载失败,请重试` | 错误提示 |
|
||||
| 4 | 错误态按钮 | `重新加载` | 重试按钮文案 |
|
||||
| 5 | 空态描述 | `暂无对话记录` | `<t-empty description="暂无对话记录" />` |
|
||||
| 6 | 底部提示 | `— 已加载全部记录 —` | 列表底部 footer |
|
||||
|
||||
---
|
||||
|
||||
## 七、联调 TODO
|
||||
|
||||
### 7.1 必须替换的 Mock 数据
|
||||
|
||||
| 优先级 | 改动项 | 当前状态 | 联调目标 |
|
||||
|--------|--------|----------|----------|
|
||||
| P0 | `mockChatHistory` → 真实 API | `loadData()` 中直接引用 mock 数组 | 调用后端 `GET /api/chat/history` 或类似接口,返回 `ChatHistoryItem[]` |
|
||||
| P0 | 移除 `setTimeout(…, 400)` 模拟延迟 | 硬编码 400ms | 替换为 `wx.request` / 封装的 `http.get()` 异步调用 |
|
||||
| P1 | 下拉刷新绑定真实请求 | `onPullDownRefresh` 中 600ms 后固定停止 | 在 API 回调中调用 `wx.stopPullDownRefresh()` |
|
||||
|
||||
### 7.2 需确认的接口契约
|
||||
|
||||
| 字段 | 问题 | 建议 |
|
||||
|------|------|------|
|
||||
| `id` | Mock 为 `chat-001` 格式,后端实际 ID 格式? | 确认是 UUID / 自增 ID / 其他 |
|
||||
| `title` | 对话标题由谁生成?后端还是前端? | 确认接口是否返回 `title` |
|
||||
| `lastMessage` | 是否需要截断?最大长度? | 确认接口返回的是完整消息还是摘要 |
|
||||
| `timestamp` | 后端返回格式?ISO 8601 / Unix ms? | `formatRelativeTime` 两种都支持 |
|
||||
| `customerName` | 可选字段,后端是否一定返回? | 确认接口契约中该字段的 nullable 语义 |
|
||||
|
||||
### 7.3 需关注的前端逻辑
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| `iconGradient` 随机分配 | 每次渲染/刷新颜色会变,是否需要按 `id` 哈希固定? |
|
||||
| `statusBarHeight` 未使用 | `data.statusBarHeight` 已计算但 WXML 中无消费,可能是自定义导航栏残留代码 |
|
||||
| 分页/无限滚动 | 当前一次性加载全部,数据量大时需要分页 |
|
||||
| 错误态重试 | `onRetry` 调用 `loadData()`,联调后需确认错误处理(网络超时/401/500 等) |
|
||||
| `import { mockChatHistory }` | 联调完成后需删除此 import 及 `mock-data.ts` 中对应导出 |
|
||||
|
||||
### 7.4 引用的工具函数(无需改动)
|
||||
|
||||
| 函数 | 文件 | 用途 | 联调影响 |
|
||||
|------|------|------|----------|
|
||||
| `sortByTimestamp()` | `utils/sort.ts` | 按 `timestamp` 降序排列 | 纯函数,无需改动 |
|
||||
| `formatRelativeTime()` | `utils/time.ts` | 时间戳 → 相对时间文案 | 纯函数,无需改动 |
|
||||
|
||||
### 7.5 引用的组件
|
||||
|
||||
| 组件 | 路径 | 数据依赖 |
|
||||
|------|------|----------|
|
||||
| `ai-float-button` | `/components/ai-float-button/` | 无 props 传入,组件内部自治 |
|
||||
| `dev-fab` | `/components/dev-fab/` | 开发调试用浮动按钮,上线前移除 |
|
||||
| `t-loading` | TDesign | 无数据依赖 |
|
||||
| `t-icon` | TDesign | `name`/`size`/`color` 硬编码 |
|
||||
| `t-empty` | TDesign | `description` 硬编码 |
|
||||
217
docs/miniprogram-dev/api-audit/chat.md
Normal file
217
docs/miniprogram-dev/api-audit/chat.md
Normal file
@@ -0,0 +1,217 @@
|
||||
# chat 页面数据来源排查
|
||||
|
||||
> 排查日期:2026-03-18
|
||||
> 页面路径:pages/chat/chat
|
||||
|
||||
## 概览
|
||||
|
||||
| 分类 | 数量 | 说明 |
|
||||
|------|------|------|
|
||||
| A. Mock 数据 | 4 | 来自 `mock-data.ts` 或页面内联 mock,联调时需替换为 API |
|
||||
| B. 硬编码数据 | 3 | 直接写死在 data 或方法中的值 |
|
||||
| C. 已对接 API | 0 | 当前页面无任何真实 API 调用 |
|
||||
| D. 前端计算/派生数据 | 6 | 由工具函数或页面逻辑计算得出 |
|
||||
| E. 路由参数 | 1 | 从 `onLoad(options)` 获取 |
|
||||
| F. WXML 硬编码文案 | 8 | 模板中直接写死的中文文案 |
|
||||
|
||||
---
|
||||
|
||||
## 一、Mock 数据
|
||||
|
||||
### A1. `mockChatMessages` — 历史消息列表
|
||||
|
||||
| 项目 | 内容 |
|
||||
|------|------|
|
||||
| 来源 | `import { mockChatMessages } from '../../utils/mock-data'` |
|
||||
| 类型 | `ChatMessage[]` |
|
||||
| 使用位置 | `loadMessages()` → `enrichMessages(mockChatMessages)` → `this.setData({ messages })` |
|
||||
| 涉及字段 | `id`, `role`, `content`, `timestamp`, `referenceCard`(含 `type`, `title`, `summary`, `data`) |
|
||||
| 联调替换 | 需替换为 **AI 对话历史 API**(如 `GET /api/chat/messages?customerId=xxx`),返回分页消息列表 |
|
||||
| 当前 mock 条数 | 6 条(`msg-001` ~ `msg-006`) |
|
||||
|
||||
### A2. `mockAIReplies` — AI 回复模板(页面内联 mock)
|
||||
|
||||
| 项目 | 内容 |
|
||||
|------|------|
|
||||
| 来源 | `chat.ts` 顶部常量 `const mockAIReplies = [...]` |
|
||||
| 类型 | `string[]`(3 条固定回复文案) |
|
||||
| 使用位置 | `triggerAIReply()` → `mockAIReplies[this._msgCounter % mockAIReplies.length]` |
|
||||
| 联调替换 | 需替换为 **AI 对话流式接口**(如 `POST /api/chat/send`,SSE/WebSocket 流式返回) |
|
||||
| 当前值 | `['根据数据分析,这位客户近期消费频次有所下降...', '好的,我已经记录了你的需求...', '这位客户偏好中式台球...']` |
|
||||
|
||||
### A3. `simulateStreamOutput` — 模拟流式输出
|
||||
|
||||
| 项目 | 内容 |
|
||||
|------|------|
|
||||
| 来源 | `import { simulateStreamOutput } from '../../utils/chat'` |
|
||||
| 类型 | 工具函数,逐字 `setTimeout(tick, 50)` 模拟打字效果 |
|
||||
| 使用位置 | `triggerAIReply()` 中调用,将 mock 回复逐字输出 |
|
||||
| 联调替换 | 替换为真实 SSE/WebSocket 流式读取逻辑,`callback` 接口可复用 |
|
||||
|
||||
### A4. `referenceCard` — 页面顶部引用卡片(条件构造)
|
||||
|
||||
| 项目 | 内容 |
|
||||
|------|------|
|
||||
| 来源 | `loadMessages()` 中根据 `customerId` 内联构造 |
|
||||
| 代码 | `{ title: '客户详情', summary: \`正在查看客户 ${customerId} 的相关信息\` }` |
|
||||
| 联调替换 | 需从 **客户详情 API** 获取客户名称等信息填充,或由跳转页面通过路由参数传入 |
|
||||
|
||||
---
|
||||
|
||||
## 二、硬编码数据
|
||||
|
||||
### B1. `statusBarHeight` 回退值
|
||||
|
||||
| 项目 | 内容 |
|
||||
|------|------|
|
||||
| 字段 | `statusBarHeight` |
|
||||
| 位置 | `onLoad()` → `sysInfo.statusBarHeight \|\| 44` |
|
||||
| 当前值 | `44`(回退默认值) |
|
||||
| 应获取自 | `wx.getWindowInfo().statusBarHeight`(已获取,44 仅为兜底) |
|
||||
| 风险等级 | **低** — 兜底值合理,不同机型差异可忽略 |
|
||||
|
||||
### B2. `loadMessages` 延迟时间
|
||||
|
||||
| 项目 | 内容 |
|
||||
|------|------|
|
||||
| 字段 | `setTimeout(() => { ... }, 500)` |
|
||||
| 位置 | `loadMessages()` 方法 |
|
||||
| 当前值 | `500`ms 模拟网络延迟 |
|
||||
| 联调处理 | 替换为真实 API 调用后移除 `setTimeout`,改用 API 响应回调 |
|
||||
| 风险等级 | **中** — 联调时必须移除,否则造成不必要的 500ms 延迟 |
|
||||
|
||||
### B3. `triggerAIReply` 延迟时间
|
||||
|
||||
| 项目 | 内容 |
|
||||
|------|------|
|
||||
| 字段 | `setTimeout(() => { this.triggerAIReply() }, 300)` |
|
||||
| 位置 | `onSendMessage()` 末尾 |
|
||||
| 当前值 | `300`ms 延迟触发 AI 回复 |
|
||||
| 联调处理 | 替换为发送消息 API 成功回调后触发流式接收 |
|
||||
| 风险等级 | **中** — 联调时需重构为 API 驱动的流程 |
|
||||
|
||||
---
|
||||
|
||||
## 三、已对接 API
|
||||
|
||||
**无。** 当前页面 0 个真实 API 调用。
|
||||
|
||||
> 注:项目中已存在 `utils/request.ts`(封装了 `wx.request`),联调时可直接使用。
|
||||
|
||||
---
|
||||
|
||||
## 四、前端计算/派生数据
|
||||
|
||||
### D1. `enrichMessages()` 派生字段
|
||||
|
||||
对每条消息计算以下展示字段:
|
||||
|
||||
| 派生字段 | 计算逻辑 | 来源函数 |
|
||||
|----------|----------|----------|
|
||||
| `timeLabel` | 相对时间文案(刚刚/N分钟前/MM-DD) | `formatRelativeTime(m.timestamp)` |
|
||||
| `imTimeLabel` | IM 时间格式(HH:mm / MM-DD HH:mm) | `formatIMTime(m.timestamp)` |
|
||||
| `showTimeDivider` | 是否显示时间分割线(首条必显示,间隔≥5分钟显示) | `shouldShowTimeDivider(prev, curr)` |
|
||||
| `referenceCard.dataList` | 将 `Record<string,string>` 转为 `{key,value}[]` 供 `wx:for` | `toDataList(m.referenceCard.data)` |
|
||||
|
||||
### D2. 用户发送消息时的派生字段
|
||||
|
||||
| 派生字段 | 计算逻辑 |
|
||||
|----------|----------|
|
||||
| `userMsg.timeLabel` | 固定为 `'刚刚'` |
|
||||
| `userMsg.imTimeLabel` | `formatIMTime(new Date().toISOString())` |
|
||||
| `userMsg.showTimeDivider` | `shouldShowTimeDivider(prevTs, now)` |
|
||||
| `userMsg.id` | `msg-user-${this._msgCounter}`(自增计数器) |
|
||||
| `userMsg.timestamp` | `new Date().toISOString()`(客户端当前时间) |
|
||||
|
||||
### D3. AI 回复消息的派生字段
|
||||
|
||||
同 D2 逻辑,`id` 格式为 `msg-ai-${this._msgCounter}`。
|
||||
|
||||
### D4. `pageState` 状态机
|
||||
|
||||
| 状态值 | 触发条件 |
|
||||
|--------|----------|
|
||||
| `'loading'` | `loadMessages()` 开始时 |
|
||||
| `'empty'` | 消息列表为空且无引用卡片 |
|
||||
| `'normal'` | 消息加载成功且有内容 |
|
||||
| `'error'` | `loadMessages()` 捕获异常 |
|
||||
|
||||
### D5. `isStreaming` / `streamingContent`
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `isStreaming` | AI 流式回复进行中标志,控制输入框禁用和打字指示器 |
|
||||
| `streamingContent` | 流式输出的当前内容片段 |
|
||||
|
||||
### D6. `scrollToId`
|
||||
|
||||
滚动锚点,通过 `scrollToBottom()` 方法设置为 `'scroll-bottom'`,驱动 `scroll-view` 滚动到底部。
|
||||
|
||||
---
|
||||
|
||||
## 五、路由参数
|
||||
|
||||
### E1. `customerId`
|
||||
|
||||
| 项目 | 内容 |
|
||||
|------|------|
|
||||
| 获取方式 | `onLoad(options)` → `options?.customerId \|\| ''` |
|
||||
| 用途 | 1. 传入 `loadMessages(customerId)` 加载对应客户的对话;2. 决定是否显示页面顶部引用卡片 |
|
||||
| 当前状态 | 仅用于 mock 逻辑判断,未传入任何 API |
|
||||
| 联调需求 | 作为 API 请求参数传入消息列表接口 |
|
||||
|
||||
---
|
||||
|
||||
## 六、WXML 硬编码文案
|
||||
|
||||
| 编号 | 文案 | 位置 | 说明 |
|
||||
|------|------|------|------|
|
||||
| F1 | `加载中...` | 加载态 `g-toast-loading-text` | 通用加载文案,可保留 |
|
||||
| F2 | `加载失败,请重试` | 错误态 `error-text` | 通用错误文案,可保留 |
|
||||
| F3 | `重新加载` | 错误态 `retry-btn-text` | 按钮文案,可保留 |
|
||||
| F4 | `引用内容` | 引用卡片 `reference-tag` | 标签文案,可保留 |
|
||||
| F5 | `你好,我是 AI 助手` | 空对话提示 `empty-text` | 欢迎语,建议后续从配置获取 |
|
||||
| F6 | `有什么可以帮你的?` | 空对话提示 `empty-sub` | 副标题,建议后续从配置获取 |
|
||||
| F7 | `输入消息...` | 输入框 `placeholder` | 占位文案,可保留 |
|
||||
| F8 | `AI 助手` | `chat.json` → `navigationBarTitleText` | 导航栏标题,可保留 |
|
||||
|
||||
---
|
||||
|
||||
## 七、联调 TODO
|
||||
|
||||
### 优先级 P0(阻塞联调)
|
||||
|
||||
- [ ] **替换消息列表加载**:`loadMessages()` 中 `mockChatMessages` → 调用 `GET /api/chat/messages` 接口
|
||||
- 入参:`customerId`(可选)、分页参数
|
||||
- 出参:`ChatMessage[]`
|
||||
- 移除 `setTimeout(..., 500)` 模拟延迟
|
||||
|
||||
- [ ] **替换 AI 回复流程**:`triggerAIReply()` 中 `mockAIReplies` + `simulateStreamOutput` → 调用 `POST /api/chat/send` 流式接口
|
||||
- 入参:`content`(用户消息)、`customerId`、`conversationId`
|
||||
- 出参:SSE/WebSocket 流式文本
|
||||
- 保留 `isStreaming` 状态控制和逐字渲染逻辑
|
||||
|
||||
- [ ] **替换引用卡片数据**:`loadMessages()` 中内联构造的 `referenceCard` → 从客户详情 API 或路由参数获取真实客户信息
|
||||
|
||||
### 优先级 P1(联调后优化)
|
||||
|
||||
- [ ] **消息发送 API**:`onSendMessage()` 中用户消息需先通过 API 持久化,再触发 AI 回复
|
||||
- [ ] **对话会话管理**:当前无 `conversationId` 概念,需增加会话创建/恢复逻辑
|
||||
- [ ] **消息 ID 生成**:当前使用前端自增计数器 `_msgCounter`,联调后应使用服务端返回的 ID
|
||||
|
||||
### 优先级 P2(体验优化)
|
||||
|
||||
- [ ] **错误重试**:`onRetry()` 当前仅重新调用 `loadMessages()`,需增加 toast 提示
|
||||
- [ ] **空对话欢迎语**:F5/F6 硬编码文案考虑从后端配置获取
|
||||
- [ ] **移除 `simulateStreamOutput`**:联调完成后,`utils/chat.ts` 中的模拟函数可删除(保留 `simulateStreamOutputSync` 用于测试)
|
||||
- [ ] **移除 `mockAIReplies`**:页面内联 mock 常量清理
|
||||
- [ ] **移除 `mock-data.ts` 中 `mockChatMessages` 引用**:确认其他页面不再使用后,清理 `ChatMessage` 相关 mock
|
||||
|
||||
### 依赖确认
|
||||
|
||||
| 依赖项 | 状态 | 说明 |
|
||||
|--------|------|------|
|
||||
| `utils/request.ts` | ✅ 已存在 | 封装了 `wx.request`,可直接使用 |
|
||||
| AI 对话 API(后端) | ❌ 待确认 | 需确认接口设计:REST vs WebSocket、流式协议 |
|
||||
| 客户详情 API | ❌ 待确认 | 引用卡片需要客户基本信息 |
|
||||
| 对话会话 API | ❌ 待确认 | 会话创建、历史会话列表 |
|
||||
272
docs/miniprogram-dev/api-audit/coach-detail.md
Normal file
272
docs/miniprogram-dev/api-audit/coach-detail.md
Normal file
@@ -0,0 +1,272 @@
|
||||
# coach-detail 页面数据来源排查
|
||||
|
||||
> 排查日期:2026-03-18
|
||||
> 页面路径:pages/coach-detail/coach-detail
|
||||
|
||||
## 概览
|
||||
|
||||
| 分类 | 数量 | 说明 |
|
||||
|------|------|------|
|
||||
| A. Mock 数据(页面内联) | 7 组 | 页面 .ts 文件顶部定义的大量 mock 常量 |
|
||||
| A. Mock 数据(mock-data.ts) | 1 处 | 仅引用 `mockCoaches` 做 ID→名称匹配 |
|
||||
| B. 硬编码数据 | 12 处 | perfCards 文案/sub、tierNodes、动画参数、avatar 路径等 |
|
||||
| C. 已对接 API | 0 | 全部数据均为 mock,无真实 API 调用 |
|
||||
| D. 前端计算/派生 | 8 处 | perfCards 组装、incomeTotal、perfPercent、进度条参数等 |
|
||||
| E. 路由参数 | 1 个 | `options.id` → `coachId` |
|
||||
| F. WXML 硬编码文案 | 22 处 | 各 section 标题、按钮文案、空态提示等 |
|
||||
|
||||
---
|
||||
|
||||
## 一、Mock 数据
|
||||
|
||||
### A1. 页面内联 Mock(coach-detail.ts 顶部常量)
|
||||
|
||||
| 变量名 | 类型 | 字段数 | 用途 | 联调替换目标 |
|
||||
|--------|------|--------|------|-------------|
|
||||
| `mockCoachDetail` | `CoachDetail` | 12 个顶层字段 | 助教基本信息 + 绩效 + 收入 + 备注 | `GET /api/xcx/coaches/:id` |
|
||||
| `mockVisibleTasks` | `TaskItem[]` | 6 条 | 当前可见任务列表 | `GET /api/xcx/coaches/:id/tasks` |
|
||||
| `mockHiddenTasks` | `TaskItem[]` | 3 条 | 折叠区任务列表 | 同上(分页/分组) |
|
||||
| `mockAbandonedTasks` | `AbandonedTask[]` | 2 条 | 已放弃任务 | 同上(status=abandoned) |
|
||||
| `mockTopCustomers` | `TopCustomer[]` | 20 条 | 客户关系 TOP20 | `GET /api/xcx/coaches/:id/top-customers` |
|
||||
| `mockServiceRecords` | `ServiceRecord[]` | 4 条 | 近期服务明细 | `GET /api/xcx/coaches/:id/service-records` |
|
||||
| `mockHistoryMonths` | `HistoryMonth[]` | 5 条 | 历史月度汇总 | `GET /api/xcx/coaches/:id/history` |
|
||||
|
||||
#### mockCoachDetail 字段明细
|
||||
|
||||
| 字段路径 | Mock 值 | 说明 |
|
||||
|----------|---------|------|
|
||||
| `id` | `'coach-001'` | 助教 ID |
|
||||
| `name` | `'小燕'` | 助教姓名 |
|
||||
| `avatar` | `'/assets/images/avatar-coach.png'` | 头像(硬编码路径) |
|
||||
| `level` | `'星级'` | 助教等级 |
|
||||
| `skills` | `['中🎱', '🎯斯诺克']` | 技能标签 |
|
||||
| `workYears` | `3` | 工龄 |
|
||||
| `customerCount` | `68` | 客户数 |
|
||||
| `hireDate` | `'2023-03-15'` | 入职日期 |
|
||||
| `performance.monthlyHours` | `87.5` | 本月定档业绩(小时) |
|
||||
| `performance.monthlySalary` | `6950` | 本月工资(预估) |
|
||||
| `performance.customerBalance` | `86200` | 客源储值余额 |
|
||||
| `performance.tasksCompleted` | `38` | 本月任务完成数 |
|
||||
| `performance.perfCurrent` | `80` | 绩效当前值 |
|
||||
| `performance.perfTarget` | `100` | 绩效目标值 |
|
||||
| `income.thisMonth[]` | 4 条 | 本月收入明细(基础课时费/激励课时费/充值提成/酒水提成) |
|
||||
| `income.lastMonth[]` | 4 条 | 上月收入明细 |
|
||||
| `notes[]` | 3 条 | 备注记录 |
|
||||
|
||||
#### mockVisibleTasks / mockHiddenTasks 字段明细
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `typeLabel` | 任务类型文案(高优先召回/优先召回/关系构建/客户回访) |
|
||||
| `typeClass` | 样式类名 |
|
||||
| `customerName` | 客户姓名 |
|
||||
| `noteCount` | 备注数量 |
|
||||
| `pinned` | 是否置顶 |
|
||||
| `notes[]` | 备注列表(pinned/text/date) |
|
||||
|
||||
#### mockAbandonedTasks 字段明细
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `customerName` | 客户姓名 |
|
||||
| `reason` | 放弃原因(客户拒绝/超时未响应) |
|
||||
|
||||
#### mockTopCustomers 字段明细(20 条)
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `id` | 客户 ID |
|
||||
| `name` | 客户姓名 |
|
||||
| `initial` | 姓氏首字(头像占位) |
|
||||
| `avatarGradient` | 头像渐变色 key |
|
||||
| `heartEmoji` | 爱心 emoji(❤️/💛/🤍) |
|
||||
| `score` | 亲密度评分 |
|
||||
| `scoreColor` | 评分颜色 key |
|
||||
| `serviceCount` | 服务次数 |
|
||||
| `balance` | 储值余额 |
|
||||
| `consume` | 消费金额 |
|
||||
|
||||
#### mockServiceRecords 字段明细(4 条)
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `customerId` | 客户 ID(可选) |
|
||||
| `customerName` | 客户姓名 |
|
||||
| `initial` | 姓氏首字 |
|
||||
| `avatarGradient` | 头像渐变色 key |
|
||||
| `type` | 服务类型(基础课/激励课) |
|
||||
| `typeClass` | 样式类名 |
|
||||
| `table` | 台号 |
|
||||
| `duration` | 时长 |
|
||||
| `income` | 收入 |
|
||||
| `date` | 日期 |
|
||||
| `perfHours` | 定档绩效时长(可选) |
|
||||
|
||||
#### mockHistoryMonths 字段明细(5 条)
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `month` | 月份标签(本月/上月/4月/3月/2月) |
|
||||
| `estimated` | 是否预估 |
|
||||
| `customers` | 服务客户数 |
|
||||
| `hours` | 业绩时长 |
|
||||
| `salary` | 工资 |
|
||||
| `callbackDone` | 回访完成数 |
|
||||
| `recallDone` | 召回完成数 |
|
||||
|
||||
### A2. 外部 Mock 引用(mock-data.ts)
|
||||
|
||||
| 引用 | 用途 | 代码位置 |
|
||||
|------|------|----------|
|
||||
| `mockCoaches` | `loadData()` 中通过 `mockCoaches.find(c => c.id === id)` 匹配助教 ID,取 `id` 和 `name` 覆盖 mockCoachDetail | `coach-detail.ts` L197-198 |
|
||||
|
||||
> `mockCoaches` 来自 `utils/mock-data.ts`,类型为 `CoachCard[]`,含 3 条记录(coach-001/002/003)。
|
||||
|
||||
---
|
||||
|
||||
## 二、硬编码数据
|
||||
|
||||
| 位置 | 硬编码内容 | 说明 |
|
||||
|------|-----------|------|
|
||||
| `loadData()` perfCards[0].sub | `'折算前 89.0h'` | 应由 API 返回折算前时长 |
|
||||
| `loadData()` perfCards[1].sub | `'含预估部分'` | 固定文案 |
|
||||
| `loadData()` perfCards[2].sub | `` `${detail.customerCount}位客户合计` `` | 模板拼接,customerCount 来自 mock |
|
||||
| `loadData()` perfCards[3].sub | `'覆盖 22 位客户'` | 应由 API 返回覆盖客户数 |
|
||||
| `loadData()` tierNodes | `[0, 100, 130, 160, 190, 220]` | 绩效档位节点,注释标注"Mock,实际由接口返回" |
|
||||
| `loadData()` maxHours | `220` | 进度条最大值,应与 tierNodes 联动 |
|
||||
| `data.taskStats` | `{ recall: 24, callback: 14 }` | 任务统计,应由 API 返回 |
|
||||
| 动画常量 SHINE_SPEED | `70` | 进度条动画速度 |
|
||||
| 动画常量 SPARK_DELAY_MS | `-150` | 火花延迟 |
|
||||
| 动画常量 SPARK_DUR_MS | `1400` | 火花持续时间 |
|
||||
| 动画常量 NEXT_LOOP_DELAY_MS | `400` | 下一轮延迟 |
|
||||
| 动画常量 SHINE_WIDTH_RPX / TRACK_WIDTH_RPX | `120 / 634` | 进度条尺寸参数(UI 常量,非业务数据) |
|
||||
|
||||
---
|
||||
|
||||
## 三、已对接 API
|
||||
|
||||
**无。** 当前页面 0 个真实 API 调用。
|
||||
|
||||
`loadData()` 使用 `setTimeout(500ms)` 模拟异步加载,内部全部使用 mock 数据。
|
||||
|
||||
代码中有两处 TODO 注释标记了待对接接口:
|
||||
1. `// TODO: 替换为真实 API 调用 GET /api/coaches/:id` — `loadData()` 内
|
||||
2. `// TODO: 替换为真实 API 调用 POST /api/xcx/notes` — `onNoteConfirm()` 内
|
||||
|
||||
---
|
||||
|
||||
## 四、前端计算/派生数据
|
||||
|
||||
| 字段 | 计算逻辑 | 依赖数据 |
|
||||
|------|----------|----------|
|
||||
| `perfCards[]` | 由 `detail.performance` 各字段组装为展示卡片数组 | `performance.*` (mock) |
|
||||
| `perfGap` | `perfTarget - perfCurrent` | `performance.perfTarget/perfCurrent` (mock) |
|
||||
| `perfPercent` | `Math.min(Math.round(perfCurrent/perfTarget*100), 100)` | 同上 |
|
||||
| `pbFilledPct` | `Math.min(100, Math.round(totalHours/maxHours*1000)/10)` | `performance.monthlyHours` + 硬编码 `maxHours=220` |
|
||||
| `pbCurrentTier` | 遍历 tierNodes 找到当前所在档位 | `performance.monthlyHours` + 硬编码 `tierNodes` |
|
||||
| `pbTicks[]` | `buildTicks(tierNodes, maxHours)` — 计算每个档位的 left 百分比 | 硬编码 `tierNodes` + `maxHours` |
|
||||
| `incomeTotal` | `items.reduce()` 累加当前 tab 的收入金额 | `detail.income.thisMonth/lastMonth` (mock) |
|
||||
| `currentIncome` | 根据 `incomeTab` 切换本月/上月数据 | `detail.income.*` (mock) |
|
||||
| `sortedNotes` | `sortByTimestamp(detail.notes, 'timestamp')` 按时间降序排列 | `detail.notes` (mock) |
|
||||
| `pbShineDurMs` | `calcShineDur(filledPct)` — 根据填充百分比计算动画时长 | `pbFilledPct` (派生) |
|
||||
| `pbClampedSparkPct` | `Math.max(0, Math.min(100, pbFilledPct))` | `pbFilledPct` (派生) |
|
||||
| 新备注 `newNote` | `onNoteConfirm()` 中用 `Date.now()` 生成 ID 和时间 | 用户输入 `content` + 当前时间 |
|
||||
|
||||
---
|
||||
|
||||
## 五、路由参数
|
||||
|
||||
| 参数 | 来源 | 用途 |
|
||||
|------|------|------|
|
||||
| `options.id` | `onLoad(options)` 从页面路由 query 获取 | 赋值给 `coachId`,传入 `loadData(id)` 用于匹配 mockCoaches |
|
||||
|
||||
---
|
||||
|
||||
## 六、WXML 硬编码文案
|
||||
|
||||
| 文案 | 位置 | 说明 |
|
||||
|------|------|------|
|
||||
| `加载中...` | 加载态 toast | 状态提示 |
|
||||
| `未找到助教信息` | 空态 | 状态提示 |
|
||||
| `加载失败` | 错误态 | 状态提示 |
|
||||
| `点击重试` | 错误态按钮 | 操作文案 |
|
||||
| `绩效概览` | section 标题 | 固定文案 |
|
||||
| `绩效档位进度` | 进度条标题 | 固定文案 |
|
||||
| `距下一档还差 {{perfGap}}h` | 进度条提示 | 模板 + 派生数据 |
|
||||
| `收入明细` | section 标题 | 固定文案 |
|
||||
| `本月` / `上月` | 收入 Tab | 固定文案 |
|
||||
| `预估` | 收入 Tab 标签 | 固定文案 |
|
||||
| `合计(预估)` / `合计` | 收入合计行 | 条件文案 |
|
||||
| `任务执行` | section 标题 | 固定文案 |
|
||||
| `本月完成` | 任务统计标签 | 固定文案 |
|
||||
| `回访` / `召回` + `个` | 任务统计 | 固定文案 |
|
||||
| `收起 ↑` / `展开全部 ↓` | 任务折叠按钮 | 条件文案 |
|
||||
| `客户关系 TOP20` | section 标题 | 固定文案 |
|
||||
| `近60天` | 客户关系副标题 | 固定文案(应由 API 返回时间范围) |
|
||||
| `近期服务明细` | section 标题 | 固定文案 |
|
||||
| `查看更多服务记录 →` | 服务明细底部 | 操作文案 |
|
||||
| `更多信息` | section 标题 | 固定文案 |
|
||||
| `入职日期` | 更多信息标签 | 固定文案 |
|
||||
| `备注记录` + `共 {{sortedNotes.length}} 条` | section 标题 | 固定 + 动态 |
|
||||
| `暂无备注` | 备注空态 | 状态提示 |
|
||||
| `问问助手` | 底部按钮 | 操作文案 |
|
||||
| `备注` | 底部按钮 | 操作文案 |
|
||||
| `备注已保存` | Toast 提示 | 操作反馈 |
|
||||
| `页面跳转失败` | 多处 navigateTo fail | 错误提示 |
|
||||
| 历史表头:`月份` / `服务客户` / `访/召完成` / `业绩时长` / `工资` | 历史月度表格 | 固定文案 |
|
||||
|
||||
---
|
||||
|
||||
## 七、引用组件
|
||||
|
||||
| 组件 | 路径 | 用途 |
|
||||
|------|------|------|
|
||||
| `coach-level-tag` | `/components/coach-level-tag/` | 助教等级标签(接收 `level` prop) |
|
||||
| `perf-progress-bar` | `/components/perf-progress-bar/` | 绩效进度条(接收多个动画参数 prop) |
|
||||
| `note-modal` | `/components/note-modal/` | 备注弹窗(接收 `visible`/`customerName`,emit `confirm`/`cancel`) |
|
||||
| `dev-fab` | `/components/dev-fab/` | 开发调试浮动按钮 |
|
||||
| `t-loading` | TDesign | 加载动画 |
|
||||
| `t-icon` | TDesign | 图标 |
|
||||
| `t-tag` | TDesign | 标签(已注册但 WXML 中未使用) |
|
||||
|
||||
---
|
||||
|
||||
## 八、引用工具函数
|
||||
|
||||
| 函数 | 来源 | 用途 |
|
||||
|------|------|------|
|
||||
| `sortByTimestamp` | `utils/sort.ts` | 对备注列表按 `timestamp` 字段降序排序 |
|
||||
|
||||
---
|
||||
|
||||
## 九、联调 TODO 清单
|
||||
|
||||
### 优先级 P0 — 核心数据
|
||||
|
||||
| # | 待对接接口 | 替换目标 | 涉及字段 |
|
||||
|---|-----------|----------|----------|
|
||||
| 1 | `GET /api/xcx/coaches/:id` | `mockCoachDetail` 全部字段 | 基本信息、绩效、收入、备注 |
|
||||
| 2 | `GET /api/xcx/coaches/:id/tasks` | `mockVisibleTasks` + `mockHiddenTasks` + `mockAbandonedTasks` | 任务列表(含备注) |
|
||||
| 3 | `GET /api/xcx/coaches/:id/top-customers` | `mockTopCustomers` | 客户关系 TOP20 |
|
||||
| 4 | `GET /api/xcx/coaches/:id/service-records` | `mockServiceRecords` | 近期服务明细 |
|
||||
| 5 | `GET /api/xcx/coaches/:id/history` | `mockHistoryMonths` | 历史月度汇总 |
|
||||
| 6 | `POST /api/xcx/notes` | `onNoteConfirm()` 内联构造 | 新增备注 |
|
||||
|
||||
### 优先级 P1 — 硬编码需接口化
|
||||
|
||||
| # | 硬编码项 | 当前值 | 建议 |
|
||||
|---|---------|--------|------|
|
||||
| 7 | `tierNodes` | `[0,100,130,160,190,220]` | 由接口 #1 返回绩效档位配置 |
|
||||
| 8 | `taskStats` | `{recall:24, callback:14}` | 由接口 #2 返回任务统计汇总 |
|
||||
| 9 | perfCards[0].sub `'折算前 89.0h'` | 硬编码 | 由接口 #1 返回折算前时长 |
|
||||
| 10 | perfCards[3].sub `'覆盖 22 位客户'` | 硬编码 | 由接口 #1 返回覆盖客户数 |
|
||||
| 11 | WXML `近60天` | 硬编码 | 由接口 #3 返回统计时间范围 |
|
||||
|
||||
### 优先级 P2 — 清理项
|
||||
|
||||
| # | 项目 | 说明 |
|
||||
|---|------|------|
|
||||
| 12 | 删除页面内联 mock 常量 | 7 个 mock 变量 + 接口类型定义迁移到 types |
|
||||
| 13 | 移除 `import { mockCoaches }` | 联调后不再需要 mock-data.ts 依赖 |
|
||||
| 14 | 移除 `setTimeout(500ms)` 模拟延迟 | 替换为真实异步请求 |
|
||||
| 15 | `t-tag` 组件注册但未使用 | 从 JSON 中移除或确认是否需要 |
|
||||
221
docs/miniprogram-dev/api-audit/customer-detail.md
Normal file
221
docs/miniprogram-dev/api-audit/customer-detail.md
Normal file
@@ -0,0 +1,221 @@
|
||||
# customer-detail 页面数据来源排查
|
||||
|
||||
> 排查日期:2026-03-18
|
||||
> 页面路径:pages/customer-detail/customer-detail
|
||||
|
||||
## 概览
|
||||
|
||||
| 分类 | 数量 | 说明 |
|
||||
|------|------|------|
|
||||
| A. Mock 数据 | 1 处 import + 1 处页面内联 | `mockCustomerDetail` 已 import 但未使用;`mockRecords` 内联定义 |
|
||||
| B. 硬编码数据 | 7 个数据块 | `detail`、`aiInsight`、`clues`、`coachTasks`、`favoriteCoaches`、`sortedNotes`、`consumptionRecords` |
|
||||
| C. 已对接 API | 0 | 页面无任何 `request()` 调用 |
|
||||
| D. 前端计算/派生 | 3 处 | `aiColor` 随机、`phoneVisible` 切换、`pageState` 状态机 |
|
||||
| E. 路由参数 | 0 | `onLoad(options)` 未读取任何参数 |
|
||||
| F. WXML 硬编码文案 | 14 处 | 标题、标签、按钮文字等 |
|
||||
|
||||
---
|
||||
|
||||
## 一、Mock 数据
|
||||
|
||||
### 1.1 `mockCustomerDetail`(已 import 未使用)
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 来源 | `import { mockCustomerDetail } from "../../utils/mock-data"` |
|
||||
| 状态 | **已 import 但页面内未引用**,属于死代码 |
|
||||
| mock-data.ts 中的字段 | `id`, `name`, `avatar`, `tags`, `heartScore`, `phone`, `spiIndex`, `consumptionRecords[]` |
|
||||
| 联调动作 | 删除此 import;页面 `detail` 对象改为从 API 获取 |
|
||||
|
||||
### 1.2 `mockRecords`(页面内联 mock)
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 来源 | 页面顶部 `const mockRecords: ConsumptionRecord[]` 硬编码 3 条记录 |
|
||||
| 赋值位置 | `data.consumptionRecords: mockRecords` |
|
||||
| 字段清单 | 每条记录含:`id`, `type`, `date`, `tableName`, `startTime`, `endTime`, `duration`, `tableFee`, `tableOrigPrice`, `coaches[]`(`name`, `level`, `levelColor`, `courseType`, `hours`, `perfHours`, `fee`), `foodAmount`, `foodOrigPrice`, `totalAmount`, `totalOrigPrice`, `payMethod`, `rechargeAmount` |
|
||||
| 联调 API | `GET /api/xcx/customer/{member_id}/consumption-records`(待开发) |
|
||||
| 风险 | 🔴 高 — 金额字段涉及 `tableFee`/`totalAmount` 等财务数据,需严格对齐 DWD-DOC 口径 |
|
||||
|
||||
---
|
||||
|
||||
## 二、硬编码数据
|
||||
|
||||
### 2.1 `detail` 对象(客户基础信息)
|
||||
|
||||
| 字段 | 硬编码值 | 应对接来源 | 风险 |
|
||||
|------|----------|-----------|------|
|
||||
| `id` | `"cust_001"` | API 返回 `member_id` | 🔴 高 |
|
||||
| `name` | `"王先生"` | API `dim_member.nickname` | 🔴 高 |
|
||||
| `avatarChar` | `"王"` | 前端从 `name` 截取首字 | 🟡 中 |
|
||||
| `phone` | `"13812345678"` | API `dim_member.mobile`(注意 DQ-6:需通过 `member_id` JOIN `dim_member`) | 🔴 高 |
|
||||
| `balance` | `"8,600"` | API 储值余额(`balance_pay`) | 🔴 高 |
|
||||
| `consumption60d` | `"2,800"` | API 近60天消费汇总 | 🔴 高 |
|
||||
| `idealInterval` | `"7天"` | API / AI 计算 | 🟡 中 |
|
||||
| `daysSinceVisit` | `"12天"` | API 最后到店距今天数 | 🔴 高 |
|
||||
|
||||
**联调 API**:`GET /api/xcx/customer/{member_id}/profile`(待开发)
|
||||
|
||||
### 2.2 `aiInsight` 对象(AI 智能洞察)
|
||||
|
||||
| 字段 | 硬编码值 | 应对接来源 | 风险 |
|
||||
|------|----------|-----------|------|
|
||||
| `summary` | 长文本("高价值 VIP 客户…") | AI 缓存 API `GET /api/xcx/ai-cache?cache_type=customer_insight&target_id={member_id}` | 🔴 高 |
|
||||
| `strategies[]` | 3 条策略,含 `color` + `text` | 同上,AI 缓存返回 | 🔴 高 |
|
||||
|
||||
**联调 API**:`GET /api/xcx/ai-cache`(已有路由 `xcx_ai_cache.py`,需确认 `cache_type` 枚举是否包含 `customer_insight`)
|
||||
|
||||
### 2.3 `clues` 数组(维客线索)
|
||||
|
||||
共 7 条硬编码线索:
|
||||
|
||||
| 索引 | category | text 摘要 | source | 应对接来源 | 风险 |
|
||||
|------|----------|----------|--------|-----------|------|
|
||||
| 0 | 客户基础 | 🎂 生日 3月15日 · VIP会员 · 注册2年 | 系统 | API 维客线索 | 🔴 高 |
|
||||
| 1 | 消费习惯 | 🌙 常来夜场 · 月均4-5次 | 系统 | API 维客线索 | 🔴 高 |
|
||||
| 2 | 消费习惯 | 💰 高客单价(含 detail) | 系统 | API 维客线索 | 🔴 高 |
|
||||
| 3 | 玩法偏好 | 🎱 偏爱中式 · 斯诺克进阶中 | 系统 | API 维客线索 | 🔴 高 |
|
||||
| 4 | 促销接受 | 🍷 爱点酒水套餐(含 detail) | 系统 | API 维客线索 | 🔴 高 |
|
||||
| 5 | 社交关系 | 👥 常带朋友(含 detail) | 系统 | API 维客线索 | 🔴 高 |
|
||||
| 6 | 重要反馈 | ⚠️ 想练斯诺克走位… | 小燕 | API 维客线索 | 🔴 高 |
|
||||
|
||||
**联调 API**:`GET /api/retention-clue/{member_id}?site_id=X`(已有路由 `member_retention_clue.py`)
|
||||
|
||||
### 2.4 `coachTasks` 数组(助教任务分配)
|
||||
|
||||
共 4 条硬编码任务卡片:
|
||||
|
||||
| 索引 | name | taskType | 字段清单 | 风险 |
|
||||
|------|------|----------|---------|------|
|
||||
| 0 | 小燕 | 高优先召回 | `level`, `levelColor`, `taskColor`, `bgClass`, `status`, `lastService`, `metrics[]`(近60天次数/总时长/次均时长) | 🔴 高 |
|
||||
| 1 | 泡芙 | 优先召回 | 同上 + `status: "pinned"` | 🔴 高 |
|
||||
| 2 | Amy | 关系构建 | 同上 | 🔴 高 |
|
||||
| 3 | Lucy | 客户回访 | 同上 + `status: "abandoned"` | 🔴 高 |
|
||||
|
||||
**联调 API**:`GET /api/xcx/customer/{member_id}/coach-tasks`(待开发;后端 `biz.coach_tasks` 表已存在)
|
||||
|
||||
### 2.5 `favoriteCoaches` 数组(最喜欢的助教)
|
||||
|
||||
共 2 条硬编码:
|
||||
|
||||
| 索引 | name | 字段清单 | 风险 |
|
||||
|------|------|---------|------|
|
||||
| 0 | 小燕 | `emoji`, `relationIndex: "9.2"`, `indexColor`, `bgClass`, `stats[]`(基础/激励/上课/充值) | 🔴 高 |
|
||||
| 1 | 泡芙 | 同上,`relationIndex: "7.8"` | 🔴 高 |
|
||||
|
||||
**联调 API**:`GET /api/xcx/customer/{member_id}/favorite-coaches`(待开发;需 DWS 层助教-客户关系聚合)
|
||||
|
||||
### 2.6 `sortedNotes` 数组(备注记录)
|
||||
|
||||
共 3 条硬编码备注:
|
||||
|
||||
| 索引 | tagLabel | createdAt | content 摘要 | 风险 |
|
||||
|------|----------|-----------|-------------|------|
|
||||
| 0 | 管理员 | 2026-03-05 14:30 | 对斯诺克课程感兴趣… | 🔴 高 |
|
||||
| 1 | 小燕 | 2026-02-20 16:45 | 客户反馈服务态度很好… | 🔴 高 |
|
||||
| 2 | 管理员 | 2026-02-10 10:00 | 储值活动当天即充值… | 🔴 高 |
|
||||
|
||||
**联调 API**:`GET /api/xcx/notes?target_type=member&target_id={member_id}`(已有路由 `xcx_notes.py`)
|
||||
|
||||
### 2.7 其他硬编码值
|
||||
|
||||
| 位置 | 字段 | 硬编码值 | 说明 | 风险 |
|
||||
|------|------|----------|------|------|
|
||||
| `data` | `pageState` | `"loading"` | 初始状态,正常 | 🟢 低 |
|
||||
| `data` | `phoneVisible` | `false` | UI 状态,正常 | 🟢 低 |
|
||||
| `data` | `loadingMore` | `false` | UI 状态,正常 | 🟢 低 |
|
||||
| `data` | `noteModalVisible` | `false` | UI 状态,正常 | 🟢 低 |
|
||||
| `loadDetail()` | `pageState` | 直接设为 `"normal"` | ⚠️ 无 API 调用,直接跳过 loading | 🔴 高 |
|
||||
| WXML | 手机号掩码 | `'138****5678'` | 硬编码掩码,应从 `detail.phone` 动态生成 | 🟡 中 |
|
||||
|
||||
---
|
||||
|
||||
## 三、已对接 API
|
||||
|
||||
| API | 状态 |
|
||||
|-----|------|
|
||||
| (无) | 页面当前 **零 API 调用**,`loadDetail()` 为空壳 |
|
||||
|
||||
---
|
||||
|
||||
## 四、前端计算/派生数据
|
||||
|
||||
| 字段 | 计算逻辑 | 说明 |
|
||||
|------|----------|------|
|
||||
| `aiColor` | `onLoad` 中随机从 6 色数组取值 | 用于 AI 洞察卡片配色,纯 UI 装饰 |
|
||||
| `phoneVisible` | `onTogglePhone()` 切换布尔值 | 控制手机号显示/隐藏 |
|
||||
| `pageState` | `loadDetail()` 中设为 `"normal"` | 应改为:loading → API 成功 normal / 失败 error / 空 empty |
|
||||
|
||||
---
|
||||
|
||||
## 五、路由参数
|
||||
|
||||
| 参数 | 状态 | 说明 |
|
||||
|------|------|------|
|
||||
| `options.id` / `options.memberId` | ❌ 未读取 | `onLoad(options)` 接收了 `options` 但未使用任何参数 |
|
||||
|
||||
**联调时必须**:从 `options` 中读取 `memberId`(或 `id`),作为所有 API 请求的入参。
|
||||
|
||||
---
|
||||
|
||||
## 六、WXML 中的硬编码文案
|
||||
|
||||
| 位置 | 文案 | 是否需要动态化 | 说明 |
|
||||
|------|------|---------------|------|
|
||||
| 导航栏 | `"客户详情"` | ❌ 保持 | JSON 配置 `navigationBarTitleText` |
|
||||
| loading 态 | `"加载中..."` | ❌ 保持 | 通用文案 |
|
||||
| empty 态 | `"未找到客户信息"` | ❌ 保持 | 通用文案 |
|
||||
| error 态 | `"加载失败"` | ❌ 保持 | 通用文案 |
|
||||
| error 按钮 | `"点击重试"` | ❌ 保持 | 通用文案 |
|
||||
| banner 统计标签 | `"储值余额"` / `"60天消费"` / `"理想间隔"` / `"距今到店"` | ❌ 保持 | 固定标签 |
|
||||
| AI 卡片标题 | `"AI 智能洞察"` | ❌ 保持 | 固定标题 |
|
||||
| 策略标题 | `"当前推荐策略"` | ❌ 保持 | 固定标题 |
|
||||
| 维客线索标题 | `"维客线索"` | ❌ 保持 | 固定标题 |
|
||||
| 助教任务标题 | `"助教任务分配"` / `"当前进行中"` | ❌ 保持 | 固定标题 |
|
||||
| 最喜欢助教标题 | `"最喜欢的助教"` / `"近60天"` | ❌ 保持 | 固定标题 |
|
||||
| 消费记录 | `"消费记录"` / `"商城订单"` / `"总金额"` / `"🍷 食品酒水"` | ❌ 保持 | 固定标签 |
|
||||
| 底部按钮 | `"问问助手"` / `"备注"` | ❌ 保持 | 固定文案 |
|
||||
| 空态提示 | `"暂无消费记录"` / `"暂无备注"` | ❌ 保持 | 通用文案 |
|
||||
|
||||
---
|
||||
|
||||
## 七、联调 TODO
|
||||
|
||||
### 优先级 P0(页面核心数据,阻塞上线)
|
||||
|
||||
| # | 数据块 | 当前状态 | 需要的 API | 后端现状 |
|
||||
|---|--------|---------|-----------|---------|
|
||||
| 1 | 客户基础信息 `detail` | 全部硬编码 | `GET /api/xcx/customer/{member_id}/profile` | ❌ 待开发 |
|
||||
| 2 | 消费记录 `consumptionRecords` | 内联 mock | `GET /api/xcx/customer/{member_id}/consumption-records` | ❌ 待开发 |
|
||||
| 3 | 路由参数 `memberId` | 未读取 | 从 `onLoad(options)` 读取 | — |
|
||||
| 4 | `loadDetail()` 空壳 | 直接设 normal | 改为真实 API 调用 + 状态管理 | — |
|
||||
|
||||
### 优先级 P1(重要业务数据)
|
||||
|
||||
| # | 数据块 | 当前状态 | 需要的 API | 后端现状 |
|
||||
|---|--------|---------|-----------|---------|
|
||||
| 5 | 维客线索 `clues` | 全部硬编码 | `GET /api/retention-clue/{member_id}` | ✅ 已有路由 |
|
||||
| 6 | 备注记录 `sortedNotes` | 全部硬编码 | `GET /api/xcx/notes?target_type=member&target_id={member_id}` | ✅ 已有路由 |
|
||||
| 7 | AI 智能洞察 `aiInsight` | 全部硬编码 | `GET /api/xcx/ai-cache?cache_type=customer_insight&target_id={member_id}` | ⚠️ 路由已有,需确认 cache_type |
|
||||
|
||||
### 优先级 P2(增强功能)
|
||||
|
||||
| # | 数据块 | 当前状态 | 需要的 API | 后端现状 |
|
||||
|---|--------|---------|-----------|---------|
|
||||
| 8 | 助教任务 `coachTasks` | 全部硬编码 | `GET /api/xcx/customer/{member_id}/coach-tasks` | ❌ 待开发(`biz.coach_tasks` 表已有) |
|
||||
| 9 | 最喜欢助教 `favoriteCoaches` | 全部硬编码 | `GET /api/xcx/customer/{member_id}/favorite-coaches` | ❌ 待开发(需 DWS 聚合) |
|
||||
|
||||
### 前端清理项
|
||||
|
||||
| # | 项目 | 说明 |
|
||||
|---|------|------|
|
||||
| 10 | 删除 `import { mockCustomerDetail }` | 死代码,import 后未使用 |
|
||||
| 11 | 删除 `const mockRecords` 内联 mock | 替换为 API 数据 |
|
||||
| 12 | 手机号掩码硬编码 `'138****5678'` | 改为从 `detail.phone` 动态生成掩码 |
|
||||
| 13 | `ConsumptionRecord` interface | 页面内重复定义,应移至 `utils/types.ts` 或复用 `mock-data.ts` 中的类型 |
|
||||
|
||||
### 注意事项
|
||||
|
||||
- **DQ-6 会员手机号断档**:`settlement_head.member_phone` 自 2025-12 起全为 NULL,后端 API 必须通过 `member_id` JOIN `dim_member.mobile` 获取
|
||||
- **金额口径**:消费记录中的金额字段需严格对齐 DWD-DOC 标杆文档,禁止直接使用 `consume_money`
|
||||
- **助教费用拆分**:消费记录中的 `coaches[].fee` 需区分陪打(`assistant_pd_money`)和超休(`assistant_cx_money`)
|
||||
178
docs/miniprogram-dev/api-audit/customer-service-records.md
Normal file
178
docs/miniprogram-dev/api-audit/customer-service-records.md
Normal file
@@ -0,0 +1,178 @@
|
||||
# customer-service-records 页面数据来源排查
|
||||
|
||||
> 排查日期:2026-03-18
|
||||
> 页面路径:pages/customer-service-records/customer-service-records
|
||||
|
||||
## 概览
|
||||
|
||||
| 分类 | 数量 | 说明 |
|
||||
|------|------|------|
|
||||
| A. Mock 数据 | 5 | 来自 `mock-data.ts` 的 `mockCustomers` + `mockCustomerDetail` |
|
||||
| B. 硬编码数据 | 8 | 页面 data 初始值、辅助函数内联常量 |
|
||||
| C. 已对接 API | 0 | 全部数据均为 Mock,无真实 API 调用 |
|
||||
| D. 前端计算/派生 | 12 | 月份筛选、统计汇总、格式转换等 |
|
||||
| E. 路由参数 | 1 | `customerId` / `id` |
|
||||
| F. WXML 硬编码文案 | 10 | 状态提示、标签文字、按钮文案等 |
|
||||
|
||||
---
|
||||
|
||||
## 一、Mock 数据(来自 mock-data.ts)
|
||||
|
||||
所有业务数据均通过 `loadData()` 中的 `setTimeout` 模拟异步获取,数据源为 `mock-data.ts`。
|
||||
|
||||
| # | 字段 / 数据 | Mock 来源 | 说明 |
|
||||
|---|------------|-----------|------|
|
||||
| A1 | `mockCustomers` 列表 | `mock-data.ts → mockCustomers` | 用于按 `id` 查找客户基本信息(`id`, `name`) |
|
||||
| A2 | `mockCustomerDetail` | `mock-data.ts → mockCustomerDetail` | 客户详情,提供 `name` 和 `consumptionRecords` |
|
||||
| A3 | `consumptionRecords` 数组 | `mockCustomerDetail.consumptionRecords` | 5 条消费记录,字段:`id`, `date`, `project`, `duration`, `amount`, `coachName` |
|
||||
| A4 | `customerName` / `customerInitial` | 从 `mockCustomerDetail.name` 或 `mockCustomers[].name` 取值 | 客户姓名及首字 |
|
||||
| A5 | `allRecords` | `detail.consumptionRecords` 经 `sortByTimestamp` 排序 | 全量消费记录(降序) |
|
||||
|
||||
### Mock 数据结构(ConsumptionRecord)
|
||||
|
||||
```typescript
|
||||
interface ConsumptionRecord {
|
||||
id: string // 如 'cr-001'
|
||||
date: string // 如 '2026-03-05'
|
||||
project: string // 如 '中式台球 1v1'、'会员充值'
|
||||
duration: number // 分钟数,如 90
|
||||
amount: number // 金额,如 380
|
||||
coachName: string // 助教名,如 '王芳'
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、硬编码数据
|
||||
|
||||
| # | 字段 / 数据 | 位置 | 硬编码值 | 说明 |
|
||||
|---|------------|------|----------|------|
|
||||
| B1 | `customerPhone` | `data` 初始值 | `'139****5678'` | 脱敏手机号,应从 API 获取 |
|
||||
| B2 | `customerPhoneFull` | `data` 初始值 | `'13900005678'` | 完整手机号,应从 API 获取 |
|
||||
| B3 | `relationIndex` | `data` 初始值 | `'0.85'` | 关系指数,应从 API 获取 |
|
||||
| B4 | `monthRelation` | `data` 初始值 | `'0.85'` | 月度关系指数,应从 API 获取(WXML 中绑定展示) |
|
||||
| B5 | `minYearMonth` | `data` 初始值 | `202601` | 数据起始年月,应从 API 返回的最早记录推算 |
|
||||
| B6 | `maxYearMonth` | `data` 初始值 | `202602` | 数据截止年月,应取当前月份动态计算 |
|
||||
| B7 | `currentYear` / `currentMonth` | `data` 初始值 | `2026` / `2` | 当前年月,应取系统时间动态计算 |
|
||||
| B8 | `tables` 数组 | `getTableNo()` 方法 | `['A12号台', '3号台', 'VIP1号房', '5号台', 'VIP2号房', '8号台']` | 模拟台号,应从消费记录 API 返回 |
|
||||
|
||||
---
|
||||
|
||||
## 三、已对接 API
|
||||
|
||||
| # | 接口 | 状态 |
|
||||
|---|------|------|
|
||||
| — | — | **无。页面当前 0 个真实 API 调用。** |
|
||||
|
||||
`loadData()` 方法中有明确 TODO 注释:
|
||||
```typescript
|
||||
// TODO: 替换为真实 API 调用
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、前端计算/派生数据
|
||||
|
||||
| # | 字段 / 数据 | 计算逻辑 | 来源依赖 |
|
||||
|---|------------|----------|----------|
|
||||
| D1 | `customerInitial` | `name[0] \|\| '?'` | Mock → `detail.name` |
|
||||
| D2 | `totalServiceCount` | `allRecords.length` | Mock → `consumptionRecords` |
|
||||
| D3 | `monthLabel` | `` `${currentYear}年${currentMonth}月` `` | 硬编码 `currentYear` / `currentMonth` |
|
||||
| D4 | `records`(当月记录) | `allRecords.filter(r => r.date.startsWith(monthPrefix))` 后 `.map()` 转换 | Mock → `allRecords` + 硬编码年月 |
|
||||
| D5 | `monthCount` | `monthRecords.length + '次'` | D4 筛选结果 |
|
||||
| D6 | `monthHours` | `(totalMinutes / 60).toFixed(1) + 'h'`,`totalMinutes = reduce(sum + r.duration)` | D4 筛选结果 |
|
||||
| D7 | `canPrev` / `canNext` | `yearMonth > minYearMonth` / `yearMonth < maxYearMonth` | 硬编码边界 |
|
||||
| D8 | `pageState` | 根据 `records.length === 0 && allRecords.length === 0` 判断 `'empty'` / `'normal'` | D4 + A5 |
|
||||
| D9 | `ServiceRecord.table` | `getTableNo(r.id)` — 按 id 数字取模从硬编码数组选取 | 硬编码 B8 |
|
||||
| D10 | `ServiceRecord.type` / `typeClass` | `getTypeLabel(r.project)` / `getTypeClass(r.project)` — 按 `project` 字符串 `includes` 匹配 | Mock → `project` 字段 |
|
||||
| D11 | `ServiceRecord.duration` | `parseFloat((r.duration / 60).toFixed(1))`(充值记录为 0) | Mock → `duration`(分钟) |
|
||||
| D12 | `ServiceRecord.date`(展示用) | 格式化为 `"X月X日 HH:MM - HH:MM"`,时间段由 `generateTimeRange()` 随机生成 | Mock → `date` + 随机数 |
|
||||
|
||||
### 辅助函数详情
|
||||
|
||||
| 函数 | 逻辑 | 数据来源 |
|
||||
|------|------|----------|
|
||||
| `generateTimeRange(durationMin)` | 随机起始小时 14-19,计算结束时间 | 入参 `duration`(Mock) + `Math.random()` |
|
||||
| `getTypeLabel(project)` | `includes('小组')→'小组课'`、`includes('1v1')→'基础课'`、`includes('充值')→'充值'`、`includes('斯诺克')→'斯诺克'`、默认 `'基础课'` | 硬编码映射规则 |
|
||||
| `getTypeClass(project)` | `includes('充值')→'recharge'`、`includes('小组'/'斯诺克')→'vip'`、默认 `'basic'` | 硬编码映射规则 |
|
||||
| `getTableNo(id)` | `id` 提取数字 → 取模 → 从 6 元素数组选取 | 硬编码台号数组 |
|
||||
| `sortByTimestamp(list, field)` | 按指定字段降序排序(`utils/sort.ts`) | 纯工具函数 |
|
||||
|
||||
### 固定为默认值的 ServiceRecord 字段
|
||||
|
||||
| 字段 | 固定值 | 说明 |
|
||||
|------|--------|------|
|
||||
| `durationRaw` | `0` | 折算前小时数,未实现 |
|
||||
| `isEstimate` | `false` | 是否预估金额,未实现 |
|
||||
| `drinks` | `''` | 商品/饮品描述,未实现 |
|
||||
| `income` | `r.amount`(直接透传) | 到手金额 = 消费金额,未做提成计算 |
|
||||
| `recordType` | `project.includes('充值') ? 'recharge' : 'course'` | 仅按项目名判断 |
|
||||
|
||||
---
|
||||
|
||||
## 五、路由参数
|
||||
|
||||
| # | 参数 | 取值逻辑 | 说明 |
|
||||
|---|------|----------|------|
|
||||
| E1 | `customerId` | `options?.customerId \|\| options?.id \|\| ''` | 从上级页面传入,支持两种参数名 |
|
||||
|
||||
---
|
||||
|
||||
## 六、WXML 硬编码文案
|
||||
|
||||
| # | 文案 | 位置 | 说明 |
|
||||
|---|------|------|------|
|
||||
| F1 | `"加载中..."` | 加载态 toast | 状态提示 |
|
||||
| F2 | `"暂无服务记录"` | `<t-empty>` description | 空态提示 |
|
||||
| F3 | `"加载失败,请点击重试"` | 错误态文案 | 错误提示 |
|
||||
| F4 | `"重试"` | 错误态按钮 | 按钮文案 |
|
||||
| F5 | `"服务 {{totalServiceCount}} 次"` | Banner 徽章 | 模板 + 动态数据 |
|
||||
| F6 | `"查看"` / `"复制"` | 手机号操作按钮 | 根据 `phoneVisible` 切换 |
|
||||
| F7 | `"本月服务"` / `"服务时长"` / `"关系指数"` | 月度统计标签 | 固定标签文案 |
|
||||
| F8 | `"本月暂无服务记录"` | 当月无数据提示 | 空月提示 |
|
||||
| F9 | `"— 已加载全部记录 —"` | 列表底部 | 底部提示 |
|
||||
| F10 | `"手机号码已复制"` | `onCopyPhone()` Toast | JS 中的提示文案 |
|
||||
|
||||
---
|
||||
|
||||
## 七、引用组件
|
||||
|
||||
| 组件 | 路径 | 用途 |
|
||||
|------|------|------|
|
||||
| `service-record-card` | `/components/service-record-card/` | 服务记录卡片,接收 `ServiceRecord` 各字段 |
|
||||
| `ai-float-button` | `/components/ai-float-button/` | AI 悬浮按钮,传入 `customerId` |
|
||||
| `dev-fab` | `/components/dev-fab/` | 开发调试浮动按钮 |
|
||||
| `t-loading` | TDesign | 加载动画 |
|
||||
| `t-icon` | TDesign | 图标 |
|
||||
| `t-empty` | TDesign | 空态组件 |
|
||||
|
||||
---
|
||||
|
||||
## 八、联调 TODO
|
||||
|
||||
### 需要对接的 API 清单
|
||||
|
||||
| 优先级 | API | 用途 | 替换目标 |
|
||||
|--------|-----|------|----------|
|
||||
| P0 | `GET /api/customers/{id}/service-records` | 获取客户服务记录列表(支持月份筛选、分页) | `loadData()` 中的 `mockCustomerDetail.consumptionRecords` |
|
||||
| P0 | `GET /api/customers/{id}` | 获取客户基本信息(姓名、手机号、关系指数) | `mockCustomers.find()` + `mockCustomerDetail` + 硬编码手机号 |
|
||||
| P1 | `GET /api/customers/{id}/monthly-stats` | 获取月度统计(服务次数、时长、关系指数) | 前端 `reduce` 计算的 `monthCount`、`monthHours` + 硬编码 `monthRelation` |
|
||||
|
||||
### 联调时需处理的改动点
|
||||
|
||||
| # | 改动点 | 当前状态 | 联调要求 |
|
||||
|---|--------|----------|----------|
|
||||
| T1 | 移除 `mock-data.ts` 导入 | `import { mockCustomerDetail, mockCustomers }` | 替换为 API 请求模块 |
|
||||
| T2 | `loadData()` 改为真实请求 | `setTimeout` + Mock 查找 | `wx.request` 或封装的 HTTP 客户端 |
|
||||
| T3 | 手机号从 API 获取 | 硬编码 `'139****5678'` / `'13900005678'` | API 返回脱敏 + 完整手机号(或按需请求完整号) |
|
||||
| T4 | 关系指数从 API 获取 | 硬编码 `'0.85'` | API 返回 `relationIndex` / `monthRelation` |
|
||||
| T5 | 台桌号从 API 获取 | `getTableNo()` 硬编码数组取模 | 消费记录 API 返回 `tableNo` 字段 |
|
||||
| T6 | 时间段从 API 获取 | `generateTimeRange()` 随机生成 | 消费记录 API 返回 `startTime` / `endTime` |
|
||||
| T7 | 年月边界动态计算 | 硬编码 `minYearMonth=202601` / `maxYearMonth=202602` | 从 API 返回的最早记录推算 `min`,`max` 取当前月 |
|
||||
| T8 | `currentYear` / `currentMonth` 动态化 | 硬编码 `2026` / `2` | 取 `new Date()` 当前年月 |
|
||||
| T9 | 分页加载 | `onReachBottom` 空实现 | 对接分页 API(`page` / `pageSize` 参数) |
|
||||
| T10 | `durationRaw` 字段 | 固定 `0` | API 返回折算前时长 |
|
||||
| T11 | `drinks` 字段 | 固定 `''` | API 返回商品/饮品信息 |
|
||||
| T12 | `isEstimate` 字段 | 固定 `false` | API 返回是否预估标记 |
|
||||
| T13 | `income` 字段 | 直接透传 `amount` | API 返回助教到手金额(需提成计算) |
|
||||
| T14 | 错误处理 | 无 `pageState='error'` 触发路径 | API 失败时 `setData({ pageState: 'error' })` |
|
||||
144
docs/miniprogram-dev/api-audit/login.md
Normal file
144
docs/miniprogram-dev/api-audit/login.md
Normal file
@@ -0,0 +1,144 @@
|
||||
# login 页面数据来源排查
|
||||
|
||||
> 排查日期:2026-03-18
|
||||
> 页面路径:pages/login/login
|
||||
> 涉及文件:login.ts, login.wxml, login.wxss, login.json, utils/request.ts, utils/config.ts
|
||||
|
||||
## 概览
|
||||
|
||||
| 分类 | 数量 | 说明 |
|
||||
|------|------|------|
|
||||
| A. Mock 数据 | 0 | 未引用 mock-data.ts,无内联 mock |
|
||||
| B. 硬编码数据 | 5 | 含开发模式 openid、默认高度、错误提示文案等 |
|
||||
| C. 已对接 API | 2 | dev-login + login(含 token 刷新机制) |
|
||||
| D. 前端计算/派生数据 | 3 | isDevMode 判断、按钮禁用态、错误消息映射 |
|
||||
| E. 路由参数 | 0 | onLoad 未接收任何 options 参数 |
|
||||
| F. WXML 硬编码文案 | 7 | 应用名称、功能标签、按钮文案、协议文案、底部提示 |
|
||||
|
||||
---
|
||||
|
||||
## 一、Mock 数据
|
||||
|
||||
**结论:无 Mock 数据。**
|
||||
|
||||
- `login.ts` 未 import `mock-data.ts`
|
||||
- 页面内无内联 mock 对象或假数据
|
||||
- 开发模式走 `/api/xcx/dev-login` 真实接口(非前端 mock)
|
||||
|
||||
---
|
||||
|
||||
## 二、硬编码数据
|
||||
|
||||
| # | 位置 | 代码 | 值 | 风险 | 说明 |
|
||||
|---|------|------|----|------|------|
|
||||
| B1 | login.ts L4 | `API_BASE.startsWith("http://127.0.0.1")` | `"http://127.0.0.1"` | 🟢 低 | 开发环境判断,config.ts develop 分支返回 `http://127.0.0.1:8000`,逻辑一致 |
|
||||
| B2 | login.ts L8 | `data: { agreed: false, loading: false }` | `false` | 🟢 低 | UI 初始状态,合理默认值 |
|
||||
| B3 | login.ts L10 | `statusBarHeight: 20` | `20` | 🟡 中 | 默认状态栏高度 fallback,onLoad 中会被 `wx.getWindowInfo()` 覆盖;但若 API 返回 0 或 undefined 则回退到 20px,部分机型可能不准 |
|
||||
| B4 | login.ts L39 | `data: { openid: "dev_test_openid" }` | `"dev_test_openid"` | 🟡 中 | 开发模式固定 openid,仅 develop 环境生效;但若 `isDevMode` 判断逻辑被绕过则有安全隐患 |
|
||||
| B5 | login.ts L93-98 | 错误提示 `msg` 三元表达式 | `"账号已被禁用"` / `"登录凭证无效,请重试"` / `"登录失败,请稍后重试"` | 🟢 低 | 用户友好提示,无需后端下发 |
|
||||
|
||||
---
|
||||
|
||||
## 三、已对接 API
|
||||
|
||||
### 3.1 开发模式登录 — `POST /api/xcx/dev-login`
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|----|
|
||||
| 触发条件 | `isDevMode === true`(API_BASE 以 `http://127.0.0.1` 开头) |
|
||||
| 请求方式 | `POST` |
|
||||
| 请求参数 | `{ openid: "dev_test_openid" }` |
|
||||
| needAuth | `false` |
|
||||
| 响应字段(使用到的) | `access_token`, `refresh_token`, `user_id`, `user_status` |
|
||||
| 后续处理 | 写入 globalData + Storage → 按 `user_status` 路由跳转 |
|
||||
|
||||
### 3.2 正式登录 — `POST /api/xcx/login`
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|----|
|
||||
| 触发条件 | `isDevMode === false`(trial / release 环境) |
|
||||
| 前置步骤 | `wx.login()` 获取临时 `code` |
|
||||
| 请求方式 | `POST` |
|
||||
| 请求参数 | `{ code: loginRes.code }` |
|
||||
| needAuth | `false` |
|
||||
| 响应字段(使用到的) | `access_token`, `refresh_token`, `user_id`, `user_status` |
|
||||
| 后续处理 | 同 3.1 |
|
||||
|
||||
### 3.3 Token 刷新(request.ts 内置) — `POST /api/xcx/refresh`
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|----|
|
||||
| 触发条件 | 任意需认证请求返回 401 时自动触发 |
|
||||
| 请求参数 | `{ refresh_token: <当前 refresh_token> }` |
|
||||
| 响应字段 | `access_token`, `refresh_token` |
|
||||
| 失败处理 | 清除 token → `wx.reLaunch` 跳转 login 页 |
|
||||
|
||||
### 登录成功后数据持久化
|
||||
|
||||
```
|
||||
globalData.token ← data.access_token
|
||||
globalData.refreshToken ← data.refresh_token
|
||||
globalData.authUser ← { userId: data.user_id, status: data.user_status }
|
||||
Storage: token, refreshToken, userId, userStatus
|
||||
```
|
||||
|
||||
### 登录后路由映射
|
||||
|
||||
| user_status | 跳转页面 |
|
||||
|-------------|----------|
|
||||
| `"approved"` | `/pages/task-list/task-list` |
|
||||
| `"pending"` | `/pages/reviewing/reviewing` |
|
||||
| `"new"` | `/pages/apply/apply` |
|
||||
| `"rejected"` | `/pages/no-permission/no-permission` |
|
||||
| `"disabled"` | `/pages/no-permission/no-permission` |
|
||||
| 其他/default | `/pages/apply/apply` |
|
||||
|
||||
---
|
||||
|
||||
## 四、前端计算/派生数据
|
||||
|
||||
| # | 变量/逻辑 | 来源 | 说明 |
|
||||
|---|-----------|------|------|
|
||||
| D1 | `isDevMode` | `API_BASE.startsWith("http://127.0.0.1")` | 模块顶层常量,决定走 dev-login 还是 wx.login 流程 |
|
||||
| D2 | 按钮禁用态 | WXML: `(!agreed \|\| loading) ? 'login-btn--disabled' : 'login-btn--active'` | 由 `agreed` 和 `loading` 两个 data 字段派生 |
|
||||
| D3 | 错误消息 `msg` | `err.statusCode` 三元映射 | 403→"账号已被禁用",401→"登录凭证无效",其他→"登录失败" |
|
||||
|
||||
---
|
||||
|
||||
## 五、路由参数
|
||||
|
||||
**结论:无路由参数。**
|
||||
|
||||
- `onLoad()` 未声明 `options` 参数
|
||||
- 页面作为小程序首页(入口页),不接收外部传参
|
||||
|
||||
---
|
||||
|
||||
## 六、WXML 硬编码文案
|
||||
|
||||
| # | 元素 | 文案 | 建议 |
|
||||
|---|------|------|------|
|
||||
| F1 | `<text class="app-name">` | `球房运营助手` | 应用名称,可考虑从配置读取 |
|
||||
| F2 | `<text class="app-desc">` | `为台球厅提升运营效率的内部管理工具` | 应用描述 |
|
||||
| F3 | `<text class="feature-text">` ×3 | `任务管理` / `数据看板` / `智能助手` | 功能亮点标签 |
|
||||
| F4 | `<text class="login-btn-text">` | `使用微信登录` | 按钮文案 |
|
||||
| F5 | `<text class="agreement-text">` | `我已阅读并同意` | 协议前缀 |
|
||||
| F6 | `<text class="link">` ×2 | `《用户协议》` / `《隐私政策》` | 协议链接文案(当前无跳转) |
|
||||
| F7 | `<text class="footer-tip">` | `仅限球房内部员工使用` | 底部提示 |
|
||||
|
||||
> 注:login.json 中 `"navigationBarTitleText": "登录"` 也是硬编码,但因使用 `"navigationStyle": "custom"` 自定义导航栏,系统标题栏不显示,实际无影响。
|
||||
|
||||
---
|
||||
|
||||
## 七、联调 TODO
|
||||
|
||||
| 优先级 | 项目 | 当前状态 | 待办 |
|
||||
|--------|------|----------|------|
|
||||
| ✅ 已完成 | 登录接口对接 | dev-login + login 均已对接 | — |
|
||||
| ✅ 已完成 | Token 持久化 | globalData + Storage 双写 | — |
|
||||
| ✅ 已完成 | 401 自动刷新 | request.ts 内置 refresh 机制 | — |
|
||||
| ✅ 已完成 | 状态路由 | 5 种 user_status 均有对应页面 | — |
|
||||
| 🟡 待确认 | 协议链接跳转 | `《用户协议》`和`《隐私政策》`仅为文案,无 `bindtap` 跳转 | 需补充协议页面或 webview 跳转 |
|
||||
| 🟡 待确认 | dev_test_openid 安全性 | 硬编码在前端代码中 | 确认后端 dev-login 仅在开发环境可用,生产环境应禁用该端点 |
|
||||
| 🟢 可优化 | statusBarHeight fallback | 默认 20px | 可改为 `wx.getSystemInfoSync().statusBarHeight` 更精确的 fallback |
|
||||
| 🟢 可优化 | 错误提示国际化 | 硬编码中文 | 当前仅面向内部员工,优先级低 |
|
||||
94
docs/miniprogram-dev/api-audit/my-profile.md
Normal file
94
docs/miniprogram-dev/api-audit/my-profile.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# my-profile 页面数据来源排查
|
||||
|
||||
> 排查日期:2026-03-18
|
||||
> 页面路径:pages/my-profile/my-profile
|
||||
|
||||
## 概览
|
||||
|
||||
| 分类 | 数量 | 说明 |
|
||||
|------|------|------|
|
||||
| Mock 数据 | 1 | `mockUserProfile` — 用户信息全量 mock |
|
||||
| 硬编码数据 | 5 | 路由映射、弹窗文案、菜单 key 等 |
|
||||
| 已对接 API | 0 | ⚠️ 无任何 API 调用 |
|
||||
| 前端计算/派生 | 1 | TabBar 选中态同步 |
|
||||
| 路由参数 | 0 | 无 |
|
||||
| WXML 硬编码文案 | 5 | 菜单文字、弹窗文案 |
|
||||
|
||||
## 一、Mock 数据
|
||||
|
||||
### 1.1 `mockUserProfile`(🔴 高风险 — 页面核心数据全量 mock)
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|-----|
|
||||
| 来源文件 | `utils/mock-data.ts` |
|
||||
| 导入方式 | `import { mockUserProfile } from '../../utils/mock-data'` |
|
||||
| 赋值位置 | `data.userInfo = mockUserProfile`(页面初始化) |
|
||||
| 类型定义 | `UserProfile { name, avatar, role, storeName }` |
|
||||
|
||||
**Mock 值:**
|
||||
|
||||
| 字段 | Mock 值 | 联调后应来自 |
|
||||
|------|---------|-------------|
|
||||
| `name` | `'小燕'` | API — 用户昵称 |
|
||||
| `avatar` | `'/assets/images/avatar-coach.png'` | API — 用户头像 URL |
|
||||
| `role` | `'助教'` | API — 用户角色 |
|
||||
| `storeName` | `'朗朗桌球'` | API — 所属门店名称 |
|
||||
|
||||
**影响范围:** 用户信息卡片区域(头像、姓名、角色标签、门店名)全部依赖此 mock。
|
||||
|
||||
## 二、硬编码数据
|
||||
|
||||
| # | 文件 | 位置 | 内容 | 风险等级 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | `router.ts` | MENU_ROUTE_MAP | `'chat-history'` → `'/pages/chat-history/chat-history'` | 🟢 低 | 菜单路由映射 |
|
||||
| 2 | `router.ts` | MENU_ROUTE_MAP | `'notes'` → `'/pages/notes/notes'` | 🟢 低 | 菜单路由映射 |
|
||||
| 3 | `router.ts` | MENU_ROUTE_MAP | `'settings'` → `''`(空字符串) | 🟡 中 | 设置页未实现,点击无响应 |
|
||||
| 4 | `.ts` | `onLogout()` | `title: '确认退出'`, `content: '确认退出当前账号吗?'` | 🟢 低 | 弹窗文案 |
|
||||
| 5 | `.ts` | `onLogout()` | `confirmColor: '#e34d59'` | 🟢 低 | 确认按钮颜色 |
|
||||
| 6 | `.ts` | `onLogout()` | `url: '/pages/login/login'` | 🟢 低 | 退出后跳转路径 |
|
||||
|
||||
## 三、已对接 API
|
||||
|
||||
⚠️ **无。** 本页面当前未调用任何 API。
|
||||
|
||||
代码中有两处 TODO 注释明确标注:
|
||||
- `.ts` L4: `// TODO: 联调时替换为真实 API 获取用户信息`
|
||||
- `.ts` L11: `// TODO: 联调时在此刷新用户信息`
|
||||
|
||||
**预期对接 API:**
|
||||
|
||||
| 端点(推测) | 用途 | 替换目标 |
|
||||
|--------------|------|----------|
|
||||
| `GET /api/xcx/me` 或专用 profile 接口 | 获取当前用户信息 | `mockUserProfile` |
|
||||
|
||||
## 四、前端计算/派生数据
|
||||
|
||||
| # | 数据 | 来源 | 说明 |
|
||||
|---|------|------|------|
|
||||
| 1 | TabBar `active: 'my'` | `this.getTabBar().setData()` | `onShow` 时同步 custom-tab-bar 选中态 |
|
||||
| 2 | `route` | `getMenuRoute(key)` 纯函数 | 从 `MENU_ROUTE_MAP` 查找菜单 key 对应的页面路径 |
|
||||
|
||||
## 五、路由参数
|
||||
|
||||
无。本页面为 TabBar 页面,不接收路由参数。
|
||||
|
||||
## 六、WXML 硬编码文案
|
||||
|
||||
| # | 内容 | 位置 | 建议 |
|
||||
|---|------|------|------|
|
||||
| 1 | `"备注记录"` | menu-text | 可保留 |
|
||||
| 2 | `"助手对话记录"` | menu-text | 可保留 |
|
||||
| 3 | `"退出账号"` | menu-text | 可保留 |
|
||||
| 4 | 菜单图标路径 `/assets/icons/menu-notes.svg` 等 | menu-icon image src | 🟢 低,静态资源 |
|
||||
| 5 | `visible="{{true}}"` | ai-float-button | 🟢 低,控制 AI 按钮显示 |
|
||||
|
||||
## 七、联调 TODO
|
||||
|
||||
| # | 优先级 | 事项 | 当前状态 | 说明 |
|
||||
|---|--------|------|----------|------|
|
||||
| 1 | 🔴 P0 | 替换 `mockUserProfile` 为真实 API 数据 | 未开始 | 页面核心数据全量 mock,需对接用户信息接口 |
|
||||
| 2 | 🟡 P1 | `onShow` 中添加 API 刷新逻辑 | 未开始 | 确保每次进入页面获取最新用户信息 |
|
||||
| 3 | 🟡 P1 | 确认用户信息接口字段映射 | 未开始 | `name`/`avatar`/`role`/`storeName` 需与后端字段对齐 |
|
||||
| 4 | 🟢 P2 | 移除 `mock-data.ts` 中 `mockUserProfile` 的导入 | 未开始 | API 对接完成后清理 |
|
||||
|
||||
**结论:** my-profile 页面当前处于纯 mock 状态,用户信息卡片(姓名、头像、角色、门店)全部来自硬编码 mock 数据,是联调优先级最高的待办项。菜单跳转和退出登录逻辑已实现,无需额外对接。
|
||||
57
docs/miniprogram-dev/api-audit/no-permission.md
Normal file
57
docs/miniprogram-dev/api-audit/no-permission.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# 无权限页面(no-permission)— 接口对接审计
|
||||
|
||||
> 审计时间:2026-03-18
|
||||
> 页面路径:`apps/miniprogram/miniprogram/pages/no-permission/`
|
||||
> 文件清单:`no-permission.ts` / `.wxml` / `.wxss` / `.json`
|
||||
|
||||
---
|
||||
|
||||
## 一、总览
|
||||
|
||||
| 指标 | 值 |
|
||||
|------|-----|
|
||||
| 已对接 API 数量 | 1 |
|
||||
| Mock 数据引用 | 0 |
|
||||
| 硬编码字段数 | 4 |
|
||||
| 对接就绪度 | ✅ 已对接 |
|
||||
|
||||
本页面功能简单:展示"无访问权限"提示,`onShow` 时调用 `/api/xcx/me` 查询最新用户状态,状态变化时自动跳转对应页面。核心逻辑已完成 API 对接。
|
||||
|
||||
---
|
||||
|
||||
## 二、已对接 API
|
||||
|
||||
| # | 接口 | 方法 | 用途 | 调用位置 |
|
||||
|---|------|------|------|----------|
|
||||
| 1 | `/api/xcx/me` | GET | 查询用户最新状态(status),根据状态路由跳转 | `_checkStatus()` |
|
||||
|
||||
---
|
||||
|
||||
## 三、Mock 数据
|
||||
|
||||
无。本页面未引用任何 mock 数据。
|
||||
|
||||
---
|
||||
|
||||
## 四、硬编码字段清单
|
||||
|
||||
| # | 位置 | 硬编码内容 | 类型 | 建议处理方式 |
|
||||
|---|------|-----------|------|-------------|
|
||||
| 1 | wxml `.reason-footer-value` | `厉超`(管理员姓名) | 文案 | 应从后端获取门店管理员信息,或写入配置 |
|
||||
| 2 | wxml `.reason-item` × 3 | 三条原因说明文案 | 文案 | 可保留前端硬编码(纯展示文案,无业务逻辑) |
|
||||
| 3 | wxml `.main-title` | `无访问权限` | 文案 | 可保留(固定标题) |
|
||||
| 4 | wxml `.sub-title` | 副标题说明文案 | 文案 | 可保留(固定说明) |
|
||||
|
||||
---
|
||||
|
||||
## 五、对接建议
|
||||
|
||||
1. **管理员姓名硬编码**:`厉超` 写死在 wxml 中,建议后端 `/api/xcx/me` 返回门店管理员联系信息,或新增配置接口
|
||||
2. **状态路由映射**:`_checkStatus()` 中 `approved` 状态跳转 `/pages/mvp/mvp`,该路径在当前页面目录中不存在,需确认是否为 tabBar 页面或后续新增页面
|
||||
3. **其余文案**:原因说明、标题等为纯展示文案,无需接口化,保留硬编码即可
|
||||
|
||||
---
|
||||
|
||||
## 六、结论
|
||||
|
||||
本页面已完成核心 API 对接,唯一需要关注的是管理员姓名硬编码问题。整体对接就绪度高,无需大幅改动。
|
||||
147
docs/miniprogram-dev/api-audit/notes.md
Normal file
147
docs/miniprogram-dev/api-audit/notes.md
Normal file
@@ -0,0 +1,147 @@
|
||||
# notes 页面数据来源排查
|
||||
|
||||
> 排查日期:2026-03-18
|
||||
> 页面路径:pages/notes/notes
|
||||
> 源文件:notes.ts / notes.wxml / notes.wxss / notes.json
|
||||
|
||||
## 概览
|
||||
|
||||
| 分类 | 数量 | 说明 |
|
||||
|------|------|------|
|
||||
| Mock 数据 | 6 个字段 | 全部列表数据来自 `mockNotes`,无真实 API |
|
||||
| 硬编码数据 | 5 处 | `statusBarHeight` 默认值、`setTimeout` 延迟、Modal 文案等 |
|
||||
| 已对接 API | 0 个接口 | 页面尚未对接任何后端 API |
|
||||
| 前端计算/派生 | 3 个字段 | `pageState`、`timeLabel`、`NoteDisplay` 扩展 |
|
||||
| 路由参数 | 0 个 | `onLoad` 未读取 `options` |
|
||||
| WXML 硬编码文案 | 5 处 | 加载/错误/空态/底部提示等 UI 文案 |
|
||||
|
||||
---
|
||||
|
||||
## 一、Mock 数据
|
||||
|
||||
### 1.1 数据源:`mockNotes`(mock-data.ts)
|
||||
|
||||
| 序号 | 字段 | 类型 | 说明 | 联调替换 API |
|
||||
|------|------|------|------|-------------|
|
||||
| 1 | `id` | `string` | 备注唯一 ID | `GET /api/xcx/notes` 返回 |
|
||||
| 2 | `content` | `string` | 备注正文 | 同上 |
|
||||
| 3 | `tagType` | `'customer' \| 'coach'` | 标签类型(客户/助教) | 同上 |
|
||||
| 4 | `tagLabel` | `string` | 标签文案,如"客户:王先生" | 同上 |
|
||||
| 5 | `createdAt` | `string` | 创建时间,格式 `YYYY-MM-DD HH:mm` | 同上 |
|
||||
| 6 | `score` | `number \| undefined` | 满意度评分 0-10(类型定义存在但 mock 数据未使用) | 同上 |
|
||||
|
||||
- 引用方式:`import { mockNotes } from '../../utils/mock-data'`(notes.ts 第 1 行)
|
||||
- mock 数据共 12 条,模拟 H5 原型 `notes.html` 中的备注列表
|
||||
- `Note` 接口在 mock-data.ts 中被定义了两次(第 2 次覆盖第 1 次),第 2 次定义不含 `score` 字段
|
||||
|
||||
### 1.2 Mock 调用链
|
||||
|
||||
```
|
||||
notes.ts → loadData()
|
||||
→ mockNotes.map(n => ({ ...n, timeLabel: formatRelativeTime(n.createdAt) }))
|
||||
→ setData({ notes, pageState })
|
||||
```
|
||||
|
||||
`setTimeout(…, 400)` 模拟网络延迟(notes.ts 第 28 行)。
|
||||
|
||||
---
|
||||
|
||||
## 二、硬编码数据
|
||||
|
||||
| 序号 | 字段/值 | 当前值 | 应改为 | 风险 | 所在位置 |
|
||||
|------|---------|--------|--------|------|----------|
|
||||
| 1 | `statusBarHeight` | `20`(默认值) | `wx.getWindowInfo().statusBarHeight` 已在 `onLoad` 中获取,20 仅为 fallback | 🟢 低 | notes.ts 第 17 行 |
|
||||
| 2 | `setTimeout` 延迟 | `400` ms | 删除,改为真实 API 异步调用 | 🟡 中 | notes.ts 第 28 行 |
|
||||
| 3 | Modal `title` | `'删除备注'` | 可保留(UI 文案),或走 i18n | 🟢 低 | notes.ts 第 56 行 |
|
||||
| 4 | Modal `content` | `'确定要删除这条备注吗?删除后无法恢复。'` | 可保留(UI 文案),或走 i18n | 🟢 低 | notes.ts 第 57 行 |
|
||||
| 5 | Modal `confirmColor` | `'#e34d59'` | 应统一走设计系统色值变量 | 🟢 低 | notes.ts 第 58 行 |
|
||||
|
||||
---
|
||||
|
||||
## 三、已对接 API
|
||||
|
||||
**无。** 页面当前 0 个真实 API 调用。
|
||||
|
||||
代码中的 TODO 注释(notes.ts 第 30 行):
|
||||
```
|
||||
// TODO: 替换为真实 API 调用 GET /api/xcx/notes
|
||||
```
|
||||
|
||||
### 需要对接的 API 清单
|
||||
|
||||
| 序号 | 操作 | 预期 API | 方法 | 说明 |
|
||||
|------|------|----------|------|------|
|
||||
| 1 | 加载备注列表 | `GET /api/xcx/notes` | GET | 替换 `mockNotes`,支持分页 |
|
||||
| 2 | 删除备注 | `DELETE /api/xcx/notes/{noteId}` | DELETE | 当前仅前端 filter 删除,未持久化 |
|
||||
| 3 | 下拉刷新 | 同接口 1 | GET | `onPullDownRefresh` 已有骨架 |
|
||||
|
||||
---
|
||||
|
||||
## 四、前端计算/派生数据
|
||||
|
||||
| 序号 | 字段 | 计算逻辑 | 所在位置 |
|
||||
|------|------|----------|----------|
|
||||
| 1 | `pageState` | 根据数据加载结果派生:`'loading'` → `'normal'` / `'empty'` / `'error'` | notes.ts 第 16/27/33/37 行 |
|
||||
| 2 | `timeLabel` | `formatRelativeTime(n.createdAt)` — 将 ISO 时间转为相对时间文案(刚刚/N分钟前/MM-DD 等) | notes.ts 第 32 行 |
|
||||
| 3 | `NoteDisplay` | 扩展 `Note` 接口,附加 `timeLabel: string` | notes.ts 第 8-10 行 |
|
||||
|
||||
### `formatRelativeTime` 规则(utils/time.ts)
|
||||
|
||||
| 时间差 | 输出 |
|
||||
|--------|------|
|
||||
| < 120s | 刚刚 |
|
||||
| 2min ~ 59min | N分钟前 |
|
||||
| 1h ~ 23h | N小时前 |
|
||||
| 1d ~ 3d | N天前 |
|
||||
| > 3d 同年 | MM-DD |
|
||||
| > 3d 跨年 | YYYY-MM-DD |
|
||||
|
||||
---
|
||||
|
||||
## 五、路由参数
|
||||
|
||||
`onLoad()` 未读取任何 `options` 参数。
|
||||
|
||||
当前页面作为独立备注列表页,不依赖路由传参。联调时需确认:
|
||||
- 是否需要按客户 ID 过滤备注(`?customerId=xxx`)
|
||||
- 是否需要按任务 ID 过滤备注(`?taskId=xxx`)
|
||||
|
||||
---
|
||||
|
||||
## 六、WXML 硬编码文案
|
||||
|
||||
| 序号 | 文案 | 位置 | 建议 |
|
||||
|------|------|------|------|
|
||||
| 1 | `加载中...` | `g-toast-loading-text`(wxml 第 6 行) | 全局加载组件文案,可保留 |
|
||||
| 2 | `😵` | `error-icon`(wxml 第 12 行) | emoji 作为错误图标 |
|
||||
| 3 | `加载失败,请重试` | `error-text`(wxml 第 13 行) | 可保留 |
|
||||
| 4 | `重新加载` | `retry-btn-text`(wxml 第 15 行) | 可保留 |
|
||||
| 5 | `暂无备注记录` | `t-empty description`(wxml 第 22 行) | 可保留 |
|
||||
| 6 | `— 已加载全部记录 —` | `footer-text`(wxml 第 40 行) | 联调后需改为分页加载逻辑,此文案需动态化 |
|
||||
|
||||
---
|
||||
|
||||
## 七、组件依赖
|
||||
|
||||
| 组件 | 来源 | 说明 |
|
||||
|------|------|------|
|
||||
| `t-icon` | tdesign-miniprogram | 删除图标 |
|
||||
| `t-loading` | tdesign-miniprogram | 加载动画 |
|
||||
| `t-empty` | tdesign-miniprogram | 空态占位 |
|
||||
| `ai-float-button` | `/components/ai-float-button/` | AI 悬浮按钮(仅正常态显示) |
|
||||
| `dev-fab` | `/components/dev-fab/` | 开发调试按钮(全局) |
|
||||
|
||||
---
|
||||
|
||||
## 八、联调 TODO
|
||||
|
||||
- [ ] **对接备注列表 API**:`GET /api/xcx/notes` 替换 `mockNotes`,移除 `import { mockNotes } from '../../utils/mock-data'`
|
||||
- [ ] **对接删除 API**:`DELETE /api/xcx/notes/{noteId}` 替换前端 filter 逻辑,增加失败回滚
|
||||
- [ ] **移除 `setTimeout` 模拟延迟**:改为真实异步请求
|
||||
- [ ] **确认路由参数**:是否需要 `customerId` / `taskId` 过滤
|
||||
- [ ] **分页支持**:当前一次性加载全部,联调时需确认是否需要分页/无限滚动
|
||||
- [ ] **底部文案动态化**:`— 已加载全部记录 —` 需根据分页状态切换为"加载更多"/"已加载全部"
|
||||
- [ ] **删除确认交互**:当前仅前端删除 + Toast,需确认后端删除成功后再更新 UI
|
||||
- [ ] **错误处理增强**:`loadData` 的 catch 仅设置 `pageState: 'error'`,需增加具体错误提示
|
||||
- [ ] **清理 mock-data.ts 中 `Note` 重复定义**:接口定义了两次,联调时统一为后端返回类型
|
||||
- [ ] **`confirmColor` 统一**:`#e34d59` 应走设计系统变量,避免散落硬编码色值
|
||||
225
docs/miniprogram-dev/api-audit/performance-records.md
Normal file
225
docs/miniprogram-dev/api-audit/performance-records.md
Normal 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` | 默认助教头像 |
|
||||
212
docs/miniprogram-dev/api-audit/performance.md
Normal file
212
docs/miniprogram-dev/api-audit/performance.md
Normal file
@@ -0,0 +1,212 @@
|
||||
# performance 页面数据来源排查
|
||||
|
||||
> 排查日期:2026-03-18
|
||||
> 页面路径:pages/performance/performance
|
||||
|
||||
## 概览
|
||||
|
||||
| 分类 | 数量 | 说明 |
|
||||
|------|------|------|
|
||||
| A. Mock 数据(页面内联) | 4 组 | incomeItems / thisMonthRecords / newCustomers / regularCustomers,全部在 `loadData()` 的 `setTimeout` 中硬构造 |
|
||||
| B. 硬编码数据 | 11 个字段 | Banner 个人信息、收入档位、月度合计等,写死在 `data` 初始值中 |
|
||||
| C. 已对接 API | 0 | 页面无任何 `wx.request` 调用,无 API 对接 |
|
||||
| D. 前端计算/派生数据 | 3 个 | 展开/收起状态、AI 配色、pageState |
|
||||
| E. 路由参数 | 0 | `onLoad()` 未读取任何路由参数 |
|
||||
| F. WXML 硬编码文案 | 28+ 处 | 标题、标签、提示文字等 |
|
||||
|
||||
---
|
||||
|
||||
## 一、Mock 数据(页面内联 mock,`loadData()` 内 setTimeout 构造)
|
||||
|
||||
> 页面未 import `utils/mock-data.ts`,所有 mock 数据均为 `loadData()` 方法内局部变量。
|
||||
|
||||
| # | 字段/变量 | 类型 | 位置 | 说明 |
|
||||
|---|-----------|------|------|------|
|
||||
| A1 | `incomeItems` | `IncomeItem[]` | `loadData()` 局部变量 | 4 条业绩明细:基础课 / 激励课 / 充值激励 / TOP3 销冠奖,icon + label + desc + value 全部硬写 |
|
||||
| A2 | `thisMonthRecords` | `DateGroup[]` | `loadData()` 局部变量 | 4 个日期组、共 7 条服务记录,含客户名 / 头像 / 时间 / 课时 / 课程类型 / 台位 / 收入 |
|
||||
| A3 | `newCustomers` | `Array<{...}>` | `loadData()` 局部变量 | 8 条新客数据:姓名 / 头像 / 最近服务日期 / 服务次数 |
|
||||
| A4 | `regularCustomers` | `Array<{...}>` | `loadData()` 局部变量 | 8 条常客数据:姓名 / 头像 / 累计课时 / 累计收入 / 服务次数 |
|
||||
| A5 | `gradients` | `string[]` | `loadData()` 局部变量 | 头像配色数组 `['blue','pink','teal',...]`,用于给 mock 数据分配 avatarColor |
|
||||
|
||||
### Mock 数据内部字段明细
|
||||
|
||||
**incomeItems 每条字段:**
|
||||
| 字段 | 示例值 | 联调时对应 API 字段(待定) |
|
||||
|------|--------|---------------------------|
|
||||
| `icon` | `'🎱'` | — |
|
||||
| `label` | `'基础课'` | 收入类型名称 |
|
||||
| `desc` | `'80元/h × 75h'` | 单价 × 课时的计算描述 |
|
||||
| `value` | `'¥6,000'` | 该类型收入金额 |
|
||||
|
||||
**thisMonthRecords → DateGroup 字段:**
|
||||
| 字段 | 示例值 | 联调时对应 API 字段(待定) |
|
||||
|------|--------|---------------------------|
|
||||
| `date` | `'2月7日'` | 服务日期 |
|
||||
| `totalHours` | `'4.0h'` | 当日总课时 |
|
||||
| `totalIncome` | `'¥350'` | 当日总收入 |
|
||||
|
||||
**thisMonthRecords → ServiceRecord 字段:**
|
||||
| 字段 | 示例值 | 联调时对应 API 字段(待定) |
|
||||
|------|--------|---------------------------|
|
||||
| `customerName` | `'王先生'` | 客户姓名 |
|
||||
| `avatarChar` | `'王'` | 姓氏首字(前端可派生) |
|
||||
| `avatarColor` | `'blue'` | 头像配色(前端可派生) |
|
||||
| `timeRange` | `'20:00-22:00'` | 服务时间段 |
|
||||
| `hours` | `'2.0h'` | 服务时长 |
|
||||
| `courseType` | `'基础课'` | 课程类型 |
|
||||
| `courseTypeClass` | `'tag-basic'` | CSS 类名(前端派生) |
|
||||
| `location` | `'3号台'` | 台位/包厢 |
|
||||
| `income` | `'¥160'` | 该次服务预估收入 |
|
||||
|
||||
**newCustomers 每条字段:**
|
||||
| 字段 | 示例值 | 联调时对应 API 字段(待定) |
|
||||
|------|--------|---------------------------|
|
||||
| `name` | `'王先生'` | 客户姓名 |
|
||||
| `avatarChar` | `'王'` | 姓氏首字(前端可派生) |
|
||||
| `avatarColor` | `'blue'` | 头像配色(前端可派生) |
|
||||
| `lastService` | `'2月7日'` | 最近服务日期 |
|
||||
| `count` | `2` | 服务次数 |
|
||||
|
||||
**regularCustomers 每条字段:**
|
||||
| 字段 | 示例值 | 联调时对应 API 字段(待定) |
|
||||
|------|--------|---------------------------|
|
||||
| `name` | `'张先生'` | 客户姓名 |
|
||||
| `avatarChar` | `'张'` | 姓氏首字(前端可派生) |
|
||||
| `avatarColor` | `'teal'` | 头像配色(前端可派生) |
|
||||
| `hours` | `12` | 累计课时(number) |
|
||||
| `income` | `'¥960'` | 累计收入 |
|
||||
| `count` | `6` | 服务次数 |
|
||||
|
||||
---
|
||||
|
||||
## 二、硬编码数据(`Page.data` 初始值)
|
||||
|
||||
| # | 字段 | 硬编码值 | 说明 |
|
||||
|---|------|----------|------|
|
||||
| B1 | `coachName` | `'小燕'` | 助教姓名,应从登录态/API 获取 |
|
||||
| B2 | `coachRole` | `'助教'` | 角色标签,应从用户信息获取 |
|
||||
| B3 | `storeName` | `'广州朗朗桌球'` | 门店名称,应从用户绑定门店获取 |
|
||||
| B4 | `monthlyIncome` | `'¥6,206'` | 本月预计收入,应从 API 获取 |
|
||||
| B5 | `lastMonthIncome` | `'¥16,880'` | 上月收入,应从 API 获取 |
|
||||
| B6 | `currentTier.basicRate` | `80` | 当前档位基础课单价(元/h) |
|
||||
| B7 | `currentTier.incentiveRate` | `95` | 当前档位激励课单价(元/h) |
|
||||
| B8 | `nextTier.basicRate` | `90` | 下一档位基础课单价(元/h) |
|
||||
| B9 | `nextTier.incentiveRate` | `114` | 下一档位激励课单价(元/h) |
|
||||
| B10 | `upgradeHoursNeeded` | `15` | 距下一阶段所需课时 |
|
||||
| B11 | `upgradeBonus` | `800` | 到达下一阶段奖金(元) |
|
||||
| B12 | `monthlyTotal` | `'¥6,950.5'` | 本月合计预估收入 |
|
||||
| B13 | `visibleRecordGroups` | `2` | 默认显示前 N 条日期组(UI 配置,可保留硬编码) |
|
||||
|
||||
---
|
||||
|
||||
## 三、已对接 API
|
||||
|
||||
**无。** 页面中不存在任何 `wx.request`、API 服务调用、或从 `getApp()` 获取全局数据的逻辑。
|
||||
|
||||
`loadData()` 使用 `setTimeout(500ms)` 模拟异步加载,内部全部为内联 mock 数据。
|
||||
|
||||
文件头部注释明确标注:`// TODO: 联调时替换为真实 API 调用`
|
||||
|
||||
---
|
||||
|
||||
## 四、前端计算/派生数据
|
||||
|
||||
| # | 字段 | 来源 | 说明 |
|
||||
|---|------|------|------|
|
||||
| D1 | `pageState` | 前端状态机 | `'loading' → 'normal' / 'error' / 'empty'`,由 `loadData()` 控制 |
|
||||
| D2 | `aiColor` | `initPageAiColor('performance')` | 从 `utils/ai-color-manager.ts` 获取,固定返回 `'blue'` |
|
||||
| D3 | `thisMonthRecordsExpanded` | 前端 toggle | 服务记录展开/收起状态,默认 `false` |
|
||||
| D4 | `newCustomerExpanded` | 前端 toggle | 新客列表展开/收起状态,默认 `false` |
|
||||
| D5 | `regularCustomerExpanded` | 前端 toggle | 常客列表展开/收起状态,默认 `false` |
|
||||
|
||||
---
|
||||
|
||||
## 五、路由参数
|
||||
|
||||
**无。** `onLoad()` 未接收任何路由参数(`options` 未使用)。
|
||||
|
||||
页面作为业绩总览入口,当前设计不依赖路由传参。联调后可能需要接收 `coachId` / `month` 等参数。
|
||||
|
||||
---
|
||||
|
||||
## 六、WXML 硬编码文案
|
||||
|
||||
| # | 文案内容 | 位置 | 说明 |
|
||||
|---|----------|------|------|
|
||||
| F1 | `加载中...` | 加载态 toast | 可保留 |
|
||||
| F2 | `暂无业绩数据` | 空数据态 | 可保留 |
|
||||
| F3 | `加载失败,请点击重试` | 错误态 | 可保留 |
|
||||
| F4 | `重试` | 错误态按钮 | 可保留 |
|
||||
| F5 | `本月预计收入` | Banner 收入卡片标签 | 可保留 |
|
||||
| F6 | `上月收入` | Banner 收入卡片标签 | 可保留 |
|
||||
| F7 | `收入情况` | section 标题 | 可保留 |
|
||||
| F8 | `当前档位`(×2) | 档位卡片 badge + label | 可保留 |
|
||||
| F9 | `元/h`(×4) | 费率单位 | 可保留 |
|
||||
| F10 | `基础课到手`(×2) | 费率描述 | 可保留 |
|
||||
| F11 | `激励课到手`(×2) | 费率描述 | 可保留 |
|
||||
| F12 | `下一阶段`(×2) | 档位卡片 badge + label | 可保留 |
|
||||
| F13 | `距离下一阶段` | 升级提示标签 | 可保留 |
|
||||
| F14 | `需完成 ... 小时` | 升级提示(含动态插值) | 可保留 |
|
||||
| F15 | `到达即得` | 升级奖金标签 | 可保留 |
|
||||
| F16 | `元`(奖金后缀) | 升级奖金值 | 可保留 |
|
||||
| F17 | `本月业绩 预估` | section 标题 | 可保留 |
|
||||
| F18 | `本月合计 预估` | 合计行标签 | 可保留 |
|
||||
| F19 | `📋 我的服务记录明细` | 服务记录 header | 可保留 |
|
||||
| F20 | `展开更多` / `收起` | 服务记录 toggle | 可保留 |
|
||||
| F21 | `查看全部` | 跳转业绩记录按钮 | 可保留 |
|
||||
| F22 | `我的预估收入` | 服务记录行内文案 | 可保留 |
|
||||
| F23 | `我的新客` | section 标题 | 可保留 |
|
||||
| F24 | `最近服务:` | 新客详情前缀 | 可保留 |
|
||||
| F25 | `次` | 新客服务次数后缀 | 可保留 |
|
||||
| F26 | `查看更多 ↓` / `收起 ↑` | 新客/常客 toggle | 可保留 |
|
||||
| F27 | `我的常客` | section 标题 | 可保留 |
|
||||
| F28 | `📊` / `🎯` / `⏱️` | 档位/升级提示 emoji | 可保留 |
|
||||
| F29 | `业绩详情` | `performance.json` → `navigationBarTitleText` | 可保留 |
|
||||
|
||||
---
|
||||
|
||||
## 七、联调 TODO
|
||||
|
||||
### 7.1 需要对接的 API(按优先级排序)
|
||||
|
||||
| 优先级 | API 用途 | 涉及字段 | 备注 |
|
||||
|--------|----------|----------|------|
|
||||
| P0 | 助教个人信息 | B1 `coachName`, B2 `coachRole`, B3 `storeName` | 可从登录态/全局 store 获取 |
|
||||
| P0 | 本月/上月收入概览 | B4 `monthlyIncome`, B5 `lastMonthIncome` | Banner 核心数据 |
|
||||
| P0 | 收入档位信息 | B6-B9 `currentTier.*`, `nextTier.*`, B10 `upgradeHoursNeeded`, B11 `upgradeBonus` | 档位规则可能来自配置表 |
|
||||
| P0 | 本月业绩明细 | A1 `incomeItems`, B12 `monthlyTotal` | 基础课/激励课/充值激励/奖金 |
|
||||
| P1 | 服务记录列表(按日期分组) | A2 `thisMonthRecords` | 需支持分页或按月查询 |
|
||||
| P1 | 新客列表 | A3 `newCustomers` | 本月新服务的客户 |
|
||||
| P1 | 常客列表 | A4 `regularCustomers` | 累计服务数据 |
|
||||
|
||||
### 7.2 联调时需要处理的事项
|
||||
|
||||
- [ ] 移除 `loadData()` 中的 `setTimeout` 模拟延迟
|
||||
- [ ] 移除所有内联 mock 数据(incomeItems / thisMonthRecords / newCustomers / regularCustomers)
|
||||
- [ ] 替换 Banner 区域硬编码字段(coachName / coachRole / storeName / monthlyIncome / lastMonthIncome)
|
||||
- [ ] 替换收入档位硬编码(currentTier / nextTier / upgradeHoursNeeded / upgradeBonus / monthlyTotal)
|
||||
- [ ] 添加 `wx.request` 或封装的 API 调用
|
||||
- [ ] 处理 API 错误 → 设置 `pageState: 'error'`
|
||||
- [ ] 处理空数据 → 设置 `pageState: 'empty'`
|
||||
- [ ] `avatarChar` / `avatarColor` / `courseTypeClass` 等前端派生字段需确认是后端返回还是前端计算
|
||||
- [ ] `onLoad()` 可能需要接收路由参数(如 `coachId`、`month`)
|
||||
- [ ] 金额格式化(`¥` 前缀、千分位)确认由后端返回还是前端处理
|
||||
- [ ] 服务记录的 `taskId` 字段当前 mock 中未提供,但 `onRecordTap` 已预留读取逻辑
|
||||
|
||||
### 7.3 页面引用的外部依赖
|
||||
|
||||
| 依赖 | 类型 | 来源 | 数据影响 |
|
||||
|------|------|------|----------|
|
||||
| `ai-color-manager.ts` | 工具函数 | `utils/` | 仅提供 AI 图标配色,无业务数据 |
|
||||
| `ai-float-button` | 自定义组件 | `components/` | AI 悬浮按钮,无业务数据 |
|
||||
| `dev-fab` | 自定义组件 | `components/` | 开发调试按钮,无业务数据 |
|
||||
| `metric-card` | 自定义组件 | `components/` | 已注册但 WXML 中未使用 |
|
||||
| `t-icon` / `t-loading` | TDesign 组件 | `tdesign-miniprogram` | UI 组件,无业务数据 |
|
||||
| `mock-data.ts` | Mock 数据文件 | `utils/` | **未被本页面引用**(页面使用内联 mock) |
|
||||
|
||||
### 7.4 注意事项
|
||||
|
||||
1. 页面文件头部已有 `// TODO: 联调时替换为真实 API 调用` 注释
|
||||
2. `loadData()` 内部也有 `// TODO: 替换为真实 API` 注释
|
||||
3. `performance.json` 中注册了 `metric-card` 组件但 WXML 中未使用,联调时可清理或启用
|
||||
4. 金额字段涉及助教收入计算,联调时需参考 DWD-DOC 标杆文档中的助教费用拆分规则(`assistant_pd_money` 陪打 / `assistant_cx_money` 超休)
|
||||
98
docs/miniprogram-dev/api-audit/reviewing.md
Normal file
98
docs/miniprogram-dev/api-audit/reviewing.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# reviewing 页面数据来源排查
|
||||
|
||||
> 排查日期:2026-03-18
|
||||
> 页面路径:pages/reviewing/reviewing
|
||||
|
||||
## 概览
|
||||
|
||||
| 分类 | 数量 | 说明 |
|
||||
|------|------|------|
|
||||
| Mock 数据 | 0 | 无 mock 引用 |
|
||||
| 硬编码数据 | 5 | 状态跳转路径、Storage key、Toast 文案等 |
|
||||
| 已对接 API | 1 | `GET /api/xcx/me` |
|
||||
| 前端计算/派生 | 3 | 状态栏高度、状态条件分支、application 提取 |
|
||||
| 路由参数 | 0 | 无 |
|
||||
| WXML 硬编码文案 | 12 | 标题、提示语、标签等 |
|
||||
|
||||
## 一、Mock 数据
|
||||
|
||||
无。本页面未引用 `mock-data.ts`,所有数据均来自 API。
|
||||
|
||||
## 二、硬编码数据
|
||||
|
||||
| # | 文件 | 行号 | 内容 | 风险等级 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | `.ts` | L39 | `url: "/api/xcx/me"` | 🟢 低 | API 端点路径,通常不变 |
|
||||
| 2 | `.ts` | L55 | `url: "/pages/mvp/mvp"` | 🟢 低 | approved 状态跳转路径 |
|
||||
| 3 | `.ts` | L61 | `url: "/pages/no-permission/no-permission"` | 🟢 低 | disabled 状态跳转路径 |
|
||||
| 4 | `.ts` | L67 | `url: "/pages/no-permission/no-permission"` | 🟢 低 | rejected 状态跳转路径 |
|
||||
| 5 | `.ts` | L73 | `url: "/pages/apply/apply"` | 🟢 低 | new 状态跳转路径 |
|
||||
| 6 | `.ts` | L82 | `title: "获取状态失败"` | 🟡 中 | Toast 错误提示文案,建议抽取为常量 |
|
||||
| 7 | `.ts` | L86–93 | Storage key: `"token"`, `"refreshToken"`, `"userId"`, `"userStatus"` | 🟡 中 | 多处重复使用,建议抽取为常量统一管理 |
|
||||
| 8 | `.ts` | L94 | `url: "/pages/login/login"` | 🟢 低 | 切换账号跳转路径 |
|
||||
|
||||
## 三、已对接 API
|
||||
|
||||
### 3.1 `GET /api/xcx/me`
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|-----|
|
||||
| 调用位置 | `fetchStatus()` — L37-81 |
|
||||
| 触发时机 | `onLoad()`、`onShow()`、`onPullDownRefresh()` |
|
||||
| 请求方式 | `GET` |
|
||||
| 需要鉴权 | ✅ `needAuth: true` |
|
||||
| 请求参数 | 无 |
|
||||
| 响应字段(使用中) | `user_id`, `status`, `nickname`, `latest_application` |
|
||||
| 响应字段(application 子对象) | `id`, `site_code`, `role_type`, `phone`, `status`, `reject_reason`, `created_at` |
|
||||
|
||||
**状态流转逻辑:**
|
||||
|
||||
| `data.status` 值 | 行为 |
|
||||
|-------------------|------|
|
||||
| `"approved"` | `wx.reLaunch` → `/pages/mvp/mvp` |
|
||||
| `"disabled"` | `wx.reLaunch` → `/pages/no-permission/no-permission` |
|
||||
| `"rejected"` | `wx.reLaunch` → `/pages/no-permission/no-permission` |
|
||||
| `"new"` | `wx.reLaunch` → `/pages/apply/apply` |
|
||||
| `"pending"` | 留在当前页,展示审核中 UI |
|
||||
|
||||
**副作用:**
|
||||
- 同步 `globalData.authUser`(`userId`, `status`, `nickname`)
|
||||
- 写入 Storage:`userId`, `userStatus`
|
||||
|
||||
## 四、前端计算/派生数据
|
||||
|
||||
| # | 数据 | 来源 | 说明 |
|
||||
|---|------|------|------|
|
||||
| 1 | `statusBarHeight` | `wx.getWindowInfo().statusBarHeight` | 系统 API,用于自定义导航栏顶部偏移 |
|
||||
| 2 | `status` | 从 API 响应 `data.status` 赋值 | 控制 pending/rejected 两种 UI 状态 |
|
||||
| 3 | `application` | 从 API 响应 `data.latest_application` 提取 | 展示申请信息摘要(球房ID、身份、手机号) |
|
||||
|
||||
## 五、路由参数
|
||||
|
||||
无。本页面不接收任何路由参数。
|
||||
|
||||
## 六、WXML 硬编码文案
|
||||
|
||||
| # | 内容 | 位置 | 建议 |
|
||||
|---|------|------|------|
|
||||
| 1 | `"加载中..."` | loading 区域 | 可保留 |
|
||||
| 2 | `"申请审核中"` | main-title(pending) | 可保留 |
|
||||
| 3 | `"申请未通过"` | main-title(rejected) | 可保留 |
|
||||
| 4 | `"您的访问申请已提交成功,正在等待管理员审核,请耐心等待"` | sub-title(pending) | 可保留 |
|
||||
| 5 | `"很抱歉,您的申请未通过审核"` | sub-title(rejected) | 可保留 |
|
||||
| 6 | `"审核进度"` | progress-title | 可保留 |
|
||||
| 7 | `"通常需要 1-3 个工作日"` | progress-desc | 🟡 如审核时效变化需同步修改 |
|
||||
| 8 | `"已提交"` / `"审核中"` / `"通过"` | 进度步骤标签 | 可保留 |
|
||||
| 9 | `"拒绝原因"` | reject-title | 可保留 |
|
||||
| 10 | `"申请信息"` | info-card-title | 可保留 |
|
||||
| 11 | `"球房ID"` / `"申请身份"` / `"手机号"` | info-label | 可保留 |
|
||||
| 12 | `"如有疑问,请联系管理员"` | contact-text | 可保留 |
|
||||
| 13 | `"更换登录账号"` | switch-btn-text | 可保留 |
|
||||
|
||||
## 七、联调 TODO
|
||||
|
||||
| # | 优先级 | 事项 | 当前状态 | 说明 |
|
||||
|---|--------|------|----------|------|
|
||||
| 1 | — | — | — | 本页面已完成 API 对接,无待联调项 |
|
||||
|
||||
**结论:** reviewing 页面已完全对接 `GET /api/xcx/me`,无 mock 数据残留,联调状态良好。唯一建议是将 Storage key 和 Toast 文案抽取为常量统一管理。
|
||||
285
docs/miniprogram-dev/api-audit/task-detail.md
Normal file
285
docs/miniprogram-dev/api-audit/task-detail.md
Normal file
@@ -0,0 +1,285 @@
|
||||
# task-detail 页面数据来源排查
|
||||
|
||||
> 排查日期:2026-03-18
|
||||
> 页面路径:pages/task-detail/task-detail
|
||||
|
||||
## 概览
|
||||
|
||||
| 分类 | 数量 | 说明 |
|
||||
|------|------|------|
|
||||
| Mock 数据 | 12 个字段 | 来自 `mock-data.ts` 的 `mockTaskDetails` + 页面内联 mock 备注 |
|
||||
| 硬编码数据 | 18 个字段/数组 | 维客线索、话术参考、服务记录、服务汇总、手机号、储值等级等 |
|
||||
| 已对接 API | 0 个接口 | 当前无任何真实 API 调用 |
|
||||
| 前端计算/派生 | 7 个字段 | 关系等级、Banner 背景、AI 配色、时间标签等 |
|
||||
| 路由参数 | 1 个 | `id`(任务 ID) |
|
||||
| WXML 硬编码文案 | 6 处 | 建议文案、section 标题等 |
|
||||
|
||||
---
|
||||
|
||||
## 一、Mock 数据
|
||||
|
||||
### 1.1 来自 mock-data.ts(`mockTaskDetails`)
|
||||
|
||||
页面通过 `import { mockTaskDetails } from '../../utils/mock-data'` 引入,在 `loadData()` 中使用 `setTimeout` 模拟异步加载。
|
||||
|
||||
| 字段 | 类型 | 来源路径 | 联调替换 API |
|
||||
|------|------|----------|-------------|
|
||||
| `detail`(整体) | `TaskDetail` | `mockTaskDetails.find(t => t.id === id) \|\| mockTaskDetails[0]` | `GET /api/tasks/{id}` |
|
||||
| `detail.id` | `string` | TaskDetail.id | 同上 |
|
||||
| `detail.customerName` | `string` | TaskDetail.customerName | 同上 |
|
||||
| `detail.customerAvatar` | `string` | TaskDetail.customerAvatar | 同上 |
|
||||
| `detail.taskType` | `TaskType` | TaskDetail.taskType(`'callback' \| 'priority_recall' \| 'relationship' \| 'high_priority'`) | 同上 |
|
||||
| `detail.taskTypeLabel` | `string` | TaskDetail.taskTypeLabel | 同上 |
|
||||
| `detail.heartScore` | `number` | TaskDetail.heartScore | 同上 |
|
||||
| `detail.status` | `'pending' \| 'completed' \| 'abandoned'` | TaskDetail.status | 同上 |
|
||||
| `detail.aiAnalysis.summary` | `string` | TaskDetail.aiAnalysis.summary | `GET /api/tasks/{id}/ai-analysis` |
|
||||
| `detail.aiAnalysis.suggestions` | `string[]` | TaskDetail.aiAnalysis.suggestions | 同上 |
|
||||
| `detail.hobbies` | `string[]` | TaskDetail.hobbies | `GET /api/tasks/{id}` |
|
||||
| `detail.deadline` | `string` | TaskDetail.deadline | 同上 |
|
||||
|
||||
**mock-data.ts 中 TaskDetail 扩展字段(已定义但页面未直接渲染):**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `lastVisitDate` | `string?` | 最后到店日期,页面未使用 |
|
||||
| `lastSpendAmount` | `number?` | 最后消费金额,页面未使用 |
|
||||
| `callbackReason` | `string?` | 回访原因,页面未使用 |
|
||||
| `daysAbsent` | `number?` | 缺席天数,页面未使用 |
|
||||
| `spendTrend` | `'up' \| 'down' \| 'flat'?` | 消费趋势,页面未使用 |
|
||||
| `churnRisk` | `'high' \| 'medium' \| 'low'?` | 流失风险,页面未使用 |
|
||||
| `preferences` | `string[]?` | 偏好,页面未使用 |
|
||||
| `consumptionHabits` | `string?` | 消费习惯,页面未使用 |
|
||||
| `socialPreference` | `string?` | 社交偏好,页面未使用 |
|
||||
|
||||
### 1.2 页面内联 Mock 备注
|
||||
|
||||
`loadData()` 方法内部硬编码了 5 条 mock 备注(`mockNotes` 局部变量,L107-L113),覆盖了 `mockTaskDetails` 中的 `notes` 字段:
|
||||
|
||||
| 字段 | 当前值 | 联调替换 API |
|
||||
|------|--------|-------------|
|
||||
| `mockNotes[0]` | `'已通过微信联系王先生...'`,score=10 | `GET /api/tasks/{id}/notes` |
|
||||
| `mockNotes[1]` | `'王先生最近出差较多...'`,score=7.5 | 同上 |
|
||||
| `mockNotes[2]` | `'上次到店时推荐了会员续费活动...'`,score=6 | 同上 |
|
||||
| `mockNotes[3]` | `'客户对今天的服务非常满意...'`,score=9.5 | 同上 |
|
||||
| `mockNotes[4]` | `'完成高优先召回任务...'`,score=8 | 同上 |
|
||||
|
||||
---
|
||||
|
||||
## 二、硬编码数据
|
||||
|
||||
### 2.1 维客线索(`retentionClues`)
|
||||
|
||||
**风险:高** — 8 条完整的维客线索数据直接写死在 `data` 对象中(L68-L77),应从 API 获取。
|
||||
|
||||
| 索引 | tag | text(摘要) | source | 所在行 | 应改为 |
|
||||
|------|-----|-------------|--------|--------|--------|
|
||||
| 0 | 客户基础 | `'生日 3月15日 · VIP会员 · 注册2年'` | By:系统 | L69 | `GET /api/tasks/{id}/retention-clues` |
|
||||
| 1 | 消费习惯 | `'常来夜场 · 月均4-5次'` | By:系统 | L70 | 同上 |
|
||||
| 2 | 消费习惯 | `'高客单价'`(含 desc) | By:系统 | L71 | 同上 |
|
||||
| 3 | 玩法偏好 | `'偏爱中式八球 · 斯诺克进阶中...'`(含 desc) | By:系统 | L72 | 同上 |
|
||||
| 4 | 重要反馈 | `'上次提到想练斯诺克走位'`(含 desc) | By:小燕 | L73 | 同上 |
|
||||
| 5 | 社交偏好 | `'喜欢带朋友来玩 · 社交型客户'`(含 desc) | By:系统 | L74 | 同上 |
|
||||
| 6 | 消费习惯 | `'酒水消费占比高 · 偏好高端酒水'`(含 desc) | By:系统 | L75 | 同上 |
|
||||
| 7 | 重要反馈 | `'上次提到想办生日派对'`(含 desc) | By:Lucy | L76 | 同上 |
|
||||
|
||||
> 注意:`mock-data.ts` 中已定义 `mockRetentionClues` 和 `RetentionClue` 接口,但页面未使用,而是自行硬编码了不同结构的数据。联调时应统一数据结构。
|
||||
|
||||
### 2.2 话术参考(`talkingPoints`)
|
||||
|
||||
**风险:高** — 5 条话术直接写死在 `data` 对象中(L80-L86),应由 AI 生成或从 API 获取。
|
||||
|
||||
| 索引 | 内容摘要 | 所在行 | 应改为 |
|
||||
|------|---------|--------|--------|
|
||||
| 0 | `'王哥您好,好久不见!最近店里新到了...'` | L81 | `GET /api/tasks/{id}/talking-points` 或 AI 生成接口 |
|
||||
| 1 | `'王哥,最近忙吗?这周末我们有个...'` | L82 | 同上 |
|
||||
| 2 | `'王哥好呀,上次您提到想练练...'` | L83 | 同上 |
|
||||
| 3 | `'王哥,好久没见您了,您的老位置...'` | L84 | 同上 |
|
||||
| 4 | `'王哥您好,我们这个月推出了...'` | L85 | 同上 |
|
||||
|
||||
### 2.3 近期服务记录(`serviceRecords`)
|
||||
|
||||
**风险:高** — 4 条服务记录直接写死在 `data` 对象中(L91-L96),应从 API 获取。
|
||||
|
||||
| 索引 | table | type | income | date | 所在行 | 应改为 |
|
||||
|------|-------|------|--------|------|--------|--------|
|
||||
| 0 | A12号台 | 基础课 | ¥200 | 2月7日 21:30 | L92 | `GET /api/tasks/{id}/service-records` |
|
||||
| 1 | 3号台 | 基础课 | ¥160 | 2月1日 20:30 | L93 | 同上 |
|
||||
| 2 | VIP1号房 | 包厢课 | ¥150 | 1月28日 19:00 | L94 | 同上 |
|
||||
| 3 | (空) | 充值 | ¥80 | 1月15日 10:00 | L95 | 同上 |
|
||||
|
||||
### 2.4 服务汇总(`serviceSummary`)
|
||||
|
||||
**风险:高** — 汇总数据直接写死(L90),应由后端计算或前端根据服务记录列表聚合。
|
||||
|
||||
| 字段 | 当前值 | 所在行 | 应改为 |
|
||||
|------|--------|--------|--------|
|
||||
| `totalHours` | `6.0` | L90 | 后端返回或前端聚合 `serviceRecords` |
|
||||
| `totalIncome` | `510` | L90 | 同上 |
|
||||
| `avgIncome` | `170` | L90 | 同上 |
|
||||
|
||||
### 2.5 手机号
|
||||
|
||||
**风险:高** — 手机号硬编码在两处。
|
||||
|
||||
| 位置 | 当前值 | 所在行 | 应改为 |
|
||||
|------|--------|--------|--------|
|
||||
| WXML 中 `phone` 显示 | `'138****5678'`(脱敏)/ `'13812345678'`(明文) | wxml L38 | 从 `detail` 对象获取,API 返回脱敏版 |
|
||||
| `onCopyPhone()` 方法 | `'13812345678'` | ts L175 | 调用 API 获取明文手机号 `GET /api/customers/{id}/phone` |
|
||||
|
||||
### 2.6 储值等级
|
||||
|
||||
**风险:中** — 直接写死在 `data` 中。
|
||||
|
||||
| 字段 | 当前值 | 所在行 | 应改为 |
|
||||
|------|--------|--------|--------|
|
||||
| `storageLevel` | `'非常多'` | ts L101 | 从 `detail` 或客户信息 API 获取 |
|
||||
|
||||
### 2.7 关系等级初始值
|
||||
|
||||
**风险:低** — 初始值会被 `updateRelationshipDisplay()` 覆盖,但仍属于硬编码。
|
||||
|
||||
| 字段 | 当前值 | 所在行 | 说明 |
|
||||
|------|--------|--------|------|
|
||||
| `relationLevel` | `'excellent'` | ts L104 | 初始值,`loadData` 后被覆盖 |
|
||||
| `relationLevelText` | `'很好'` | ts L105 | 同上 |
|
||||
| `relationColor` | `'#e91e63'` | ts L106 | 同上 |
|
||||
|
||||
### 2.8 任务建议文案
|
||||
|
||||
**风险:中** — WXML 中硬编码了建议引导文案。
|
||||
|
||||
| 位置 | 当前值 | 所在行 | 应改为 |
|
||||
|------|--------|--------|--------|
|
||||
| `suggestion-intro` | `'该客户已有 15 天未到店,存在流失风险。建议通过微信联系:'` | wxml L82 | 从 `detail.aiAnalysis` 获取或后端生成 |
|
||||
|
||||
---
|
||||
|
||||
## 三、已对接 API
|
||||
|
||||
**当前无任何真实 API 调用。**
|
||||
|
||||
`loadData()` 使用 `setTimeout` + `mockTaskDetails` 模拟异步请求(L128-L155)。所有数据操作(放弃任务、保存备注、删除备注)均为本地 `setData` 操作,未与后端通信。
|
||||
|
||||
涉及的伪操作:
|
||||
|
||||
| 操作 | 当前实现 | 需对接 API |
|
||||
|------|---------|-----------|
|
||||
| 加载任务详情 | `mockTaskDetails.find()` | `GET /api/tasks/{id}` |
|
||||
| 放弃任务 | `setData({ 'detail.status': 'abandoned' })` | `POST /api/tasks/{id}/abandon` |
|
||||
| 取消放弃 | `setData({ 'detail.status': 'pending' })` | `POST /api/tasks/{id}/cancel-abandon` |
|
||||
| 保存备注 | `setData({ sortedNotes: [...] })` | `POST /api/tasks/{id}/notes` |
|
||||
| 删除备注 | `filter` 本地数组 | `DELETE /api/tasks/{id}/notes/{noteId}` |
|
||||
|
||||
---
|
||||
|
||||
## 四、前端计算/派生数据
|
||||
|
||||
| 字段 | 计算逻辑 | 所在方法 | 依赖数据 |
|
||||
|------|---------|---------|---------|
|
||||
| `relationLevel` | `heartScore` 分段映射:>8.5→excellent, ≥6→good, ≥3.5→normal, <3.5→poor | `updateRelationshipDisplay()` L157 | `detail.heartScore` |
|
||||
| `relationLevelText` | 同上分段映射为中文:很好/良好/一般/待发展 | 同上 | 同上 |
|
||||
| `relationColor` | 同上分段映射为颜色值 | 同上 | 同上 |
|
||||
| `bannerBgSvg` | 根据 `detail.taskType` 映射 SVG 路径 | `loadData()` L133-L142 | `detail.taskType` |
|
||||
| `sortedNotes` | `mockNotes` → 附加 `timeLabel`(`formatRelativeTime`)→ `sortByTimestamp` 排序 | `loadData()` L116-L118 | mock 备注数据 |
|
||||
| `aiColor` | 随机从 6 色中选取 | `onLoad()` L124 | 无(随机) |
|
||||
| `copiedIndex` | 复制话术后设为当前索引,2 秒后重置为 -1 | `onCopySpeech()` L189 | 用户交互 |
|
||||
| `pageState` | 加载状态机:loading → normal/empty/error | `loadData()` | 数据加载结果 |
|
||||
| `phoneVisible` | 手机号显示/隐藏切换 | `onTogglePhone()` L173 | 用户交互 |
|
||||
| `noteModalVisible` | 备注弹窗显隐 | `onAddNote()`/`onNoteConfirm()`/`onNoteCancel()` | 用户交互 |
|
||||
| `abandonModalVisible` | 放弃弹窗显隐 | `onAbandon()`/`onAbandonConfirm()`/`onAbandonCancel()` | 用户交互 |
|
||||
| `formatServiceDate()` 返回值 | ISO 日期 → `M月D日 HH:mm` 中文短格式 | 页面顶部函数 L55 | 服务记录日期字符串 |
|
||||
|
||||
---
|
||||
|
||||
## 五、路由参数
|
||||
|
||||
| 参数 | 来源 | 用途 | 所在行 |
|
||||
|------|------|------|--------|
|
||||
| `options.id` | `onLoad(options)` | 任务 ID,用于查找 mock 数据;缺失时回退空字符串 | ts L123 |
|
||||
|
||||
---
|
||||
|
||||
## 六、WXML 中的硬编码文案
|
||||
|
||||
| 文案 | 位置 | 类型 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `'该客户已有 15 天未到店,存在流失风险。建议通过微信联系:'` | wxml L82 | 业务数据 | 应从 AI 分析结果获取 |
|
||||
| `'💡 建议执行'` | wxml L79 | UI 文案 | 可保留 |
|
||||
| `'💬 话术参考'` | wxml L89 | UI 文案 | 可保留 |
|
||||
| `'60天内服务记录'` | wxml L121 | UI 文案 | "60天"为业务规则,可保留但需确认是否动态 |
|
||||
| `'未找到任务信息'` / `'加载失败'` / `'加载中...'` | wxml L7-L15 | 状态文案 | 可保留 |
|
||||
| `'暂无备注'` | wxml L117 | 空态文案 | 可保留 |
|
||||
|
||||
---
|
||||
|
||||
## 七、引用的工具函数
|
||||
|
||||
| 函数 | 来源文件 | 用途 |
|
||||
|------|---------|------|
|
||||
| `mockTaskDetails` | `utils/mock-data.ts` | Mock 任务详情数据 |
|
||||
| `TaskDetail`, `Note` | `utils/mock-data.ts` | 类型定义 |
|
||||
| `sortByTimestamp` | `utils/sort.ts` | 备注按时间降序排序 |
|
||||
| `formatRelativeTime` | `utils/time.ts` | 备注时间 → 相对时间文案 |
|
||||
| `formatMoney` | `utils/money.ts` | 金额格式化(已 import 但页面 TS 中未直接调用,WXML 使用 WXS 版) |
|
||||
| `fmt.toFixed` | `utils/format.wxs` | WXML 中数字保留小数 |
|
||||
| `fmt.money` | `utils/format.wxs` | WXML 中金额格式化 |
|
||||
| `fmt.hours` | `utils/format.wxs` | WXML 中课时格式化 |
|
||||
|
||||
---
|
||||
|
||||
## 八、引用的自定义组件
|
||||
|
||||
| 组件 | 路径 | 用途 |
|
||||
|------|------|------|
|
||||
| `note-modal` | `/components/note-modal/note-modal` | 备注弹窗 |
|
||||
| `abandon-modal` | `/components/abandon-modal/abandon-modal` | 放弃任务弹窗 |
|
||||
| `star-rating` | `/components/star-rating/star-rating` | 星级评分展示 |
|
||||
| `heart-icon` | `/components/heart-icon/heart-icon` | 爱心图标(关系等级) |
|
||||
| `ai-inline-icon` | `/components/ai-inline-icon/ai-inline-icon` | AI 内联图标 |
|
||||
| `ai-title-badge` | `/components/ai-title-badge/ai-title-badge` | AI 标题徽章 |
|
||||
| `clue-card` | `/components/clue-card/clue-card` | 维客线索卡片 |
|
||||
| `service-record-card` | `/components/service-record-card/service-record-card` | 服务记录卡片 |
|
||||
| `dev-fab` | `/components/dev-fab/dev-fab` | 开发调试浮动按钮(JSON 注册但 WXML 未使用) |
|
||||
|
||||
---
|
||||
|
||||
## 九、调试面板数据
|
||||
|
||||
页面包含一个调试面板(`showDebugPanel`),用于开发阶段切换任务类型和关系数值。联调前应移除或隐藏。
|
||||
|
||||
| 字段 | 用途 | 所在行 |
|
||||
|------|------|--------|
|
||||
| `showDebugPanel` | 调试面板显隐 | ts L110 |
|
||||
| `debugTaskType` | 调试用任务类型 | ts L111 |
|
||||
| `debugHeartScore` | 调试用关系数值 | ts L112 |
|
||||
| `debugShowExpandBtn` | 调试用展开按钮开关 | ts L113 |
|
||||
|
||||
---
|
||||
|
||||
## 十、联调 TODO
|
||||
|
||||
### 高优先级(数据完全依赖 Mock/硬编码)
|
||||
|
||||
- [ ] 替换 `mockTaskDetails` 为 `GET /api/tasks/{id}` 真实 API 调用
|
||||
- [ ] 替换页面内联 mock 备注为 `GET /api/tasks/{id}/notes` API
|
||||
- [ ] 替换硬编码 `retentionClues`(8 条维客线索)为 `GET /api/tasks/{id}/retention-clues` API
|
||||
- [ ] 替换硬编码 `talkingPoints`(5 条话术)为 `GET /api/tasks/{id}/talking-points` 或 AI 生成接口
|
||||
- [ ] 替换硬编码 `serviceRecords`(4 条服务记录)为 `GET /api/tasks/{id}/service-records?days=60` API
|
||||
- [ ] 替换硬编码 `serviceSummary` 为后端返回或前端聚合计算
|
||||
- [ ] 替换硬编码手机号 `'13812345678'` / `'138****5678'` 为 API 返回的客户手机号
|
||||
- [ ] 替换硬编码 `storageLevel: '非常多'` 为客户信息 API 返回值
|
||||
|
||||
### 中优先级(操作类接口)
|
||||
|
||||
- [ ] `onAbandonConfirm` 对接 `POST /api/tasks/{id}/abandon`
|
||||
- [ ] `cancelAbandon` 对接 `POST /api/tasks/{id}/cancel-abandon`
|
||||
- [ ] `onNoteConfirm` 对接 `POST /api/tasks/{id}/notes`
|
||||
- [ ] `onDeleteNote` 对接 `DELETE /api/tasks/{id}/notes/{noteId}`
|
||||
|
||||
### 低优先级(优化项)
|
||||
|
||||
- [ ] WXML 中 `suggestion-intro` 硬编码文案改为从 AI 分析结果动态获取
|
||||
- [ ] 移除或条件隐藏调试面板(`showDebugPanel` 相关代码)
|
||||
- [ ] 统一维客线索数据结构:页面硬编码的 `RetentionClue` 接口与 `mock-data.ts` 中定义的不一致
|
||||
- [ ] `formatMoney` 已 import 但 TS 中未使用(WXML 用 WXS 版),可清理无用 import
|
||||
- [ ] `dev-fab` 组件已在 JSON 注册但 WXML 未引用,可清理
|
||||
278
docs/miniprogram-dev/api-audit/task-list.md
Normal file
278
docs/miniprogram-dev/api-audit/task-list.md
Normal file
@@ -0,0 +1,278 @@
|
||||
# task-list 页面数据来源排查
|
||||
|
||||
> 排查日期:2026-03-18
|
||||
> 页面路径:pages/task-list/task-list
|
||||
> 引用文件:`utils/mock-data.ts`、`utils/money.ts`、`utils/time.ts`、`utils/request.ts`(未使用)
|
||||
|
||||
## 概览
|
||||
|
||||
| 分类 | 数量 | 说明 |
|
||||
|------|------|------|
|
||||
| Mock 数据 | 28 个字段 | 全部任务列表 + 业绩进度卡片均来自 mock |
|
||||
| 硬编码数据 | 12 个字段 | 用户信息、头像、动画参数、AI 建议文案等 |
|
||||
| 已对接 API | 0 个接口 | 页面当前无任何真实 API 调用 |
|
||||
| 前端计算/派生 | 14 个字段 | enrichTask 派生字段 + 进度条动画计算 |
|
||||
| 路由参数 | 0 个 | onLoad 未读取 options |
|
||||
| WXML 硬编码文案 | 5 处 | 标题、分组标签等 UI 文案 |
|
||||
|
||||
---
|
||||
|
||||
## 一、Mock 数据
|
||||
|
||||
### 1.1 来自 mock-data.ts
|
||||
|
||||
#### 1.1.1 `mockTasks` — 任务列表主数据
|
||||
|
||||
| 字段 | 类型 | 说明 | 联调替换 API |
|
||||
|------|------|------|-------------|
|
||||
| `id` | `string` | 任务 ID | `GET /api/xcx/tasks` |
|
||||
| `customerName` | `string` | 客户姓名 | 同上 |
|
||||
| `customerAvatar` | `string` | 客户头像 URL | 同上 |
|
||||
| `taskType` | `TaskType` | 任务类型枚举:`callback` / `priority_recall` / `relationship` / `high_priority` | 同上 |
|
||||
| `taskTypeLabel` | `string` | 任务类型中文标签 | 同上(或前端根据 taskType 映射) |
|
||||
| `deadline` | `string` | 截止日期 ISO 字符串 | 同上 |
|
||||
| `heartScore` | `number` | 爱心评分 0-10 | 同上 |
|
||||
| `hobbies` | `string[]` | 客户爱好标签 | 同上 |
|
||||
| `isPinned` | `boolean` | 是否置顶 | 同上 |
|
||||
| `hasNote` | `boolean` | 是否有备注 | 同上 |
|
||||
| `status` | `'pending' \| 'completed' \| 'abandoned'` | 任务状态 | 同上 |
|
||||
|
||||
- 导入方式:`import { mockTasks } from '../../utils/mock-data'`(task-list.ts L2)
|
||||
- `loadData()` 中直接展开 `mockTasks` 并追加了一条内联 mock 任务 `task-007`(见 1.2 节)
|
||||
|
||||
#### 1.1.2 `mockPerformance` — 绩效概览数据
|
||||
|
||||
| 字段 | 类型 | 当前 Mock 值 | 联调替换 API |
|
||||
|------|------|-------------|-------------|
|
||||
| `monthlyIncome` | `number` | `12680` | `GET /api/xcx/performance/summary` |
|
||||
| `incomeChange` | `number` | `15.3` | 同上 |
|
||||
| `currentTier` | `string` | `'银牌助教'` | 同上 |
|
||||
| `nextTierGap` | `number` | `3320` | 同上 |
|
||||
| `todayServiceCount` | `number` | `4` | 同上 |
|
||||
| `weekServiceCount` | `number` | `18` | 同上 |
|
||||
| `monthServiceCount` | `number` | `67` | 同上 |
|
||||
|
||||
- 导入方式:`import { mockPerformance } from '../../utils/mock-data'`(task-list.ts L2)
|
||||
- 当前仅用 `mockPerformance.currentTier` 赋值给 `bannerTitle`(L233)
|
||||
|
||||
### 1.2 页面内联 Mock
|
||||
|
||||
#### 1.2.1 `task-007` 内联任务(loadData 内,约 L213-L225)
|
||||
|
||||
```typescript
|
||||
{
|
||||
id: 'task-007',
|
||||
customerName: '孙丽',
|
||||
customerAvatar: '/assets/images/avatar-default.png',
|
||||
taskType: 'callback',
|
||||
taskTypeLabel: '客户回访',
|
||||
deadline: '2026-03-06',
|
||||
heartScore: 3.5,
|
||||
hobbies: [],
|
||||
isPinned: false,
|
||||
hasNote: false,
|
||||
status: 'abandoned',
|
||||
}
|
||||
```
|
||||
|
||||
- 联调时需删除,改为 API 返回的完整任务列表
|
||||
|
||||
#### 1.2.2 `buildPerfData()` — 业绩进度卡片构造函数(约 L130-L160)
|
||||
|
||||
| 字段 | Mock 值 | 说明 | 联调替换 API |
|
||||
|------|---------|------|-------------|
|
||||
| `nextTierHours` | `100` | 下一档位小时数 | `GET /api/xcx/performance/progress` |
|
||||
| `remainHours` | `12.5` | 距下一档剩余小时 | 同上 |
|
||||
| `currentTier` | `1` | 当前档位序号 | 同上 |
|
||||
| `tierProgress` | `58` | 当前段内进度% | 同上(或前端计算) |
|
||||
| `filledPct` | `39.8`(87.5/220×100) | 总进度条百分比 | 同上(或前端计算) |
|
||||
| `ticks` | `buildTicks([0,100,130,160,190,220], 220)` | 刻度数组 | 同上(档位节点由接口返回) |
|
||||
| `basicHours` | `'77.5'` | 基础课时 | 同上 |
|
||||
| `bonusHours` | `'12'` | 激励课时 | 同上 |
|
||||
| `totalHours` | `'87.5'` | 总课时 | 同上 |
|
||||
| `tierCompleted` | `true` | 是否达标 | 同上 |
|
||||
| `bonusMoney` | `'800'` | 达标奖金(元) | 同上 |
|
||||
| `incomeMonth` | `'2月'` | 收入月份 | 同上 |
|
||||
| `prevMonth` | `'1月'` | 对比月份 | 同上 |
|
||||
| `incomeFormatted` | `'6,206'` | 预计收入格式化 | 同上 |
|
||||
| `incomeTrend` | `'↓368'` | 收入趋势文案 | 同上 |
|
||||
| `incomeTrendDir` | `'down'` | 趋势方向 | 同上 |
|
||||
|
||||
#### 1.2.3 `enrichTask()` 内联 Mock 逻辑(约 L100-L125)
|
||||
|
||||
| 派生字段 | Mock 算法 | 说明 |
|
||||
|----------|-----------|------|
|
||||
| `lastVisitDays` | `(id.charCodeAt(last) % 15) + 1` | 伪随机天数,联调时应由 API 返回 |
|
||||
| `balanceLabel` | `formatMoney((id.charCodeAt(last) * 137) % 5000 + 200)` | 伪随机余额,联调时应由 API 返回 |
|
||||
| `aiSuggestion` | 从 5 条硬编码文案中按 id 取模选取 | 联调时应由 AI 接口返回 |
|
||||
|
||||
5 条硬编码 AI 建议文案:
|
||||
1. `'建议推荐斯诺克进阶课程,提升客户粘性'`
|
||||
2. `'客户近期消费下降,建议电话关怀了解原因'`
|
||||
3. `'适合推荐周末球友赛活动,增强社交体验'`
|
||||
4. `'高价值客户,建议维护关系并推荐VIP权益'`
|
||||
5. `'新客户首次体验后未续费,建议跟进意向'`
|
||||
|
||||
#### 1.2.4 `loadData()` 中的 600ms 模拟延迟
|
||||
|
||||
```typescript
|
||||
setTimeout(() => { /* 全部数据构造逻辑 */ }, 600)
|
||||
```
|
||||
|
||||
- 联调时替换为真实 API 请求
|
||||
|
||||
---
|
||||
|
||||
## 二、硬编码数据
|
||||
|
||||
| 字段 | 当前值 | 应改为 | 风险 | 所在位置 |
|
||||
|------|--------|--------|------|----------|
|
||||
| `userName` | `'小燕'` | `globalData.userInfo.name` 或登录 API | 高 | data 初始化 |
|
||||
| `userRole` | `'助教'` | `globalData.userInfo.role` 或登录 API | 高 | data 初始化 |
|
||||
| `storeName` | `'广州朗朗桌球'` | `globalData.storeInfo.name` 或登录 API | 高 | data 初始化 |
|
||||
| `avatarUrl` | `'/assets/images/avatar-coach.png'` | `globalData.userInfo.avatar` 或登录 API | 中 | data 初始化 |
|
||||
| `DETAIL_ROUTE` | `'/pages/task-detail/task-detail'` | 保持硬编码(路由常量) | 低 | 常量定义 |
|
||||
| `tierNodes` | `[0, 100, 130, 160, 190, 220]` | 业绩进度 API 返回 | 高 | `buildPerfData()` + `_applyDebugHours()` |
|
||||
| `maxHours` | `220` | 业绩进度 API 返回 | 高 | `buildPerfData()` |
|
||||
| `total` | `87.5` | 业绩进度 API 返回 | 高 | `buildPerfData()` |
|
||||
| `aiColor` | 随机选取 `['red','orange','yellow','blue','indigo','purple']` | 可保持前端随机(纯 UI) | 低 | `onLoad()` / `onShow()` |
|
||||
| `abandonReason` | `'客户已转至其他门店'` | API 返回(放弃原因由用户输入) | 中 | `enrichTask()` |
|
||||
| `suggestions[]` | 5 条固定文案 | AI 建议 API 返回 | 高 | `enrichTask()` |
|
||||
| `hasMore` | `true` → 触底后 `false` | 分页 API 返回 `has_next` | 中 | `loadData()` / `onReachBottom()` |
|
||||
|
||||
---
|
||||
|
||||
## 三、已对接 API
|
||||
|
||||
**当前页面无任何真实 API 调用。**
|
||||
|
||||
- `utils/request.ts` 已导出 `request()` 函数(支持 token 自动附加、401 刷新重试)
|
||||
- task-list.ts 未 import `request`,所有数据均来自 mock
|
||||
|
||||
---
|
||||
|
||||
## 四、前端计算/派生数据
|
||||
|
||||
### 4.1 任务分组(loadData 内)
|
||||
|
||||
| 派生字段 | 计算逻辑 | 依赖源数据 |
|
||||
|----------|----------|-----------|
|
||||
| `pinnedTasks` | `enriched.filter(t => t.isPinned && !t.isAbandoned)` | `allTasks` |
|
||||
| `normalTasks` | `enriched.filter(t => !t.isPinned && !t.isAbandoned && t.status === 'pending')` | `allTasks` |
|
||||
| `abandonedTasks` | `enriched.filter(t => t.isAbandoned)` | `allTasks` |
|
||||
| `taskCount` | `pinnedTasks.length + normalTasks.length + abandonedTasks.length` | 三组任务 |
|
||||
| `pageState` | `totalCount > 0 ? 'normal' : 'empty'` | `taskCount` |
|
||||
|
||||
### 4.2 enrichTask 派生字段
|
||||
|
||||
| 派生字段 | 计算逻辑 | 依赖 |
|
||||
|----------|----------|------|
|
||||
| `isAbandoned` | `task.status === 'abandoned'` | `task.status` |
|
||||
| `deadlineLabel` | `formatDeadline(task.deadline).text` | `task.deadline` + `utils/time.ts` |
|
||||
| `deadlineStyle` | `formatDeadline(task.deadline).style` | `task.deadline` + `utils/time.ts` |
|
||||
| `balanceLabel` | `formatMoney(balanceSeedNum)` | Mock 种子值 + `utils/money.ts`(联调后改为 API 余额) |
|
||||
|
||||
### 4.3 进度条动画计算
|
||||
|
||||
| 派生字段 | 计算逻辑 | 依赖 |
|
||||
|----------|----------|------|
|
||||
| `filledPct` | `Math.min(100, (total / 220) * 100)` | `total`、`maxHours` |
|
||||
| `clampedSparkPct` | `Math.max(0, Math.min(100, filledPct))` | `filledPct` |
|
||||
| `shineDurMs` | `calcShineDur(filledPct)` — 基于 `SHINE_SPEED` 和进度百分比 | `filledPct`、`SHINE_SPEED` |
|
||||
| `ticks[]` | `buildTicks(tierNodes, maxHours)` — 计算每个刻度的 `left` 百分比位置 | `tierNodes`、`maxHours` |
|
||||
|
||||
### 4.4 调试面板计算(`_applyDebugHours`)
|
||||
|
||||
| 派生字段 | 说明 |
|
||||
|----------|------|
|
||||
| `currentTier` | 遍历 tiers 数组确定当前档位 |
|
||||
| `tierProgress` | 当前段内进度百分比 |
|
||||
| `remainHours` | `nextTierHours - total` |
|
||||
| `tierCompleted` | `total >= 220` |
|
||||
|
||||
> 调试面板仅开发阶段使用,联调时可保留或移除。
|
||||
|
||||
---
|
||||
|
||||
## 五、路由参数
|
||||
|
||||
`onLoad()` 未读取任何 `options` 参数。页面作为 TabBar 页面,不接收路由参数。
|
||||
|
||||
---
|
||||
|
||||
## 六、WXML 中的硬编码文案
|
||||
|
||||
| 文案 | 位置 | 类型 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `'今日 客户维护'` | section-header | UI 文案 | 可保持硬编码,或由 API 返回标题 |
|
||||
| `'📌 置顶'` | group-label--pinned | UI 文案 | 保持硬编码 |
|
||||
| `'正常任务'` | group-label--normal | UI 文案 | 保持硬编码 |
|
||||
| `'已放弃'` | group-label--abandoned | UI 文案 | 保持硬编码 |
|
||||
| `'暂无待办任务'` | state-empty | UI 文案 | 保持硬编码 |
|
||||
| `'加载失败,请重试'` | state-error | UI 文案 | 保持硬编码 |
|
||||
| `'没有更多了'` | load-more | UI 文案 | 保持硬编码 |
|
||||
| `'最近到店:{{...}}天前 · 余额:{{...}}'` | card-row-2 | 模板文案 | 保持硬编码(数据部分由变量填充) |
|
||||
| `'放弃原因:{{...}}'` | card-row-abandon | 模板文案 | 保持硬编码 |
|
||||
| `'基础课 \| 激励课 \| 全部'` | hours-label | UI 文案 | 保持硬编码 |
|
||||
|
||||
> 以上均为 UI 展示文案,非业务数据硬编码,联调时无需替换。
|
||||
|
||||
---
|
||||
|
||||
## 七、联调 TODO
|
||||
|
||||
### 高优先(P0 — 页面核心功能)
|
||||
|
||||
- [ ] 替换 `mockTasks` + 内联 `task-007` 为 `GET /api/xcx/tasks` API 调用
|
||||
- [ ] 替换 `buildPerfData()` 为 `GET /api/xcx/performance/progress` API 调用
|
||||
- [ ] 从 `globalData` 或登录 API 获取 `userName`、`userRole`、`storeName`、`avatarUrl`
|
||||
- [ ] 替换 `enrichTask()` 中的 mock 派生字段(`lastVisitDays`、`balanceLabel`)为 API 返回字段
|
||||
- [ ] 替换 `enrichTask()` 中的 `aiSuggestion` 硬编码文案为 AI 建议 API
|
||||
- [ ] 替换 `mockPerformance` 引用为真实绩效 API
|
||||
- [ ] 档位节点 `tierNodes` 和 `maxHours` 改为 API 返回
|
||||
|
||||
### 中优先(P1 — 交互完整性)
|
||||
|
||||
- [ ] 实现分页加载:`onReachBottom` 对接分页 API(`page` / `cursor` 参数)
|
||||
- [ ] `onCtxPin` 置顶操作对接 `POST /api/xcx/tasks/{id}/pin` API
|
||||
- [ ] `onCtxAbandon` / `onAbandonConfirm` 放弃操作对接 `POST /api/xcx/tasks/{id}/abandon` API
|
||||
- [ ] `onCtxCancelAbandon` 取消放弃对接 `POST /api/xcx/tasks/{id}/cancel-abandon` API
|
||||
- [ ] `onNoteConfirm` 备注保存对接 `POST /api/xcx/tasks/{id}/notes` API
|
||||
- [ ] `loadData` 中移除 `setTimeout 600ms` 模拟延迟
|
||||
|
||||
### 低优先(P2 — 可后续处理)
|
||||
|
||||
- [ ] 移除 `enrichTask()` 函数(字段由 API 直接返回)
|
||||
- [ ] 移除 `buildPerfData()` 函数(数据由 API 直接返回)
|
||||
- [ ] 移除 `buildTicks()` 函数(或保留为前端工具函数,接收 API 返回的 tierNodes)
|
||||
- [ ] 评估是否保留调试面板(`showDebugPanel` 相关逻辑)
|
||||
- [ ] 清理 `mock-data.ts` 中 task-list 不再使用的导出
|
||||
|
||||
---
|
||||
|
||||
## 八、依赖组件清单
|
||||
|
||||
| 组件 | 路径 | 数据来源 |
|
||||
|------|------|----------|
|
||||
| `perf-progress-bar` | `/components/perf-progress-bar/` | 接收 `perfData.*` 属性(全部 mock) |
|
||||
| `heart-icon` | `/components/heart-icon/` | 接收 `item.heartScore`(mock) |
|
||||
| `ai-inline-icon` | `/components/ai-inline-icon/` | 接收 `aiColor`(前端随机) |
|
||||
| `ai-float-button` | `/components/ai-float-button/` | 无数据依赖 |
|
||||
| `note-modal` | `/components/note-modal/` | 接收 `noteTarget.customerName`(mock) |
|
||||
| `abandon-modal` | `/components/abandon-modal/` | 接收 `abandonTarget.customerName`(mock) |
|
||||
| `dev-fab` | `/components/dev-fab/` | 无数据依赖(开发工具) |
|
||||
| `t-icon` | TDesign 组件 | 无业务数据依赖 |
|
||||
|
||||
---
|
||||
|
||||
## 九、风险总结
|
||||
|
||||
| 风险项 | 等级 | 说明 |
|
||||
|--------|------|------|
|
||||
| 全页面零 API 调用 | 🔴 高 | 联调时需一次性替换所有数据源,工作量集中 |
|
||||
| 用户信息硬编码 | 🔴 高 | `userName`/`userRole`/`storeName` 写死,多用户场景必崩 |
|
||||
| AI 建议文案固定 | 🔴 高 | 5 条文案轮转,联调时需对接 AI 服务 |
|
||||
| 业绩进度全量 mock | 🔴 高 | 档位、课时、收入全部硬编码,数值与真实数据无关 |
|
||||
| 分页未实现 | 🟡 中 | `onReachBottom` 仅显示"没有更多了",无真实分页 |
|
||||
| 操作无持久化 | 🟡 中 | 置顶/放弃/备注仅修改本地 data,刷新即丢失 |
|
||||
| `enrichTask` 伪随机 | 🟡 中 | `lastVisitDays`/`balanceLabel` 由 id 字符码计算,非真实数据 |
|
||||
Reference in New Issue
Block a user