feat: batch update - gift card breakdown spec, backend APIs, miniprogram pages, ETL finance recharge, docs & migrations
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
> 本文档记录项目中所有文档资产的位置、类型和内容概要,方便快速定位。
|
||||
> 归档规则见末尾「文档归档规则」章节;程序输出路径规范见 `docs/deployment/EXPORT-PATHS.md`。
|
||||
> 最后更新:2026-03-19(RNS1.3 三看板接口文档补充)
|
||||
|
||||
---
|
||||
|
||||
@@ -53,8 +54,9 @@
|
||||
| `audit_dashboard.md` | 审计仪表盘,汇总所有变更审计记录 |
|
||||
| `README.md` | 审计目录说明 |
|
||||
| `SESSION-LOG-GUIDE.md` | Session 日志使用指南:索引字段说明、查询方法、典型场景、与其他审计产物的关系 |
|
||||
| `changes/` | 39 份变更审计文档(`YYYY-MM-DD__<slug>.md` 格式),每份包含:变更原因、影响范围、回滚策略、验证 SQL |
|
||||
| `prompt_logs/` | ~500 份 Prompt 日志(`prompt_log_YYYYMMDD_HHMMSS.md`),记录每次 AI 交互的输入输出 |
|
||||
| `AUDIT-HOOKS-GUIDE.md` | 审计 Hooks 使用指南:Hook 触发机制、配置方式、与审计流程的集成 |
|
||||
| `changes/` | 56 份变更审计文档(`YYYY-MM-DD__<slug>.md` 格式),每份包含:变更原因、影响范围、回滚策略、验证 SQL |
|
||||
| `prompt_logs/` | Prompt 日志(`prompt_log_YYYYMMDD_HHMMSS.md`),记录每次 AI 交互的输入输出 |
|
||||
| `session_logs/` | 全量会话记录(按 `YYYY-MM/DD/` 分层),含双索引(`_session_index.json` / `_session_index_full.json`)、每轮 execution 的完整 Markdown 记录、LLM 生成的操作摘要 |
|
||||
|
||||
### 2.4 数据契约 `docs/contracts/`
|
||||
@@ -70,19 +72,26 @@
|
||||
| 文件 | 内容 |
|
||||
|------|------|
|
||||
| `LAUNCH-CHECKLIST.md` | 上线检查清单:环境配置、数据库迁移、服务启动、验证步骤 |
|
||||
| `launch-checklist-patch.md` | 上线检查清单补丁 |
|
||||
| `EXPORT-PATHS.md` | 输出路径规范:环境变量映射表、目录结构、新增场景检查清单 |
|
||||
| `PRIVACY-POLICY.md` | 隐私政策文档 |
|
||||
| `wx-api-security-guide.md` | 微信 API 安全指南 |
|
||||
| `wx-encrypt-guide.md` | 微信加密指南 |
|
||||
|
||||
### 2.6 产品需求 `docs/prd/`
|
||||
|
||||
| 路径 | 内容 |
|
||||
|------|------|
|
||||
| `小程序前后端.txt` | 小程序前后端原始需求描述 |
|
||||
| `后端接口需求说明_数据需求PRD.md` | 后端接口需求说明与数据需求 PRD |
|
||||
| `PRD审阅-Q&A.md` | PRD 审阅问答记录(第一轮) |
|
||||
| `PRD审阅-Q&A-R2.md` | PRD 审阅问答记录(第二轮) |
|
||||
| `SPI 消费力指数.md` | 消费力指数(SPI)算法需求说明 |
|
||||
| `ai-app-prompts.md` | AI 应用 Prompt 设计 |
|
||||
| `AI需求2.md` | AI 需求第二版 |
|
||||
| `specs/00-数据依赖矩阵.md` | 各 SPEC 间的数据依赖关系矩阵 |
|
||||
| `specs/01-SPEC任务拆分总览.md` | 11 个 SPEC 的任务拆分总览 |
|
||||
| `specs/P1~P11` | 11 份 SPEC 拆分文档,覆盖:数据库基础(P1)、ETL DWS 扩展(P2)、认证系统(P3)、核心业务(P4)、AI 集成(P5)、前端任务/绩效/看板/详情(P6-P9)、租户管理后台(P10)、部署上线(P11) |
|
||||
| `specs/P1~P11` | 15 份 SPEC 拆分文档,覆盖:数据库基础(P1)、ETL DWS 扩展(P2)、认证系统(P3)、核心业务(P4)、AI 集成(P5/P5.1/P5.2)、前端任务/绩效/看板/详情(P6-P9)、租户管理后台(P10)、部署上线(P11) |
|
||||
|
||||
### 2.7 小程序 UI 原型 `docs/h5_ui/`
|
||||
|
||||
@@ -91,40 +100,32 @@ H5 静态原型页面,用于小程序 UI 设计参考。
|
||||
| 路径 | 内容 |
|
||||
|------|------|
|
||||
| `index.html` | 原型首页入口 |
|
||||
| `pages/` | 23 个页面原型,包括:登录(`login`)、申请(`apply`)、审核中(`reviewing`)、无权限(`no-permission`)、任务列表/详情(`task-list`/`task-detail`)、绩效(`performance`/`performance-records`)、助教详情(`coach-detail`)、客户详情(`customer-detail`)、客户服务记录(`customer-service-records`)、看板(`board-coach`/`board-customer`/`board-finance`)、聊天(`chat`/`chat-history`)、个人中心(`my-profile`)、首页设置(`home-settings`)、笔记(`notes`)、AI 图标演示(`ai-icon-demo`) |
|
||||
| `pages/` | 23+ 个页面原型,包括:登录(`login`)、申请(`apply`)、审核中(`reviewing`)、无权限(`no-permission`)、任务列表/详情(`task-list`/`task-detail`/`task-detail-callback`/`task-detail-priority`/`task-detail-relationship`)、绩效(`performance`/`performance-records`)、助教详情(`coach-detail`)、客户详情(`customer-detail`)、客户服务记录(`customer-service-records`)、看板(`board-coach`/`board-customer`/`board-finance`)、聊天(`chat`/`chat-history`)、个人中心(`my-profile`)、笔记(`notes`) |
|
||||
| `pages/_baked/` | 已烘焙(固化)的页面快照 |
|
||||
| `css/` | 6 个样式文件 |
|
||||
| `js/` | 8 个交互脚本 |
|
||||
| `img/` | 图片资源 |
|
||||
| `tools/` | H5 转小程序检查工具(`h5-to-mp-checker/`) |
|
||||
|
||||
### 2.8 小程序前端开发指南 `docs/miniprogram-dev/`
|
||||
|
||||
微信小程序前端页面开发与 H5 原型迁移的统一文档中心。覆盖批量自动迁移、用户指定半自动开发、新页面从零开发三种模式。
|
||||
微信小程序前端页面开发与 H5 原型迁移的统一文档中心。
|
||||
|
||||
| 路径 | 内容 |
|
||||
|------|------|
|
||||
| `README.md` | 入口索引:快速导航、目录结构、Power 依赖、关联资源 |
|
||||
| `README.md` | 入口索引:快速导航、目录结构、关联资源 |
|
||||
| `QUICK-REFERENCE.md` | 快速参考手册 |
|
||||
| `API-contract.md` | API 契约文档 |
|
||||
| `API-OUTPUT-SPEC.md` | 后端接口输出规范(DS-API-OUTPUT-001):字段类型约定 / 枚举值 / 各页面接口字段对照表 |
|
||||
| `API-requirement.md` | API 需求文档 |
|
||||
| `design-system/VI-DESIGN-SYSTEM.md` | VI 设计系统:任务/客户/等级配色、AI 图标、CSS 变量速查 |
|
||||
| `design-system/DATETIME-DISPLAY-STANDARD.md` | 日期时间展示规范:由近及远 6 级展示规则、边界处理、JS 实现参考 |
|
||||
| `design-system/DISPLAY-STANDARDS.md` | 前端展示规范(第1-6章):金额 / 课时 / 计数 / 空值 / 百分比 / 等级文案 |
|
||||
| `design-system/DISPLAY-STANDARDS-2.md` | 前端展示规范(第7-9章):截止日期 / 评分星级 / Mock 数据规范 + 工具函数总表 |
|
||||
| `API-OUTPUT-SPEC.md` | 后端接口输出规范(DS-API-OUTPUT-001):字段类型约定 / 枚举值 / 各页面接口字段对照表 |
|
||||
| `01-orchestration/batch-auto-playbook.md` | 批量自动模式:89 单元编排 + 4 代理流水线 |
|
||||
| `01-orchestration/user-guided-playbook.md` | 半自动模式:对话触发 + 按需执行 |
|
||||
| `01-orchestration/new-page-playbook.md` | 新页面开发:有原型 / 无原型两条路径 |
|
||||
| `02-action/screenshot-agent.md` | 截图代理:H5/MP 双端截图执行手册 |
|
||||
| `02-action/audit-agent.md` | 审计代理:结构化审计执行手册 |
|
||||
| `02-action/fix-agent.md` | 修正代理:P0-P7 分级修正执行手册 |
|
||||
| `02-action/verify-agent.md` | 验证代理:回归校验执行手册 |
|
||||
| `02-action/page-dev-agent.md` | 页面开发代理:新页面完整开发执行手册 |
|
||||
| `03-reference/wxss-rules.md` | WXSS 规范 + rpx 换算 + 颜色/字号/圆角标准值 |
|
||||
| `03-reference/page-structure-map.md` | 各页面特殊结构速查 |
|
||||
| `03-reference/css-risk-features.md` | CSS 风险特性 + 替代方案 |
|
||||
| `03-reference/power-integration.md` | Power 集成指南(4 个 Power 的调用方式) |
|
||||
| `03-reference/benchmark-history.md` | 基准测试历史记录 |
|
||||
| `04-audit/PROGRESS.md` | 89 单元进度跟踪(实时更新) |
|
||||
| `04-audit/CHANGELOG.md` | 文档体系版本变更记录 |
|
||||
| `05-lessons/pitfalls.md` | 踩坑速查 + 页面迁移经验 |
|
||||
| `05-lessons/convergence-patterns.md` | 收敛模式 + 不可消除差异白名单 |
|
||||
| `design-system/vi-guide.html` | VI 设计系统可视化指南(HTML) |
|
||||
| `api-audit/` | 19 个页面的 API 审计文档(每页一份 `.md`),含硬编码汇总(`_hardcode-summary.md`) |
|
||||
| `h5-migration/` | H5 转小程序迁移指南:桥接文档(`h5-to-mp-bridge.md`)、附录(`appendix/`) |
|
||||
| `API-requirement/` | API 需求子目录(预留) |
|
||||
|
||||
### 2.9 参考资料 `docs/reference/`
|
||||
|
||||
@@ -137,10 +138,15 @@ H5 静态原型页面,用于小程序 UI 设计参考。
|
||||
|
||||
一次性数据分析、调研产出的报告文档。与 `prd/specs/` 的区别:specs 是需求规格,reports 是基于数据的分析结论。
|
||||
|
||||
| 文件 | 内容 |
|
||||
| 路径 | 内容 |
|
||||
|------|------|
|
||||
| `complex-orders-analysis.md` | 复杂订单结构分析(ODS 全量扫描,关联键与复杂度定义) |
|
||||
| `dwd-amount-duration-calibration.md` | DWD 层金额·绩效·时长字段口径全景(置信度与存疑项标注) |
|
||||
| `DWD-DOC/` | **权威标杆文档**(业务模型与财务数据权威数据源):消费链路全景(01)、支付渠道与对账公式(02)、收入构成与储值卡资金流(03)、维度表全景(04)、F2 收支平衡公式(05)、校准清单(06)、GAP 闭环状态(07)、consume_money 口径时间线(`consume/`) |
|
||||
| `business-analysis/` | 业务分析报告:每日营收报告(`daily-revenue-latest.md`)、高价值充值客户分析(`high-value-recharge-customer-analysis.md`) |
|
||||
| `etl-calibration/` | ETL 校准报告:DWS BD 手册校准(2026-03-07)、DWS 代码校准(2026-03-07)、财务看板 DWS 审计(2026-03-07) |
|
||||
| `h5-mp-conversion/` | H5 转小程序报告:财务看板审计(`board-finance-h5-mp-audit.md`)、H5 UI 提取(`h5-ui-extraction.md`) |
|
||||
| `p4-task/` | P4 任务报告:Spec 与实现差距分析、任务生命周期全景 |
|
||||
| `tech-solution/` | 技术方案:百炼技术方案(`bailian-technical-solution.md`) |
|
||||
| `vi-color-audit/` | VI 配色审计:详细审计(Phase2)、完成报告、实施文档、合规审计 |
|
||||
|
||||
### 2.11 架构文档 `docs/architecture/`
|
||||
|
||||
@@ -148,20 +154,26 @@ H5 静态原型页面,用于小程序 UI 设计参考。
|
||||
|------|------|
|
||||
| `etl-feiqiu-architecture.md` | ETL Connector 整体架构说明(数据流、DWS/INDEX 任务、调度编排、CLI) |
|
||||
|
||||
### 2.12 其他项目级文档目录
|
||||
### 2.12 MCP 文档 `docs/mcp/`
|
||||
|
||||
| 文件 | 内容 |
|
||||
|------|------|
|
||||
| `AI-DATABASE-QUERY-MANUAL.md` | AI 数据库查询手册 |
|
||||
| `WEIXIN-DEVTOOLS-MCP.md` | 微信开发者工具 MCP 集成文档 |
|
||||
|
||||
### 2.13 其他项目级文档目录
|
||||
|
||||
| 路径 | 内容 |
|
||||
|------|------|
|
||||
| `docs/architecture/` | 架构文档(预留,待填充) |
|
||||
| `docs/roadmap/BACKLOG.md` | 项目待办事项 |
|
||||
| `docs/roadmap/2026-02-24__fdw-dwd-to-core-migration-plan.md` | FDW + DWD→Core 迁移计划 |
|
||||
| `docs/migrate/monorepo-migration-summary.md` | Monorepo 迁移总结 |
|
||||
| `docs/migrate/oldworkspace-kiro-agent-config-summary.md` | 旧工作区 Kiro 配置迁移记录 |
|
||||
| `docs/ops/` | 运维文档(预留,待填充) |
|
||||
| `docs/ops/init-test-user.md` | 测试用户初始化指南 |
|
||||
| `docs/permission_matrix/` | 权限矩阵(预留,待填充) |
|
||||
| `docs/vi-standards/` | VI 设计标准(预留,待填充) |
|
||||
| `docs/spec-input/2026-02-22__etl-aggregation-fix-spec-input.md` | ETL 聚合修复的 Spec 输入文档 |
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 三、模块内部文档
|
||||
@@ -173,6 +185,38 @@ H5 静态原型页面,用于小程序 UI 设计参考。
|
||||
| `README.md` | 架构概览、双库连接、认证系统、17 个路由模块摘要、服务层、配置加载、触发器系统 |
|
||||
| `docs/API-REFERENCE.md` | 完整 API 参考:17 个路由模块的所有端点、请求/响应示例、认证要求、错误码 |
|
||||
|
||||
RNS1.2 新增模块(客户与助教接口):
|
||||
|
||||
| 路径 | 内容 |
|
||||
|------|------|
|
||||
| `app/routers/xcx_customers.py` | 客户端点:CUST-1 客户详情、CUST-2 客户服务记录 |
|
||||
| `app/routers/xcx_coaches.py` | 助教端点:COACH-1 助教详情 |
|
||||
| `app/services/customer_service.py` | 客户查询服务:Banner 概览、AI 洞察、消费记录嵌套、coachTasks、favoriteCoaches |
|
||||
| `app/services/coach_service.py` | 助教查询服务:绩效、收入、任务分组、TOP 客户、历史月份 |
|
||||
| `app/schemas/xcx_customers.py` | 客户相关 Pydantic Schema(CustomerDetailResponse、CustomerRecordsResponse 等) |
|
||||
| `app/schemas/xcx_coaches.py` | 助教相关 Pydantic Schema(CoachDetailResponse 等) |
|
||||
| `tests/unit/test_degradation_rns12.py` | RNS1.2 优雅降级单元测试 |
|
||||
| `tests/unit/test_auth_rns12.py` | RNS1.2 权限校验单元测试 |
|
||||
| `tests/integration/test_e2e_customer_coach.py` | CUST-1/CUST-2/COACH-1 端到端集成测试 |
|
||||
|
||||
RNS1.3 新增模块(三看板接口):
|
||||
|
||||
| 路径 | 内容 |
|
||||
|------|------|
|
||||
| `app/routers/xcx_board.py` | 看板端点:BOARD-1 助教看板、BOARD-2 客户看板、BOARD-3 财务看板 |
|
||||
| `app/routers/xcx_config.py` | 配置端点:CONFIG-1 技能类型 |
|
||||
| `app/services/board_service.py` | 看板编排服务:日期范围/环比/排序/分页/降级 |
|
||||
| `app/schemas/xcx_board.py` | 看板相关 Pydantic Schema(7 枚举 + ~40 响应 Schema) |
|
||||
| `app/schemas/xcx_config.py` | 配置相关 Pydantic Schema(SkillTypeItem) |
|
||||
|
||||
Monorepo 级属性测试(`tests/`):
|
||||
|
||||
| 路径 | 内容 |
|
||||
|------|------|
|
||||
| `tests/test_rns12_properties.py` | RNS1.2 属性测试(14 个 Property,Hypothesis 框架) |
|
||||
| `tests/test_board_properties.py` | RNS1.3 属性测试(18 个测试函数,12 个 Property,Hypothesis 框架) |
|
||||
| `tests/test_board_service_unit.py` | RNS1.3 看板工具函数单元测试 |
|
||||
|
||||
### 3.2 ETL Connector `apps/etl/connectors/feiqiu/`
|
||||
|
||||
| 路径 | 内容 |
|
||||
@@ -234,44 +278,111 @@ H5 静态原型页面,用于小程序 UI 设计参考。
|
||||
|
||||
### 5.1 Steering 文件(`.kiro/steering/`)
|
||||
|
||||
13 个 Steering 文件,控制 AI 助手的行为规范:
|
||||
17 个 Steering 文件,控制 AI 助手的行为规范:
|
||||
|
||||
| 文件 | 作用 |
|
||||
|------|------|
|
||||
| `language-zh.md` | 语言规范:输出简体中文,代码标识符保留英文 |
|
||||
| `governance.md` | 治理规范:审计触发条件、执行方式、产物要求 |
|
||||
| `product.md` / `product-full.md` | 产品概述(精简版 / 完整版) |
|
||||
| `agent-behavior.md` | AI 执行行为约束:上下文保护、子代理委托场景 |
|
||||
| `planning-interrogation.md` | 编码前需求审问:必问清单、追问规则 |
|
||||
| `pre-change-research.md` | 逻辑改动前置调研:子代理调研流程、Session 索引查询 |
|
||||
| `project-overview.md` | 项目概览(精简版) |
|
||||
| `product-full.md` | 产品概述(完整版,fileMatch 自动加载) |
|
||||
| `tech.md` / `tech-full.md` | 技术栈与构建(精简版 / 完整版) |
|
||||
| `structure-lite.md` / `structure.md` | 项目结构(精简版 / 完整版) |
|
||||
| `structure.md` | 项目结构(完整版,fileMatch 自动加载) |
|
||||
| `export-paths.md` / `export-paths-full.md` | 输出路径规范(精简版 / 完整版) |
|
||||
| `testing-env.md` | 测试环境规范:环境变量加载、cwd 要求、测试库使用 |
|
||||
| `db-docs.md` | 数据库文档规范 |
|
||||
| `doc-map.md` | 文档地图 Steering(fileMatch 触发) |
|
||||
| `deprecated-objects.md` | 归档目录与废弃对象规则 |
|
||||
| `dwd-doc-authority.md` | DWD-DOC 标杆文档权威性声明 |
|
||||
| `steering-readme-maintainer.md` | README 维护者技能:变更影响审查与文档同步 |
|
||||
|
||||
### 5.2 Spec 文件(`.kiro/specs/`)
|
||||
### 5.2 Hooks(`.kiro/hooks/`)
|
||||
|
||||
17 个 Spec 目录,每个包含 `requirements.md`、`design.md`、`tasks.md` 三件套:
|
||||
11 个 Agent Hook,自动化审计与流程控制:
|
||||
|
||||
| Hook | 触发事件 | 作用 |
|
||||
|------|----------|------|
|
||||
| `agent-on-stop` | agentStop | Agent 停止时触发审计流程 |
|
||||
| `prompt-on-submit` | promptSubmit | Prompt 提交时触发合规预扫描 |
|
||||
| `pre-change-guard` | preToolUse | 写操作前的变更守卫 |
|
||||
| `cwd-guard-shell` | preToolUse | Shell 命令 cwd 守卫 |
|
||||
| `run-audit-writer` | userTriggered | 手动触发审计写入 |
|
||||
| `session-summary` | agentStop | Session 摘要生成 |
|
||||
| `etl-fullstack-integration` | postTaskExecution | ETL 全栈集成任务后处理 |
|
||||
| `etl-unified-analysis` | userTriggered | ETL 统一分析 |
|
||||
| `field-disappearance-scan` | userTriggered | 字段消失扫描 |
|
||||
| `h5-screenshot` | userTriggered | H5 截图 |
|
||||
| `daily-revenue-report` | userTriggered | 每日营收报告 |
|
||||
|
||||
### 5.3 Skills(`.kiro/skills/`)
|
||||
|
||||
3 个技能模块:
|
||||
|
||||
| 技能 | 作用 |
|
||||
|------|------|
|
||||
| `bd-manual-db-docs` | PostgreSQL schema 变更时落盘 BD 手册到 `docs/database/` |
|
||||
| `change-annotation-audit` | 每次修改生成审计记录、AI_CHANGELOG、CHANGE 标记注释 |
|
||||
| `steering-readme-maintainer` | 变更影响审查并同步更新 README 与审计记录 |
|
||||
|
||||
### 5.4 Agents(`.kiro/agents/`)
|
||||
|
||||
| Agent | 作用 |
|
||||
|-------|------|
|
||||
| `audit-writer.md` | 变更后审计 + 文档同步,输出审计产物 |
|
||||
|
||||
### 5.5 Scripts(`.kiro/scripts/`)
|
||||
|
||||
11 个自动化脚本,支撑 Hooks 和审计流程:
|
||||
|
||||
| 脚本 | 作用 |
|
||||
|------|------|
|
||||
| `agent_on_stop.py` | Agent 停止时的审计处理 |
|
||||
| `prompt_on_submit.py` | Prompt 提交时的合规预扫描 |
|
||||
| `audit_flagger.py` | 审计标记器 |
|
||||
| `audit_reminder.py` | 审计提醒器 |
|
||||
| `build_audit_context.py` | 构建审计上下文 |
|
||||
| `change_compliance_prescan.py` | 变更合规预扫描 |
|
||||
| `file_baseline.py` | 文件基线管理 |
|
||||
| `prompt_audit_log.py` | Prompt 审计日志 |
|
||||
| `session_log.py` | Session 日志管理 |
|
||||
| `_ensure_root.py` | 确保根目录工具 |
|
||||
|
||||
### 5.6 Spec 文件(`.kiro/specs/`)
|
||||
|
||||
24 个 Spec 目录,每个包含 `requirements.md`、`design.md`、`tasks.md` 三件套:
|
||||
|
||||
| Spec | 内容 |
|
||||
|------|------|
|
||||
| `01-miniapp-db-foundation` | P1:小程序数据库基础建设 |
|
||||
| `02-etl-dws-miniapp-extensions` | P2:ETL DWS 小程序扩展 |
|
||||
| `03-miniapp-auth-system` | P3:小程序认证系统 |
|
||||
| `04-miniapp-core-business` | P4:小程序核心业务 |
|
||||
| `05-miniapp-ai-integration` | P5:小程序 AI 集成 |
|
||||
| `[ETL]-fullstack-integration` | ETL 全栈集成 |
|
||||
| `miniapp-core-business` | 小程序核心业务 |
|
||||
| `miniapp-db-foundation` | 小程序数据库基础(早期版本) |
|
||||
| `admin-web-console` | 管理后台控制台 |
|
||||
| `etl-aggregation-fix` | ETL 聚合修复 |
|
||||
| `assistant-abolish-cleanup` | 助教废除清理 |
|
||||
| `business-day-cutoff` | 营业日截止逻辑 |
|
||||
| `dataflow-field-completion` | 数据流字段补全 |
|
||||
| `dataflow-structure-audit` | 数据流结构审计 |
|
||||
| `dwd-business-panorama` | DWD 业务全景 |
|
||||
| `dwd-phase1-refactor` | DWD 第一阶段重构 |
|
||||
| `etl-coupon-detail` | ETL 优惠券明细 |
|
||||
| `etl-dws-flow-refactor` | ETL DWS 流程重构 |
|
||||
| `etl-pipeline-debug` | ETL 管道调试 |
|
||||
| `etl-staff-dimension` | ETL 员工维度 |
|
||||
| `dwd-phase1-refactor` | DWD 第一阶段重构 |
|
||||
| `etl-unified-pipeline` | ETL 统一管道 |
|
||||
| `h5-miniprogram-migration` | H5 转小程序迁移 |
|
||||
| `h5-miniprogram-migration-subsequent` | H5 转小程序迁移(后续) |
|
||||
| `ods-dedup-standardize` | ODS 去重标准化 |
|
||||
| `p4-prerequisite-fixes` | P4 前置修复 |
|
||||
| `p52-miniapp-fe-all-pages` | P5.2 小程序前端全页面 |
|
||||
| `rns1-infra-contract-rewrite` | RNS1.0 基础设施与契约重写(响应包装、CamelModel、路由修正、API 契约) |
|
||||
| `rns1-task-performance-api` | RNS1.1 任务与绩效接口(TASK-1 扩展、TASK-2、PERF-1、PERF-2、前端适配) |
|
||||
| `rns1-customer-coach-api` | RNS1.2 客户与助教接口(CUST-1 客户详情、CUST-2 客户服务记录、COACH-1 助教详情) |
|
||||
| `rns1-board-apis` | RNS1.3 三看板接口(BOARD-1 助教看板、BOARD-2 客户看板、BOARD-3 财务看板、CONFIG-1 技能类型) |
|
||||
| `spi-spending-power-index` | SPI 消费力指数 |
|
||||
| `dataflow-field-completion` | 数据流字段补全 |
|
||||
| `dataflow-structure-audit` | 数据流结构审计 |
|
||||
| `assistant-abolish-cleanup` | 助教废除清理 |
|
||||
|
||||
|
||||
---
|
||||
|
||||
@@ -287,16 +398,17 @@ H5 静态原型页面,用于小程序 UI 设计参考。
|
||||
| 变更审计记录 | `audit/changes/` | `YYYY-MM-DD__<slug>.md` 格式 |
|
||||
| 产品需求规格 | `prd/specs/` | P1-P11 等需求 spec,不放分析报告 |
|
||||
| 数据契约 | `contracts/` | OpenAPI spec、JSON Schema、数据字典 |
|
||||
| 部署与运维配置 | `deployment/` | 启动清单、路径规范、安全指南 |
|
||||
| 部署与运维配置 | `deployment/` | 启动清单、路径规范、安全指南、隐私政策 |
|
||||
| 路线图与规划 | `roadmap/` | 迁移计划、BACKLOG |
|
||||
| Spec 需求输入 | `spec-input/` | 问题汇总,供开启 Spec 流程 |
|
||||
| 外部参考资料 | `reference/` | 第三方 API/SDK 指南 |
|
||||
| 迁移记录 | `migrate/` | 仓库迁移总结、配置迁移记录 |
|
||||
| MCP 相关 | `mcp/` | AI 工具集成文档 |
|
||||
| UI 原型 | `h5_ui/` | H5 静态原型页面 |
|
||||
| 小程序前端开发指南 | `miniprogram-dev/` | 页面开发流程、代理手册、规范参考、进度审计、经验教训 |
|
||||
| 运维手册 | `ops/` | 故障排查、日常运维流程 |
|
||||
| 小程序前端开发指南 | `miniprogram-dev/` | 页面开发流程、API 审计、设计系统、H5 迁移指南 |
|
||||
| 运维手册 | `ops/` | 故障排查、日常运维流程、测试用户初始化 |
|
||||
| 权限矩阵 | `permission_matrix/` | 角色-资源权限映射 |
|
||||
| VI 设计标准 | `vi-standards/` | 视觉识别设计标准 |
|
||||
|
||||
### 判断流程
|
||||
|
||||
|
||||
@@ -71,17 +71,19 @@ NeoZQYY 是面向台球门店业务的全栈数据平台,包含 6 个子系统
|
||||
| `audit/` | 统一审计目录(变更记录 + Prompt 日志) | `changes/` 变更审计、`prompt_logs/` 交互日志 |
|
||||
| `contracts/` | 数据契约 | OpenAPI spec、JSON Schema、数据字典 |
|
||||
| `database/` | 数据库设计与变更文档(BD 手册) | `BD_Manual_*.md` 表结构变更审计、`ddl/` 基线 |
|
||||
| `deployment/` | 部署与运维配置文档 | 启动清单、输出路径规范、隐私政策 |
|
||||
| `h5_ui/` | 小程序 UI 原型(H5 静态页面) | 页面原型、样式、交互脚本 |
|
||||
| `mcp/` | MCP Server 相关文档 | AI 数据库查询手册 |
|
||||
| `deployment/` | 部署与运维配置文档 | 启动清单、输出路径规范、隐私政策、微信安全/加密指南 |
|
||||
| `h5_ui/` | 小程序 UI 原型(H5 静态页面) | 页面原型、样式、交互脚本、H5 转 MP 检查工具 |
|
||||
| `mcp/` | MCP Server 相关文档 | AI 数据库查询手册、微信开发者工具 MCP |
|
||||
| `migrate/` | 迁移记录与指南 | Monorepo 迁移总结、旧配置迁移记录 |
|
||||
| `ops/` | 运维手册 | 故障排查、日常运维流程(待填充) |
|
||||
| `miniprogram-dev/` | 小程序前端开发指南 | API 审计、设计系统、H5 迁移指南、展示规范 |
|
||||
| `ops/` | 运维手册 | 测试用户初始化、故障排查 |
|
||||
| `permission_matrix/` | 权限矩阵 | 角色-资源权限映射(待填充) |
|
||||
| `prd/` | 产品需求文档 | PRD 审阅 Q&A、`specs/` 下 P1-P11 需求规格 |
|
||||
| `prd/` | 产品需求文档 | PRD 审阅 Q&A、`specs/` 下 P1-P11 需求规格(含 P5.1/P5.2) |
|
||||
| `reference/` | 外部参考资料 | 百炼 Agent 指南、DashScope API 参考 |
|
||||
| `reports/` | 数据分析报告与调研产出 | 复杂订单分析、DWD 字段口径全景等一次性分析报告 |
|
||||
| `reports/` | 数据分析报告与调研产出 | DWD-DOC 标杆文档、业务分析、ETL 校准、VI 配色审计等(按主题分子目录) |
|
||||
| `roadmap/` | 路线图与待办 | 迁移计划、BACKLOG |
|
||||
| `spec-input/` | Spec 流程的需求输入文档 | 用户提交的问题汇总,供开启 Spec 使用 |
|
||||
| `vi-standards/` | VI 设计标准 | 视觉识别设计标准(待填充) |
|
||||
|
||||
## 技术栈速览
|
||||
|
||||
|
||||
234
docs/architecture/backend-architecture.md
Normal file
234
docs/architecture/backend-architecture.md
Normal file
@@ -0,0 +1,234 @@
|
||||
# 后端架构文档 — FastAPI 服务层
|
||||
|
||||
> 更新日期:2026-03-19(RNS1.3 三看板接口 + CONFIG-1 技能类型)
|
||||
> 位置:`apps/backend/`
|
||||
> 框架:FastAPI + psycopg2(同步连接池)
|
||||
> 数据库:`zqyy_app`(业务库)+ `etl_feiqiu`(ETL 库,直连只读访问)
|
||||
|
||||
---
|
||||
|
||||
## 1. 模块交互总览
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "微信小程序"
|
||||
MP[miniprogram pages]
|
||||
end
|
||||
|
||||
subgraph "管理后台"
|
||||
ADMIN[admin-web]
|
||||
end
|
||||
|
||||
subgraph "FastAPI 后端"
|
||||
subgraph "路由层 (routers/)"
|
||||
R_AUTH[xcx_auth]
|
||||
R_TASKS[xcx_tasks]
|
||||
R_NOTES[xcx_notes]
|
||||
R_PERF[xcx_performance]
|
||||
R_CUST[xcx_customers<br/>CUST-1, CUST-2]
|
||||
R_COACH[xcx_coaches<br/>COACH-1]
|
||||
R_CHAT[xcx_ai_chat]
|
||||
R_CACHE[xcx_ai_cache]
|
||||
R_ADMIN[admin_*]
|
||||
end
|
||||
|
||||
subgraph "服务层 (services/)"
|
||||
S_TM[task_manager<br/>任务 CRUD + 列表扩展]
|
||||
S_PS[performance_service<br/>绩效概览 + 明细]
|
||||
S_NS[note_service<br/>备注 CRUD + 评分]
|
||||
S_CS[customer_service<br/>客户详情 + 服务记录]
|
||||
S_CO[coach_service<br/>助教详情]
|
||||
S_FDW[fdw_queries<br/>FDW 查询集中封装]
|
||||
S_TG[task_generator<br/>任务自动生成]
|
||||
S_TE[task_expiry<br/>过期检测]
|
||||
S_RD[recall_detector<br/>召回完成检测]
|
||||
S_WX[wechat<br/>微信登录/Token]
|
||||
end
|
||||
|
||||
subgraph "中间件 + 认证"
|
||||
MW[ResponseWrapperMiddleware]
|
||||
AUTH[JWT + require_approved]
|
||||
end
|
||||
end
|
||||
|
||||
subgraph "数据库"
|
||||
BIZ[(zqyy_app<br/>biz / auth / public)]
|
||||
ETL[(etl_feiqiu<br/>直连 app.v_*)]
|
||||
end
|
||||
|
||||
MP --> R_AUTH & R_TASKS & R_NOTES & R_PERF & R_CUST & R_COACH & R_CHAT
|
||||
ADMIN --> R_ADMIN
|
||||
|
||||
R_TASKS --> S_TM
|
||||
R_PERF --> S_PS
|
||||
R_NOTES --> S_NS
|
||||
R_CUST --> S_CS
|
||||
R_COACH --> S_CO
|
||||
R_CHAT --> S_TM
|
||||
|
||||
S_TM --> S_FDW
|
||||
S_PS --> S_FDW
|
||||
S_CS --> S_FDW
|
||||
S_CO --> S_FDW
|
||||
S_TM --> BIZ
|
||||
S_PS --> BIZ
|
||||
S_NS --> BIZ
|
||||
S_CS --> BIZ
|
||||
S_CO --> BIZ
|
||||
S_FDW --> ETL
|
||||
|
||||
style S_FDW fill:#9f9,stroke:#333
|
||||
style S_PS fill:#9f9,stroke:#333
|
||||
style R_PERF fill:#9f9,stroke:#333
|
||||
style S_CS fill:#9f9,stroke:#333
|
||||
style S_CO fill:#9f9,stroke:#333
|
||||
style R_CUST fill:#9f9,stroke:#333
|
||||
style R_COACH fill:#9f9,stroke:#333
|
||||
```
|
||||
|
||||
## 2. 路由模块清单
|
||||
|
||||
| 路由文件 | 前缀 | 说明 |
|
||||
|----------|------|------|
|
||||
| `xcx_auth.py` | `/api/xcx/auth` | 微信登录、Token 刷新、入驻申请 |
|
||||
| `xcx_tasks.py` | `/api/xcx/tasks` | 任务列表(TASK-1)、详情(TASK-2)、pin/unpin、abandon/restore |
|
||||
| `xcx_notes.py` | `/api/xcx/notes` | 备注 CRUD(含 score 评分) |
|
||||
| `xcx_performance.py` | `/api/xcx/performance` | 绩效概览(PERF-1)、明细(PERF-2) |
|
||||
| `xcx_customers.py` | `/api/xcx/customers` | 客户详情(CUST-1)、客户服务记录(CUST-2)(RNS1.2 新增) |
|
||||
| `xcx_coaches.py` | `/api/xcx/coaches` | 助教详情(COACH-1)(RNS1.2 新增) |
|
||||
| `xcx_board.py` | `/api/xcx/board` | 助教看板(BOARD-1)、客户看板(BOARD-2)、财务看板(BOARD-3)(RNS1.3 新增) |
|
||||
| `xcx_config.py` | `/api/xcx/config` | 技能类型配置(CONFIG-1)(RNS1.3 新增) |
|
||||
| `xcx_ai_chat.py` | `/api/xcx/ai` | AI 对话(SSE 流式) |
|
||||
| `xcx_ai_cache.py` | `/api/xcx/ai-cache` | AI 缓存查询 |
|
||||
| `admin_applications.py` | `/api/admin/applications` | 入驻审核 |
|
||||
| `auth.py` | `/api/auth` | 管理后台登录 |
|
||||
| `member_retention_clue.py` | `/api/member-retention-clue` | 维客线索 |
|
||||
| `ops_panel.py` | `/api/ops` | 运维面板 |
|
||||
| `business_day.py` | `/api/business-day` | 营业日配置 |
|
||||
|
||||
|
||||
## 3. 服务层模块清单
|
||||
|
||||
| 服务文件 | 职责 | 数据源 |
|
||||
|----------|------|--------|
|
||||
| `task_manager.py` | 任务 CRUD、`get_task_list_v2()`、`get_task_detail()` | biz.coach_tasks + FDW |
|
||||
| `performance_service.py` | `get_overview()`、`get_records()`,绩效汇总与明细 | FDW |
|
||||
| `note_service.py` | 备注创建(含 score)、AI 占位、回访触发 | biz.notes |
|
||||
| `customer_service.py` | 客户详情(CUST-1)、客户服务记录(CUST-2)(RNS1.2 新增) | biz.coach_tasks + biz.ai_cache + biz.notes + public.member_retention_clue + FDW |
|
||||
| `coach_service.py` | 助教详情(COACH-1):绩效/收入/任务分组/TOP客户/历史月份(RNS1.2 新增) | biz.coach_tasks + biz.notes + FDW |
|
||||
| `fdw_queries.py` | ETL RLS 视图查询集中封装,直连 ETL 库 + `SET LOCAL app.current_site_id` 门店隔离 | app.v_* (ETL 直连) |
|
||||
| `task_generator.py` | 定时任务:基于 WBI/NCI/RS 指数自动生成助教任务 | biz + FDW |
|
||||
| `task_expiry.py` | 定时任务:检测过期任务并标记 inactive | biz.coach_tasks |
|
||||
| `recall_detector.py` | 事件驱动:ETL 数据更新后检测召回完成 | biz + FDW |
|
||||
| `note_reclassifier.py` | 事件驱动:召回完成后回溯重分类备注 | biz.notes |
|
||||
| `wechat.py` | 微信 code2session、Token 管理 | 外部 API |
|
||||
| `role.py` | 角色权限查询 | auth.* |
|
||||
| `scheduler.py` | 触发器调度引擎 | biz.trigger_jobs |
|
||||
| `board_service.py` | 三看板编排:`get_coach_board()`、`get_customer_board()`、`get_finance_board()`,含日期范围/环比/排序/分页/降级(RNS1.3 新增) | FDW + biz.coach_tasks |
|
||||
| `application.py` | 入驻申请处理 | auth.applications |
|
||||
|
||||
## 4. FDW 查询封装(fdw_queries.py)
|
||||
|
||||
所有跨库查询集中在此模块,确保 DWD-DOC 强制规则统一实施。
|
||||
|
||||
⚠️ 架构变更(2026-03-18):不再使用 zqyy_app 的 `fdw_etl.*` foreign table,改为通过 `get_etl_readonly_connection(site_id)` 直连 ETL 库查询 `app.v_*` RLS 视图。原因:`postgres_fdw` 不传递自定义 GUC 参数到远端连接,导致 RLS 视图的 `current_setting('app.current_site_id')` 在远端未设置而报错。
|
||||
|
||||
所有函数通过 `_fdw_context()` 上下文管理器:创建 ETL 直连 → `SET LOCAL app.current_site_id` → 执行查询 → 关闭连接。
|
||||
|
||||
| 函数 | 用途 | DWD-DOC 规则 |
|
||||
|------|------|-------------|
|
||||
| `get_member_info()` | 批量查询会员姓名/手机号 | DQ-6:JOIN v_dim_member |
|
||||
| `get_member_balance()` | 批量查询储值卡余额 | DQ-7:JOIN v_dim_member_card_account |
|
||||
| `get_last_visit_days()` | 批量查询距上次到店天数 | 废单排除:is_delete = 0 |
|
||||
| `get_salary_calc()` | 助教绩效/档位/收入 | 收入用 gross_salary;费用用 base_income + bonus_income |
|
||||
| `get_service_records()` | 服务记录明细(分页) | 收入用 ledger_amount + DQ-6 + 废单排除 |
|
||||
| `get_service_records_for_task()` | 特定客户服务记录 | 同上 |
|
||||
| `get_consumption_60d()` | 客户近 60 天消费金额(RNS1.2 新增) | 收入用 ledger_amount(items_sum)+ 废单排除 |
|
||||
| `get_relation_index()` | 客户与助教关系指数列表(RNS1.2 新增) | 来源 v_dws_member_assistant_relation_index |
|
||||
| `get_consumption_records()` | 客户消费记录嵌套查询(RNS1.2 新增) | items_sum + DWD-DOC 规则 2 + 废单排除 |
|
||||
| `get_total_service_count()` | 客户累计服务总次数(RNS1.2 新增) | 废单排除:is_delete = 0 |
|
||||
| `get_coach_60d_stats()` | 助教对客户近 60 天统计(RNS1.2 新增) | 废单排除:is_delete = 0 |
|
||||
| `get_assistant_info()` | 助教基本信息(RNS1.2 新增) | 来源 v_dim_assistant |
|
||||
| `get_salary_calc_multi_months()` | 批量多月绩效数据(RNS1.2 新增) | 来源 v_dws_assistant_salary_calc |
|
||||
| `get_monthly_customer_count()` | 各月不重复客户数(RNS1.2 新增) | 废单排除:is_delete = 0 |
|
||||
| `get_coach_top_customers()` | 助教 TOP 客户(RNS1.2 新增) | DQ-6 + DQ-7 + items_sum + 废单排除 |
|
||||
| `get_customer_service_records()` | 客户按月服务记录(RNS1.2 新增) | DQ-6 + items_sum + 废单排除 |
|
||||
| `get_all_assistants()` | BOARD-1:按技能筛选助教列表(RNS1.3 新增) | v_dim_assistant |
|
||||
| `get_salary_calc_batch()` | BOARD-1:批量查询当期/上期绩效(RNS1.3 新增) | items_sum + assistant_pd/cx_money |
|
||||
| `get_top_customers_for_coaches()` | BOARD-1:按亲密度 Top3 客户 + 四级 emoji(RNS1.3 新增) | DQ-6 + v_dws_member_assistant_relation_index |
|
||||
| `get_coach_sv_data()` | BOARD-1:助教客源储值数据(RNS1.3 新增) | v_dws_assistant_monthly_summary |
|
||||
| `get_customer_board_recall()` | BOARD-2:召回维度(WBI 降序)(RNS1.3 新增) | v_dws_member_winback_index + DQ-6 |
|
||||
| `get_customer_board_potential()` | BOARD-2:潜力维度(SPI 降序)(RNS1.3 新增) | v_dws_member_spending_power_index |
|
||||
| `get_customer_board_balance()` | BOARD-2:余额维度(RNS1.3 新增) | v_dim_member_card_account + DQ-7 |
|
||||
| `get_customer_board_recharge()` | BOARD-2:充值维度(RNS1.3 新增) | v_dwd_recharge_order |
|
||||
| `get_customer_board_recent()` | BOARD-2:最近到店维度(RNS1.3 新增) | v_dws_member_visit_detail |
|
||||
| `get_customer_board_spend60()` | BOARD-2:60 天消费维度(RNS1.3 新增) | items_sum 口径 |
|
||||
| `get_customer_board_freq60()` | BOARD-2:60 天频次维度 + weeklyVisits(RNS1.3 新增) | v_dws_member_consumption_summary |
|
||||
| `get_customer_board_loyal()` | BOARD-2:忠诚度维度(RS 降序)(RNS1.3 新增) | v_dws_member_assistant_relation_index |
|
||||
| `get_customer_assistants()` | BOARD-2:批量查询客户关联助教(RNS1.3 新增) | 含亲密度 + 当前跟进置顶 |
|
||||
| `get_finance_overview()` | BOARD-3:经营一览 8 项指标(RNS1.3 新增) | v_dws_finance_daily_summary + items_sum |
|
||||
| `get_finance_recharge()` | BOARD-3:预收资产(储值卡 + 赠送卡矩阵)(RNS1.3 新增) | v_dws_finance_recharge_summary |
|
||||
| `get_finance_revenue()` | BOARD-3:应计收入结构(RNS1.3 新增) | v_dws_finance_income_structure + discount_detail |
|
||||
| `get_finance_cashflow()` | BOARD-3:现金流入(RNS1.3 新增) | v_dws_finance_daily_summary + 互斥规则 7 |
|
||||
| `get_finance_expense()` | BOARD-3:现金流出 4 子分组(RNS1.3 新增) | v_dws_finance_expense_summary + platform_settlement |
|
||||
| `get_finance_coach_analysis()` | BOARD-3:助教分析(basic + incentive)(RNS1.3 新增) | v_dws_assistant_salary_calc |
|
||||
| `get_skill_types()` | CONFIG-1:技能类型配置(RNS1.3 新增) | ETL cfg 表 |
|
||||
|
||||
### 列名映射(design.md 理想名 → 实际 FDW 视图列名)
|
||||
|
||||
RLS 视图直接暴露 DWD/DWS 原始列名,后端代码在 SQL 中使用 AS 别名转换。
|
||||
|
||||
**v_dwd_assistant_service_log**(基于 `dwd.dwd_assistant_service_log`,非 _ex 表):
|
||||
|
||||
| 代码语义 | 实际列名 | 说明 |
|
||||
|----------|----------|------|
|
||||
| id | `assistant_service_id` | 服务记录主键 |
|
||||
| assistant_id (WHERE) | `site_assistant_id` | 与 salary_calc.assistant_id 同源 |
|
||||
| member_id | `tenant_member_id` | 会员 ID |
|
||||
| is_trash = false | `is_delete = 0` | 整数类型,0=正常 |
|
||||
| settle_time | `create_time` | 结算时间 |
|
||||
| start_time | `start_use_time` | 开始使用时间 |
|
||||
| end_time | `last_use_time` | 最后使用时间 |
|
||||
| service_hours | `income_seconds / 3600.0` | 折算工时(计算字段) |
|
||||
| service_hours_raw | `real_use_seconds / 3600.0` | 原始工时(计算字段) |
|
||||
| income (items_sum) | `ledger_amount` | 收入金额 |
|
||||
| course_type | `skill_name` | 课程类型 |
|
||||
| table_name | `site_table_id` | 仅有台桌 ID,无名称 |
|
||||
|
||||
**v_dws_assistant_salary_calc**:
|
||||
|
||||
| 代码语义 | 实际列名 | 说明 |
|
||||
|----------|----------|------|
|
||||
| calc_month | `salary_month` | date 类型,存储为 YYYY-MM-01 |
|
||||
| coach_level | `assistant_level_name` | 档位名称 |
|
||||
| tier_index | `tier_id` | 档位 ID |
|
||||
| basic_hours | `base_hours` | 基础课时 |
|
||||
| total_hours | `effective_hours` | 有效总工时 |
|
||||
| total_income | `gross_salary` | 总收入 |
|
||||
| basic_rate | `base_course_price` | 基础课单价 |
|
||||
| incentive_rate | `bonus_course_price` | 激励课单价 |
|
||||
| bonus_money | `sprint_bonus` | 冲刺奖金 |
|
||||
| assistant_pd_money_total | `base_income` | 基础课总收入 |
|
||||
| assistant_cx_money_total | `bonus_income` | 激励课总收入 |
|
||||
|
||||
> 注意:`tier_nodes`、`total_customers`、`next_tier_*`、`tier_completed` 在视图中不存在,后端使用默认值或从其他数据源推算。
|
||||
|
||||
## 5. 数据流向
|
||||
|
||||
```
|
||||
小程序请求
|
||||
→ JWT 认证 (require_approved)
|
||||
→ 路由层 (routers/)
|
||||
→ 服务层 (services/)
|
||||
→ 业务库 (zqyy_app) 直连
|
||||
→ ETL 库 (etl_feiqiu) 直连只读(app.v_* RLS 视图)
|
||||
→ 响应包装 (ResponseWrapperMiddleware)
|
||||
→ { code: 0, data: ..., message: "ok" }
|
||||
```
|
||||
|
||||
## 6. 关键设计决策
|
||||
|
||||
- **ETL 直连**:所有 ETL 查询封装在 `fdw_queries.py`,通过 `_fdw_context()` 直连 ETL 库查询 `app.v_*` RLS 视图(不使用 FDW foreign table,因 postgres_fdw 不传递自定义 GUC)
|
||||
- **优雅降级**:扩展字段(lastVisitDays/balance/aiSuggestion)查询失败返回 null,不影响核心响应
|
||||
- **camelCase 转换**:所有小程序端响应通过 `CamelModel` 自动转换为 camelCase
|
||||
- **门店隔离**:业务库通过 `site_id` 参数过滤,ETL 查询通过 `SET LOCAL app.current_site_id` + RLS 视图隔离
|
||||
@@ -1,11 +1,17 @@
|
||||
# 审计一览表
|
||||
|
||||
> 自动生成于 2026-03-15 10:14:07,请勿手动编辑。
|
||||
> 自动生成于 2026-03-19 18:47:42,请勿手动编辑。
|
||||
|
||||
## 时间线视图
|
||||
|
||||
| 日期 | 项目 | 需求摘要 | 变更类型 | 影响模块 | 风险 | 详情 |
|
||||
|------|------|----------|----------|----------|------|------|
|
||||
| 2026-03-20 | ETL-feiqiu, 后端, 项目级 | 变更审计记录:RNS1.3 三看板 FDW 查询层数据口径修复 | bugfix | 其他, 文档 | 未知 | [链接](changes/2026-03-20__rns13-board-apis-e2e-fix.md) |
|
||||
| 2026-03-19 | ETL-feiqiu, 后端 | 变更审计记录:card_type_id 年卡/月卡映射文档同步 | bugfix | 其他, 文档 | 未知 | [链接](changes/2026-03-19__card-type-id-doc-sync.md) |
|
||||
| 2026-03-19 | 项目级 | 变更审计记录:coach_service 绩效档位硬编码修复 | bugfix | 其他 | 低 | [链接](changes/2026-03-19__coach-tier-hardcode-fix.md) |
|
||||
| 2026-03-19 | ETL-feiqiu, 后端 | 变更审计记录:助教等级映射硬编码修复(P2-9) | bugfix | 其他, 文档 | 低 | [链接](changes/2026-03-19__level-map-hardcode-fix.md) |
|
||||
| 2026-03-19 | 项目级 | 数据库变更审计:RNS1.2 客户与助教接口 | 文档 | 其他 | 低 | [链接](changes/2026-03-19__rns12-db-audit.md) |
|
||||
| 2026-03-18 | 项目级 | RNS1.1 E2E 测试 — FDW 直连改造 + performance_service bug 修复 | bugfix | 其他 | 未知 | [链接](changes/2026-03-18__rns1-e2e-fdw-direct-connect-bugfix.md) |
|
||||
| 2026-03-15 | 项目级 | 变更审计记录:DDL 基线统一整理 + BD 手册重组 + 小程序副本清理 | bugfix | 其他 | 低 | [链接](changes/2026-03-15__ddl-baseline-consolidation-bd-manual-reorg.md) |
|
||||
| 2026-03-13 | ETL-feiqiu, 小程序 | 审计记录:board-finance line-height 全量补齐 | bugfix | 其他, 文档 | 低 | [链接](changes/2026-03-13__board-finance-line-height-audit.md) |
|
||||
| 2026-03-13 | 项目级 | 审计记录:board-finance.wxss rpx 换算公式修正 | 功能 | 其他 | 未知 | [链接](changes/2026-03-13__board-finance-rpx-formula-correction.md) |
|
||||
@@ -69,6 +75,9 @@
|
||||
|
||||
| 日期 | 需求摘要 | 变更类型 | 影响模块 | 风险 | 详情 |
|
||||
|------|----------|----------|----------|------|------|
|
||||
| 2026-03-20 | 变更审计记录:RNS1.3 三看板 FDW 查询层数据口径修复 | bugfix | 其他, 文档 | 未知 | [链接](changes/2026-03-20__rns13-board-apis-e2e-fix.md) |
|
||||
| 2026-03-19 | 变更审计记录:card_type_id 年卡/月卡映射文档同步 | bugfix | 其他, 文档 | 未知 | [链接](changes/2026-03-19__card-type-id-doc-sync.md) |
|
||||
| 2026-03-19 | 变更审计记录:助教等级映射硬编码修复(P2-9) | bugfix | 其他, 文档 | 低 | [链接](changes/2026-03-19__level-map-hardcode-fix.md) |
|
||||
| 2026-03-13 | 审计记录:board-finance line-height 全量补齐 | bugfix | 其他, 文档 | 低 | [链接](changes/2026-03-13__board-finance-line-height-audit.md) |
|
||||
| 2026-03-08 | 变更审计记录:P5 AI 集成需求审视 — 7 项歧义修补 + category 枚举对齐 | 文档 | 其他, 文档, 脚本工具 | 未知 | [链接](changes/2026-03-08__p5-ai-spec-review-category-enum-align.md) |
|
||||
| 2026-03-02 | 变更审计:合并 ETL Hook 为统一分析入口 | 文档 | 其他, 脚本工具 | 未知 | [链接](changes/2026-03-02__etl-unified-analysis-hook-merge.md) |
|
||||
@@ -104,6 +113,9 @@
|
||||
|
||||
| 日期 | 需求摘要 | 变更类型 | 影响模块 | 风险 | 详情 |
|
||||
|------|----------|----------|----------|------|------|
|
||||
| 2026-03-20 | 变更审计记录:RNS1.3 三看板 FDW 查询层数据口径修复 | bugfix | 其他, 文档 | 未知 | [链接](changes/2026-03-20__rns13-board-apis-e2e-fix.md) |
|
||||
| 2026-03-19 | 变更审计记录:card_type_id 年卡/月卡映射文档同步 | bugfix | 其他, 文档 | 未知 | [链接](changes/2026-03-19__card-type-id-doc-sync.md) |
|
||||
| 2026-03-19 | 变更审计记录:助教等级映射硬编码修复(P2-9) | bugfix | 其他, 文档 | 低 | [链接](changes/2026-03-19__level-map-hardcode-fix.md) |
|
||||
| 2026-03-08 | 变更审计记录:P5 AI 集成需求审视 — 7 项歧义修补 + category 枚举对齐 | 文档 | 其他, 文档, 脚本工具 | 未知 | [链接](changes/2026-03-08__p5-ai-spec-review-category-enum-align.md) |
|
||||
| 2026-02-28 | 变更审计记录:多模块累积变更(营业日/核心业务/认证/ETL DWS 重构/参考文档合并) | 重构 | 其他 | 未知 | [链接](changes/2026-02-28__multi-module-accumulated-changes.md) |
|
||||
| 2026-02-15 | 审计记录:后端依赖补全使 FastAPI 可启动 | bugfix | 其他 | 未知 | [链接](changes/2026-02-15__backend-deps-bootstrap.md) |
|
||||
@@ -142,6 +154,10 @@
|
||||
|
||||
| 日期 | 需求摘要 | 变更类型 | 影响模块 | 风险 | 详情 |
|
||||
|------|----------|----------|----------|------|------|
|
||||
| 2026-03-20 | 变更审计记录:RNS1.3 三看板 FDW 查询层数据口径修复 | bugfix | 其他, 文档 | 未知 | [链接](changes/2026-03-20__rns13-board-apis-e2e-fix.md) |
|
||||
| 2026-03-19 | 变更审计记录:coach_service 绩效档位硬编码修复 | bugfix | 其他 | 低 | [链接](changes/2026-03-19__coach-tier-hardcode-fix.md) |
|
||||
| 2026-03-19 | 数据库变更审计:RNS1.2 客户与助教接口 | 文档 | 其他 | 低 | [链接](changes/2026-03-19__rns12-db-audit.md) |
|
||||
| 2026-03-18 | RNS1.1 E2E 测试 — FDW 直连改造 + performance_service bug 修复 | bugfix | 其他 | 未知 | [链接](changes/2026-03-18__rns1-e2e-fdw-direct-connect-bugfix.md) |
|
||||
| 2026-03-15 | 变更审计记录:DDL 基线统一整理 + BD 手册重组 + 小程序副本清理 | bugfix | 其他 | 低 | [链接](changes/2026-03-15__ddl-baseline-consolidation-bd-manual-reorg.md) |
|
||||
| 2026-03-13 | 审计记录:board-finance.wxss rpx 换算公式修正 | 功能 | 其他 | 未知 | [链接](changes/2026-03-13__board-finance-rpx-formula-correction.md) |
|
||||
| 2026-03-13 | 变更审计记录:task-list 页面 H5 原型 1:1 重写 | bugfix | 其他 | 未知 | [链接](changes/2026-03-13__task-list-h5-rewrite.md) |
|
||||
@@ -215,6 +231,12 @@
|
||||
|
||||
| 日期 | 需求摘要 | 变更类型 | 风险 | 详情 |
|
||||
|------|----------|----------|------|------|
|
||||
| 2026-03-20 | 变更审计记录:RNS1.3 三看板 FDW 查询层数据口径修复 | bugfix | 未知 | [链接](changes/2026-03-20__rns13-board-apis-e2e-fix.md) |
|
||||
| 2026-03-19 | 变更审计记录:card_type_id 年卡/月卡映射文档同步 | bugfix | 未知 | [链接](changes/2026-03-19__card-type-id-doc-sync.md) |
|
||||
| 2026-03-19 | 变更审计记录:coach_service 绩效档位硬编码修复 | bugfix | 低 | [链接](changes/2026-03-19__coach-tier-hardcode-fix.md) |
|
||||
| 2026-03-19 | 变更审计记录:助教等级映射硬编码修复(P2-9) | bugfix | 低 | [链接](changes/2026-03-19__level-map-hardcode-fix.md) |
|
||||
| 2026-03-19 | 数据库变更审计:RNS1.2 客户与助教接口 | 文档 | 低 | [链接](changes/2026-03-19__rns12-db-audit.md) |
|
||||
| 2026-03-18 | RNS1.1 E2E 测试 — FDW 直连改造 + performance_service bug 修复 | bugfix | 未知 | [链接](changes/2026-03-18__rns1-e2e-fdw-direct-connect-bugfix.md) |
|
||||
| 2026-03-15 | 变更审计记录:DDL 基线统一整理 + BD 手册重组 + 小程序副本清理 | bugfix | 低 | [链接](changes/2026-03-15__ddl-baseline-consolidation-bd-manual-reorg.md) |
|
||||
| 2026-03-13 | 审计记录:board-finance line-height 全量补齐 | bugfix | 低 | [链接](changes/2026-03-13__board-finance-line-height-audit.md) |
|
||||
| 2026-03-13 | 审计记录:board-finance.wxss rpx 换算公式修正 | 功能 | 未知 | [链接](changes/2026-03-13__board-finance-rpx-formula-correction.md) |
|
||||
@@ -272,6 +294,9 @@
|
||||
|
||||
| 日期 | 需求摘要 | 变更类型 | 风险 | 详情 |
|
||||
|------|----------|----------|------|------|
|
||||
| 2026-03-20 | 变更审计记录:RNS1.3 三看板 FDW 查询层数据口径修复 | bugfix | 未知 | [链接](changes/2026-03-20__rns13-board-apis-e2e-fix.md) |
|
||||
| 2026-03-19 | 变更审计记录:card_type_id 年卡/月卡映射文档同步 | bugfix | 未知 | [链接](changes/2026-03-19__card-type-id-doc-sync.md) |
|
||||
| 2026-03-19 | 变更审计记录:助教等级映射硬编码修复(P2-9) | bugfix | 低 | [链接](changes/2026-03-19__level-map-hardcode-fix.md) |
|
||||
| 2026-03-13 | 审计记录:board-finance line-height 全量补齐 | bugfix | 低 | [链接](changes/2026-03-13__board-finance-line-height-audit.md) |
|
||||
| 2026-03-08 | 变更审计记录:P5 AI 集成需求审视 — 7 项歧义修补 + category 枚举对齐 | 文档 | 未知 | [链接](changes/2026-03-08__p5-ai-spec-review-category-enum-align.md) |
|
||||
| 2026-02-15 | 变更审计记录(Change Audit Record) | 文档 | 极低 | [链接](changes/2026-02-15__audit-consolidation-doc-reorg.md) |
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,9 @@
|
||||
# BD_Manual:app Schema 与 RLS 视图层
|
||||
|
||||
> 目标库:`test_etl_feiqiu`(通过 `PG_DSN` 连接)
|
||||
> 迁移脚本:`db/etl_feiqiu/migrations/2026-02-24__p1_create_app_schema_rls_views.sql`
|
||||
> 迁移脚本:`db/etl_feiqiu/migrations/2026-02-24__p1_create_app_schema_rls_views.sql`、`db/etl_feiqiu/migrations/2026-03-19_add_board_rls_views.sql`
|
||||
> DDL 位置:`docs/database/ddl/etl_feiqiu__app.sql`(执行后需重新生成)
|
||||
> 关联 SPEC:`miniapp-db-foundation`(P1 基础设施层)
|
||||
> 关联 SPEC:`miniapp-db-foundation`(P1 基础设施层)、`rns1-board-apis`(BOARD 看板)
|
||||
|
||||
---
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
### 新增角色
|
||||
- `app_reader`:只读角色(`LOGIN`),拥有 `app` Schema 的 `USAGE` + `SELECT` 权限
|
||||
|
||||
### 新增视图(35 张)
|
||||
### 新增视图(38 张)
|
||||
|
||||
**DWD 层(11 张,全部含 `site_id` 过滤):**
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
| `app.v_dim_staff` | `dwd.dim_staff` | 同上 |
|
||||
| `app.v_dim_staff_ex` | `dwd.dim_staff_ex` | 同上 |
|
||||
|
||||
**DWS 层 — 含 `site_id` 过滤(20 张):**
|
||||
**DWS 层 — 含 `site_id` 过滤(23 张):**
|
||||
|
||||
| 视图 | 源表 |
|
||||
|------|------|
|
||||
@@ -57,6 +57,9 @@
|
||||
| `app.v_dws_platform_settlement` | `dws.dws_platform_settlement` |
|
||||
| `app.v_dws_assistant_recharge_commission` | `dws.dws_assistant_recharge_commission` |
|
||||
| `app.v_dws_order_summary` | `dws.dws_order_summary` |
|
||||
| `app.v_dws_assistant_project_tag` | `dws.dws_assistant_project_tag` |
|
||||
| `app.v_dws_member_project_tag` | `dws.dws_member_project_tag` |
|
||||
| `app.v_dws_member_spending_power_index` | `dws.dws_member_spending_power_index` |
|
||||
|
||||
**DWS 层 — cfg_* 配置表(4 张,无 `site_id`,直接 `SELECT *`):**
|
||||
|
||||
@@ -74,9 +77,10 @@
|
||||
| `app_reader` | `app` | `USAGE` + `SELECT ON ALL TABLES` + `ALTER DEFAULT PRIVILEGES` |
|
||||
|
||||
### P2 预留(注释形式,暂不创建)
|
||||
- `dws.dws_member_spending_power_index` → `app.v_dws_member_spending_power_index`
|
||||
- `dws.dws_assistant_order_contribution` → `app.v_dws_assistant_order_contribution`
|
||||
|
||||
> `v_dws_member_spending_power_index`、`v_dws_assistant_project_tag`、`v_dws_member_project_tag` 已于 2026-03-19 正式创建(迁移脚本 `2026-03-19_add_board_rls_views.sql`)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 兼容性影响
|
||||
@@ -113,7 +117,7 @@ DROP ROLE IF EXISTS app_reader;
|
||||
-- 1. 验证 app Schema 存在
|
||||
SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'app';
|
||||
|
||||
-- 2. 验证视图数量(应为 35 张)
|
||||
-- 2. 验证视图数量(应为 38 张:原 35 + 2026-03-19 新增 3)
|
||||
SELECT count(*) FROM information_schema.views WHERE table_schema = 'app';
|
||||
|
||||
-- 3. 验证 app_reader 角色存在且有 app Schema 权限
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|---|------|------|--------|
|
||||
| 1 | `biz.coach_tasks` | 助教任务表:存储任务分配、状态、有效期、置顶、放弃原因等 | 15 |
|
||||
| 2 | `biz.coach_task_history` | 任务变更历史表:记录任务关闭/新建/置顶/放弃的追溯链 | 9 |
|
||||
| 3 | `biz.notes` | 统一备注表:通过 `type` 字段区分普通备注/回访备注/放弃原因,含星星评分 | 14 |
|
||||
| 3 | `biz.notes` | 统一备注表:通过 `type` 字段区分普通备注/回访备注/放弃原因,含星星评分 | 15 |
|
||||
| 4 | `biz.trigger_jobs` | 触发器配置表:存储 cron/interval/event 三种触发方式的配置与执行状态 | 9 |
|
||||
|
||||
### 表字段明细
|
||||
@@ -55,7 +55,7 @@
|
||||
| `detail` | JSONB | 可空 | 附加详情(如放弃原因等) |
|
||||
| `created_at` | TIMESTAMPTZ | DEFAULT NOW() | 记录时间 |
|
||||
|
||||
#### biz.notes(14 字段)
|
||||
#### biz.notes(15 字段)
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
@@ -73,6 +73,7 @@
|
||||
| `ai_analysis` | TEXT | 可空 | AI 分析结果(P5 实现) |
|
||||
| `created_at` | TIMESTAMPTZ | DEFAULT NOW() | 创建时间 |
|
||||
| `updated_at` | TIMESTAMPTZ | DEFAULT NOW() | 更新时间 |
|
||||
| `score` | SMALLINT | CHECK (1-5),可空 | 备注星星评分,助教创建备注时可选填写,不参与 AI 分析(RNS1.1 新增) |
|
||||
|
||||
#### biz.trigger_jobs(9 字段)
|
||||
|
||||
@@ -99,6 +100,7 @@
|
||||
| `notes` | `idx_notes_target` | INDEX | `(site_id, target_type, target_id)`,按目标查询备注加速 |
|
||||
| `notes` | CHECK `rating_service_willingness` | CHECK | `BETWEEN 1 AND 5` |
|
||||
| `notes` | CHECK `rating_revisit_likelihood` | CHECK | `BETWEEN 1 AND 5` |
|
||||
| `notes` | CHECK `score` | CHECK | `score IS NULL OR (score >= 1 AND score <= 5)`(RNS1.1 新增) |
|
||||
| `notes` | FK `task_id` | FK | → `biz.coach_tasks(id)` |
|
||||
| `trigger_jobs` | UNIQUE `job_name` | UNIQUE | 触发器名称唯一 |
|
||||
|
||||
@@ -128,6 +130,44 @@
|
||||
|
||||
---
|
||||
|
||||
## 2.1 RNS1.3 看板接口引用说明(2026-03-20 补充)
|
||||
|
||||
RNS1.3(三看板接口)BOARD-1 助教看板的任务维度查询引用了 `biz.coach_tasks`(无 schema 变更,仅新增读取路径):
|
||||
|
||||
| 表 | 引用接口 | 用途 |
|
||||
|----|---------|------|
|
||||
| `biz.coach_tasks` | BOARD-1 `_query_coach_tasks()` | 按 `site_id` + `assistant_id` + 日期范围查询任务完成数,按 `task_type` 分类统计 `recall`(召回类:`high_priority_recall` / `priority_recall`)和 `callback`(回访类:`follow_up_visit` / `relationship_building`),筛选条件 `status = 'completed'` + `completed_at BETWEEN start_date AND end_date` |
|
||||
|
||||
查询模式:
|
||||
```sql
|
||||
SELECT assistant_id, task_type, COUNT(*) AS cnt
|
||||
FROM biz.coach_tasks
|
||||
WHERE site_id = :site_id
|
||||
AND assistant_id = ANY(:assistant_ids)
|
||||
AND status = 'completed'
|
||||
AND completed_at BETWEEN :start_date AND :end_date
|
||||
GROUP BY assistant_id, task_type
|
||||
```
|
||||
|
||||
> 该查询走 `idx_coach_tasks_assistant_status` 索引(`site_id, assistant_id, status`),无需新增索引。
|
||||
|
||||
---
|
||||
|
||||
## 2.2 RNS1.2 接口引用说明(2026-03-18 补充)
|
||||
|
||||
RNS1.2(客户与助教接口)新增 3 个端点,引用了以下 biz/public 表(无 schema 变更,仅新增读取路径):
|
||||
|
||||
| 表 | 引用接口 | 用途 |
|
||||
|----|---------|------|
|
||||
| `biz.coach_tasks` | CUST-1 `coachTasks` 模块、COACH-1 `visibleTasks`/`hiddenTasks`/`abandonedTasks`/`tasksCompleted` | 查询客户关联的助教任务(按 `member_id`);查询助教任务分组(按 `assistant_id`、`status` 分组);统计当月完成任务数 |
|
||||
| `biz.notes` | CUST-1 `notes` 模块、COACH-1 `notes`/任务备注 | 查询客户备注(`target_type='member'`,最多 20 条);查询助教相关备注(最多 20 条);查询任务关联备注(`task_id` 关联) |
|
||||
| `biz.ai_cache` | CUST-1 `aiInsight` 模块 | 查询 AI 分析缓存(`cache_type='app4_analysis'`,`target_id=customerId`),解析 `cache_value` JSON 生成洞察摘要和策略建议 |
|
||||
| `public.member_retention_clue` | CUST-1 `retentionClues` 模块 | 查询维客线索(按 `created_at` 倒序),格式与 TASK-2 一致 |
|
||||
|
||||
> 以上均为只读查询,不涉及表结构变更。`biz.ai_cache` 表由 P5(AI 集成)创建,`public.member_retention_clue` 表由独立迁移创建。
|
||||
|
||||
---
|
||||
|
||||
## 3. 回滚策略
|
||||
|
||||
按逆序 `DROP TABLE IF EXISTS CASCADE`(迁移脚本末尾已包含注释形式的回滚语句):
|
||||
|
||||
@@ -34,9 +34,9 @@
|
||||
|
||||
### 导入的外部表
|
||||
|
||||
通过 `IMPORT FOREIGN SCHEMA app` 批量导入,外部表与 ETL 库 `app` Schema 中的 RLS 视图一一对应(共 35 张):
|
||||
通过 `IMPORT FOREIGN SCHEMA app` 批量导入,外部表与 ETL 库 `app` Schema 中的 RLS 视图一一对应(共 38 张,2026-03-19 新增 3 张 BOARD 看板视图):
|
||||
- 11 张 DWD 视图:`v_dim_member`、`v_dim_assistant`、`v_dim_member_card_account`、`v_dim_table`、`v_dwd_settlement_head`、`v_dwd_table_fee_log`、`v_dwd_assistant_service_log`、`v_dwd_recharge_order`、`v_dwd_store_goods_sale`、`v_dim_staff`、`v_dim_staff_ex`
|
||||
- 24 张 DWS 视图:`v_dws_member_consumption_summary`、`v_dws_member_visit_detail` 等
|
||||
- 27 张 DWS 视图(含 4 张 cfg_* 配置表):`v_dws_member_consumption_summary`、`v_dws_member_visit_detail` 等
|
||||
|
||||
### 权限配置
|
||||
|
||||
@@ -46,6 +46,23 @@
|
||||
|
||||
---
|
||||
|
||||
## 1.1 RNS1.2 接口引用说明(2026-03-19 补充)
|
||||
|
||||
RNS1.2(客户与助教接口)通过 `fdw_queries.py` **直连 ETL 库**查询以下 `app.v_*` RLS 视图(不使用 `fdw_etl.*` 外部表,原因:`postgres_fdw` 不传递自定义 GUC 参数到远端连接):
|
||||
|
||||
| 视图 | 引用接口 | 用途 |
|
||||
|------|---------|------|
|
||||
| `app.v_dim_member` | CUST-1, CUST-2, COACH-1 | 会员信息(nickname, mobile),DQ-6 合规 |
|
||||
| `app.v_dim_member_card_account` | CUST-1, COACH-1 | 会员卡余额,DQ-7 合规 |
|
||||
| `app.v_dim_assistant` | CUST-1, COACH-1 | 助教基本信息 |
|
||||
| `app.v_dwd_assistant_service_log` | CUST-1, CUST-2, COACH-1 | 服务记录明细(`is_delete=0` 废单排除,`ledger_amount` items_sum 口径) |
|
||||
| `app.v_dws_assistant_salary_calc` | COACH-1 | 助教绩效/档位/收入 |
|
||||
| `app.v_dws_member_assistant_relation_index` | CUST-1, COACH-1 | 会员-助教关系指数 |
|
||||
|
||||
> 本次无 FDW 配置变更。所有视图已存在于 ETL 库 `app` schema 中。审计详情见 `docs/audit/changes/2026-03-19__rns12-db-audit.md`。
|
||||
|
||||
---
|
||||
|
||||
## 2. 兼容性影响
|
||||
|
||||
| 组件 | 影响 |
|
||||
|
||||
261
docs/database/BD_Manual_fdw_reverse_retention_clue.md
Normal file
261
docs/database/BD_Manual_fdw_reverse_retention_clue.md
Normal file
@@ -0,0 +1,261 @@
|
||||
# BD 手册:FDW 反向映射 — ETL 库读取业务库维客线索
|
||||
|
||||
> 创建日期:2026-02-26(原 `member_birthday_manual`),2026-03-19 重写为当前版本
|
||||
> 替代文档:`docs/database/_archived/BD_Manual_fdw_reverse_member_birthday.md`
|
||||
> 关联 SQL:`db/fdw/setup_fdw_reverse.sql`(生产)、`db/fdw/setup_fdw_reverse_test.sql`(测试)
|
||||
> 关联表文档:`docs/database/BD_Manual_member_retention_clue.md`
|
||||
|
||||
---
|
||||
|
||||
## 1. 概述
|
||||
|
||||
在 `etl_feiqiu`(生产)/ `test_etl_feiqiu`(测试)数据库中,通过 `postgres_fdw` 创建指向 `zqyy_app` / `test_zqyy_app` 的外部表 `fdw_app.member_retention_clue`,使 ETL DWS 任务可只读访问助教为会员记录的维客线索数据。
|
||||
|
||||
方向:`etl_feiqiu → zqyy_app`(与正向 FDW `setup_fdw.sql` 的 `zqyy_app → etl_feiqiu` 方向相反)。
|
||||
|
||||
### 数据流向
|
||||
|
||||
```
|
||||
zqyy_app.public.member_retention_clue (业务库源表,后端 API 写入)
|
||||
│
|
||||
│ postgres_fdw(只读)
|
||||
▼
|
||||
etl_feiqiu.fdw_app.member_retention_clue (ETL 库外部表,DWS 任务读取)
|
||||
```
|
||||
|
||||
### 历史沿革
|
||||
|
||||
| 日期 | 事件 |
|
||||
|------|------|
|
||||
| 2026-02-22 | 初版:`fdw_app.member_birthday_manual`(映射助教手动补录生日表) |
|
||||
| 2026-02-26 | 重构:`member_birthday_manual` → `member_retention_clue`(维客线索替代单一生日方案) |
|
||||
| 2026-02-27 | 业务库侧新增 `source` 列(线索来源),FDW 外部表定义未同步更新 |
|
||||
| 2026-03-08 | 业务库侧 `category` 枚举对齐:`客户基础信息` → `客户基础` |
|
||||
|
||||
---
|
||||
|
||||
## 2. 变更说明
|
||||
|
||||
### 2.1 新增对象
|
||||
|
||||
| 所在库 | 对象类型 | 名称 | 说明 |
|
||||
|--------|---------|------|------|
|
||||
| etl_feiqiu / test_etl_feiqiu | 扩展 | `postgres_fdw` | PostgreSQL 外部数据包装器(如已安装则跳过) |
|
||||
| etl_feiqiu | 外部服务器 | `zqyy_app_server` | 指向 `zqyy_app` 业务库(host/port 按环境配置) |
|
||||
| test_etl_feiqiu | 外部服务器 | `test_zqyy_app_server` | 指向 `test_zqyy_app` 测试业务库 |
|
||||
| etl_feiqiu / test_etl_feiqiu | 用户映射 | `etl_user → app_reader` | ETL 连接角色映射到业务库只读角色 |
|
||||
| etl_feiqiu / test_etl_feiqiu | Schema | `fdw_app` | 存放来自业务库的外部表 |
|
||||
| etl_feiqiu / test_etl_feiqiu | 外部表 | `fdw_app.member_retention_clue` | 映射 `public.member_retention_clue` |
|
||||
|
||||
### 2.2 外部表列定义
|
||||
|
||||
| 列名 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | BIGINT | 自增主键 |
|
||||
| member_id | BIGINT | 会员 ID |
|
||||
| category | VARCHAR(20) | 线索大类(6 值枚举:客户基础/消费习惯/玩法偏好/促销偏好/社交关系/重要反馈) |
|
||||
| summary | VARCHAR(200) | 摘要 |
|
||||
| detail | TEXT | 详情 |
|
||||
| recorded_by_assistant_id | BIGINT | 记录助教 ID |
|
||||
| recorded_by_name | VARCHAR(50) | 记录助教姓名 |
|
||||
| recorded_at | TIMESTAMPTZ | 记录时间 |
|
||||
| site_id | BIGINT | 门店 ID |
|
||||
|
||||
> **注意**:业务库侧 `member_retention_clue` 已于 2026-02-27 新增 `source VARCHAR(20)` 列(线索来源:`manual` / `ai_consumption` / `ai_note`),但当前 FDW 外部表定义(`db/fdw/setup_fdw_reverse*.sql`)尚未包含此列。如 ETL 任务需要读取 `source` 字段,需更新外部表定义并重新执行部署脚本。
|
||||
|
||||
### 2.3 角色与权限
|
||||
|
||||
| 角色 | 所在库 | 用途 |
|
||||
|------|--------|------|
|
||||
| `etl_user` | etl_feiqiu / test_etl_feiqiu | ETL 连接角色,通过 FDW 只读访问业务库数据 |
|
||||
| `app_reader` | zqyy_app / test_zqyy_app | 业务库只读角色,供 FDW 用户映射使用 |
|
||||
|
||||
权限配置:
|
||||
|
||||
| 角色 | Schema | 权限 |
|
||||
|------|--------|------|
|
||||
| `etl_user` | `fdw_app` | `USAGE` + `SELECT ON ALL TABLES` + `ALTER DEFAULT PRIVILEGES`(自动授权未来新增外部表) |
|
||||
|
||||
---
|
||||
|
||||
## 3. 与正向 FDW 的对比
|
||||
|
||||
| 维度 | 正向 FDW(`setup_fdw.sql`) | 反向 FDW(`setup_fdw_reverse.sql`) |
|
||||
|------|---------------------------|-------------------------------------|
|
||||
| 执行位置 | `zqyy_app` 业务库 | `etl_feiqiu` ETL 库 |
|
||||
| 数据方向 | 业务库读取 ETL 数据 | ETL 库读取业务库数据 |
|
||||
| 目标 Schema | `fdw_etl`(35 张外部表) | `fdw_app`(1 张外部表) |
|
||||
| 导入方式 | `IMPORT FOREIGN SCHEMA app`(批量) | `CREATE FOREIGN TABLE`(逐表定义) |
|
||||
| 消费方 | 后端 API | ETL DWS 任务 |
|
||||
| 文档 | `BD_Manual_fdw_etl_setup.md` | 本文档 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 兼容性影响
|
||||
|
||||
| 组件 | 影响 |
|
||||
|------|------|
|
||||
| ETL DWS 任务 | 可通过 `fdw_app.member_retention_clue` 只读访问维客线索数据。当前无 DWS 任务直接消费此表(原 `member_birthday_manual` 的生日读取逻辑已移除,生日仅从 `dim_member.birthday` 读取) |
|
||||
| 后端 API | 无影响。后端直接写入 `zqyy_app.public.member_retention_clue`,不经过 FDW |
|
||||
| 小程序 | 无影响。小程序通过后端 API 间接访问 |
|
||||
| 管理后台 | 无影响 |
|
||||
| 正向 FDW(`fdw_etl`) | 无影响。两个方向的 FDW 配置完全独立 |
|
||||
| 业务库 `member_retention_clue` 表 | 无影响。FDW 为只读映射,不修改源表 |
|
||||
|
||||
### 幂等性说明
|
||||
|
||||
`CREATE FOREIGN TABLE IF NOT EXISTS` 确保重复执行不会报错。如需更新列定义(如添加 `source` 列),需先 `DROP FOREIGN TABLE` 再重建,或使用 `ALTER FOREIGN TABLE ADD COLUMN`。
|
||||
|
||||
---
|
||||
|
||||
## 5. 回滚策略
|
||||
|
||||
### 5.1 完整回滚(按逆序执行)
|
||||
|
||||
```sql
|
||||
-- 在 etl_feiqiu(生产)中执行:
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA fdw_app REVOKE SELECT ON TABLES FROM etl_user;
|
||||
REVOKE SELECT ON ALL TABLES IN SCHEMA fdw_app FROM etl_user;
|
||||
REVOKE USAGE ON SCHEMA fdw_app FROM etl_user;
|
||||
DROP FOREIGN TABLE IF EXISTS fdw_app.member_retention_clue;
|
||||
DROP SCHEMA IF EXISTS fdw_app CASCADE;
|
||||
DROP USER MAPPING IF EXISTS FOR etl_user SERVER zqyy_app_server;
|
||||
DROP SERVER IF EXISTS zqyy_app_server CASCADE;
|
||||
-- 注意:如果其他外部表也使用 postgres_fdw,不要执行 DROP EXTENSION
|
||||
```
|
||||
|
||||
```sql
|
||||
-- 在 test_etl_feiqiu(测试)中执行:
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA fdw_app REVOKE SELECT ON TABLES FROM etl_user;
|
||||
REVOKE SELECT ON ALL TABLES IN SCHEMA fdw_app FROM etl_user;
|
||||
REVOKE USAGE ON SCHEMA fdw_app FROM etl_user;
|
||||
DROP FOREIGN TABLE IF EXISTS fdw_app.member_retention_clue;
|
||||
DROP SCHEMA IF EXISTS fdw_app CASCADE;
|
||||
DROP USER MAPPING IF EXISTS FOR etl_user SERVER test_zqyy_app_server;
|
||||
DROP SERVER IF EXISTS test_zqyy_app_server CASCADE;
|
||||
```
|
||||
|
||||
### 5.2 仅删除外部表(保留 FDW 基础设施)
|
||||
|
||||
```sql
|
||||
DROP FOREIGN TABLE IF EXISTS fdw_app.member_retention_clue;
|
||||
```
|
||||
|
||||
回滚无数据丢失风险:外部表不存储数据,仅为远程表的映射定义。
|
||||
|
||||
---
|
||||
|
||||
## 6. 验证 SQL
|
||||
|
||||
以下 SQL 在 ETL 库(`etl_feiqiu` 或 `test_etl_feiqiu`)中执行:
|
||||
|
||||
```sql
|
||||
-- 1. 确认 postgres_fdw 扩展已安装
|
||||
SELECT extname, extversion
|
||||
FROM pg_extension
|
||||
WHERE extname = 'postgres_fdw';
|
||||
-- 预期:1 行
|
||||
|
||||
-- 2. 确认外部服务器存在
|
||||
-- 生产:
|
||||
SELECT srvname, srvoptions FROM pg_foreign_server WHERE srvname = 'zqyy_app_server';
|
||||
-- 测试:
|
||||
SELECT srvname, srvoptions FROM pg_foreign_server WHERE srvname = 'test_zqyy_app_server';
|
||||
-- 预期:1 行,srvoptions 包含正确的 host/dbname/port
|
||||
|
||||
-- 3. 确认用户映射存在
|
||||
SELECT um.umid, r.rolname AS local_role, s.srvname, um.umoptions
|
||||
FROM pg_user_mappings um
|
||||
JOIN pg_foreign_server s ON s.srvname = um.srvname
|
||||
JOIN pg_roles r ON r.rolname = um.usename
|
||||
WHERE s.srvname IN ('zqyy_app_server', 'test_zqyy_app_server')
|
||||
AND r.rolname = 'etl_user';
|
||||
-- 预期:1 行
|
||||
|
||||
-- 4. 确认 fdw_app schema 存在
|
||||
SELECT schema_name
|
||||
FROM information_schema.schemata
|
||||
WHERE schema_name = 'fdw_app';
|
||||
-- 预期:1 行
|
||||
|
||||
-- 5. 确认外部表列结构完整(当前 9 列)
|
||||
SELECT column_name, data_type
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'fdw_app'
|
||||
AND table_name = 'member_retention_clue'
|
||||
ORDER BY ordinal_position;
|
||||
-- 预期:9 行(id, member_id, category, summary, detail,
|
||||
-- recorded_by_assistant_id, recorded_by_name, recorded_at, site_id)
|
||||
|
||||
-- 6. 确认外部表在 information_schema.foreign_tables 中注册
|
||||
SELECT foreign_table_schema, foreign_table_name, foreign_server_name
|
||||
FROM information_schema.foreign_tables
|
||||
WHERE foreign_table_schema = 'fdw_app'
|
||||
AND foreign_table_name = 'member_retention_clue';
|
||||
-- 预期:1 行
|
||||
|
||||
-- 7. 确认 etl_user 对 fdw_app 有 USAGE 权限
|
||||
SELECT has_schema_privilege('etl_user', 'fdw_app', 'USAGE') AS has_usage;
|
||||
-- 预期:true
|
||||
|
||||
-- 8. 确认外部表可读取(需业务库侧表已存在且网络连通)
|
||||
SELECT COUNT(*) FROM fdw_app.member_retention_clue;
|
||||
-- 预期:返回行数(可能为 0)
|
||||
|
||||
-- 9. 确认 ALTER DEFAULT PRIVILEGES 已设置
|
||||
SELECT n.nspname AS schema_name, d.defaclacl AS default_acl
|
||||
FROM pg_default_acl d
|
||||
JOIN pg_namespace n ON n.oid = d.defaclnamespace
|
||||
WHERE n.nspname = 'fdw_app';
|
||||
-- 预期:1 行,default_acl 包含 etl_user 的 SELECT 权限
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 已知差异与待办
|
||||
|
||||
| 项目 | 状态 | 说明 |
|
||||
|------|------|------|
|
||||
| `source` 列缺失 | ⚠️ 待同步 | 业务库侧已有 `source VARCHAR(20) NOT NULL DEFAULT 'manual'`(2026-02-27),FDW 外部表定义未包含。当前无 ETL 任务需要此字段,但未来如需读取线索来源需先更新外部表 |
|
||||
| DWS 任务消费 | 📋 待规划 | 原 `member_birthday_manual` 的 DWS 消费逻辑已移除。维客线索的 DWS 聚合任务尚未规划 |
|
||||
|
||||
### source 列同步方法(备用)
|
||||
|
||||
如需同步 `source` 列,在 ETL 库中执行:
|
||||
|
||||
```sql
|
||||
-- 方法 1:ALTER 追加列(推荐,无需重建)
|
||||
ALTER FOREIGN TABLE fdw_app.member_retention_clue
|
||||
ADD COLUMN source VARCHAR(20);
|
||||
|
||||
-- 方法 2:DROP + 重建(完整重置)
|
||||
DROP FOREIGN TABLE IF EXISTS fdw_app.member_retention_clue;
|
||||
CREATE FOREIGN TABLE fdw_app.member_retention_clue (
|
||||
id BIGINT,
|
||||
member_id BIGINT,
|
||||
category VARCHAR(20),
|
||||
summary VARCHAR(200),
|
||||
detail TEXT,
|
||||
recorded_by_assistant_id BIGINT,
|
||||
recorded_by_name VARCHAR(50),
|
||||
recorded_at TIMESTAMPTZ,
|
||||
site_id BIGINT,
|
||||
source VARCHAR(20)
|
||||
) SERVER zqyy_app_server
|
||||
OPTIONS (schema_name 'public', table_name 'member_retention_clue');
|
||||
```
|
||||
|
||||
同步后需更新 `db/fdw/setup_fdw_reverse.sql` 和 `db/fdw/setup_fdw_reverse_test.sql` 中的外部表定义。
|
||||
|
||||
---
|
||||
|
||||
## 8. 关联文件
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `db/fdw/setup_fdw_reverse.sql` | 生产环境部署脚本(在 `etl_feiqiu` 中执行) |
|
||||
| `db/fdw/setup_fdw_reverse_test.sql` | 测试环境部署脚本(在 `test_etl_feiqiu` 中执行) |
|
||||
| `docs/database/BD_Manual_member_retention_clue.md` | 业务库侧 `member_retention_clue` 表结构文档 |
|
||||
| `docs/database/BD_Manual_fdw_etl_setup.md` | 正向 FDW 配置文档(方向相反) |
|
||||
| `docs/database/BD_Manual_app_schema_rls_views.md` | ETL 库 `app` Schema RLS 视图层文档 |
|
||||
| `db/zqyy_app/migrations/2026-02-26__refactor_birthday_to_retention_clue.sql` | 业务库侧建表迁移脚本 |
|
||||
@@ -1187,3 +1187,34 @@ SELECT site_id,
|
||||
FROM core.dim_site s;
|
||||
;
|
||||
|
||||
|
||||
-- 2026-03-19 新增:BOARD 看板所需的 3 个 RLS 视图
|
||||
-- 迁移脚本:db/etl_feiqiu/migrations/2026-03-19_add_board_rls_views.sql
|
||||
|
||||
CREATE OR REPLACE VIEW app.v_dws_assistant_project_tag AS
|
||||
SELECT id, site_id, tenant_id, assistant_id, time_window,
|
||||
category_code, category_name, short_name,
|
||||
duration_seconds, total_seconds, percentage, is_tagged,
|
||||
computed_at, created_at, updated_at
|
||||
FROM dws.dws_assistant_project_tag
|
||||
WHERE site_id = current_setting('app.current_site_id')::bigint;
|
||||
|
||||
CREATE OR REPLACE VIEW app.v_dws_member_project_tag AS
|
||||
SELECT id, site_id, tenant_id, member_id, time_window,
|
||||
category_code, category_name, short_name,
|
||||
duration_seconds, total_seconds, percentage, is_tagged,
|
||||
computed_at, created_at, updated_at
|
||||
FROM dws.dws_member_project_tag
|
||||
WHERE site_id = current_setting('app.current_site_id')::bigint;
|
||||
|
||||
CREATE OR REPLACE VIEW app.v_dws_member_spending_power_index AS
|
||||
SELECT spi_id, site_id, member_id,
|
||||
spend_30, spend_90, recharge_90,
|
||||
orders_30, orders_90, visit_days_30, visit_days_90,
|
||||
avg_ticket_90, active_weeks_90, daily_spend_ewma_90,
|
||||
score_level_raw, score_speed_raw, score_stability_raw,
|
||||
score_level_display, score_speed_display, score_stability_display,
|
||||
raw_score, display_score,
|
||||
calc_time, created_at, updated_at
|
||||
FROM dws.dws_member_spending_power_index
|
||||
WHERE site_id = current_setting('app.current_site_id')::bigint;
|
||||
|
||||
@@ -481,6 +481,12 @@ CREATE TABLE dws.dws_finance_recharge_summary (
|
||||
total_card_balance numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
cash_card_balance numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
gift_card_balance numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
gift_liquor_balance numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
gift_table_fee_balance numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
gift_voucher_balance numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
gift_liquor_recharge numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
gift_table_fee_recharge numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
gift_voucher_recharge numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
@@ -92,7 +92,8 @@ CREATE TABLE biz.notes (
|
||||
ai_score smallint,
|
||||
ai_analysis text,
|
||||
created_at timestamp with time zone DEFAULT now(),
|
||||
updated_at timestamp with time zone DEFAULT now()
|
||||
updated_at timestamp with time zone DEFAULT now(),
|
||||
score smallint
|
||||
);
|
||||
|
||||
CREATE TABLE biz.trigger_jobs (
|
||||
|
||||
@@ -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 字符码计算,非真实数据 |
|
||||
997
docs/prd/Neo_Specs/NS1-xcx-backend-api.md
Normal file
997
docs/prd/Neo_Specs/NS1-xcx-backend-api.md
Normal file
@@ -0,0 +1,997 @@
|
||||
# NS1:小程序后端 API 补全 — xcx-backend-api
|
||||
|
||||
> 优先级:最高(上线关键路径)
|
||||
> 预估工作量:大
|
||||
> 前置条件:P1-P5 已完成(数据库、认证、任务、备注、AI 骨架均就绪)
|
||||
> 契约基准:`docs/miniprogram-dev/API-contract.md`
|
||||
|
||||
---
|
||||
|
||||
## 一、背景与目标
|
||||
|
||||
小程序前端 13 个业务页面已全部完成(P5.2 交付),`services/api.ts` 已统一封装 mock → real 切换机制(`USE_REAL_API` 开关)。但后端 23 个接口中仅 16 个已实现,剩余 12 个业务接口缺失,是前后端联调的最大阻塞。
|
||||
|
||||
本 SPEC 目标:补全全部缺失接口 + FDW 端到端验证 + 前后端联调修复,使小程序可以连接真实后端运行。
|
||||
|
||||
### 已实现的接口(16 个)
|
||||
|
||||
| 模块 | 接口 | 路由文件 |
|
||||
|------|------|---------|
|
||||
| Auth(5) | AUTH-1~5(登录/dev-login/me/refresh/apply) | `xcx_auth.py` |
|
||||
| Tasks(5) | GET tasks + pin/unpin/abandon/cancel-abandon | `xcx_tasks.py` |
|
||||
| Notes(3) | POST/GET/DELETE notes | `xcx_notes.py` |
|
||||
| AI Chat(3) | SSE stream + conversations + messages | `xcx_ai_chat.py` |
|
||||
|
||||
### 缺失的接口(12 个)
|
||||
|
||||
| 接口 ID | 端点 | 页面依赖 | 数据源 |
|
||||
|---------|------|---------|--------|
|
||||
| TASK-1 扩展 | GET /tasks(增加 performance 附带字段) | task-list | `dws_assistant_salary_calc` via FDW |
|
||||
| TASK-2 | GET /tasks/{id}(完整详情) | task-detail | biz.coach_tasks + FDW 多表 + ai_cache |
|
||||
| PERF-1 | GET /performance | performance | `dws_assistant_salary_calc` + `dws_assistant_daily_detail` via FDW |
|
||||
| PERF-2 | GET /performance/records | performance-records | `dwd_assistant_service_log` via FDW |
|
||||
| CUST-1 | GET /customers/{id} | customer-detail | `dim_member` + `dws_member_consumption_summary` via FDW |
|
||||
| CUST-2 | GET /customers/{id}/records | customer-service-records | `dwd_assistant_service_log` + `dwd_table_fee_log` via FDW |
|
||||
| COACH-1 | GET /coaches/{id} | coach-detail | `dim_assistant` + `dws_member_assistant_relation_index` via FDW |
|
||||
| BOARD-1 | GET /board/coaches | board-coach | `dws_assistant_salary_calc` + `dws_assistant_monthly_summary` via FDW |
|
||||
| BOARD-2 | GET /board/customers | board-customer | 多个 DWS 指数表 via FDW |
|
||||
| BOARD-3 | GET /board/finance | board-finance | 6 个 `dws_finance_*` 表 via FDW |
|
||||
| CONFIG-1 | GET /config/skill-types | board-coach | 静态配置或 cfg 表 |
|
||||
| CHAT 路径对齐 | 对齐 CHAT-1/2/3 路径与契约 | chat/chat-history | 现有 xcx_ai_chat.py 路径调整 |
|
||||
|
||||
---
|
||||
|
||||
## 二、技术架构
|
||||
|
||||
### 2.1 后端代码模式(沿用现有模式)
|
||||
|
||||
```
|
||||
apps/backend/app/
|
||||
├── routers/ # FastAPI 路由(xcx_*.py)
|
||||
│ ├── xcx_auth.py ✅ 已有
|
||||
│ ├── xcx_tasks.py ✅ 已有(需扩展 TASK-1/TASK-2)
|
||||
│ ├── xcx_notes.py ✅ 已有
|
||||
│ ├── xcx_ai_chat.py ✅ 已有(需路径对齐)
|
||||
│ ├── xcx_performance.py 🆕 新建
|
||||
│ ├── xcx_customers.py 🆕 新建
|
||||
│ ├── xcx_coaches.py 🆕 新建
|
||||
│ ├── xcx_board.py 🆕 新建
|
||||
│ └── xcx_config.py 🆕 新建
|
||||
├── services/ # 业务逻辑层
|
||||
│ ├── task_manager.py ✅ 已有(需扩展)
|
||||
│ ├── note_service.py ✅ 已有
|
||||
│ ├── perf_service.py 🆕 新建
|
||||
│ ├── customer_service.py 🆕 新建
|
||||
│ ├── coach_service.py 🆕 新建
|
||||
│ └── board_service.py 🆕 新建
|
||||
├── schemas/ # Pydantic 响应模型
|
||||
└── auth/ # 认证依赖注入 ✅ 已有
|
||||
```
|
||||
|
||||
### 2.2 数据库连接模式
|
||||
|
||||
- 业务库(zqyy_app):`get_connection()` → psycopg2 直连,读写
|
||||
- ETL 库(通过 FDW):`fdw_etl.*` schema 查询,需先 `SET LOCAL app.current_site_id`
|
||||
- ETL 库(直连只读):`get_etl_readonly_connection(site_id)` → 用于复杂聚合查询
|
||||
|
||||
### 2.3 权限控制
|
||||
|
||||
- 所有接口需 JWT(`require_approved()`)
|
||||
- 看板接口需额外权限检查(`require_permission("view_board_finance")` 等)
|
||||
- 权限种子数据已就绪(auth.role_permissions,14 条映射)
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详细设计
|
||||
|
||||
### 3.1 TASK-1 扩展:任务列表增加绩效概览
|
||||
|
||||
现有 `GET /api/xcx/tasks` 返回 `list[TaskListItem]`,需扩展为:
|
||||
- 增加 `performance` 字段(当月工时/收入/客户数/月份标签)
|
||||
- 增加分页支持(`page`/`pageSize` 参数)
|
||||
- 增加 `status` 筛选参数
|
||||
|
||||
数据源:
|
||||
- 任务列表:`biz.coach_tasks` + `fdw_etl.v_dim_member`(客户信息)+ `fdw_etl.v_dws_member_winback_index`/`v_dws_member_newconv_index`(指数)
|
||||
- 绩效概览:`fdw_etl.v_dws_assistant_salary_calc`(当月薪资计算快照)
|
||||
|
||||
### 3.2 TASK-2:任务详情(完整版)
|
||||
|
||||
现有 `xcx_tasks.py` 无 `GET /tasks/{id}` 端点,需新增。
|
||||
|
||||
数据源(多表聚合):
|
||||
- 基础信息:`biz.coach_tasks`
|
||||
- 客户信息:`fdw_etl.v_dim_member` + `fdw_etl.v_dws_member_consumption_summary`
|
||||
- 亲密度:`fdw_etl.v_dws_member_assistant_intimacy`
|
||||
- 近期服务记录:`fdw_etl.v_dwd_assistant_service_log`(最近 N 条)
|
||||
- 备注:`biz.notes`(最近 N 条,含 ai_score)
|
||||
- 维客线索:`public.member_retention_clue`
|
||||
- AI 缓存:`biz.ai_cache`(app4/app5/app6/app7 缓存)
|
||||
- 喜好标签:`fdw_etl.v_dwd_table_fee_log`(按房间类型统计)
|
||||
|
||||
> ⚠️ 这是最复杂的接口,涉及 8+ 张表的聚合。建议拆分为多个 service 函数组合。
|
||||
|
||||
### 3.3 PERF-1/PERF-2:绩效模块
|
||||
|
||||
PERF-1(绩效概览)数据源:
|
||||
- `fdw_etl.v_dws_assistant_salary_calc`(收入/档位/工资)
|
||||
- `fdw_etl.v_dwd_assistant_service_log`(当月服务记录明细)
|
||||
- `fdw_etl.v_dws_assistant_customer_stats`(新客/常客统计)
|
||||
|
||||
PERF-2(绩效明细)数据源:
|
||||
- `fdw_etl.v_dwd_assistant_service_log`(按月筛选,按日分组)
|
||||
- `fdw_etl.v_dws_assistant_daily_detail`(定档折算惩罚字段)
|
||||
|
||||
### 3.4 CUST-1/CUST-2:客户模块
|
||||
|
||||
CUST-1(客户详情)数据源:
|
||||
- `fdw_etl.v_dim_member` + `fdw_etl.v_dim_member_card_account`(基本信息 + 会员卡)
|
||||
- `fdw_etl.v_dws_member_consumption_summary`(消费汇总)
|
||||
- `fdw_etl.v_dws_member_assistant_relation_index`(关系指数)
|
||||
- `public.member_retention_clue`(维客线索)
|
||||
- 消费记录:`fdw_etl.v_dwd_settlement_head` + `fdw_etl.v_dwd_table_fee_log` + `fdw_etl.v_dwd_store_goods_sale` + `fdw_etl.v_dwd_recharge_order`
|
||||
|
||||
> ⚠️ 会员字段断档规则(DQ-6/DQ-7):`member_phone`/`member_name`/`member_card_type_name` 不可靠,必须通过 `member_id` JOIN `dim_member`/`dim_member_card_account`
|
||||
|
||||
CUST-2(客户服务记录)数据源:
|
||||
- `fdw_etl.v_dwd_assistant_service_log`(按客户+助教筛选)
|
||||
- `fdw_etl.v_dwd_table_fee_log`(台桌信息)
|
||||
|
||||
### 3.5 COACH-1:助教详情
|
||||
|
||||
数据源:
|
||||
- `fdw_etl.v_dim_assistant`(基本信息)
|
||||
- `fdw_etl.v_dws_member_assistant_relation_index`(客户数统计,RS>2)
|
||||
- `biz.coach_tasks`(任务统计:可见/隐藏/已放弃)
|
||||
- `biz.notes`(备注列表)
|
||||
|
||||
### 3.6 BOARD-1/2/3:看板模块
|
||||
|
||||
BOARD-1(助教看板)数据源:
|
||||
- `fdw_etl.v_dws_assistant_salary_calc`(定档业绩/工资)
|
||||
- `fdw_etl.v_dws_assistant_monthly_summary`(月度汇总)
|
||||
- `biz.coach_tasks`(任务完成数统计)
|
||||
|
||||
BOARD-2(客户看板)数据源(8 个维度排序):
|
||||
- 最应召回:`fdw_etl.v_dws_member_winback_index`(WBI 降序)
|
||||
- 最大消费潜力:`fdw_etl.v_dws_member_spending_power_index`(SPI 降序)
|
||||
- 最高余额:`fdw_etl.v_dws_member_consumption_summary`
|
||||
- 最近充值:`fdw_etl.v_dwd_recharge_order`
|
||||
- 最高消费 60 天:`fdw_etl.v_dws_member_consumption_summary`(基于 `items_sum`)
|
||||
- 最频繁 60 天:`fdw_etl.v_dws_member_consumption_summary`
|
||||
- 最近到店:`fdw_etl.v_dws_member_visit_detail`
|
||||
- 最专一:`fdw_etl.v_dws_member_assistant_relation_index`(RS 最大值)
|
||||
|
||||
BOARD-3(财务看板)数据源:
|
||||
- `fdw_etl.v_dws_finance_daily_summary`
|
||||
- `fdw_etl.v_dws_finance_income_structure`
|
||||
- `fdw_etl.v_dws_finance_recharge_summary`
|
||||
- `fdw_etl.v_dws_finance_discount_detail`
|
||||
- `fdw_etl.v_dws_finance_expense_summary`
|
||||
- `fdw_etl.v_dws_platform_settlement`
|
||||
- `biz.ai_cache`(app2_finance 缓存,AI 洞察)
|
||||
|
||||
### 3.7 CONFIG-1:技能类型列表
|
||||
|
||||
静态配置接口,返回助教技能类型列表。数据来源待定(硬编码 or cfg 表)。
|
||||
|
||||
### 3.8 CHAT 路径对齐
|
||||
|
||||
现有 `xcx_ai_chat.py` 路径:
|
||||
- `POST /api/ai/chat/stream`
|
||||
- `GET /api/ai/conversations`
|
||||
- `GET /api/ai/conversations/{id}/messages`
|
||||
|
||||
契约定义路径:
|
||||
- `GET /api/xcx/chat/history`
|
||||
- `GET /api/xcx/chat/{chatId}/messages`
|
||||
- `POST /api/xcx/chat/{chatId}/messages`
|
||||
|
||||
需要对齐路径,或在前端 service 层适配。
|
||||
|
||||
---
|
||||
|
||||
## 四、数据库审查与新增表建议
|
||||
|
||||
### 4.1 现有表结构满足度
|
||||
|
||||
| 接口 | 现有表是否满足 | 缺口 |
|
||||
|------|--------------|------|
|
||||
| TASK-1/2 | ✅ 基本满足 | 需 FDW 多表 JOIN |
|
||||
| PERF-1/2 | ✅ 满足 | 无 |
|
||||
| CUST-1/2 | ✅ 满足 | 消费记录需 3 种类型 UNION |
|
||||
| COACH-1 | ✅ 满足 | 无 |
|
||||
| BOARD-1 | ⚠️ 部分 | 任务完成数需实时统计 |
|
||||
| BOARD-2 | ⚠️ 部分 | 8 维度排序需多表查询,性能风险 |
|
||||
| BOARD-3 | ✅ 满足 | 环比需后端计算 |
|
||||
|
||||
### 4.2 建议新增的中间表/物化视图
|
||||
|
||||
#### 表 1:`biz.board_coach_snapshot`(助教看板快照)
|
||||
|
||||
用途:预聚合助教看板数据,避免每次请求实时 JOIN 多张 FDW 表。
|
||||
|
||||
```sql
|
||||
CREATE TABLE biz.board_coach_snapshot (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
site_id BIGINT NOT NULL,
|
||||
assistant_id BIGINT NOT NULL,
|
||||
snapshot_date DATE NOT NULL,
|
||||
-- 定档业绩
|
||||
performance_tier VARCHAR(20),
|
||||
tier_revenue NUMERIC(12,2),
|
||||
-- 工资
|
||||
salary_total NUMERIC(12,2),
|
||||
-- 客户数
|
||||
customer_count INTEGER,
|
||||
-- 高客源储值额
|
||||
high_value_balance NUMERIC(12,2),
|
||||
-- 任务完成数
|
||||
task_completed_count INTEGER,
|
||||
-- 服务时长
|
||||
total_hours NUMERIC(8,2),
|
||||
total_income NUMERIC(12,2),
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
UNIQUE (site_id, assistant_id, snapshot_date)
|
||||
);
|
||||
```
|
||||
|
||||
刷新策略:ETL 数据更新后触发(`trigger_jobs` 事件驱动),或每日凌晨批量刷新。
|
||||
|
||||
#### 表 2:`biz.board_customer_snapshot`(客户看板快照)
|
||||
|
||||
用途:预聚合客户看板 8 个维度的排序数据,避免实时查询 8 张 FDW 表。
|
||||
|
||||
```sql
|
||||
CREATE TABLE biz.board_customer_snapshot (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
site_id BIGINT NOT NULL,
|
||||
member_id BIGINT NOT NULL,
|
||||
snapshot_date DATE NOT NULL,
|
||||
-- 8 个维度排序值
|
||||
wbi_score NUMERIC(8,4), -- 最应召回
|
||||
spi_score NUMERIC(8,4), -- 最大消费潜力
|
||||
balance_amount NUMERIC(12,2), -- 最高余额
|
||||
last_recharge_date DATE, -- 最近充值
|
||||
spend_60d NUMERIC(12,2), -- 最高消费 60 天(items_sum 口径)
|
||||
visit_count_60d INTEGER, -- 最频繁 60 天
|
||||
last_visit_date DATE, -- 最近到店
|
||||
max_rs_score NUMERIC(8,4), -- 最专一
|
||||
-- 展示字段
|
||||
member_name VARCHAR(100),
|
||||
member_avatar VARCHAR(500),
|
||||
member_tags TEXT[], -- 喜好标签
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
UNIQUE (site_id, member_id, snapshot_date)
|
||||
);
|
||||
```
|
||||
|
||||
#### 表 3:`biz.board_finance_snapshot`(财务看板快照)
|
||||
|
||||
用途:预聚合财务看板数据 + 环比计算结果。
|
||||
|
||||
```sql
|
||||
CREATE TABLE biz.board_finance_snapshot (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
site_id BIGINT NOT NULL,
|
||||
biz_date DATE NOT NULL,
|
||||
-- 核心指标(JSON 存储,灵活扩展)
|
||||
metrics JSONB NOT NULL,
|
||||
-- 环比数据
|
||||
prev_day_metrics JSONB,
|
||||
prev_week_metrics JSONB,
|
||||
prev_month_metrics JSONB,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
UNIQUE (site_id, biz_date)
|
||||
);
|
||||
```
|
||||
|
||||
#### 表 4:`biz.task_detail_cache`(任务详情缓存)
|
||||
|
||||
用途:TASK-2 接口涉及 8+ 张表聚合,首次查询后缓存结果,后续读缓存。
|
||||
|
||||
```sql
|
||||
CREATE TABLE biz.task_detail_cache (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
task_id BIGINT NOT NULL REFERENCES biz.coach_tasks(id),
|
||||
site_id BIGINT NOT NULL,
|
||||
detail_json JSONB NOT NULL,
|
||||
version INTEGER NOT NULL DEFAULT 1,
|
||||
expires_at TIMESTAMPTZ NOT NULL,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
UNIQUE (task_id)
|
||||
);
|
||||
```
|
||||
|
||||
失效策略:备注新增/AI 缓存更新/任务状态变更时,DELETE 对应缓存行。
|
||||
|
||||
---
|
||||
|
||||
## 五、FDW 端到端验证
|
||||
|
||||
### 5.1 验证范围
|
||||
|
||||
需验证 `fdw_etl` schema 下所有外部表可正常 SELECT,重点关注:
|
||||
|
||||
| 外部表 | 用途 | 验证 SQL |
|
||||
|--------|------|---------|
|
||||
| `v_dim_member` | 客户信息 | `SELECT count(*) FROM fdw_etl.v_dim_member` |
|
||||
| `v_dim_assistant` | 助教信息 | `SELECT count(*) FROM fdw_etl.v_dim_assistant` |
|
||||
| `v_dws_assistant_salary_calc` | 薪资计算 | `SELECT * FROM fdw_etl.v_dws_assistant_salary_calc LIMIT 5` |
|
||||
| `v_dws_finance_daily_summary` | 财务日报 | `SELECT * FROM fdw_etl.v_dws_finance_daily_summary LIMIT 5` |
|
||||
| `v_dws_member_winback_index` | WBI 指数 | `SELECT * FROM fdw_etl.v_dws_member_winback_index LIMIT 5` |
|
||||
| `v_dws_member_spending_power_index` | SPI 指数 | `SELECT * FROM fdw_etl.v_dws_member_spending_power_index LIMIT 5` |
|
||||
|
||||
### 5.2 RLS 验证
|
||||
|
||||
```sql
|
||||
-- 设置门店隔离
|
||||
SET LOCAL app.current_site_id = '目标 site_id';
|
||||
-- 验证只返回该门店数据
|
||||
SELECT site_id, count(*) FROM fdw_etl.v_dim_member GROUP BY site_id;
|
||||
-- 预期:只有一个 site_id
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、前后端联调
|
||||
|
||||
### 6.1 联调步骤
|
||||
|
||||
1. 后端接口开发完成后,逐个验证响应格式与 `API-contract.md` 一致
|
||||
2. 小程序 `services/api.ts` 中 `USE_REAL_API = true`
|
||||
3. 逐页面联调:login → task-list → task-detail → performance → board → customer-detail → chat
|
||||
4. 修复 snake_case → camelCase 映射差异
|
||||
5. 处理 inline-mock 页面的数据替换(7 个页面有 TODO 标记)
|
||||
|
||||
### 6.2 已知需要对齐的差异
|
||||
|
||||
- TASK-1 响应格式:现有返回 `list[TaskListItem]`,契约要求 `{ items, total, page, pageSize, performance }`
|
||||
- CHAT 路径:现有 `/api/ai/*`,契约要求 `/api/xcx/chat/*`
|
||||
- 响应包装:现有直接返回数据,契约要求 `{ code: 0, data: ... }` 包装
|
||||
|
||||
---
|
||||
|
||||
## 七、参考文档
|
||||
|
||||
| 文档 | 路径 | 用途 |
|
||||
|------|------|------|
|
||||
| API 契约 | `docs/miniprogram-dev/API-contract.md` | 接口定义基准 |
|
||||
| 前端 Service 层 | `apps/miniprogram/miniprogram/services/api.ts` | 前端期望的响应格式 |
|
||||
| BD 手册-认证表 | `docs/database/BD_Manual_auth_tables.md` | auth schema 表结构 |
|
||||
| BD 手册-业务表 | `docs/database/BD_Manual_biz_tables.md` | biz schema 表结构 |
|
||||
| DWD-DOC 标杆 | `docs/reports/DWD-DOC/` | 金额口径、字段语义权威参考 |
|
||||
| 数据依赖矩阵 | `docs/prd/specs/00-数据依赖矩阵.md` | 页面→数据表映射 |
|
||||
| 现有 task_manager | `apps/backend/app/services/task_manager.py` | 任务服务层模式参考 |
|
||||
| 现有 note_service | `apps/backend/app/services/note_service.py` | 备注服务层模式参考 |
|
||||
| 权限中间件 | `apps/backend/app/middleware/permission.py` | 权限检查模式参考 |
|
||||
| 数据库连接 | `apps/backend/app/database.py` | 连接方式参考 |
|
||||
| FDW 配置 | `db/fdw/setup_fdw_test.sql` | FDW 外部表映射 |
|
||||
|
||||
---
|
||||
|
||||
## 八、预审查清单(SPEC 启动前确认)
|
||||
|
||||
以下问题需要在启动 SPEC 前与用户确认,确保需求无歧义:
|
||||
|
||||
### 8.1 接口设计
|
||||
|
||||
1. **响应包装格式**:现有后端直接返回数据对象,契约要求 `{ code: 0, data: ... }` 包装。是全局统一改造(中间件),还是仅新接口使用包装格式?
|
||||
2. **CHAT 路径对齐**:是修改后端路径从 `/api/ai/*` 改为 `/api/xcx/chat/*`,还是在前端 service 层适配现有路径?
|
||||
3. **分页策略**:TASK-1 契约要求分页,但现有实现返回全量列表。是否所有列表接口都需要分页?看板接口(前 100 名)是否需要分页?
|
||||
4. **CONFIG-1 技能类型**:数据来源是硬编码还是从 `cfg_*` 表读取?如果硬编码,具体的技能类型列表是什么?
|
||||
|
||||
### 8.2 数据库
|
||||
|
||||
5. **快照表刷新策略**:3 个看板快照表(coach/customer/finance)的刷新频率?是 ETL 数据更新后触发,还是每日定时刷新,还是请求时按需刷新?
|
||||
6. **任务详情缓存**:`task_detail_cache` 的过期时间设多久?缓存失效触发条件是否完整(备注新增、AI 缓存更新、任务状态变更)?
|
||||
7. **FDW 外部表命名**:现有代码中使用 `fdw_etl.v_dim_member`(带 `v_` 前缀),需确认所有 FDW 外部表的实际命名是否一致。
|
||||
|
||||
### 8.3 业务逻辑
|
||||
|
||||
8. **TASK-2 服务记录范围**:近期服务记录取最近多少条?最近 30 天还是最近 10 条?
|
||||
9. **BOARD-2 客户看板**:8 个维度排序是否都取前 100 名?是否需要支持类型筛选(如按会员等级筛选)?
|
||||
10. **BOARD-3 财务看板**:环比计算的基准是什么?日环比、周环比、月环比都需要吗?
|
||||
11. **CUST-1 消费记录**:3 种消费类型(台桌/商城/充值)是混合排序还是分类展示?分页策略?
|
||||
12. **COACH-1 任务统计**:可见/隐藏/已放弃的分类逻辑是什么?"隐藏"指什么状态?
|
||||
|
||||
### 8.4 性能与安全
|
||||
|
||||
13. **FDW 查询性能**:看板接口涉及多张 FDW 表的聚合查询,是否需要设置查询超时?超时后返回什么?
|
||||
14. **维客线索脱敏**:TASK-2 返回维客线索时,后端脱敏规则是否与 P6 spec 中定义的一致(移除 recorded_by_assistant_id/name)?
|
||||
15. **金额口径确认**:所有涉及消费金额的接口,确认使用 `items_sum` 口径(= table_charge_money + goods_money + assistant_pd_money + assistant_cx_money + electricity_money),不使用 `consume_money`。
|
||||
|
||||
---
|
||||
|
||||
## 八½、前置审查发现(2026-03-18 Kiro 审查)
|
||||
|
||||
> 审查方法:对照现有代码(`xcx_auth.py`、`xcx_tasks.py`、`xcx_ai_chat.py`、`task_manager.py`、`permission.py`、`database.py`)、API 契约(`API-contract.md`)、前端 Service 层(`api.ts`)、FDW 配置(`setup_fdw_test.sql`)、BD 手册、数据依赖矩阵、DWD-DOC 标杆文档,逐项交叉验证。
|
||||
|
||||
### R1:接口清单准确性
|
||||
|
||||
| # | 问题 | 严重度 | 说明 |
|
||||
|---|------|--------|------|
|
||||
| R1-1 | 接口总数算术错误 | 🟡 低 | spec 称"23 个接口中仅 16 个已实现,剩余 12 个缺失",16+12=28≠23。需修正总数或重新盘点 |
|
||||
| R1-2 | Auth 已实现接口数低估 | 🟡 低 | spec 列 Auth 5 个,实际 `xcx_auth.py` 生产端点 7 个(login/apply/me/me-sites/switch-site/refresh/dev-login),含 dev 端点共 11 个 |
|
||||
| R1-3 | TASK-4 路径不一致 | 🔴 高 | API 契约定义 `POST /tasks/{taskId}/restore`,前端 `api.ts` 调用 `/tasks/${taskId}/restore`,但后端实现的是 `/tasks/{taskId}/cancel-abandon`。联调必然失败 |
|
||||
|
||||
**R1-3 ✅ 已确认**:统一为 `/restore`(改后端路由路径,契约和前端不动)。后端 `xcx_tasks.py` 中 `/cancel-abandon` 端点已实现,需改路径为 `/restore`。
|
||||
|
||||
### R2:响应格式(全局架构决策,阻塞所有新接口开发)
|
||||
|
||||
| # | 问题 | 严重度 | 说明 |
|
||||
|---|------|--------|------|
|
||||
| R2-1 | 成功响应无包装 | 🔴 高 | 契约要求 `{ code: 0, data: ... }`,现有后端直接返回 Pydantic model。全局改造影响所有已实现接口 |
|
||||
| R2-2 | 错误响应格式不一致 | 🔴 高 | 契约要求 `{ code: number, message: string }`,现有 FastAPI 默认 `{ detail: "..." }`。需自定义异常处理器 |
|
||||
| R2-3 | snake_case vs camelCase | 🔴 高 | 后端返回 snake_case,前端期望 camelCase。api.ts 有 TODO 标记但未实现转换。需决定转换层位置 |
|
||||
|
||||
**R2 ✅ 已确认:采用方案 A**(后端全局包装 `{ code: 0, data: ... }` + Pydantic `alias_generator=to_camel`)
|
||||
|
||||
实施路径:
|
||||
1. 新增全局响应包装中间件(或 `response_model` 基类),成功响应统一 `{ code: 0, data: ... }`
|
||||
2. 新增全局异常处理器,`HTTPException` → `{ code: status_code, message: detail }`
|
||||
3. 所有 Pydantic schema 加 `model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)`
|
||||
4. 前端 `request()` 工具函数加 `.data` 解包(一处改动)
|
||||
5. 已有接口(Auth/Tasks/Notes/AI Chat)逐个验证兼容性
|
||||
|
||||
### R3:CHAT 模块路径与功能差异
|
||||
|
||||
| # | 问题 | 严重度 | 说明 |
|
||||
|---|------|--------|------|
|
||||
| R3-1 | 路径前缀不同 | 🟠 中 | 现有 `/api/ai/*`,契约 `/api/xcx/chat/*` |
|
||||
| R3-2 | 功能模式不同 | 🔴 高 | 现有是 SSE 流式(`POST /chat/stream`),契约是同步 JSON(`POST /chat/{chatId}/messages` 返回 `{ userMessage, aiReply }`)。两者不可互替 |
|
||||
| R3-3 | 历史列表字段不同 | 🟠 中 | 现有返回 `{ id, app_id, source_page, first_message_preview }`,契约要求 `{ id, customer_name, last_message, last_time, unread_count }` |
|
||||
|
||||
**R3 ✅ 已确认:采用选项 A**(保留 SSE 流式 + 新增同步端点)
|
||||
|
||||
实施方案:
|
||||
- 保留 `POST /api/xcx/chat/stream`(SSE 流式对话,路径从 `/api/ai/chat/stream` 迁移)
|
||||
- 新增 `GET /api/xcx/chat/history`(历史对话列表,替代 `/api/ai/conversations`)
|
||||
- 新增 `GET /api/xcx/chat/{chatId}/messages`(消息查看,替代 `/api/ai/conversations/{id}/messages`)
|
||||
- 契约补充 SSE 端点定义
|
||||
- `ai_conversations` 表无 `unread_count` 字段,CHAT-1 响应中此字段暂返回 0(或后续按需新增)
|
||||
|
||||
### R4:数据库新增表设计
|
||||
|
||||
| # | 问题 | 严重度 | 说明 |
|
||||
|---|------|--------|------|
|
||||
| R4-1 | 快照表刷新时机未定义 | 🟠 中 | 3 个 board_*_snapshot 表的刷新策略(ETL 后触发 / 每日定时 / 按需)直接影响数据新鲜度和实现复杂度 |
|
||||
| R4-2 | task_detail_cache 命中率存疑 | 🟡 低 | TASK-2 涉及 8+ 张表,备注/AI 缓存/服务记录变更频繁,缓存频繁失效。建议评估:优化 SQL + 并行查询是否足够,不一定需要缓存表 |
|
||||
| R4-3 | board_finance_snapshot.metrics 缺 schema | 🟡 低 | JSONB 灵活但无约束,建议在 spec 中定义 metrics 的 key 清单(如 `total_revenue`、`total_expense`、`net_income` 等) |
|
||||
| R4-4 | board_customer_snapshot.spend_60d 窗口定义 | 🟡 低 | "60 天"是 `snapshot_date - 60` 到 `snapshot_date`,还是自然月?需明确 |
|
||||
| R4-5 | 快照表是否真的需要 | 🟠 中 | 看板数据量不大(单店助教 < 50 人、活跃客户 < 5000 人),FDW 直查 + 合理索引可能已够用。快照表增加了维护复杂度(刷新逻辑、数据一致性)。建议先不建快照表,性能不足时再加 |
|
||||
|
||||
**R4 ✅ 已确认:采用选项 A**(先直查 FDW,做好索引和查询优化)
|
||||
- 不建 `board_coach_snapshot`、`board_customer_snapshot`、`board_finance_snapshot` 三张快照表
|
||||
- 不建 `task_detail_cache` 缓存表
|
||||
- 看板接口直接查 FDW 表,通过合理索引 + SQL 优化保证性能
|
||||
- 如后续出现性能瓶颈,再按需引入快照/缓存机制
|
||||
- spec 第四章中的 4 张新增表建议全部移除
|
||||
|
||||
### R5:权限模型
|
||||
|
||||
| # | 问题 | 严重度 | 说明 |
|
||||
|---|------|--------|------|
|
||||
| R5-1 | 看板接口权限映射未明确 | 🟠 中 | BOARD-1 → `view_board_coach`?BOARD-2 → `view_board_customer`?BOARD-3 → `view_board_finance`?需确认 |
|
||||
| R5-2 | PERF/CUST/COACH 权限未定义 | 🟠 中 | 绩效/客户/助教模块是否只需 `require_approved()`?助教只能看自己的绩效,管理员能看所有人的? |
|
||||
| R5-3 | 数据隔离粒度 | 🟠 中 | 助教只能看自己的任务/绩效/客户(通过 `assistant_id` 过滤),管理员能看全店。这个逻辑在 service 层实现还是中间件层? |
|
||||
|
||||
**R5 ✅ 已确认**:现有 5 个权限 code 够用,从上游入口控制即可。
|
||||
- `view_tasks`:任务模块(TASK-1/2、PERF-1/2、CUST-1/2、COACH-1 均通过此权限 + `require_approved()` 控制)
|
||||
- `view_board` + `view_board_finance` / `view_board_customer` / `view_board_coach`:看板模块
|
||||
- 数据隔离在 service 层实现:助教通过 `user_assistant_binding` 获取 `assistant_id`,只查自己的数据;管理员角色可查全店
|
||||
- 权限选项固定,角色-权限映射通过租户管理后台灵活配置
|
||||
- 不新增权限 code
|
||||
|
||||
### R6:数据源与口径
|
||||
|
||||
| # | 问题 | 严重度 | 说明 |
|
||||
|---|------|--------|------|
|
||||
| R6-1 | TASK-1 performance.total_customers 来源不明 | 🟡 低 | `dws_assistant_salary_calc` 是否有客户数字段?可能需要从 `dws_assistant_customer_stats` 单独查 |
|
||||
| R6-2 | CUST-1 消费记录混合 vs 分类 | 🟡 低 | 3 种消费类型(台桌/商城/充值)在 API 契约中是单一 `consumption_records` 数组,暗示混合排序。确认? |
|
||||
| R6-3 | BOARD-2 维度切换方式 | 🟡 低 | API 契约通过 `dimension` 参数切换 8 个维度,一次返回一个维度的排序结果。确认? |
|
||||
|
||||
### R7:spec 中引用但未定义的表
|
||||
|
||||
| # | 表名 | 引用位置 | 说明 |
|
||||
|---|------|---------|------|
|
||||
| R7-1 | `biz.ai_cache` | TASK-2(app4/5/6/7)、BOARD-3(app2) | 多处引用但 BD 手册无定义。需确认表结构或补充 BD 手册 |
|
||||
| R7-2 | `biz.ai_conversations` + `biz.ai_messages` | CHAT 模块 | 现有 `xcx_ai_chat.py` 通过 `ConversationService` 操作,但 spec 未引用其结构 |
|
||||
| R7-3 | `public.member_retention_clue` | TASK-2、CUST-1 | 维客线索表,spec 未描述字段结构和脱敏规则 |
|
||||
|
||||
**R7 ✅ 已确认(查库验证)**:4 张表全部已建,无需新增 DDL。
|
||||
|
||||
| 表名 | Schema | 字段数 | 关键字段 |
|
||||
|------|--------|--------|---------|
|
||||
| `ai_cache` | biz | 9 | `cache_type`(app2_finance/app3_clue/app4_analysis/app5_script/app6_note/app7_task)、`target_id`、`result_json`(JSONB)、`expires_at` |
|
||||
| `ai_conversations` | biz | 8 | `user_id`(varchar)、`app_id`、`site_id`、`source_page`、`source_context`(JSONB) |
|
||||
| `ai_messages` | biz | 6 | `conversation_id`(FK)、`role`、`content`、`tokens_used` |
|
||||
| `member_retention_clue` | public | 10 | `member_id`、`category`、`summary`、`detail`、`recorded_by_assistant_id`、`recorded_by_name`、`source`(默认 'manual') |
|
||||
|
||||
脱敏规则:TASK-2/CUST-1 返回维客线索时,后端应排除 `recorded_by_assistant_id` 和 `recorded_by_name` 字段(仅返回 `category`、`summary`、`detail`、`source`、`recorded_at`)。
|
||||
|
||||
### R8:其他待确认项
|
||||
|
||||
| # | 问题 | 来源 | 状态 |
|
||||
|---|------|------|------|
|
||||
| R8-1 | CONFIG-1 技能类型 | spec §3.7 + api.ts | ✅ 使用 cfg 表 |
|
||||
| R8-2 | TASK-2 近期服务记录分页 | spec §3.2 | ✅ 一批 20 条,懒加载 |
|
||||
| R8-3 | TASK-2 备注分页 | spec §3.2 | ✅ 一批 20 条,懒加载 |
|
||||
| R8-4 | BOARD-2 每维度取前多少名 | spec §3.6 | ✅ 一批 20 条,懒加载,无上限 |
|
||||
| R8-5 | BOARD-3 环比 | spec §3.6 | ✅ 全部月环比(详见下方) |
|
||||
| R8-6 | COACH-1 "隐藏任务" | spec §3.5 + API 契约 | ✅ `inactive` 状态(详见下方) |
|
||||
| R8-7 | FDW 查询超时 | 性能风险 | 待定(实施时按需设置) |
|
||||
| R8-8 | 金额口径 | DWD-DOC 标杆 | ✅ `items_sum`,禁用 `consume_money` |
|
||||
|
||||
**R8-1 ✅ 已确认**:使用 cfg 表。后端从 ETL 库的 cfg 表读取技能类型列表,前端 api.ts 中的硬编码仅作为 mock 回退。
|
||||
|
||||
**R8-2/3 ✅ 已确认**:一批 20 条,懒加载。
|
||||
- 前端修改需求:TASK-2 页面的服务记录区域和备注区域需从一次性渲染改为懒加载模式(`page` + `pageSize=20` 参数 + 滚动到底部触发"加载更多")
|
||||
|
||||
**R8-4 ✅ 已确认**:一批 20 条,懒加载,无上限。
|
||||
- 前端修改需求:BOARD-2 客户看板需从一次性渲染改为懒加载模式
|
||||
|
||||
**R8-5 ✅ 已确认(环比调研完成)**:
|
||||
- 所有环比均为**月环比**(与上月同期对比)
|
||||
- 共 60+ 个环比数据点,覆盖 6 个板块:经营一览(8 项)、预收资产(6 项 + 赠送卡矩阵 12 项)、应计收入确认(收入结构 9 项 + 正价 4 项 + 优惠 4 项 + 渠道 3 项)、现金流入(5 项)、现金流出(15 项)、助教分析(基础课 + 激励课各 6 项)
|
||||
- 前端已实现环比开关(`compareEnabled`),环比值格式为百分比字符串(如 `"+12.5%"`)
|
||||
- API 契约缺陷:BOARD-3 响应中 `trend` 字段仅支持 `up/down/flat`,**未定义环比百分比值**,需补充 `compareValue: string`(如 `"12.5%"`)字段
|
||||
- 后端需计算所有指标的月环比值(当前期 vs 上期),返回百分比和方向
|
||||
- 页面筛选支持:本月、上月、本周、上周、前3个月、本季度、上季度、最近6个月
|
||||
|
||||
**R8-6 ✅ 已确认**:"隐藏任务"= 回访任务被顶替后,仍有一段生效时间但前端不显示。
|
||||
- 映射关系:`hidden_tasks` 对应 `coach_tasks.status = 'inactive'`(被新任务顶替但未过期)
|
||||
- 后端在 COACH-1 接口中按 status 分组返回:`active` → `visible_tasks`,`inactive` → `hidden_tasks`,`abandoned` → `abandoned_tasks`
|
||||
- 前端不展示 `hidden_tasks`,但后端仍需返回(管理端可能需要查看)
|
||||
|
||||
---
|
||||
|
||||
## 八¾、看板筛选项交叉矩阵(2026-03-18 Kiro 调研)
|
||||
|
||||
> 调研方法:逐文件阅读 `board-coach.ts`、`board-customer.ts`、`board-finance.ts` 的完整源码,提取所有筛选项定义、可选值、默认值、交叉约束、前端传参方式,并与 API 契约和 `api.ts` service 层交叉验证。
|
||||
|
||||
### B1:BOARD-1 助教看板(board-coach)
|
||||
|
||||
#### 筛选项清单
|
||||
|
||||
| 参数名 | 类型 | 可选值 | 默认值 | 说明 |
|
||||
|--------|------|--------|--------|------|
|
||||
| `sort` | 排序维度 | `perf_desc`、`perf_asc`、`salary_desc`、`salary_asc`、`sv_desc`、`task_desc` | `perf_desc` | 6 种排序,决定卡片模板 |
|
||||
| `skill` | 技能筛选 | `all`、`chinese`、`snooker`、`mahjong`、`karaoke` | `all` | 不限、中式/追分、斯诺克、麻将/棋牌、团建/K歌 |
|
||||
| `time` | 时间范围 | `month`、`quarter`、`last_month`、`last_3m`、`last_quarter`、`last_6m` | `month` | 本月、本季度、上月、前3个月、上季度、最近6个月 |
|
||||
|
||||
#### 排序→卡片模板映射(`SORT_TO_DIM`)
|
||||
|
||||
| sort 值 | dimType | 卡片显示内容 |
|
||||
|---------|---------|------------|
|
||||
| `perf_desc` / `perf_asc` | `perf` | 定档业绩工时、上期工时、距升档差距、是否达标 |
|
||||
| `salary_desc` / `salary_asc` | `salary` | 工资总额、定档工时、上期工时 |
|
||||
| `sv_desc` | `sv` | 客源储值额、储值客户数、储值消耗 |
|
||||
| `task_desc` | `task` | 召回任务完成数、回访任务完成数 |
|
||||
|
||||
#### 各维度卡片所需字段(后端响应必须包含)
|
||||
|
||||
所有维度共享的基础字段(每个 item 都返回):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `id` | string | 助教 ID |
|
||||
| `name` | string | 助教姓名 |
|
||||
| `initial` | string | 姓名首字(头像占位) |
|
||||
| `avatarGradient` | string | 头像渐变色(blue/green/pink/amber/violet/cyan) |
|
||||
| `level` | string | 等级 key:`star`/`senior`/`middle`/`junior` |
|
||||
| `levelClass` | string | 等级样式类(前端可自行映射,后端可不返回) |
|
||||
| `skills` | array | 技能列表 `[{ text: '🎱', cls: 'skill--chinese' }]` |
|
||||
| `topCustomers` | string[] | Top 客户列表(如 `['💖 王先生', '💛 李女士']`) |
|
||||
|
||||
各维度专属字段:
|
||||
|
||||
| dimType | 维度名 | 专属字段 | 类型 | 说明 |
|
||||
|---------|--------|---------|------|------|
|
||||
| `perf` | 定档业绩 | `perfHours` | number | 当期定档工时 |
|
||||
| | | `perfHoursBefore` | number? | 上期定档工时(可选,无上期数据时不返回) |
|
||||
| | | `perfGap` | string? | 距升档差距描述(如 `"距升档 13.8h"`,已达标时不返回) |
|
||||
| | | `perfReached` | boolean | 是否已达标 |
|
||||
| `salary` | 工资 | `salary` | number | 工资总额(元) |
|
||||
| | | `salaryPerfHours` | number | 定档工时 |
|
||||
| | | `salaryPerfBefore` | number? | 上期定档工时 |
|
||||
| `sv` | 客源储值 | `svAmount` | number | 客源储值总额(元) |
|
||||
| | | `svCustomerCount` | number | 储值客户数 |
|
||||
| | | `svConsume` | number | 储值消耗额(元) |
|
||||
| `task` | 任务完成 | `taskRecall` | number | 召回任务完成数 |
|
||||
| | | `taskCallback` | number | 回访任务完成数 |
|
||||
|
||||
> 后端设计建议:所有维度的字段统一返回在同一个 item 对象中(扁平结构),前端根据当前 `dimType` 选择性渲染。这样切换维度时无需重新请求(数据已在本地),仅切换卡片模板即可。如果数据量大或查询开销高,也可按 `sort` 参数只返回当前维度所需字段。
|
||||
|
||||
#### 交叉约束
|
||||
|
||||
| 约束 | 说明 |
|
||||
|------|------|
|
||||
| `time=last_6m` + `sort=sv_desc` | ⚠️ 不兼容。前端 TIME_OPTIONS 注释标注"不支持客源储值最高"。后端需返回 400 或忽略 |
|
||||
| 其他组合 | 无限制,3 参数自由组合 |
|
||||
|
||||
#### 交叉组合总数
|
||||
- sort(6) × skill(5) × time(6) = **180 种**(减去 1 个不兼容 = **175 种有效组合**)
|
||||
|
||||
#### 前端传参(`api.ts`)
|
||||
```typescript
|
||||
fetchBoardCoaches({ skill, sort, time })
|
||||
```
|
||||
|
||||
#### ⚠️ 前端 Bug:筛选变更不触发重新请求
|
||||
`onSortChange`/`onSkillChange`/`onTimeChange` 仅更新 `data` 状态(`selectedSort`/`selectedSkill`/`selectedTime`),**未调用 `loadData()`**。联调时前端需修复:筛选变更后重新调用 `loadData()`。
|
||||
|
||||
---
|
||||
|
||||
### B2:BOARD-2 客户看板(board-customer)
|
||||
|
||||
#### 筛选项清单
|
||||
|
||||
| 参数名 | 类型 | 可选值 | 默认值 | 说明 |
|
||||
|--------|------|--------|--------|------|
|
||||
| `dimension` | 维度切换 | `recall`、`potential`、`balance`、`recharge`、`recent`、`spend60`、`freq60`、`loyal` | `recall` | 8 个维度,决定卡片模板和排序逻辑 |
|
||||
| `project` | 项目筛选 | `all`、`chinese`、`snooker`、`mahjong`、`karaoke` | `all` | 全部、中式/追分、斯诺克、麻将/棋牌、团建/K歌 |
|
||||
| `page` | 分页页码 | 正整数 | `1` | 前端待补充(R8-4 决策:20 条懒加载) |
|
||||
| `pageSize` | 每页条数 | 正整数 | `20` | 前端待补充 |
|
||||
|
||||
#### 8 维度→卡片模板映射(`DIMENSION_TO_DIM`)
|
||||
|
||||
| dimension | 中文名 | 排序依据(后端) | 数据源(FDW 表) | 卡片关键字段 |
|
||||
|-----------|--------|----------------|-----------------|------------|
|
||||
| `recall` | 最应召回 | WBI 降序 | `v_dws_member_winback_index` | idealDays、elapsedDays、overdueDays、visits30d、balance、recallIndex |
|
||||
| `potential` | 最大消费潜力 | SPI 降序 | `v_dws_member_spending_power_index` | potentialTags、spend30d、avgVisits、avgSpend |
|
||||
| `balance` | 最高余额 | balance_amount 降序 | `v_dws_member_consumption_summary` | lastVisit、monthlyConsume、availableMonths |
|
||||
| `recharge` | 最近充值 | last_recharge_date 降序 | `v_dwd_recharge_order` | lastRecharge、rechargeAmount、recharges60d、currentBalance |
|
||||
| `recent` | 最近到店 | last_visit_date 降序 | `v_dws_member_visit_detail` | daysAgo、visitFreq、idealDays |
|
||||
| `spend60` | 最高消费 近60天 | items_sum_60d 降序 | `v_dws_member_consumption_summary` | spend60d、visits60d、highSpendTag |
|
||||
| `freq60` | 最频繁 近60天 | visit_count_60d 降序 | `v_dws_member_consumption_summary` | avgInterval、weeklyVisits(8 周柱状图) |
|
||||
| `loyal` | 最专一 近60天 | max_rs 降序 | `v_dws_member_assistant_relation_index` | intimacy、topCoachName、coachDetails(助教明细表) |
|
||||
|
||||
#### 各维度卡片所需字段(后端响应必须包含)
|
||||
|
||||
所有维度共享的基础字段(每个 item 都返回):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `id` | string | 客户 member_id |
|
||||
| `name` | string | 客户姓名 |
|
||||
| `initial` | string | 姓名首字(头像占位) |
|
||||
| `avatarCls` | string | 头像样式类(avatar--amber/pink/blue 等) |
|
||||
| `assistants` | array | 关联助教列表 `[{ name, cls, heartScore, badge?, badgeCls? }]` |
|
||||
|
||||
各维度专属字段:
|
||||
|
||||
| dimType | 维度名 | 专属字段 | 类型 | 说明 |
|
||||
|---------|--------|---------|------|------|
|
||||
| `recall` | 最应召回 | `idealDays` | number | 理想到店间隔(天) |
|
||||
| | | `elapsedDays` | number | 已过天数 |
|
||||
| | | `overdueDays` | number | 超期天数(= elapsedDays - idealDays) |
|
||||
| | | `visits30d` | number | 近 30 天到店次数 |
|
||||
| | | `balance` | string | 余额(格式化,如 `"¥2,680"`) |
|
||||
| | | `recallIndex` | string | 召回指数(如 `"9.2"`) |
|
||||
| `potential` | 最大消费潜力 | `potentialTags` | array | 潜力标签 `[{ text: '高频', theme: 'primary' }]` |
|
||||
| | | `spend30d` | string | 近 30 天消费 |
|
||||
| | | `avgVisits` | string | 月均到店(如 `"6.2次"`) |
|
||||
| | | `avgSpend` | string | 次均消费 |
|
||||
| `balance` | 最高余额 | `balance` | string | 当前余额 |
|
||||
| | | `lastVisit` | string | 最近到店(如 `"3天前"`) |
|
||||
| | | `monthlyConsume` | string | 月均消耗 |
|
||||
| | | `availableMonths` | string | 可用月数(如 `"约0.8个月"`) |
|
||||
| `recharge` | 最近充值 | `lastRecharge` | string | 最后充值日期(如 `"2月15日"`) |
|
||||
| | | `rechargeAmount` | string | 充值金额 |
|
||||
| | | `recharges60d` | string | 近 60 天充值次数 |
|
||||
| | | `currentBalance` | string | 当前余额 |
|
||||
| `recent` | 最近到店 | `daysAgo` | number | 距今天数(右上角大字) |
|
||||
| | | `visitFreq` | string | 到店频率(如 `"6.2次/月"`) |
|
||||
| | | `idealDays` | number | 理想间隔 |
|
||||
| | | `visits30d` | number | 近 30 天到店 |
|
||||
| | | `avgSpend` | string | 次均消费 |
|
||||
| `spend60` | 最高消费 近60天 | `spend60d` | string | 近 60 天消费总额 |
|
||||
| | | `visits60d` | string | 近 60 天到店次数 |
|
||||
| | | `highSpendTag` | boolean | 是否高消费标签 |
|
||||
| | | `avgSpend` | string | 次均消费 |
|
||||
| `freq60` | 最频繁 近60天 | `visits60d` | string | 近 60 天到店次数(右上角大字) |
|
||||
| | | `avgInterval` | string | 平均到店间隔(如 `"5.0天"`) |
|
||||
| | | `weeklyVisits` | array | 8 周到店柱状图 `[{ val: number, pct: number }]` |
|
||||
| | | `spend60d` | string | 近 60 天消费 |
|
||||
| `loyal` | 最专一 近60天 | `intimacy` | string | 亲密度指数 |
|
||||
| | | `topCoachName` | string | 最亲密助教姓名 |
|
||||
| | | `topCoachHeart` | number | 最亲密助教爱心分 |
|
||||
| | | `topCoachScore` | string | 最亲密助教关系指数 |
|
||||
| | | `coachName` | string | 主助教姓名 |
|
||||
| | | `coachRatio` | string | 主助教占比(如 `"78%"`) |
|
||||
| | | `coachDetails` | array | 助教服务明细表 `[{ name, cls, heartScore, badge?, avgDuration, serviceCount, coachSpend, relationIdx }]` |
|
||||
|
||||
> 后端设计建议:与助教看板不同,客户看板 8 个维度的字段差异很大,且数据来源不同(8 张不同的 FDW 表)。建议后端按 `dimension` 参数只查询和返回当前维度所需字段,切换维度时前端重新请求。这样避免一次查询 8 张表的性能开销。
|
||||
|
||||
#### 交叉约束
|
||||
- **无交叉限制**:dimension 和 project 完全独立,可自由组合
|
||||
|
||||
#### 交叉组合总数
|
||||
- dimension(8) × project(5) = **40 种有效组合**
|
||||
|
||||
#### 前端传参(`api.ts`)
|
||||
```typescript
|
||||
fetchBoardCustomers({ dimension, project, sort })
|
||||
// 注意:sort 参数在 api.ts 签名中存在但前端未使用(无排序筛选 UI)
|
||||
// page/pageSize 参数缺失,需前端补充(R8-4 决策)
|
||||
```
|
||||
|
||||
#### ⚠️ 前端 Bug:筛选变更不触发重新请求
|
||||
`onDimensionChange`/`onProjectChange` 仅更新 `data` 状态,**未调用 `loadData()`**。联调时前端需修复。
|
||||
|
||||
#### ⚠️ 前端缺失:分页参数
|
||||
R8-4 已确认 20 条懒加载,但 `fetchBoardCustomers` 签名和 `board-customer.ts` 均无 `page`/`pageSize` 参数和"加载更多"逻辑。联调时前端需补充。
|
||||
|
||||
---
|
||||
|
||||
### B3:BOARD-3 财务看板(board-finance)
|
||||
|
||||
#### 筛选项清单
|
||||
|
||||
| 参数名 | 类型 | 可选值 | 默认值 | 说明 |
|
||||
|--------|------|--------|--------|------|
|
||||
| `time` | 时间范围 | `month`、`lastMonth`、`week`、`lastWeek`、`quarter3`、`quarter`、`lastQuarter`、`half6` | `month` | 8 种时间范围 |
|
||||
| `area` | 区域筛选 | `all`、`hall`、`hallA`、`hallB`、`hallC`、`mahjong`、`teamBuilding` | `all` | 7 种区域 |
|
||||
| `compareEnabled` | 环比开关 | `true` / `false` | `false` | 控制环比数据显示/隐藏 |
|
||||
|
||||
#### 6 个板块及筛选影响
|
||||
|
||||
| 板块 | sectionId | 受 time 影响 | 受 area 影响 | 受 compare 影响 | 特殊规则 |
|
||||
|------|-----------|:-----------:|:-----------:|:--------------:|---------|
|
||||
| 经营一览 | section-overview | ✅ | ✅ | ✅ | 无 |
|
||||
| 预收资产 | section-recharge | ✅ | ⚠️ | ✅ | **仅 `area=all` 时显示**,选中具体区域时整个板块隐藏 |
|
||||
| 应计收入确认 | section-revenue | ✅ | ✅ | ✅ | 含收入结构表(按区域分行)、正价明细、优惠明细、渠道明细 |
|
||||
| 现金流入 | section-cashflow | ✅ | ✅ | ✅ | 无 |
|
||||
| 现金流出 | section-expense | ✅ | ✅ | ✅ | 含经营支出、固定支出、助教分成、平台服务费 4 个子分组 |
|
||||
| 助教分析 | section-coach | ✅ | ✅ | ✅ | 含基础课、激励课两个子表(各按等级分行) |
|
||||
|
||||
#### 环比开关行为
|
||||
|
||||
| 状态 | 行为 |
|
||||
|------|------|
|
||||
| `compareEnabled=false`(默认) | 仅显示当期数值,隐藏所有环比箭头和百分比 |
|
||||
| `compareEnabled=true` | 每个数据单元格下方显示环比方向+百分比 |
|
||||
|
||||
环比样式规则:
|
||||
- 上升:绿色 ↑ + 百分比(如 `↑12.5%`)
|
||||
- 下降:红色 ↓ + 百分比(如 `↓3.2%`)
|
||||
- 持平:灰色文字 `持平`
|
||||
|
||||
环比计算基准:与上一个相同时间周期对比(本月 vs 上月、本周 vs 上周、本季度 vs 上季度等)
|
||||
|
||||
#### 交叉约束
|
||||
|
||||
| 约束 | 说明 |
|
||||
|------|------|
|
||||
| `area ≠ all` → 预收资产板块隐藏 | 储值卡数据不按区域拆分,选中具体区域时无意义 |
|
||||
| `compareEnabled` 独立 | 与 time/area 无交叉限制 |
|
||||
| time + area 自由组合 | 无其他限制 |
|
||||
|
||||
#### 交叉组合总数
|
||||
- time(8) × area(7) × compare(2) = **112 种组合**
|
||||
- 其中 area≠all 时预收资产板块隐藏(不影响请求参数,仅影响前端渲染)
|
||||
|
||||
#### ⚠️ 重大设计缺陷:筛选参数不传后端
|
||||
|
||||
当前 `fetchBoardFinance` 签名:
|
||||
```typescript
|
||||
fetchBoardFinance({ date?: string }) // 仅 date 参数
|
||||
```
|
||||
|
||||
前端 `onTimeChange`/`onAreaChange`/`toggleCompare` 仅更新本地 data 状态,**不重新调用 API**。mock 模式下不需要重新请求(数据内联),但联调后必须:
|
||||
|
||||
1. `fetchBoardFinance` 签名扩展为 `{ time, area, compareEnabled }` 三个参数
|
||||
2. 筛选变更后重新调用 `fetchBoardFinance`
|
||||
3. 后端根据 `time` 计算日期范围、根据 `area` 过滤区域、根据 `compareEnabled` 决定是否返回环比数据
|
||||
|
||||
#### 后端 API 参数设计建议
|
||||
|
||||
```
|
||||
GET /api/xcx/board/finance?time={time}&area={area}&compare={0|1}
|
||||
```
|
||||
|
||||
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|
||||
|------|------|:----:|--------|------|
|
||||
| `time` | string | 否 | `month` | 时间范围枚举 |
|
||||
| `area` | string | 否 | `all` | 区域枚举 |
|
||||
| `compare` | 0/1 | 否 | `0` | 是否返回环比数据(减少不需要时的计算开销) |
|
||||
|
||||
---
|
||||
|
||||
### 前端修改需求汇总(联调前必须完成)
|
||||
|
||||
#### 看板页修复(F1-F6)
|
||||
|
||||
| # | 页面 | 问题 | 修改内容 |
|
||||
|---|------|------|---------|
|
||||
| F1 | board-coach | 筛选变更不触发重新请求 | `onSortChange`/`onSkillChange`/`onTimeChange` 末尾加 `this.loadData()` |
|
||||
| F2 | board-customer | 筛选变更不触发重新请求 | `onDimensionChange`/`onProjectChange` 末尾加 `this.loadData()` |
|
||||
| F3 | board-customer | 缺少分页参数 | `fetchBoardCustomers` 签名加 `page`/`pageSize`,页面加"加载更多"逻辑 |
|
||||
| F4 | board-finance | 筛选参数不传后端 | `fetchBoardFinance` 签名扩展为 `{ time, area, compare }`,筛选变更后重新调用 |
|
||||
| F5 | board-finance | 筛选变更不触发重新请求 | `onTimeChange`/`onAreaChange` 末尾加 `this.loadData()` |
|
||||
| F6 | board-coach | `time=last_6m` + `sort=sv_desc` 不兼容 | 前端在选择 `last_6m` 时禁用 `sv_desc` 选项,或选择 `sv_desc` 时禁用 `last_6m` |
|
||||
|
||||
#### 列表页修复(F7-F11)
|
||||
|
||||
| # | 页面 | 问题 | 修改内容 |
|
||||
|---|------|------|---------|
|
||||
| F7 | task-list | status 筛选 UI 未实现 | API 支持 `status` 参数但页面无筛选控件,需添加 Tab 或筛选栏 |
|
||||
| F8 | performance | 无月份切换功能 | API 支持 `year`/`month` 参数但页面固定当前月,需添加月份切换按钮 |
|
||||
| F9 | performance-records | 月份切换时未重置分页 | `switchMonth()` 中需将 `page` 重置为 1 |
|
||||
| F10 | customer-service-records | 月份切换采用本地筛选 | `updateMonthView()` 仅本地过滤,联调后需改为重新调用 API |
|
||||
| F11 | notes | 无触底加载逻辑 | API 支持分页但页面未实现 `onReachBottom()` |
|
||||
|
||||
---
|
||||
|
||||
### 非看板列表页筛选矩阵
|
||||
|
||||
#### L1:task-list(任务列表)
|
||||
|
||||
| 参数名 | 类型 | 可选值 | 默认值 | UI 实现 | 说明 |
|
||||
|--------|------|--------|--------|:-------:|------|
|
||||
| `status` | string | `pending`、`completed`、`abandoned` | 无(全部) | ❌ 缺失 | API 支持但页面无筛选控件 |
|
||||
| `page` | number | 1+ | `1` | ✅ 自动 | 触底加载 |
|
||||
| `pageSize` | number | 1+ | `20` | ✅ 硬编码 | — |
|
||||
|
||||
交互:下拉刷新重置 page=1;触底 page++。前端将任务分为 pinnedTasks / normalTasks / abandonedTasks 三组渲染。
|
||||
|
||||
#### L2:performance(绩效概览)
|
||||
|
||||
| 参数名 | 类型 | 可选值 | 默认值 | UI 实现 | 说明 |
|
||||
|--------|------|--------|--------|:-------:|------|
|
||||
| `year` | number | 当前年 ± N | 当前年 | ❌ 缺失 | 固定当前年 |
|
||||
| `month` | number | 1-12 | 当前月 | ❌ 缺失 | 固定当前月 |
|
||||
|
||||
交互:无筛选 UI,页面加载时固定请求当前年月。无分页。
|
||||
|
||||
#### L3:performance-records(绩效明细)
|
||||
|
||||
| 参数名 | 类型 | 可选值 | 默认值 | UI 实现 | 说明 |
|
||||
|--------|------|--------|--------|:-------:|------|
|
||||
| `year` | number | 当前年 ± N | 当前年 | ✅ 月份切换按钮 | 上月/下月箭头 |
|
||||
| `month` | number | 1-12 | 当前月 | ✅ 月份切换按钮 | 不可超过当前月 |
|
||||
| `page` | number | 1+ | `1` | ✅ 自动 | 触底加载 |
|
||||
| `pageSize` | number | 1+ | `20` | ✅ 硬编码 | — |
|
||||
|
||||
月份切换详细交互:
|
||||
1. 页面顶部显示 `{year}年{month}月` 标签,左右各有箭头按钮
|
||||
2. 点击左箭头 → `currentMonth--`(跨年自动 `currentYear--`,`currentMonth=12`)
|
||||
3. 点击右箭头 → `currentMonth++`(跨年自动 `currentYear++`,`currentMonth=1`)
|
||||
4. 边界:`canGoNext = false` 当 `currentYear == nowYear && currentMonth == nowMonth`(不可超过当前月);`canGoPrev = true`(无下限)
|
||||
5. 月份变更后 → 调用 `loadData()` → 重新请求 API `fetchPerformanceRecords({ year, month, page, pageSize })`
|
||||
6. ⚠️ Bug:`switchMonth()` 中未将 `page` 重置为 1,月份切换后可能请求第 N 页数据
|
||||
|
||||
后端响应结构(按月返回):
|
||||
- `summary`:当月汇总(总笔数、总工时、折算前总工时、总收入)
|
||||
- `date_groups`:按日期降序分组,每组含日期标签、当日总工时/总收入、记录列表
|
||||
- 每条记录字段:`id`、`customerName`、`timeRange`(如 `"20:00-22:00"`)、`hours`(折算后)、`hoursRaw`(折算前,可选)、`courseType`、`courseTypeClass`(`tag-basic`/`tag-vip`/`tag-tip`)、`location`(台号)、`income`
|
||||
|
||||
后端时间范围处理:
|
||||
- 接收 `year` + `month` → 转换为 `biz_date BETWEEN '{year}-{month}-01' AND '{year}-{month}-{lastDay}'`
|
||||
- 数据源:`fdw_etl.v_dwd_assistant_service_log`(按 `assistant_id` + `biz_date` 范围查询)
|
||||
|
||||
#### L4:customer-service-records(客户服务记录)
|
||||
|
||||
| 参数名 | 类型 | 可选值 | 默认值 | UI 实现 | 说明 |
|
||||
|--------|------|--------|--------|:-------:|------|
|
||||
| `customerId` | string | 客户 ID | 从 options 读取 | ✅ 自动 | 页面参数 |
|
||||
| `year` | number | 当前年 ± N | 当前年 | ✅ 月份切换按钮 | 上月/下月箭头 |
|
||||
| `month` | number | 1-12 | 当前月 | ✅ 月份切换按钮 | 不可超过当前月 |
|
||||
| `table` | string | 台号 | 无 | ❌ 缺失 | API 支持但页面未使用 |
|
||||
|
||||
月份切换详细交互:
|
||||
1. 页面顶部显示 `{year}年{month}月` 标签,左右各有箭头按钮
|
||||
2. 点击左箭头 → `currentMonth--`(跨年处理同 L3)
|
||||
3. 点击右箭头 → `currentMonth++`(跨年处理同 L3)
|
||||
4. 边界:`canPrev` = `yearMonth > minYearMonth`(最早 12 个月前);`canNext` = `yearMonth < maxYearMonth`(不超过当前月)
|
||||
5. 月份变更后 → 调用 `updateMonthView()` → **仅本地过滤**(从 `allRecords` 中按 `date.startsWith('{year}-{month}')` 筛选)
|
||||
6. ⚠️ 设计问题:首次 `loadData()` 一次性拉取全部记录,月份切换仅本地过滤。联调后如果数据量大(单客户数百条记录),需改为按月请求 API
|
||||
|
||||
本地筛选逻辑:
|
||||
- `allRecords`(首次加载时获取全部)→ 按 `date` 字段前缀 `{year}-{month}` 过滤 → 转换为 `ServiceRecord` 展示格式
|
||||
- 月度统计:`monthCount`(当月记录数)、`monthHours`(当月总时长)
|
||||
- 记录字段:`table`(台号)、`type`(课程类型标签)、`typeClass`(`basic`/`vip`/`tip`/`recharge`)、`recordType`(`course`/`recharge`)、`duration`(折算后小时)、`income`(到手金额)、`drinks`(饮品描述)、`date`(显示日期+时间段)
|
||||
|
||||
后端时间范围处理(联调后建议改为按月请求):
|
||||
- 当前方案:一次返回全部记录,前端本地按月过滤
|
||||
- 建议方案:`fetchCustomerRecords({ customerId, year, month })` → 后端按月查询 `fdw_etl.v_dwd_assistant_service_log`(按 `member_id` + `assistant_id` + `biz_date` 范围)
|
||||
|
||||
#### L5:notes(备注列表)
|
||||
|
||||
| 参数名 | 类型 | 可选值 | 默认值 | UI 实现 | 说明 |
|
||||
|--------|------|--------|--------|:-------:|------|
|
||||
| `page` | number | 1+ | `1` | ✅ 自动 | 下拉刷新重置 |
|
||||
| `pageSize` | number | 1+ | `20` | ✅ 硬编码 | — |
|
||||
|
||||
交互:下拉刷新重置 page=1。⚠️ 无触底加载(`onReachBottom()` 未实现)。
|
||||
|
||||
#### L6:customer-detail / coach-detail(详情页)
|
||||
|
||||
无筛选项,单次加载全部数据。
|
||||
|
||||
---
|
||||
|
||||
## 八⅞、Storyboard 走查 Gap 汇总(2026-03-18)
|
||||
|
||||
> 走查方法:两个子代理分别以助教视角和管理层视角,逐页面对照前端内联 mock 数据 vs API 契约 vs 八¾节字段定义,提取所有未记录的接口需求 Gap。
|
||||
> 完整报告:`docs/reports/storyboard-walkthrough-assistant-view.md`(助教视角,51 Gap)+ `docs/reports/miniprogram-storyboard-walkthrough-gaps.md`(管理层视角,31 Gap)
|
||||
|
||||
### 统计
|
||||
|
||||
| 严重度 | 助教视角 | 管理层视角 | 去重后合计 |
|
||||
|--------|:--------:|:----------:|:----------:|
|
||||
| 🔴 高(阻塞联调) | 17 | 14 | ~20 |
|
||||
| 🟠 中(影响完整性) | 19 | 12 | ~18 |
|
||||
| 🟡 低(可后续修复) | 15 | 5 | ~12 |
|
||||
|
||||
### 核心发现
|
||||
|
||||
1. **契约与前端严重脱节**:BOARD-1/2/3、CUST-1、COACH-1、PERF-1、TASK-1 performance 的契约响应定义与前端实际需求严重不匹配,需完全重写
|
||||
2. **BOARD-3 财务看板**:契约定义扁平 `metrics` 数组,前端需要 6 个独立板块(overview/recharge/revenue/cashflow/expense/coachAnalysis)的深度嵌套结构,含 200+ 字段
|
||||
3. **CUST-1 客户详情**:缺少 5 大模块(aiInsight/coachTasks/favoriteCoaches/消费记录嵌套结构/备注列表)
|
||||
4. **COACH-1 助教详情**:缺少 6 大模块(performance/income/tierNodes/historyMonths/topCustomers 扩展/备注列表)
|
||||
5. **跨页面参数传递**:多处 taskId/customerId 混淆、未传参数、用 name 代替 id 跳转(用户决策:统一用唯一 ID)
|
||||
6. **CHAT 模块**:referenceCard 未定义、customerId→chatId 映射缺失、SSE 端点未在契约中定义
|
||||
|
||||
### 处置方案
|
||||
|
||||
全部 Gap 已纳入 **[RNS1 拆分计划](./RNS1-split-plan.md)**,分 5 个子 Spec 逐步实施。Gap 追溯矩阵见拆分计划末尾。
|
||||
|
||||
---
|
||||
|
||||
## 九、任务清单
|
||||
|
||||
> ⚠️ **已拆分**:原始任务清单(T0-1 ~ T18)已被 Storyboard 走查发现的 80+ 个 Gap 大幅扩展,体量超出单个 SPEC 可控范围。
|
||||
>
|
||||
> 完整的拆分计划见 **[RNS1-split-plan.md](./RNS1-split-plan.md)**,将 NS1 拆为 5 个子 Spec:
|
||||
>
|
||||
> | 子 Spec | 名称 | 任务数 | 状态 |
|
||||
> |---------|------|:------:|:----:|
|
||||
> | RNS1.0 | 基础设施与契约重写 | 6 | 待启动 |
|
||||
> | RNS1.1 | 任务与绩效接口 | 6 | 待启动 |
|
||||
> | RNS1.2 | 客户与助教接口 | 6 | 待启动 |
|
||||
> | RNS1.3 | 三看板接口 | 7 | 待启动 |
|
||||
> | RNS1.4 | CHAT 对齐与联调收尾 | 5 | 待启动 |
|
||||
>
|
||||
> 执行顺序:RNS1.0 → (RNS1.1 ∥ RNS1.2 ∥ RNS1.3) → RNS1.4
|
||||
>
|
||||
> 需求追溯:两份走查报告(`docs/reports/storyboard-walkthrough-assistant-view.md` + `docs/reports/miniprogram-storyboard-walkthrough-gaps.md`)中的全部 Gap 已在拆分计划的追溯矩阵中映射到具体任务。
|
||||
|
||||
### 原始任务清单(归档参考)
|
||||
|
||||
<details>
|
||||
<summary>点击展开原始 Batch 0~C 任务清单(已被 RNS1 拆分计划替代)</summary>
|
||||
|
||||
#### Batch 0:基础设施改造(阻塞所有新接口)
|
||||
- [ ] T0-1:全局响应包装中间件(`{ code: 0, data: ... }` + 异常处理器 `{ code, message }`)
|
||||
- [ ] T0-2:Pydantic schema 统一 camelCase(`alias_generator=to_camel`,所有现有 + 新增 schema)
|
||||
- [ ] T0-3:后端 TASK-4 路径从 `/cancel-abandon` 改为 `/restore`(对齐契约)
|
||||
- [ ] T0-4:前端 `request()` 工具函数加 `.data` 解包适配
|
||||
|
||||
#### Batch A:核心业务接口
|
||||
- [ ] T1:扩展 TASK-1(任务列表 + 绩效概览 + 分页 + 懒加载)
|
||||
- [ ] T2:实现 TASK-2(任务详情完整版,服务记录/备注各 20 条懒加载)
|
||||
- [ ] T3:实现 PERF-1(绩效概览)
|
||||
- [ ] T4:实现 PERF-2(绩效明细,20 条懒加载)
|
||||
- [ ] T5:实现 CUST-1(客户详情,消费记录 3 类混合排序 + 懒加载)
|
||||
- [ ] T6:实现 CUST-2(客户服务记录,20 条懒加载)
|
||||
- [ ] T7:实现 COACH-1(助教详情,任务按 active/inactive/abandoned 分组)
|
||||
|
||||
#### Batch B:看板 + 配置接口
|
||||
- [ ] T8:实现 BOARD-1(助教看板,FDW 直查)
|
||||
- [ ] T9:实现 BOARD-2(客户看板,8 维度切换,20 条懒加载,FDW 直查)
|
||||
- [ ] T10:实现 BOARD-3(财务看板,60+ 指标月环比计算,FDW 直查)
|
||||
- [ ] T11:实现 CONFIG-1(技能类型列表,从 cfg 表读取)
|
||||
|
||||
#### Batch C:CHAT 对齐、前端修复与联调
|
||||
- [ ] T12:CHAT 路径迁移(`/api/ai/*` → `/api/xcx/chat/*`,保留 SSE 流式端点)
|
||||
- [ ] T13:新增 CHAT-1/2 同步端点(历史列表 + 消息查看)
|
||||
- [ ] T14:API 契约补充(SSE 端点定义 + BOARD-3 `compareValue` 字段 + BOARD-3 `time`/`area`/`compare` 参数)
|
||||
- [ ] T15:FDW 端到端验证
|
||||
- [ ] T16:前端懒加载改造(TASK-2 服务记录/备注、BOARD-2 客户列表)
|
||||
- [ ] T17:前端看板筛选修复(F1-F6,详见八¾节)
|
||||
- [ ] T18:前后端联调 + 修复
|
||||
|
||||
</details>
|
||||
419
docs/prd/Neo_Specs/NS2-ai-prompt-refinement.md
Normal file
419
docs/prd/Neo_Specs/NS2-ai-prompt-refinement.md
Normal file
@@ -0,0 +1,419 @@
|
||||
# NS2:AI Prompt 细化 — ai-prompt-refinement
|
||||
|
||||
> 优先级:高(NS1 完成后立即执行,Prompt 细化是 AI 功能闭环的关键)
|
||||
> 预估工作量:中等
|
||||
> 前置条件:P5-A 已完成(AI 骨架就绪)、NS1 已完成(后端 API 数据结构确定)
|
||||
> 参考基准:`docs/prd/specs/P5-miniapp-ai-integration.md`(P5-B 阶段定义)
|
||||
|
||||
---
|
||||
|
||||
## 一、背景与目标
|
||||
|
||||
P5-A 阶段已交付 AI 集成管道:百炼封装、缓存 API、SSE 框架、应用 2/8 完整实现、应用 1/3/4/5/6/7 的触发机制和调用骨架。但应用 1/3/4/5/6/7 的 `build_prompt()` 函数仍为占位骨架,核心数据字段(`consumption_records`、`service_history`、`objective_data` 等)标记为 TODO。
|
||||
|
||||
本 SPEC 目标:将 6 个应用的 Prompt 拼接函数从骨架升级为完整实现,使 AI 应用能基于真实数据生成有价值的分析结果。
|
||||
|
||||
### 当前骨架状态
|
||||
|
||||
| 应用 | 文件 | 骨架状态 | 待细化字段 |
|
||||
|------|------|---------|-----------|
|
||||
| 应用 1 | `app1_chat.py` | 页面上下文文本化工具留接口 | `page_context`、`screen_content` 各页面文本化 |
|
||||
| 应用 3 | `app3_clue.py` | `build_prompt()` 占位 | `consumption_records`(DWD+DWS 订单明细全维度) |
|
||||
| 应用 4 | `app4_analysis.py` | `build_prompt()` 占位 | `service_history`、`assistant_info` |
|
||||
| 应用 5 | `app5_tactics.py` | `build_prompt()` 占位 | `service_history`、`assistant_info`(同应用 4) |
|
||||
| 应用 6 | `app6_note.py` | `build_prompt()` 占位 | `consumption_data`(同应用 3 的 main_data) |
|
||||
| 应用 7 | `app7_customer.py` | `build_prompt()` 占位 | `objective_data`(同应用 3 的 main_data) |
|
||||
|
||||
---
|
||||
|
||||
## 二、技术架构
|
||||
|
||||
### 2.1 数据获取层(新建共享模块)
|
||||
|
||||
Prompt 细化的核心是从数据库获取真实数据并格式化为 AI 可读的 JSON。多个应用共享相同的数据获取逻辑,应抽取为共享模块:
|
||||
|
||||
```
|
||||
apps/backend/app/ai/
|
||||
├── data_fetchers/ 🆕 新建
|
||||
│ ├── __init__.py
|
||||
│ ├── member_data.py # 客户数据获取(消费记录、会员卡、余额等)
|
||||
│ ├── assistant_data.py # 助教数据获取(基本信息、服务历史)
|
||||
│ ├── service_history.py # 服务记录获取(助教-客户维度)
|
||||
│ └── page_context.py # 页面上下文文本化(应用 1 专用)
|
||||
├── apps/
|
||||
│ ├── app1_chat.py 🔧 补充页面文本化调用
|
||||
│ ├── app3_clue.py 🔧 补充 consumption_records 拼接
|
||||
│ ├── app4_analysis.py 🔧 补充 service_history + assistant_info
|
||||
│ ├── app5_tactics.py 🔧 补充 service_history + assistant_info
|
||||
│ ├── app6_note.py 🔧 补充 consumption_data
|
||||
│ └── app7_customer.py 🔧 补充 objective_data
|
||||
```
|
||||
|
||||
### 2.2 数据获取函数设计
|
||||
|
||||
#### `member_data.py` — 客户数据获取(应用 3/6/7 共用)
|
||||
|
||||
```python
|
||||
async def fetch_member_consumption_data(
|
||||
site_id: int, member_id: int, months: int = 3
|
||||
) -> dict:
|
||||
"""获取客户近 N 个月消费数据(DWD+DWS 全维度)。
|
||||
|
||||
返回结构对应 P5 spec 中 main_data:
|
||||
- consumption_records: 台桌结账 + 商城订单明细列表
|
||||
- member_cards: 会员卡明细
|
||||
- card_balance_total: 储值卡余额合计
|
||||
- stored_value_balance_total: 储值余额合计
|
||||
- expected_visit_date: 预计到店日期
|
||||
- days_since_last_visit: 距上次到店天数
|
||||
"""
|
||||
```
|
||||
|
||||
数据源(全部通过 FDW):
|
||||
- `fdw_etl.v_dwd_settlement_head` + `fdw_etl.v_dwd_table_fee_log`(台桌结账)
|
||||
- `fdw_etl.v_dwd_store_goods_sale`(商城订单)
|
||||
- `fdw_etl.v_dwd_recharge_order`(充值记录)
|
||||
- `fdw_etl.v_dim_member_card_account`(会员卡明细)
|
||||
- `fdw_etl.v_dws_member_consumption_summary`(消费汇总 + 余额)
|
||||
- `fdw_etl.v_dws_member_visit_detail`(到店明细 → 计算预计到店日期)
|
||||
|
||||
> ⚠️ 金额口径:使用 `items_sum`(= table_charge_money + goods_money + assistant_pd_money + assistant_cx_money + electricity_money),禁止 `consume_money`
|
||||
> ⚠️ 会员字段断档(DQ-6/DQ-7):member_phone/member_name 通过 member_id JOIN dim_member
|
||||
|
||||
#### `assistant_data.py` — 助教数据获取(应用 4/5 共用)
|
||||
|
||||
```python
|
||||
async def fetch_assistant_info(
|
||||
site_id: int, assistant_id: int
|
||||
) -> dict:
|
||||
"""获取助教基本信息(花名、级别、工龄等)。"""
|
||||
|
||||
async def fetch_service_history(
|
||||
site_id: int, assistant_id: int, member_id: int, months: int = 3
|
||||
) -> list[dict]:
|
||||
"""获取助教服务该客户的历史记录。"""
|
||||
```
|
||||
|
||||
数据源:
|
||||
- `fdw_etl.v_dim_assistant`(基本信息)
|
||||
- `fdw_etl.v_dwd_assistant_service_log`(服务记录,按助教+客户筛选)
|
||||
- `fdw_etl.v_dws_member_assistant_relation_index`(关系指数)
|
||||
- `fdw_etl.v_dws_member_assistant_intimacy`(亲密度)
|
||||
|
||||
#### `page_context.py` — 页面上下文文本化(应用 1 专用)
|
||||
|
||||
```python
|
||||
async def build_page_text(
|
||||
source_page: str, context_data: dict, site_id: int
|
||||
) -> str:
|
||||
"""将页面数据转换为 AI 可读的结构化文本。
|
||||
|
||||
根据 source_page 类型,从数据库获取对应数据并格式化。
|
||||
"""
|
||||
```
|
||||
|
||||
支持的页面类型及数据获取:
|
||||
| source_page | 数据获取 | 文本化内容 |
|
||||
|-------------|---------|-----------|
|
||||
| `task-detail` | coach_tasks + member + notes + ai_cache | 任务信息 + 客户信息 + 备注 + AI 分析 |
|
||||
| `customer-detail` | member + consumption + clues | 客户全信息 + 消费记录 + 维客线索 |
|
||||
| `coach-detail` | assistant + tasks + notes | 助教信息 + 任务统计 + 备注 |
|
||||
| `board-finance` | finance DWS 汇总 | 财务数据摘要 |
|
||||
| `board-customer` | 当前筛选维度 top 列表 | 客户排名摘要 |
|
||||
| `board-coach` | 当前筛选维度排名 | 助教排名摘要 |
|
||||
| `performance` | salary_calc + daily_detail | 绩效数据摘要 |
|
||||
| `my-profile` | user info + assistant binding | 个人信息摘要 |
|
||||
|
||||
### 2.3 数据库连接模式
|
||||
|
||||
- 所有 FDW 查询通过 `get_etl_readonly_connection(site_id)` 或 `fdw_etl.*` 视图
|
||||
- 业务库查询通过 `get_connection()`
|
||||
- 查询前必须 `SET LOCAL app.current_site_id`(RLS 隔离)
|
||||
|
||||
---
|
||||
|
||||
## 三、各应用 Prompt 细化详细设计
|
||||
|
||||
### 3.1 应用 3:客户数据维客线索分析
|
||||
|
||||
`build_prompt()` 需要拼接的完整 JSON 结构(参考 P5 spec):
|
||||
|
||||
```json
|
||||
{
|
||||
"current_time": "2026-03-08 14:30:25",
|
||||
"member_nickname": "客户昵称",
|
||||
"main_data": {
|
||||
"consumption_records": [
|
||||
{
|
||||
"settle_date": "2026-03-05",
|
||||
"settle_type": 1,
|
||||
"items_sum": 280.00,
|
||||
"table_charge_money": 180.00,
|
||||
"assistant_pd_money": 80.00,
|
||||
"assistant_cx_money": 0,
|
||||
"goods_money": 20.00,
|
||||
"room_name": "VIP-3",
|
||||
"duration_minutes": 120,
|
||||
"assistant_names": ["张助教"]
|
||||
}
|
||||
],
|
||||
"member_cards": [
|
||||
{"card_type": "金卡", "balance": 1500.00, "gift_balance": 200.00}
|
||||
],
|
||||
"card_balance_total": 1700.00,
|
||||
"stored_value_balance_total": 1700.00,
|
||||
"expected_visit_date": "2026-03-10",
|
||||
"days_since_last_visit": 15
|
||||
},
|
||||
"reference": {
|
||||
"app6_clues": [...],
|
||||
"app8_history": [
|
||||
{"generated_at": "2026-03-01", "clues": [...]},
|
||||
{"generated_at": "2026-02-15", "clues": [...]}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
实现要点:
|
||||
- `consumption_records` 从 `v_dwd_settlement_head` + `v_dwd_table_fee_log` 获取,settle_type IN (1,3)
|
||||
- 金额字段逐项拆分(table_charge_money / assistant_pd_money / assistant_cx_money / goods_money),不使用 consume_money
|
||||
- `expected_visit_date` 从 `v_dws_member_visit_detail` 的到店间隔推算
|
||||
- reference 从 `ai_cache` 获取 app6 + app8 最近 2 套历史
|
||||
|
||||
### 3.2 应用 4:关系分析/任务建议
|
||||
|
||||
`build_prompt()` 需要拼接的完整 JSON 结构:
|
||||
|
||||
```json
|
||||
{
|
||||
"current_time": "2026-03-08 14:30:25",
|
||||
"assistant_info": {
|
||||
"nickname": "张助教",
|
||||
"level": "高级",
|
||||
"hire_date": "2024-06-01",
|
||||
"tenure_months": 21,
|
||||
"monthly_customers": 45,
|
||||
"performance_tier": "A档"
|
||||
},
|
||||
"service_history": [
|
||||
{
|
||||
"service_date": "2026-03-01",
|
||||
"duration_minutes": 90,
|
||||
"items_sum": 250.00,
|
||||
"room_name": "VIP-3",
|
||||
"is_pd": true
|
||||
}
|
||||
],
|
||||
"task_assignment_basis": "优先召回",
|
||||
"customer_data": {
|
||||
"system_data": { /* 同应用 3 的 main_data */ },
|
||||
"notes": [
|
||||
{"recorded_by": "张助教", "content": "客户喜欢打斯诺克", "created_at": "2026-02-28"}
|
||||
]
|
||||
},
|
||||
"reference": {
|
||||
"app8_current": {...},
|
||||
"app8_history": [...]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
实现要点:
|
||||
- `assistant_info` 从 `v_dim_assistant` + `v_dws_assistant_salary_calc` 获取
|
||||
- `service_history` 从 `v_dwd_assistant_service_log` 按助教+客户筛选
|
||||
- `customer_data.system_data` 复用 `member_data.fetch_member_consumption_data()`
|
||||
- `customer_data.notes` 从 `biz.notes` 获取该客户的全部备注
|
||||
- reference 从 `ai_cache` 获取 app8 最新 + 最近 2 套历史
|
||||
|
||||
### 3.3 应用 5:话术参考
|
||||
|
||||
结构与应用 4 基本一致,额外增加 `task_suggestion` 字段(应用 4 的返回结果)。
|
||||
|
||||
实现要点:
|
||||
- 复用应用 4 的 `assistant_info` + `service_history` + `customer_data` 获取逻辑
|
||||
- `task_suggestion` 从应用 4 的 `run()` 返回值传入(调用链:应用 4 → 应用 5)
|
||||
|
||||
### 3.4 应用 6:备注分析
|
||||
|
||||
```json
|
||||
{
|
||||
"current_time": "2026-03-08 14:30:25",
|
||||
"current_note": {
|
||||
"content": "客户说下周要带朋友来打球",
|
||||
"recorded_by": "张助教",
|
||||
"created_at": "2026-03-08 14:25:00"
|
||||
},
|
||||
"reference": {
|
||||
"member_nickname": "王先生",
|
||||
"consumption_data": { /* 同应用 3 的 main_data */ },
|
||||
"all_notes": [...],
|
||||
"app3_clues": [...],
|
||||
"app8_history": [...]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
实现要点:
|
||||
- `current_note` 从触发上下文获取(备注提交事件传入)
|
||||
- `consumption_data` 复用 `member_data.fetch_member_consumption_data()`
|
||||
- `all_notes` 从 `biz.notes` 获取所有助教对该客户的全部备注
|
||||
- reference 从 `ai_cache` 获取 app3 + app8 历史
|
||||
|
||||
### 3.5 应用 7:客户分析
|
||||
|
||||
```json
|
||||
{
|
||||
"current_time": "2026-03-08 14:30:25",
|
||||
"member_id": 12345,
|
||||
"member_nickname": "王先生",
|
||||
"objective_data": { /* 同应用 3 的 main_data */ },
|
||||
"subjective_data": {
|
||||
"notes": [
|
||||
{"recorded_by": "张助教", "content": "...", "created_at": "..."}
|
||||
]
|
||||
},
|
||||
"reference": {
|
||||
"app8_current": {...},
|
||||
"app8_history": [...]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
实现要点:
|
||||
- `objective_data` 复用 `member_data.fetch_member_consumption_data()`
|
||||
- `subjective_data.notes` 从 `biz.notes` 获取全部备注,标注创建者
|
||||
- 主观信息需在 Prompt 中标注【来源:XXX,请甄别信息真实性】
|
||||
- reference 从 `ai_cache` 获取 app8 最新 + 最近 2 套历史
|
||||
|
||||
### 3.6 应用 1:页面上下文文本化
|
||||
|
||||
应用 1 的 `_build_page_context()` 和 `_build_source_context()` 需要根据 `source_page` 类型,调用对应的数据获取函数并格式化为文本。
|
||||
|
||||
实现要点:
|
||||
- 每个页面类型对应一个文本化函数
|
||||
- 文本化输出为结构化中文描述(非 JSON),便于 AI 理解
|
||||
- 数据量控制:每个页面上下文不超过 2000 字符,避免 token 浪费
|
||||
- 敏感信息脱敏:不传入 member_phone 等断档字段
|
||||
|
||||
---
|
||||
|
||||
## 四、数据库审查
|
||||
|
||||
### 4.1 数据获取涉及的表
|
||||
|
||||
| 数据获取函数 | 涉及表 | 连接方式 |
|
||||
|-------------|--------|---------|
|
||||
| `fetch_member_consumption_data` | v_dwd_settlement_head, v_dwd_table_fee_log, v_dwd_store_goods_sale, v_dwd_recharge_order, v_dim_member_card_account, v_dws_member_consumption_summary, v_dws_member_visit_detail | FDW |
|
||||
| `fetch_assistant_info` | v_dim_assistant, v_dws_assistant_salary_calc | FDW |
|
||||
| `fetch_service_history` | v_dwd_assistant_service_log, v_dws_member_assistant_relation_index, v_dws_member_assistant_intimacy | FDW |
|
||||
| `build_page_text` | 以上全部 + biz.coach_tasks, biz.notes, biz.ai_cache, public.member_retention_clue | FDW + 业务库 |
|
||||
|
||||
### 4.2 无需新建表
|
||||
|
||||
本 SPEC 不需要新建数据库表。所有数据获取基于 NS1 已建立的 FDW 映射和 P4/P5-A 已建立的业务表。
|
||||
|
||||
### 4.3 性能考虑
|
||||
|
||||
- `fetch_member_consumption_data` 涉及 7 张 FDW 表查询,建议:
|
||||
- 消费记录限制最近 3 个月(WHERE settle_date >= NOW() - INTERVAL '3 months')
|
||||
- 单次查询最多返回 100 条记录
|
||||
- 考虑使用 NS1 中建议的 `biz.task_detail_cache` 缓存聚合结果
|
||||
- 页面文本化(应用 1)需要实时获取数据,建议设置 5 秒查询超时
|
||||
|
||||
---
|
||||
|
||||
## 五、调用链与数据流
|
||||
|
||||
### 5.1 消费事件链
|
||||
|
||||
```
|
||||
新结算单到达
|
||||
→ 应用 3.build_prompt(member_data) → 百炼 API → ai_cache
|
||||
→ 应用 8.run() → member_retention_clue
|
||||
→ 应用 7.build_prompt(member_data + notes) → 百炼 API → ai_cache
|
||||
→ 应用 4.build_prompt(assistant_data + member_data) → 百炼 API → ai_cache
|
||||
→ 应用 5.build_prompt(app4_result) → 百炼 API → ai_cache
|
||||
```
|
||||
|
||||
### 5.2 备注提交事件链
|
||||
|
||||
```
|
||||
备注提交
|
||||
→ 应用 6.build_prompt(note + member_data) → 百炼 API → ai_cache
|
||||
→ 应用 8.run() → member_retention_clue
|
||||
```
|
||||
|
||||
### 5.3 应用 1 对话流
|
||||
|
||||
```
|
||||
用户点击 AI 入口
|
||||
→ 前端传入 source_page + context_data
|
||||
→ 后端 build_page_text(source_page, context_data)
|
||||
→ 拼接为首条 user message
|
||||
→ SSE 流式调用百炼 API
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、参考文档
|
||||
|
||||
| 文档 | 路径 | 用途 |
|
||||
|------|------|------|
|
||||
| P5 AI 集成 spec | `docs/prd/specs/P5-miniapp-ai-integration.md` | Prompt JSON 结构定义、调用链时序 |
|
||||
| API 契约 | `docs/miniprogram-dev/API-contract.md` | 接口响应格式(决定数据结构) |
|
||||
| DWD-DOC 标杆 | `docs/reports/DWD-DOC/` | 金额口径、字段语义权威参考 |
|
||||
| AI 应用骨架代码 | `apps/backend/app/ai/apps/app*.py` | 当前骨架实现 |
|
||||
| 百炼客户端 | `apps/backend/app/ai/bailian_client.py` | API 调用封装 |
|
||||
| 缓存服务 | `apps/backend/app/ai/cache_service.py` | ai_cache 读写 |
|
||||
| NS1 接口设计 | `docs/prd/Neo_Specs/NS1-xcx-backend-api.md` | 后端数据结构参考 |
|
||||
|
||||
---
|
||||
|
||||
## 七、预审查清单(SPEC 启动前确认)
|
||||
|
||||
### 7.1 数据结构
|
||||
|
||||
1. **消费记录字段范围**:`consumption_records` 中每条记录需要包含哪些字段?是否需要包含折扣信息(discount_manual/discount_other)?是否需要包含支付方式明细(balance_pay/cash_pay/online_pay)?
|
||||
2. **服务记录字段范围**:`service_history` 中每条记录需要包含哪些字段?是否需要包含台桌类型(room_category)和客户评价?
|
||||
3. **备注内容截断**:`all_notes` 中每条备注是否需要全文传入?长备注是否截断?截断长度?
|
||||
4. **会员卡明细粒度**:`member_cards` 是否需要包含卡号、开卡日期、有效期等详细信息,还是只需要卡类型和余额?
|
||||
|
||||
### 7.2 Prompt 优化
|
||||
|
||||
5. **Token 预算**:每个应用的首条 Prompt 的 token 上限是多少?百炼 API 的单次请求 token 限制?
|
||||
6. **数据时间窗口**:消费记录默认取近 3 个月,是否需要可配置?不同应用是否需要不同时间窗口?
|
||||
7. **空数据处理**:当客户无消费记录/无备注/无服务历史时,Prompt 如何处理?是否需要特殊提示词?
|
||||
|
||||
### 7.3 页面文本化(应用 1)
|
||||
|
||||
8. **文本化格式**:页面上下文是输出为结构化中文文本还是 JSON?AI 对哪种格式理解更好?
|
||||
9. **数据量控制**:每个页面上下文的字符上限?是否需要根据页面类型动态调整?
|
||||
10. **实时性要求**:应用 1 的页面上下文是否需要实时获取最新数据?还是可以使用缓存(如 task_detail_cache)?
|
||||
|
||||
### 7.4 性能与安全
|
||||
|
||||
11. **FDW 查询并发**:多个数据获取函数是否可以并发执行(asyncio.gather)?FDW 连接池是否支持?
|
||||
12. **数据脱敏**:传入百炼 API 的数据中,哪些字段需要脱敏?member_phone 已断档不传,还有其他敏感字段吗?
|
||||
13. **错误降级**:某个数据获取函数失败时(如 FDW 超时),是否跳过该部分继续生成 Prompt?还是整体失败?
|
||||
|
||||
---
|
||||
|
||||
## 八、任务清单(草案,SPEC 细化后调整)
|
||||
|
||||
### Batch A:共享数据获取层
|
||||
- [ ] T1:创建 `data_fetchers/member_data.py`(客户消费数据获取,应用 3/6/7 共用)
|
||||
- [ ] T2:创建 `data_fetchers/assistant_data.py`(助教信息 + 服务历史获取,应用 4/5 共用)
|
||||
- [ ] T3:创建 `data_fetchers/page_context.py`(页面上下文文本化框架,应用 1 专用)
|
||||
|
||||
### Batch B:Prompt 拼接实现
|
||||
- [ ] T4:完善 `app3_clue.py` 的 `build_prompt()`(客户消费数据 → 维客线索分析)
|
||||
- [ ] T5:完善 `app4_analysis.py` 的 `build_prompt()`(助教+客户数据 → 关系分析)
|
||||
- [ ] T6:完善 `app5_tactics.py` 的 `build_prompt()`(复用应用 4 数据 + task_suggestion)
|
||||
- [ ] T7:完善 `app6_note.py` 的 `build_prompt()`(备注+客户数据 → 备注分析)
|
||||
- [ ] T8:完善 `app7_customer.py` 的 `build_prompt()`(客户全量数据 → 运营策略)
|
||||
|
||||
### Batch C:应用 1 页面文本化 + 联调
|
||||
- [ ] T9:实现各页面类型的文本化函数(task-detail/customer-detail/board-*/performance 等)
|
||||
- [ ] T10:补充 `app1_chat.py` 的 `_build_page_context()` 调用文本化函数
|
||||
- [ ] T11:端到端联调(触发事件 → Prompt 拼接 → 百炼调用 → 缓存写入 → 前端展示)
|
||||
305
docs/prd/Neo_Specs/NS3-mcp-server-ai-extension.md
Normal file
305
docs/prd/Neo_Specs/NS3-mcp-server-ai-extension.md
Normal file
@@ -0,0 +1,305 @@
|
||||
# NS3:MCP Server 扩展 — mcp-server-ai-extension
|
||||
|
||||
> 优先级:中(可与 NS1/NS2 并行,批次 A 无前置依赖)
|
||||
> 预估工作量:中等
|
||||
> 前置条件:批次 A 无依赖;批次 B 依赖 P5-A(biz 表已建);批次 C 依赖批次 B
|
||||
> 参考基准:`docs/prd/specs/P5.1-mcp-server-ai-extension.md`
|
||||
|
||||
---
|
||||
|
||||
## 一、背景与目标
|
||||
|
||||
当前 MCP Server(`apps/mcp-server/server.py`)仅连接 `etl_feiqiu` 数据仓库,提供 4 个工具(list_tables、describe_table、describe_schemas、query_sql),schema 白名单为 ods/dwd/dws/core/meta/app。SQL 安全通过正则禁词检测实现只读保护。
|
||||
|
||||
查库手册(`docs/mcp/AI-DATABASE-QUERY-MANUAL.md`)内容陈旧,DWS 层 34 张表缺少完整字段说明,业务库(zqyy_app)完全未覆盖。
|
||||
|
||||
本 SPEC 目标:
|
||||
1. MCP Server 新增 `zqyy_app` 业务库连接,支持多数据库路由
|
||||
2. 实现业务库敏感字段脱敏策略
|
||||
3. 重写查库手册(ETL 全字段 + 业务库全字段 + 常用查询模式)
|
||||
4. 手册上传百炼平台验证 AI 引用效果
|
||||
|
||||
### 当前 MCP Server 能力
|
||||
|
||||
| 工具 | 功能 | 限制 |
|
||||
|------|------|------|
|
||||
| `list_tables` | 列出指定 schema 下的表 | 仅 etl_feiqiu |
|
||||
| `describe_table` | 查看表结构(列名、类型、注释) | 仅 etl_feiqiu |
|
||||
| `describe_schemas` | 列出可用 schema 及表数量 | 仅 ods/dwd/dws/core/meta/app |
|
||||
| `query_sql` | 执行只读 SQL 查询 | 正则禁词检测,单 schema 限制 |
|
||||
|
||||
---
|
||||
|
||||
## 二、技术架构
|
||||
|
||||
### 2.1 多数据库连接
|
||||
|
||||
```
|
||||
apps/mcp-server/
|
||||
├── server.py 🔧 扩展:多连接池 + schema 路由
|
||||
├── db_pool.py 🆕 新建:连接池管理(etl_feiqiu + zqyy_app)
|
||||
├── security.py 🆕 新建:敏感字段脱敏策略
|
||||
└── config.py 🆕 新建:数据库配置(DSN、schema 白名单)
|
||||
```
|
||||
|
||||
### 2.2 连接池设计
|
||||
|
||||
```python
|
||||
# db_pool.py
|
||||
class DatabasePool:
|
||||
"""管理两个数据库的连接池。"""
|
||||
|
||||
pools = {
|
||||
"etl": {
|
||||
"dsn_env": "PG_DSN", # etl_feiqiu / test_etl_feiqiu
|
||||
"schemas": ["ods", "dwd", "dws", "core", "meta", "app"],
|
||||
"readonly": True,
|
||||
},
|
||||
"biz": {
|
||||
"dsn_env": "APP_DB_DSN", # zqyy_app / test_zqyy_app
|
||||
"schemas": ["auth", "biz", "public"],
|
||||
"readonly": True, # MCP 只读,写操作走后端 API
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 Schema 自动路由
|
||||
|
||||
工具参数中不再需要显式指定 `database`,而是根据 schema 名称自动路由:
|
||||
|
||||
| Schema | 路由目标 | 说明 |
|
||||
|--------|---------|------|
|
||||
| `ods` / `dwd` / `dws` / `core` / `meta` / `app` | etl_feiqiu | ETL 数据仓库 |
|
||||
| `auth` / `biz` / `public` | zqyy_app | 业务库 |
|
||||
|
||||
路由逻辑:
|
||||
```python
|
||||
def resolve_database(schema: str) -> str:
|
||||
ETL_SCHEMAS = {"ods", "dwd", "dws", "core", "meta", "app"}
|
||||
BIZ_SCHEMAS = {"auth", "biz", "public"}
|
||||
if schema in ETL_SCHEMAS:
|
||||
return "etl"
|
||||
elif schema in BIZ_SCHEMAS:
|
||||
return "biz"
|
||||
else:
|
||||
raise ValueError(f"未知 schema: {schema}")
|
||||
```
|
||||
|
||||
### 2.4 工具扩展
|
||||
|
||||
4 个现有工具的参数不变,内部根据 schema 参数自动选择连接池:
|
||||
|
||||
| 工具 | 变更 |
|
||||
|------|------|
|
||||
| `list_tables(schema)` | schema 参数扩展接受 auth/biz/public |
|
||||
| `describe_table(table, schema)` | 同上 |
|
||||
| `describe_schemas()` | 返回结果增加 auth/biz/public 三个 schema |
|
||||
| `query_sql(schema, sql)` | 根据 schema 路由到对应连接池 |
|
||||
|
||||
### 2.5 跨库查询限制
|
||||
|
||||
- 单次 `query_sql` 调用只能查询单个数据库(由 schema 参数决定)
|
||||
- 禁止在 SQL 中引用其他数据库的 schema(现有 `_reject_cross_schema` 逻辑扩展)
|
||||
- 如需跨库关联,AI 应分两次查询后在应用层组合
|
||||
|
||||
---
|
||||
|
||||
## 三、敏感字段脱敏策略
|
||||
|
||||
### 3.1 脱敏规则
|
||||
|
||||
| Schema | 表 | 敏感字段 | 脱敏方式 |
|
||||
|--------|-----|---------|---------|
|
||||
| `auth` | `users` | `wx_openid` | 前 4 后 4 保留,中间 `***` |
|
||||
| `auth` | `users` | `phone` | 前 3 后 4 保留,中间 `****` |
|
||||
| `auth` | `users` | `wx_session_key` | 完全隐藏,返回 `[REDACTED]` |
|
||||
| `biz` | `ai_messages` | `content`(role=user) | 不脱敏(AI 需要理解对话内容) |
|
||||
| `biz` | `ai_conversations` | — | 无敏感字段 |
|
||||
| `public` | `member_retention_clue` | `recorded_by_assistant_id` | 不脱敏(非 PII) |
|
||||
|
||||
### 3.2 实现方式
|
||||
|
||||
在 `query_sql` 返回结果时,对命中脱敏规则的列进行后处理:
|
||||
|
||||
```python
|
||||
# security.py
|
||||
MASKING_RULES = {
|
||||
"auth.users": {
|
||||
"wx_openid": lambda v: f"{v[:4]}***{v[-4:]}" if v and len(v) > 8 else "[REDACTED]",
|
||||
"phone": lambda v: f"{v[:3]}****{v[-4:]}" if v and len(v) >= 11 else "[REDACTED]",
|
||||
"wx_session_key": lambda v: "[REDACTED]",
|
||||
},
|
||||
}
|
||||
|
||||
def mask_row(schema: str, table: str, columns: list[str], row: tuple) -> tuple:
|
||||
"""对查询结果行应用脱敏规则。"""
|
||||
```
|
||||
|
||||
### 3.3 脱敏绕过防护
|
||||
|
||||
- `SELECT *` 查询 auth.users 时自动应用脱敏
|
||||
- 禁止通过 `CAST`、`CONCAT`、子查询等方式绕过脱敏(在 SQL 解析阶段检测)
|
||||
- 对 auth.users 的查询结果始终应用列级脱敏,无论 SQL 写法
|
||||
|
||||
---
|
||||
|
||||
## 四、查库手册重写
|
||||
|
||||
### 4.1 手册结构
|
||||
|
||||
重写后的手册分为两个文件,便于维护和上传:
|
||||
|
||||
```
|
||||
docs/mcp/
|
||||
├── AI-DATABASE-QUERY-MANUAL.md 🔧 重写(总览 + ETL 库)
|
||||
└── AI-DATABASE-QUERY-MANUAL-BIZ.md 🆕 新建(业务库)
|
||||
```
|
||||
|
||||
### 4.2 ETL 库手册内容(重写)
|
||||
|
||||
保留现有架构流程和工具说明,重点补充:
|
||||
|
||||
1. **DWS 层完整表清单**(34 张表按业务域分组)
|
||||
- 每张表列出全部字段(字段名、类型、中文说明)
|
||||
- 标注关键字段的业务含义和使用注意事项
|
||||
- 按业务域分组:会员域、助教域、财务域、配置域
|
||||
|
||||
2. **金额口径专章**
|
||||
- `consume_money` 三种历史口径说明(A/B/C),明确标注"禁止直接使用"
|
||||
- `items_sum` 定义和计算公式
|
||||
- settle_type 枚举映射表(1=台桌结账 78.6%、3=商城订单 21.4%、5=充值、7=充值退款、6=结算退款)
|
||||
- 助教费用拆分规则(assistant_pd_money 陪打 / assistant_cx_money 超休)
|
||||
- 支付渠道恒等式(balance_amount = recharge_card_amount + gift_card_amount)
|
||||
|
||||
3. **会员字段断档说明**
|
||||
- DQ-6:member_phone/member_name 自 2025-12 起 NULL,需 JOIN dim_member
|
||||
- DQ-7:member_card_type_name 自 2025-07-21 起 NULL,需 JOIN dim_member_card_account
|
||||
|
||||
4. **常用查询模式**(ETL 侧)
|
||||
- 客户消费汇总查询
|
||||
- 助教绩效查询
|
||||
- 财务日报查询
|
||||
- 指数排名查询
|
||||
|
||||
5. **数据量统计与性能建议**
|
||||
- 各表的大致数据量级
|
||||
- 推荐的 WHERE 条件(时间范围、site_id)
|
||||
- 避免全表扫描的建议
|
||||
|
||||
### 4.3 业务库手册内容(新建)
|
||||
|
||||
1. **Schema 概览**
|
||||
- `auth`:用户认证(users、user_applications、site_code_mapping、user_assistant_binding)
|
||||
- `biz`:业务数据(coach_tasks、notes、ai_conversations、ai_messages、ai_cache、trigger_jobs、salary_adjustments、excel_upload_log)
|
||||
- `public`:共享数据(member_retention_clue)
|
||||
|
||||
2. **每张表完整字段说明**
|
||||
- 字段名、类型、是否可空、默认值、中文说明
|
||||
- 关键枚举值说明(如 coach_tasks.task_type、notes.type、ai_cache.cache_type)
|
||||
- 外键关系和关联查询建议
|
||||
|
||||
3. **常用查询模式**(业务侧)
|
||||
- 维客线索查询(按客户、按来源、按标签)
|
||||
- 任务系统查询(按助教、按状态、按类型)
|
||||
- 备注查询(按客户、按助教、含星星评分)
|
||||
- AI 缓存查询(按应用类型、按 target_id)
|
||||
- Excel 上传记录查询
|
||||
|
||||
4. **跨库关联指南**
|
||||
- 业务库 member_id → ETL 库 dim_member 的关联方式
|
||||
- 业务库 assistant_id → ETL 库 dim_assistant 的关联方式
|
||||
- 注意:MCP 不支持单次跨库 JOIN,需分两次查询
|
||||
|
||||
### 4.4 数据字段权威参考
|
||||
|
||||
手册中涉及的 DWD/DWS 层字段来源、金额口径、业务逻辑,以 `docs/reports/DWD-DOC/` 校准文档为准(数据快照 2026-03-06)。
|
||||
|
||||
---
|
||||
|
||||
## 五、百炼平台上传与验证
|
||||
|
||||
### 5.1 上传方式
|
||||
|
||||
- 将重写后的手册作为知识库文档上传至百炼平台
|
||||
- 关联到应用 1(通用对话),使 AI 在回答数据查询问题时可引用手册
|
||||
|
||||
### 5.2 验证场景
|
||||
|
||||
| 测试场景 | 预期行为 |
|
||||
|---------|---------|
|
||||
| "查询本月营业额" | AI 引用手册中的 dws_finance_daily_summary 表,使用 items_sum 口径 |
|
||||
| "查看客户王先生的消费记录" | AI 引用 dim_member + dwd_settlement_head,通过 member_id 关联 |
|
||||
| "助教张三的本月绩效" | AI 引用 dws_assistant_salary_calc,正确使用 assistant_pd_money 拆分 |
|
||||
| "查看维客线索" | AI 引用业务库 member_retention_clue 表 |
|
||||
| "查看用户申请列表" | AI 引用 auth.user_applications,对 phone 字段脱敏 |
|
||||
|
||||
### 5.3 验证标准
|
||||
|
||||
- AI 不再频繁调用 `describe_table`(手册已提供完整字段信息)
|
||||
- AI 生成的 SQL 使用正确的金额口径(items_sum 而非 consume_money)
|
||||
- AI 查询 auth.users 时返回结果中敏感字段已脱敏
|
||||
- AI 能正确区分 ETL 库和业务库的查询路由
|
||||
|
||||
---
|
||||
|
||||
## 六、参考文档
|
||||
|
||||
| 文档 | 路径 | 用途 |
|
||||
|------|------|------|
|
||||
| P5.1 原始 spec | `docs/prd/specs/P5.1-mcp-server-ai-extension.md` | 需求定义基准 |
|
||||
| 现有 MCP Server | `apps/mcp-server/server.py` | 当前实现参考 |
|
||||
| 现有查库手册 | `docs/mcp/AI-DATABASE-QUERY-MANUAL.md` | 重写基础 |
|
||||
| DWD-DOC 标杆 | `docs/reports/DWD-DOC/` | 字段语义、金额口径权威参考 |
|
||||
| BD 手册-业务表 | `docs/database/BD_Manual_biz_tables.md` | biz schema 表结构 |
|
||||
| BD 手册-认证表 | `docs/database/BD_Manual_auth_tables.md` | auth schema 表结构 |
|
||||
| ETL BD 手册 | `apps/etl/connectors/feiqiu/docs/database/` | DWD/DWS 表结构 |
|
||||
|
||||
---
|
||||
|
||||
## 七、预审查清单(SPEC 启动前确认)
|
||||
|
||||
### 7.1 多数据库连接
|
||||
|
||||
1. **连接池大小**:zqyy_app 连接池的 min/max 连接数?与 etl_feiqiu 共享还是独立?MCP Server 的并发查询量预估?
|
||||
2. **DSN 环境变量**:MCP Server 运行环境中 `APP_DB_DSN` 是否已配置?测试环境使用 `test_zqyy_app` 还是 `zqyy_app`?
|
||||
3. **RLS 隔离**:MCP 查询 zqyy_app 时是否需要 `SET LOCAL app.current_site_id`?如果需要,site_id 从哪里获取(MCP 调用方传入?固定值?)?
|
||||
4. **连接超时**:FDW 查询和业务库查询的超时时间是否需要不同设置?
|
||||
|
||||
### 7.2 安全与脱敏
|
||||
|
||||
5. **脱敏范围确认**:除 auth.users 外,还有哪些表/字段需要脱敏?biz.notes 的备注内容是否需要脱敏?
|
||||
6. **脱敏粒度**:是否需要根据 MCP 调用方身份区分脱敏级别?(如管理员可看完整数据,AI 应用只看脱敏数据)
|
||||
7. **SQL 注入防护**:现有正则禁词检测是否足够?是否需要升级为 SQL 解析器(如 sqlparse)?
|
||||
8. **审计日志**:MCP 查询是否需要记录审计日志(谁查了什么表、什么时间)?
|
||||
|
||||
### 7.3 查库手册
|
||||
|
||||
9. **手册格式**:百炼平台对知识库文档的格式要求?Markdown 是否直接支持?是否需要转换为 PDF/TXT?
|
||||
10. **手册拆分**:ETL 库和业务库是否拆分为两个知识库文档?还是合并为一个?
|
||||
11. **手册更新机制**:表结构变更后,手册如何同步更新?是否需要自动化脚本从数据库 DDL 生成手册?
|
||||
12. **DWD 层覆盖**:手册是否需要覆盖 DWD 层全部表的完整字段?还是只覆盖 DWS + 常用 DWD 表?
|
||||
|
||||
### 7.4 部署与运维
|
||||
|
||||
13. **MCP Server 重启**:新增连接池后,MCP Server 是否需要重启?是否支持热加载配置?
|
||||
14. **监控指标**:是否需要监控 MCP 查询的响应时间、错误率、查询频次?
|
||||
15. **降级策略**:zqyy_app 连接失败时,是否影响 etl_feiqiu 的查询?两个连接池是否需要独立故障隔离?
|
||||
|
||||
---
|
||||
|
||||
## 八、任务清单(草案,SPEC 细化后调整)
|
||||
|
||||
### 批次 A:查库手册 — ETL 库(无前置依赖)
|
||||
- [ ] T1:重写 `AI-DATABASE-QUERY-MANUAL.md` — DWS 层 34 张表全字段说明(按业务域分组)
|
||||
- [ ] T2:补充金额口径专章(consume_money 禁用说明 + items_sum 定义 + settle_type 映射)
|
||||
- [ ] T3:补充会员字段断档说明(DQ-6/DQ-7)+ 常用 ETL 查询模式
|
||||
|
||||
### 批次 B:MCP Server 扩展 + 业务库手册(依赖 P5-A)
|
||||
- [ ] T4:创建 `db_pool.py` — 双连接池管理(etl_feiqiu + zqyy_app)
|
||||
- [ ] T5:扩展 `server.py` — 4 个工具支持 schema 自动路由
|
||||
- [ ] T6:创建 `security.py` — 敏感字段脱敏策略实现
|
||||
- [ ] T7:创建 `AI-DATABASE-QUERY-MANUAL-BIZ.md` — 业务库全字段说明 + 常用查询模式
|
||||
|
||||
### 批次 C:百炼平台验证
|
||||
- [ ] T8:手册上传百炼平台并关联 AI 应用
|
||||
- [ ] T9:执行验证场景测试(5 个场景),确认 AI 引用效果
|
||||
532
docs/prd/Neo_Specs/NS4-tenant-admin-web.md
Normal file
532
docs/prd/Neo_Specs/NS4-tenant-admin-web.md
Normal file
@@ -0,0 +1,532 @@
|
||||
# NS4:租户管理后台 — tenant-admin-web
|
||||
|
||||
> 优先级:中(可与 NS1/NS2 并行,依赖 P1+P3)
|
||||
> 预估工作量:大
|
||||
> 前置条件:P1(数据库基础)、P3(用户认证体系)、admin-web-console 需求 11(租户管理员账号管理)
|
||||
> 参考基准:`docs/prd/specs/P10-tenant-admin-web.md`
|
||||
|
||||
---
|
||||
|
||||
## 一、背景与目标
|
||||
|
||||
当前系统缺少面向租户管理员的独立管理界面。用户审核、Excel 数据上传、维客线索管理等运营操作无法自助完成,依赖开发人员手动操作数据库。
|
||||
|
||||
本 SPEC 目标:构建独立的租户管理 Web 应用,提供:
|
||||
1. 用户审核与管理(申请审核、身份编辑、店铺归属、助教/员工绑定)
|
||||
2. Excel 数据上传(4 种模板:财务支出/团购收入/助教奖罚/充值业绩归属)
|
||||
3. 维客线索管理(查看、修改、删除、隐藏)
|
||||
|
||||
### 与现有系统的关系
|
||||
|
||||
| 系统 | 用途 | 用户 |
|
||||
|------|------|------|
|
||||
| `apps/admin-web/`(系统管理后台) | 平台级管理(Operator 操作) | 系统管理员 |
|
||||
| `apps/tenant-admin/`(本 SPEC) | 租户级管理 | 租户管理员 |
|
||||
| `apps/miniprogram/`(小程序) | C 端业务 | 助教/管理者 |
|
||||
|
||||
租户管理员账号由系统管理后台(`apps/admin-web/`)的 Operator 创建,租户管理员不可自行注册。
|
||||
|
||||
---
|
||||
|
||||
## 二、技术架构
|
||||
|
||||
### 2.1 前端
|
||||
|
||||
- 独立 Web 应用:React + Vite + Ant Design(与 `apps/admin-web/` 同技术栈)
|
||||
- 部署路径:`apps/tenant-admin/`
|
||||
- 独立登录入口,与系统管理后台完全隔离
|
||||
|
||||
```
|
||||
apps/tenant-admin/
|
||||
├── src/
|
||||
│ ├── pages/
|
||||
│ │ ├── Login/ # 登录页
|
||||
│ │ ├── UserApproval/ # 用户审核
|
||||
│ │ ├── UserManagement/ # 用户管理
|
||||
│ │ ├── ExcelUpload/ # Excel 上传(4 种模板)
|
||||
│ │ └── RetentionClues/ # 维客线索管理
|
||||
│ ├── components/
|
||||
│ │ ├── DiffTable/ # 冲突 diff 交互组件
|
||||
│ │ └── ClueEditor/ # 线索编辑组件
|
||||
│ ├── services/
|
||||
│ │ └── api.ts # API 调用封装
|
||||
│ ├── hooks/
|
||||
│ ├── utils/
|
||||
│ └── App.tsx
|
||||
├── package.json
|
||||
├── vite.config.ts
|
||||
└── tsconfig.json
|
||||
```
|
||||
|
||||
### 2.2 后端
|
||||
|
||||
复用 `apps/backend/` 的 FastAPI,新增租户管理路由模块:
|
||||
|
||||
```
|
||||
apps/backend/app/routers/
|
||||
├── tenant_auth.py 🆕 租户管理员登录/鉴权
|
||||
├── tenant_users.py 🆕 用户审核 + 用户管理
|
||||
├── tenant_excel.py 🆕 Excel 上传/校验/冲突处理
|
||||
└── tenant_clues.py 🆕 维客线索管理
|
||||
```
|
||||
|
||||
### 2.3 认证体系
|
||||
|
||||
- 独立凭据:用户名 + 密码(非微信登录)
|
||||
- JWT 签发:与小程序 JWT 独立(不同 issuer 或 audience)
|
||||
- 账号创建:由系统管理后台 Operator 创建,指定用户名、初始密码、所属租户、管辖 site_id 列表
|
||||
- 权限级别:
|
||||
- 租户级管理员:管辖该租户下所有店铺
|
||||
- 店铺级管理员:只能管理 Operator 分配的 site_id 列表内的店铺
|
||||
|
||||
### 2.4 数据隔离
|
||||
|
||||
- 所有查询附加 `site_id IN (管辖列表)` 条件
|
||||
- FDW 查询需 `SET LOCAL app.current_site_id`(单店铺场景)
|
||||
- 多店铺场景下,逐 site_id 查询后合并结果
|
||||
|
||||
---
|
||||
|
||||
## 三、功能详细设计
|
||||
|
||||
### 3.1 用户审核页面
|
||||
|
||||
#### 页面功能
|
||||
|
||||
- 申请列表:展示所有待审核/已审核的用户申请
|
||||
- 状态筛选:全部 / 待审核(pending) / 已通过(approved) / 已拒绝(rejected)
|
||||
- 关联建议:根据申请中的球房 ID + 手机号,同时在助教表和员工信息表中匹配
|
||||
- 审核操作:通过(分配身份+关联助教/员工)/ 拒绝(填写原因)
|
||||
|
||||
#### 关联匹配逻辑
|
||||
|
||||
```
|
||||
用户申请(球房ID + 手机号)
|
||||
→ site_code_mapping 查 site_id
|
||||
→ 并行匹配:
|
||||
├── fdw_etl.v_dim_assistant(phone 匹配,scd2_is_current=1)
|
||||
└── fdw_etl.v_dim_staff + v_dim_staff_ex(phone 匹配)
|
||||
→ 返回匹配建议列表(可能多条)
|
||||
→ 管理员选择关联目标
|
||||
```
|
||||
|
||||
#### 审核通过后操作
|
||||
|
||||
1. 更新 `auth.users.status = 'approved'`
|
||||
2. 分配角色(助教/管理者)→ 写入 `auth.user_roles`
|
||||
3. 关联助教 → 写入 `auth.user_assistant_binding`(含 staff_id)
|
||||
4. 分配 site_id → 更新 `auth.users.site_id`
|
||||
|
||||
#### 接口设计
|
||||
|
||||
| 接口 | 方法 | 路径 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 申请列表 | GET | `/api/tenant/applications` | 支持 status 筛选、分页 |
|
||||
| 关联建议 | GET | `/api/tenant/applications/{id}/match-suggestions` | 返回助教+员工匹配结果 |
|
||||
| 审核通过 | POST | `/api/tenant/applications/{id}/approve` | body: role, assistant_id, staff_id |
|
||||
| 审核拒绝 | POST | `/api/tenant/applications/{id}/reject` | body: reason |
|
||||
|
||||
### 3.2 用户管理页面
|
||||
|
||||
#### 页面功能
|
||||
|
||||
- 用户列表:展示已通过审核的用户(姓名、角色、关联助教、店铺、状态)
|
||||
- 身份编辑:修改角色(助教↔管理者)
|
||||
- 店铺归属:修改用户的 site_id
|
||||
- 关联助教/员工:修改绑定关系
|
||||
- 禁用/启用:冻结用户账号
|
||||
|
||||
#### 接口设计
|
||||
|
||||
| 接口 | 方法 | 路径 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 用户列表 | GET | `/api/tenant/users` | 支持角色筛选、搜索、分页 |
|
||||
| 编辑用户 | PATCH | `/api/tenant/users/{id}` | body: role, site_id, status |
|
||||
| 修改绑定 | PUT | `/api/tenant/users/{id}/binding` | body: assistant_id, staff_id |
|
||||
|
||||
### 3.3 Excel 上传
|
||||
|
||||
#### 4 种模板
|
||||
|
||||
##### 模板 1:财务支出(按月)
|
||||
|
||||
| 列名 | 类型 | 必填 | 校验规则 |
|
||||
|------|------|------|---------|
|
||||
| 月份 | YYYY-MM | 是 | 格式校验,不超过当前月 |
|
||||
| 支出类别 | 文本 | 是 | 枚举:房租/水电/物业/食品饮料进货/耗材/报销/固定人员工资/其他费用 |
|
||||
| 金额 | 数值(2) | 是 | > 0,精度 2 位小数 |
|
||||
| 备注 | 文本 | 否 | 最长 500 字符 |
|
||||
|
||||
主键:月份 + 支出类别
|
||||
写入目标:`dws.dws_finance_expense_summary`(通过后端 API 写入 ETL 库,或写入业务库 staging 表后由 ETL 同步)
|
||||
|
||||
##### 模板 2:团购平台收入(按月)
|
||||
|
||||
| 列名 | 类型 | 必填 | 校验规则 |
|
||||
|------|------|------|---------|
|
||||
| 月份 | YYYY-MM | 是 | 格式校验 |
|
||||
| 平台名称 | 文本 | 是 | 非空 |
|
||||
| 收入金额 | 数值(2) | 是 | > 0 |
|
||||
| 备注 | 文本 | 否 | 最长 500 字符 |
|
||||
|
||||
主键:月份 + 平台名称
|
||||
写入目标:`dws.dws_platform_settlement`(或业务库 staging 表)
|
||||
|
||||
##### 模板 3:助教奖罚(按月)
|
||||
|
||||
| 列名 | 类型 | 必填 | 校验规则 |
|
||||
|------|------|------|---------|
|
||||
| 月份 | YYYY-MM | 是 | 格式校验 |
|
||||
| 助教姓名 | 文本 | 是 | 非空 |
|
||||
| 助教编号 | 文本 | 是 | 非空 |
|
||||
| 类型 | 文本 | 是 | 枚举:扣款/奖金 |
|
||||
| 金额 | 数值(2) | 是 | > 0 |
|
||||
| 原因 | 文本 | 是 | 非空,最长 200 字符 |
|
||||
|
||||
主键:月份 + 助教姓名 + 助教编号 + 类型 + 原因(同一助教同月可多笔)
|
||||
写入目标:`biz.salary_adjustments`
|
||||
|
||||
##### 模板 4:充值业绩归属(按月)
|
||||
|
||||
| 列名 | 类型 | 必填 | 校验规则 |
|
||||
|------|------|------|---------|
|
||||
| 充值日期 | YYYY-MM-DD | 是 | 格式校验 |
|
||||
| 会员名称 | 文本 | 是 | 非空 |
|
||||
| 充值金额 | 数值(2) | 是 | > 0 |
|
||||
| 归属助教 | 文本 | 是 | 非空 |
|
||||
| 奖励金额 | 数值(2) | 是 | ≥ 0 |
|
||||
|
||||
主键:充值日期 + 会员名称 + 归属助教
|
||||
写入目标:`dws.dws_assistant_recharge_commission`(或业务库 staging 表)
|
||||
|
||||
#### 人员匹配校验(模板 3/4)
|
||||
|
||||
上传助教奖罚和充值业绩归属时,需校验助教姓名+编号是否存在:
|
||||
|
||||
```
|
||||
助教姓名 + 助教编号
|
||||
→ fdw_etl.v_dim_assistant(nickname + assistant_number 匹配,scd2_is_current=1)
|
||||
→ 如不匹配,尝试 fdw_etl.v_dim_staff + v_dim_staff_ex(name + staff_number 匹配)
|
||||
→ 匹配失败:标记为校验警告(不阻断上传,但提示管理员确认)
|
||||
```
|
||||
|
||||
#### 冲突处理流程
|
||||
|
||||
```
|
||||
上传 Excel
|
||||
→ 后端解析 + 格式校验
|
||||
→ 返回校验结果:
|
||||
├── 格式错误行 → 前端标红,要求修正后重新上传
|
||||
├── 无冲突行 → 标记为"待写入"
|
||||
└── 冲突行(主键已存在)→ 返回 diff 数据(旧值 vs 新值)
|
||||
→ 前端展示 diff 交互表格:
|
||||
- 每行显示:字段名 | 旧值 | 新值 | 操作(替换/保留)
|
||||
- 支持"全部替换"/"全部保留"快捷操作
|
||||
→ 用户确认后提交
|
||||
→ 后端按选择写入
|
||||
```
|
||||
|
||||
#### 接口设计
|
||||
|
||||
| 接口 | 方法 | 路径 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 上传解析 | POST | `/api/tenant/excel/upload` | multipart/form-data,返回校验结果+冲突列表 |
|
||||
| 确认写入 | POST | `/api/tenant/excel/confirm` | body: upload_id, resolutions[] |
|
||||
| 上传记录 | GET | `/api/tenant/excel/logs` | 历史上传记录列表 |
|
||||
| 模板下载 | GET | `/api/tenant/excel/template/{type}` | 下载空白模板 |
|
||||
|
||||
### 3.4 维客线索管理
|
||||
|
||||
#### 页面功能
|
||||
|
||||
- 客户搜索:按客户姓名/手机号搜索(姓名从 dim_member.nickname,手机从 dim_member.mobile)
|
||||
- 门店筛选:按管辖 site_id 筛选
|
||||
- 线索列表:展示该客户的全部维客线索
|
||||
- 标签(大类枚举:客户基础/消费习惯/玩法偏好/促销偏好/社交关系/重要反馈)
|
||||
- 摘要(含 Emoji 前缀)
|
||||
- 详情
|
||||
- 提供人(recorded_by_name)
|
||||
- 来源(manual / ai_consumption / ai_note)
|
||||
- 记录时间
|
||||
- 隐藏状态
|
||||
- 操作:
|
||||
- 修改:编辑标签、摘要、详情
|
||||
- 删除:二次确认后物理删除
|
||||
- 隐藏/显示:切换 `is_hidden` 状态
|
||||
|
||||
#### 数据源
|
||||
|
||||
- `zqyy_app.public.member_retention_clue`(线索数据)
|
||||
- `fdw_etl.v_dim_member`(客户信息:nickname、mobile,通过 member_id 关联)
|
||||
|
||||
> ⚠️ 会员字段断档(DQ-6):客户姓名/手机必须从 dim_member 获取,不可使用结算单上的冗余字段
|
||||
|
||||
#### 接口设计
|
||||
|
||||
| 接口 | 方法 | 路径 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 客户搜索 | GET | `/api/tenant/customers/search` | query: keyword, site_id |
|
||||
| 线索列表 | GET | `/api/tenant/customers/{member_id}/clues` | 该客户全部线索 |
|
||||
| 修改线索 | PATCH | `/api/tenant/clues/{id}` | body: category, summary, detail |
|
||||
| 删除线索 | DELETE | `/api/tenant/clues/{id}` | 二次确认后物理删除 |
|
||||
| 隐藏/显示 | PATCH | `/api/tenant/clues/{id}/visibility` | body: is_hidden |
|
||||
|
||||
---
|
||||
|
||||
## 四、数据库审查与新增表
|
||||
|
||||
### 4.1 现有表满足度
|
||||
|
||||
| 功能 | 现有表 | 是否满足 | 缺口 |
|
||||
|------|--------|---------|------|
|
||||
| 用户审核 | auth.users, auth.user_applications | ✅ 满足 | 无 |
|
||||
| 用户管理 | auth.users, auth.user_roles, auth.user_assistant_binding | ✅ 满足 | 无 |
|
||||
| 维客线索 | public.member_retention_clue | ⚠️ 部分 | 缺 is_hidden 字段 |
|
||||
| 助教奖罚 | — | ❌ 不满足 | 需新建 biz.salary_adjustments |
|
||||
| 上传记录 | — | ❌ 不满足 | 需新建 biz.excel_upload_log |
|
||||
| 财务支出/团购收入/充值归属 | DWS 表 | ⚠️ 待定 | 可能需要 staging 表 |
|
||||
|
||||
### 4.2 需新建的表
|
||||
|
||||
#### 表 1:`biz.salary_adjustments`(助教奖罚明细)
|
||||
|
||||
```sql
|
||||
CREATE TABLE biz.salary_adjustments (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
site_id BIGINT NOT NULL,
|
||||
assistant_id BIGINT, -- 匹配到的助教 ID(可空,匹配失败时为 NULL)
|
||||
assistant_name VARCHAR(100) NOT NULL,
|
||||
assistant_number VARCHAR(50) NOT NULL,
|
||||
salary_month VARCHAR(7) NOT NULL, -- YYYY-MM
|
||||
adjustment_type VARCHAR(20) NOT NULL CHECK (adjustment_type IN ('deduction', 'bonus')),
|
||||
amount NUMERIC(12,2) NOT NULL CHECK (amount > 0),
|
||||
reason VARCHAR(200) NOT NULL,
|
||||
upload_batch_id BIGINT REFERENCES biz.excel_upload_log(id),
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
created_by BIGINT -- 上传人(租户管理员 ID)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_salary_adj_site_month ON biz.salary_adjustments(site_id, salary_month);
|
||||
CREATE INDEX idx_salary_adj_assistant ON biz.salary_adjustments(assistant_id, salary_month);
|
||||
```
|
||||
|
||||
#### 表 2:`biz.excel_upload_log`(Excel 上传记录)
|
||||
|
||||
```sql
|
||||
CREATE TABLE biz.excel_upload_log (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
site_id BIGINT NOT NULL,
|
||||
upload_type VARCHAR(30) NOT NULL CHECK (upload_type IN (
|
||||
'expense', 'platform_income', 'salary_adj', 'recharge_commission'
|
||||
)),
|
||||
file_name VARCHAR(255) NOT NULL,
|
||||
uploaded_by BIGINT NOT NULL, -- 租户管理员 ID
|
||||
row_count INTEGER NOT NULL DEFAULT 0,
|
||||
conflict_count INTEGER NOT NULL DEFAULT 0,
|
||||
resolved_count INTEGER NOT NULL DEFAULT 0,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'pending' CHECK (status IN (
|
||||
'pending', 'confirmed', 'failed'
|
||||
)),
|
||||
error_detail JSONB, -- 校验错误详情
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
confirmed_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE INDEX idx_excel_log_site ON biz.excel_upload_log(site_id, created_at DESC);
|
||||
```
|
||||
|
||||
### 4.3 需变更的表
|
||||
|
||||
#### `public.member_retention_clue` — 新增字段
|
||||
|
||||
```sql
|
||||
ALTER TABLE public.member_retention_clue
|
||||
ADD COLUMN is_hidden BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
COMMENT ON COLUMN public.member_retention_clue.is_hidden
|
||||
IS '是否隐藏(true=管理后台保留但小程序不展示)';
|
||||
```
|
||||
|
||||
> 小程序端查询线索时需增加 `WHERE is_hidden = false` 条件
|
||||
|
||||
#### `public.member_retention_clue` — 确认 source 字段
|
||||
|
||||
P10 spec 中提到需新增 `source` 字段,需确认该字段是否已在 P4/P5 阶段建立。如未建立:
|
||||
|
||||
```sql
|
||||
ALTER TABLE public.member_retention_clue
|
||||
ADD COLUMN source VARCHAR(20) NOT NULL DEFAULT 'manual'
|
||||
CHECK (source IN ('manual', 'ai_consumption', 'ai_note'));
|
||||
|
||||
COMMENT ON COLUMN public.member_retention_clue.source
|
||||
IS '线索来源:manual=人工录入, ai_consumption=应用3消费分析, ai_note=应用6备注分析';
|
||||
```
|
||||
|
||||
### 4.4 Excel 写入目标表的 staging 策略
|
||||
|
||||
财务支出、团购收入、充值业绩归属三种模板的数据最终需要进入 DWS 层。有两种策略:
|
||||
|
||||
**方案 A:直接写入 DWS 表**(通过后端 API 直连 ETL 库写入)
|
||||
- 优点:数据即时可用
|
||||
- 缺点:绕过 ETL 流程,数据一致性风险
|
||||
|
||||
**方案 B:写入业务库 staging 表,ETL 定时同步**
|
||||
- 优点:数据经过 ETL 标准流程,一致性有保障
|
||||
- 缺点:数据有延迟(取决于 ETL 调度频率)
|
||||
|
||||
建议采用方案 B,需新建 3 张 staging 表:
|
||||
|
||||
```sql
|
||||
-- biz.stg_finance_expense(财务支出 staging)
|
||||
CREATE TABLE biz.stg_finance_expense (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
site_id BIGINT NOT NULL,
|
||||
expense_month VARCHAR(7) NOT NULL,
|
||||
category VARCHAR(50) NOT NULL,
|
||||
amount NUMERIC(12,2) NOT NULL,
|
||||
remark TEXT,
|
||||
upload_batch_id BIGINT REFERENCES biz.excel_upload_log(id),
|
||||
synced_at TIMESTAMPTZ, -- ETL 同步时间(NULL=未同步)
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- biz.stg_platform_income(团购收入 staging)
|
||||
CREATE TABLE biz.stg_platform_income (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
site_id BIGINT NOT NULL,
|
||||
income_month VARCHAR(7) NOT NULL,
|
||||
platform_name VARCHAR(100) NOT NULL,
|
||||
amount NUMERIC(12,2) NOT NULL,
|
||||
remark TEXT,
|
||||
upload_batch_id BIGINT REFERENCES biz.excel_upload_log(id),
|
||||
synced_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- biz.stg_recharge_commission(充值业绩归属 staging)
|
||||
CREATE TABLE biz.stg_recharge_commission (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
site_id BIGINT NOT NULL,
|
||||
recharge_date DATE NOT NULL,
|
||||
member_name VARCHAR(100) NOT NULL,
|
||||
recharge_amount NUMERIC(12,2) NOT NULL,
|
||||
assigned_assistant VARCHAR(100) NOT NULL,
|
||||
reward_amount NUMERIC(12,2) NOT NULL,
|
||||
upload_batch_id BIGINT REFERENCES biz.excel_upload_log(id),
|
||||
synced_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、租户管理员账号体系
|
||||
|
||||
### 5.1 账号存储
|
||||
|
||||
租户管理员账号存储在 `auth.tenant_admins` 表(需新建):
|
||||
|
||||
```sql
|
||||
CREATE TABLE auth.tenant_admins (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
username VARCHAR(50) NOT NULL UNIQUE,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
display_name VARCHAR(100),
|
||||
tenant_id BIGINT NOT NULL, -- 所属租户
|
||||
managed_site_ids BIGINT[] NOT NULL, -- 管辖的 site_id 列表
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
created_by BIGINT, -- 创建该账号的 Operator ID
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
last_login_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE INDEX idx_tenant_admin_tenant ON auth.tenant_admins(tenant_id);
|
||||
```
|
||||
|
||||
### 5.2 与小程序用户体系的隔离
|
||||
|
||||
- 租户管理员使用 `auth.tenant_admins` 表,小程序用户使用 `auth.users` 表
|
||||
- JWT 签发时使用不同的 `aud`(audience)字段区分
|
||||
- 后端路由通过不同的认证依赖注入区分(`require_tenant_admin()` vs `require_approved()`)
|
||||
|
||||
---
|
||||
|
||||
## 六、参考文档
|
||||
|
||||
| 文档 | 路径 | 用途 |
|
||||
|------|------|------|
|
||||
| P10 原始 spec | `docs/prd/specs/P10-tenant-admin-web.md` | 需求定义基准 |
|
||||
| admin-web 现有代码 | `apps/admin-web/` | 技术栈参考(React + Vite + Ant Design) |
|
||||
| BD 手册-认证表 | `docs/database/BD_Manual_auth_tables.md` | auth schema 表结构 |
|
||||
| BD 手册-业务表 | `docs/database/BD_Manual_biz_tables.md` | biz schema 表结构 |
|
||||
| 权限矩阵 | `docs/permission_matrix/` | 角色-权限映射参考 |
|
||||
| DWD-DOC 标杆 | `docs/reports/DWD-DOC/` | 金额口径权威参考 |
|
||||
| 数据依赖矩阵 | `docs/prd/specs/00-数据依赖矩阵.md` | 租户管理后台数据源映射 |
|
||||
| member_retention_clue DDL | `db/zqyy_app/` | 维客线索表结构 |
|
||||
|
||||
---
|
||||
|
||||
## 七、预审查清单(SPEC 启动前确认)
|
||||
|
||||
### 7.1 账号与认证
|
||||
|
||||
1. **租户管理员账号模型**:是否需要独立的 `auth.tenant_admins` 表?还是复用 `auth.users` 表增加 `user_type` 字段区分?
|
||||
2. **密码策略**:初始密码是否需要强制修改?密码复杂度要求?是否需要密码过期机制?
|
||||
3. **多租户隔离**:一个管理员是否可以管辖多个租户?还是严格一对一?
|
||||
4. **会话管理**:JWT 过期时间?是否需要 refresh token?是否支持多设备同时登录?
|
||||
|
||||
### 7.2 用户审核
|
||||
|
||||
5. **关联匹配优先级**:助教表和员工信息表同时匹配到时,优先展示哪个?是否需要合并展示?
|
||||
6. **审核拒绝后**:用户是否可以重新申请?重新申请是新建记录还是更新原记录?
|
||||
7. **批量审核**:是否需要支持批量通过/拒绝?
|
||||
8. **审核通知**:审核结果是否需要通知用户(小程序消息/微信模板消息)?
|
||||
|
||||
### 7.3 Excel 上传
|
||||
|
||||
9. **写入策略**:财务支出/团购收入/充值归属是直接写入 DWS 表(方案 A)还是写入 staging 表由 ETL 同步(方案 B)?
|
||||
10. **文件大小限制**:单次上传的 Excel 文件大小上限?行数上限?
|
||||
11. **历史数据**:是否允许上传历史月份的数据?是否有时间范围限制?
|
||||
12. **模板版本**:Excel 模板是否需要版本管理?表头变更时如何兼容旧模板?
|
||||
13. **助教匹配失败处理**:模板 3/4 中助教姓名+编号匹配失败时,是阻断上传还是允许上传但标记警告?
|
||||
|
||||
### 7.4 维客线索
|
||||
|
||||
14. **隐藏 vs 删除**:隐藏的线索是否可以恢复显示?删除是否需要软删除(保留记录但标记删除)?
|
||||
15. **AI 线索保护**:AI 生成的线索(source=ai_consumption/ai_note)是否允许管理员修改/删除?修改后 source 是否变更为 manual?
|
||||
16. **线索审计**:线索的修改/删除操作是否需要记录操作日志(谁在什么时间做了什么操作)?
|
||||
17. **批量操作**:是否需要支持批量隐藏/删除线索?
|
||||
|
||||
### 7.5 部署与运维
|
||||
|
||||
18. **部署方式**:租户管理后台是独立部署还是与系统管理后台共享服务器?域名/路径如何规划?
|
||||
19. **前端构建**:是否与 admin-web 共享 pnpm workspace?还是完全独立的 package.json?
|
||||
20. **监控**:是否需要独立的访问日志和错误监控?
|
||||
|
||||
---
|
||||
|
||||
## 八、任务清单(草案,SPEC 细化后调整)
|
||||
|
||||
### Batch A:基础设施
|
||||
- [ ] T1:创建 `apps/tenant-admin/` 项目骨架(React + Vite + Ant Design)
|
||||
- [ ] T2:创建 `auth.tenant_admins` 表 + DDL 迁移脚本
|
||||
- [ ] T3:实现租户管理员登录 API(`tenant_auth.py`:登录/JWT 签发/鉴权中间件)
|
||||
- [ ] T4:创建 `biz.salary_adjustments` + `biz.excel_upload_log` 表 + DDL 迁移脚本
|
||||
|
||||
### Batch B:用户审核与管理
|
||||
- [ ] T5:实现用户审核后端 API(申请列表/关联建议/审核通过/审核拒绝)
|
||||
- [ ] T6:实现用户审核前端页面(申请列表 + 状态筛选 + 关联建议展示 + 审核操作)
|
||||
- [ ] T7:实现用户管理后端 API(用户列表/编辑/绑定修改)
|
||||
- [ ] T8:实现用户管理前端页面(用户列表 + 身份编辑 + 店铺归属)
|
||||
|
||||
### Batch C:Excel 上传
|
||||
- [ ] T9:实现 Excel 解析+校验后端(4 种模板的格式校验 + 人员匹配校验)
|
||||
- [ ] T10:实现冲突检测后端(主键匹配 + diff 数据生成)
|
||||
- [ ] T11:实现 Excel 上传前端(模板下载 + 上传 + 校验结果展示 + diff 交互 + 确认)
|
||||
- [ ] T12:创建 staging 表(如采用方案 B)+ 写入逻辑
|
||||
|
||||
### Batch D:维客线索管理
|
||||
- [ ] T13:`member_retention_clue` 新增 `is_hidden` 字段 + DDL 迁移
|
||||
- [ ] T14:实现维客线索后端 API(客户搜索/线索列表/修改/删除/隐藏)
|
||||
- [ ] T15:实现维客线索前端页面(客户搜索 + 线索列表 + 编辑/删除/隐藏操作)
|
||||
- [ ] T16:小程序端线索查询增加 `WHERE is_hidden = false` 条件
|
||||
300
docs/prd/Neo_Specs/RNS1-split-plan.md
Normal file
300
docs/prd/Neo_Specs/RNS1-split-plan.md
Normal file
@@ -0,0 +1,300 @@
|
||||
# RNS1 拆分计划 — NS1 小程序后端 API 补全
|
||||
|
||||
> 创建时间:2026-03-18
|
||||
> 来源:NS1 原始 spec + 两份 Storyboard 走查报告(助教视角 51 Gap + 管理层视角 31 Gap)
|
||||
> 拆分原则:每个子 spec 控制在 5-8 个任务、可独立交付验证、依赖关系清晰
|
||||
|
||||
---
|
||||
|
||||
## 拆分总览
|
||||
|
||||
| 子 Spec | 名称 | 任务数 | 依赖 | 交付物 |
|
||||
|---------|------|:------:|------|--------|
|
||||
| RNS1.0 | 基础设施与契约重写 | 6 | 无 | 全局中间件 + 重写后的 API 契约 + 前端跨页面参数修复 |
|
||||
| RNS1.1 | 任务与绩效接口 | 6 | RNS1.0 | TASK-1 扩展 + TASK-2 + PERF-1 + PERF-2 + 前端适配 |
|
||||
| RNS1.2 | 客户与助教接口 | 6 | RNS1.0 | CUST-1 + CUST-2 + COACH-1 + 前端适配 |
|
||||
| RNS1.3 | 三看板接口 | 7 | RNS1.0 | BOARD-1 + BOARD-2 + BOARD-3 + CONFIG-1 + 前端筛选修复 |
|
||||
| RNS1.4 | CHAT 对齐与联调收尾 | 5 | RNS1.1-1.3 | CHAT 迁移 + FDW 验证 + 前后端联调 |
|
||||
|
||||
总计:30 个任务,5 个子 spec。
|
||||
|
||||
---
|
||||
|
||||
## RNS1.0:基础设施与契约重写
|
||||
|
||||
> 阻塞所有后续子 spec,必须最先完成。
|
||||
> 预估:2-3 天
|
||||
|
||||
### 目标
|
||||
1. 后端全局响应包装 + camelCase 转换(R2 决策)
|
||||
2. API 契约按走查报告发现完全重写(BOARD-1/2/3、CUST-1、COACH-1、PERF-1、TASK-1 performance)
|
||||
3. 前端跨页面参数传递统一用唯一 ID
|
||||
|
||||
### 任务清单
|
||||
|
||||
| # | 任务 | 说明 | 产出 |
|
||||
|---|------|------|------|
|
||||
| T0-1 | 全局响应包装中间件 | `{ code: 0, data }` + 异常处理器 `{ code, message }` | `apps/backend/app/middleware/` |
|
||||
| T0-2 | Pydantic schema 统一 camelCase | `alias_generator=to_camel`,所有现有 + 新增 schema | `apps/backend/app/schemas/` |
|
||||
| T0-3 | 后端 TASK-4 路径 `/cancel-abandon` → `/restore` | 对齐契约(R1-3) | `xcx_tasks.py` |
|
||||
| T0-4 | 前端 `request()` 加 `.data` 解包 | 适配全局包装 | `api.ts` |
|
||||
| T0-5 | **API 契约完全重写** | 按走查报告重写全部接口响应定义(详见下方) | `API-contract.md` |
|
||||
| T0-6 | **前端跨页面参数修复** | 统一用唯一 ID 传参(详见下方) | 多个页面 `.ts` |
|
||||
|
||||
#### T0-5 契约重写范围
|
||||
|
||||
需要完全重写响应定义的接口:
|
||||
|
||||
| 接口 | 问题 | 重写要点 |
|
||||
|------|------|---------|
|
||||
| BOARD-3 | 扁平 metrics → 6 板块嵌套 | overview/recharge/revenue/cashflow/expense/coachAnalysis 完整字段定义,赠送卡 3×4 矩阵,收入结构表 9 行含子行,助教分析按等级分行,现金流出 4 子分组,环比格式(xxxCompare + isDown/isFlat) |
|
||||
| BOARD-1 | 10 通用字段 → 20 字段含 4 维度 | skills 改为 `Array<{text,cls}>`,补充 topCustomers,4 维度专属字段(perf/salary/sv/task),sort 参数改为枚举 |
|
||||
| BOARD-2 | 8 通用字段 → 40+ 维度专属字段 | 8 维度各自字段集,补充 assistants 关联助教列表,weeklyVisits 柱状图,coachDetails 明细表,potentialTags |
|
||||
| CUST-1 | 缺少 5 大模块 | 补充 balance/consumption60d/idealInterval/daysSinceVisit,aiInsight,coachTasks,favoriteCoaches,消费记录改为嵌套结构(含 coaches 子数组),备注列表 |
|
||||
| COACH-1 | 缺少 6 大模块 | 补充 performance(6 指标),income(本月/上月各 4 项),tierNodes,historyMonths,topCustomers 扩展(heartEmoji/score/balance),备注列表 |
|
||||
| PERF-1 | 扁平数组 → DateGroup | this_month_records 改为按日期分组结构,补充收入档位(currentTier/nextTier/upgradeHoursNeeded/upgradeBonus),lastMonthIncome,incomeItems.desc |
|
||||
| TASK-1 perf | 4 字段 → 15+ 字段 | 补充 tierNodes/basicHours/bonusHours/currentTier/nextTierHours/tierCompleted/bonusMoney/incomeTrend/incomeTrendDir/prevMonth |
|
||||
| CHAT-1/2 | 字段名不一致 + 缺失 | timestamp→created_at 统一,补充 title,referenceCard 结构,SSE 端点定义,customerId→chatId 映射 |
|
||||
|
||||
#### T0-6 前端跨页面参数修复范围
|
||||
|
||||
用户决策:统一用唯一 ID 传参。
|
||||
|
||||
| 来源页面 | 目标页面 | 当前问题 | 修复方案 |
|
||||
|---------|---------|---------|---------|
|
||||
| task-detail | chat | 传 `detail.id`(taskId)当 customerId | TASK-2 响应增加 `customer_id`,跳转改为 `?customerId={detail.customerId}` |
|
||||
| task-detail | customer-service-records | 同上 | 同上 |
|
||||
| customer-detail | customer-service-records | 未传 customerId | 跳转加 `?customerId={detail.id}` |
|
||||
| customer-detail | chat | 未传 customerId | 跳转加 `?customerId={detail.id}` |
|
||||
| coach-detail | customer-detail | 任务项用 name 跳转 | 改为 `?id={customerId}`(需 TaskItem 含 customerId) |
|
||||
| performance | task-detail | 传 customerName | 改为 `?id={taskId}`(需记录含 taskId) |
|
||||
| customer-detail | — | loadDetail() 用 `__route__` 解析 id | 改为从 `onLoad(options)` 获取 |
|
||||
| chat | API | 传 customerId 但 API 需 chatId | 后端支持 `?customerId=` 查询参数自动查找/创建对话 |
|
||||
|
||||
---
|
||||
|
||||
## RNS1.1:任务与绩效接口
|
||||
|
||||
> 助教日常使用频率最高的 4 个接口。
|
||||
> 依赖:RNS1.0(全局中间件 + 契约)
|
||||
> 预估:3-4 天
|
||||
|
||||
### 目标
|
||||
实现任务列表扩展、任务详情、绩效概览、绩效明细 4 个接口,覆盖助教视角的核心工作流。
|
||||
|
||||
### 任务清单
|
||||
|
||||
| # | 任务 | 说明 | 涉及 Gap |
|
||||
|---|------|------|---------|
|
||||
| T1-1 | 扩展 TASK-1(任务列表 + 绩效概览) | performance 从 4 字段扩展到 15+ 字段(tierNodes/basicHours/bonusHours/currentTier/nextTierHours/tierCompleted/bonusMoney/incomeTrend/incomeTrendDir/prevMonth);enrichTask 补充 lastVisitDays/balance/aiSuggestion | GAP-02, GAP-03 |
|
||||
| T1-2 | 实现 TASK-2(任务详情完整版) | 服务记录/备注各 20 条懒加载;响应含 `customer_id`;维客线索统一 tag 格式和 source 枚举;aiAnalysis 明确 cache_type 映射 | GAP-06~11 |
|
||||
| T1-3 | 实现 PERF-1(绩效概览) | this_month_records 按 DateGroup 分组;收入档位数据(currentTier/nextTier/upgradeHoursNeeded/upgradeBonus);lastMonthIncome;incomeItems 含 desc | GAP-12~16 |
|
||||
| T1-4 | 实现 PERF-2(绩效明细) | 20 条懒加载;按日期分组;courseTypeClass 枚举统一(`basic`/`vip`/`tip`,不带 `tag-` 前缀) | GAP-19~20 |
|
||||
| T1-5 | 实现 pin/unpin API 端点 | `POST /tasks/{id}/pin` + `POST /tasks/{id}/unpin`;前端 api.ts 补充对应函数 | GAP-04 |
|
||||
| T1-6 | 前端适配(任务+绩效页面) | createNote 补充 score 参数;performance 页添加月份切换(F8);performance-records 月份切换重置分页(F9);avatarChar/avatarColor 前端自行计算(GAP-19 决策) | GAP-05, F8, F9 |
|
||||
|
||||
### 数据源
|
||||
- `dws_assistant_salary_calc` via FDW → 绩效/档位/收入
|
||||
- `dwd_assistant_service_log` via FDW → 服务记录
|
||||
- `biz.coach_tasks` → 任务列表
|
||||
- `biz.ai_cache` → AI 分析/话术
|
||||
- `public.member_retention_clue` → 维客线索
|
||||
|
||||
### 验收标准
|
||||
- 助教登录后可看到真实任务列表 + 绩效卡片
|
||||
- 点击任务可查看完整详情(服务记录、维客线索、AI 分析、备注)
|
||||
- 绩效概览展示真实档位进度和收入明细
|
||||
- 绩效明细按日期分组展示,支持月份切换
|
||||
|
||||
---
|
||||
|
||||
## RNS1.2:客户与助教接口
|
||||
|
||||
> 详情页是 Gap 最集中的区域,数据结构最复杂。
|
||||
> 依赖:RNS1.0(全局中间件 + 契约)
|
||||
> 预估:3-4 天
|
||||
|
||||
### 目标
|
||||
实现客户详情、客户服务记录、助教详情 3 个接口,覆盖两个角色视角的详情查看需求。
|
||||
|
||||
### 任务清单
|
||||
|
||||
| # | 任务 | 说明 | 涉及 Gap |
|
||||
|---|------|------|---------|
|
||||
| T2-1 | 实现 CUST-1(客户详情) | 含 balance/consumption60d/idealInterval/daysSinceVisit(Banner);aiInsight(biz.ai_cache app4);维客线索;消费记录嵌套结构(含 coaches 子数组、tableFee/foodAmount 分项);备注列表 | GAP-23~30 |
|
||||
| T2-2 | 实现 CUST-1 coachTasks 模块 | 关联助教任务列表:从 biz.coach_tasks + FDW 聚合,每位助教含任务类型/状态/最后服务时间/近60天服务次数/总时长/次均时长 | GAP-25 |
|
||||
| T2-3 | 实现 CUST-1 favoriteCoaches 模块 | 最亲密助教:从 `v_dws_member_assistant_relation_index` 获取关系指数,聚合基础课时/激励课时/上课次数/充值金额 | GAP-26 |
|
||||
| T2-4 | 实现 CUST-2(客户服务记录) | 按月查询(改掉前端全量加载本地过滤);补充 recordType/isEstimate;月度统计汇总(monthCount/monthHours);customerPhoneFull;totalServiceCount | GAP-32~35 |
|
||||
| T2-5 | 实现 COACH-1(助教详情) | performance(6 指标);income(本月/上月各 4 项);tierNodes;topCustomers 扩展(heartEmoji/score/balance/consume);近期服务明细含 perfHours;任务分组(active/inactive/abandoned 含 notes 子数组和 reason);备注列表 | GAP-38~44 |
|
||||
| T2-6 | 实现 COACH-1 historyMonths 模块 | 历史月份统计(最近 5+ 个月):客户数/工时/工资/回访完成数/召回完成数/是否预估 | GAP-41 |
|
||||
|
||||
### 数据源
|
||||
- `fdw_etl.v_dim_member` → 客户基本信息(DQ-6:member_phone 不可靠,用 dim_member.mobile)
|
||||
- `fdw_etl.v_dws_member_consumption_summary` → 消费汇总/余额
|
||||
- `fdw_etl.v_dws_member_assistant_relation_index` → 关系指数
|
||||
- `fdw_etl.v_dwd_assistant_service_log` → 服务记录明细
|
||||
- `fdw_etl.v_dwd_table_fee_log` → 台费明细
|
||||
- `fdw_etl.v_dim_assistant` → 助教基本信息
|
||||
- `fdw_etl.v_dws_assistant_salary_calc` → 助教绩效/收入
|
||||
- `biz.coach_tasks` → 任务关联
|
||||
- `biz.ai_cache` → AI 洞察
|
||||
- `public.member_retention_clue` → 维客线索
|
||||
|
||||
### 关键约束
|
||||
- 金额口径:统一使用 `items_sum`,禁用 `consume_money`(DWD-DOC 强制规则 1)
|
||||
- 助教费用拆分:`assistant_pd_money`(陪打)+ `assistant_cx_money`(超休),禁用 `service_fee`(DWD-DOC 强制规则 2)
|
||||
- 会员信息:通过 `member_id` JOIN `dim_member`,禁用 `settlement_head.member_phone`(DQ-6)
|
||||
|
||||
### 验收标准
|
||||
- 客户详情页展示真实余额、消费、AI 洞察、关联助教、最亲密助教、消费记录(含助教明细)、备注
|
||||
- 客户服务记录支持按月切换,展示真实数据
|
||||
- 助教详情页展示真实绩效、收入明细、档位进度、TOP20 客户、历史月份统计、任务分组
|
||||
|
||||
---
|
||||
|
||||
## RNS1.3:三看板接口
|
||||
|
||||
> 看板是管理层视角的核心,BOARD-3 财务看板是全项目最复杂的单个接口。
|
||||
> 依赖:RNS1.0(全局中间件 + 契约)
|
||||
> 预估:4-5 天
|
||||
|
||||
### 目标
|
||||
实现 3 个看板接口 + 技能配置接口,修复前端看板筛选 Bug。
|
||||
|
||||
### 任务清单
|
||||
|
||||
| # | 任务 | 说明 | 涉及 Gap |
|
||||
|---|------|------|---------|
|
||||
| T3-1 | 实现 BOARD-1(助教看板) | 4 维度专属字段(perf/salary/sv/task);skills 为 `Array<{text,cls}>`;topCustomers;sort 枚举参数;time 参数日期范围计算 | G2, 八¾ B1 |
|
||||
| T3-2 | 实现 BOARD-2(客户看板) | 8 维度各自字段集;assistants 关联助教列表;weeklyVisits 柱状图(freq60);coachDetails 明细表(loyal);potentialTags(potential);20 条懒加载 | G3, 八¾ B2 |
|
||||
| T3-3 | 实现 BOARD-3 经营一览 + 预收资产 | overview(8 指标 + 8 环比);recharge(储值卡 5 指标 + 赠送卡 3×4 矩阵 = 24 字段 + 24 环比);area≠all 时预收资产隐藏 | G1, G6, G10 |
|
||||
| T3-4 | 实现 BOARD-3 应计收入 + 现金流 | revenue(收入结构表 9 行含子行 + 正价/优惠/渠道明细);cashflow(消费收款 + 充值收款 + 合计) | G7 |
|
||||
| T3-5 | 实现 BOARD-3 现金流出 + 助教分析 | expense(4 子分组:经营/固定/助教分成/平台服务费);coachAnalysis(基础课 + 激励课各按 4 等级分行) | G8, G9 |
|
||||
| T3-6 | 实现 CONFIG-1(技能类型列表) | 从 ETL cfg 表读取,前端 api.ts 硬编码作为 mock 回退 | R8-1 |
|
||||
| T3-7 | 前端看板筛选修复 | F1-F6(筛选变更触发重新请求);BOARD-2 补充分页参数;BOARD-3 fetchBoardFinance 签名扩展为 `{time,area,compare}` | F1~F6 |
|
||||
|
||||
### BOARD-3 环比计算说明
|
||||
- 所有环比为月环比(与上一个相同时间周期对比)
|
||||
- 后端根据 `time` 参数计算当期和上期日期范围,分别查询后计算百分比
|
||||
- 返回格式:每个值旁有 `xxxCompare: string`(如 `"+12.5%"`)+ `isDown: boolean` + `isFlat: boolean`
|
||||
- `compare=0` 时不计算环比,减少查询开销
|
||||
|
||||
### 数据源
|
||||
- BOARD-1:`v_dws_assistant_salary_calc` + `v_dws_assistant_monthly_summary` + `v_dim_assistant`
|
||||
- BOARD-2:8 个维度对应不同 FDW 表(详见八¾ B2 维度映射)
|
||||
- BOARD-3:6 个 `v_dws_finance_*` 表 + `v_dws_assistant_salary_calc`(助教分析)
|
||||
- CONFIG-1:ETL cfg 表
|
||||
|
||||
### 验收标准
|
||||
- 助教看板 4 维度切换正常,筛选变更触发重新请求
|
||||
- 客户看板 8 维度切换正常,懒加载 20 条
|
||||
- 财务看板 6 板块数据正确,环比开关工作,区域筛选隐藏预收资产
|
||||
- 技能类型从 cfg 表读取
|
||||
|
||||
---
|
||||
|
||||
## RNS1.4:CHAT 对齐与联调收尾
|
||||
|
||||
> 最后阶段,依赖前面所有子 spec 完成。
|
||||
> 预估:2-3 天
|
||||
|
||||
### 目标
|
||||
CHAT 模块路径迁移和功能补全,FDW 端到端验证,全量前后端联调。
|
||||
|
||||
### 任务清单
|
||||
|
||||
| # | 任务 | 说明 | 涉及 Gap |
|
||||
|---|------|------|---------|
|
||||
| T4-1 | CHAT 路径迁移 | `/api/ai/*` → `/api/xcx/chat/*`;保留 SSE 流式端点 | R3, GAP-51 |
|
||||
| T4-2 | 新增 CHAT-1/2 同步端点 | 历史列表(含 title 字段)+ 消息查看(timestamp→created_at 统一);支持 `?customerId=` 查询参数自动查找/创建对话 | GAP-45~50 |
|
||||
| T4-3 | CHAT referenceCard 支持 | 消息中附带结构化引用卡片(type/title/summary/data);多入口参数路由(customerId/historyId/coachId) | GAP-45, GAP-50 |
|
||||
| T4-4 | FDW 端到端验证 | 验证所有 FDW 查询在 test_zqyy_app → test_etl_feiqiu 链路上正常工作;检查索引和查询性能 | — |
|
||||
| T4-5 | 前后端联调 + 修复 | 全量联调 13 个页面;修复联调中发现的问题;notes 页补充触底加载(F11);customer-service-records 改为按月请求 API(F10) | F10, F11 |
|
||||
|
||||
### 验收标准
|
||||
- CHAT 模块 SSE 流式 + 同步端点均可用
|
||||
- 从任意入口进入 chat 页面均能正确关联上下文
|
||||
- 13 个页面全部连接真实后端运行,无 mock 数据残留
|
||||
|
||||
---
|
||||
|
||||
## 执行顺序与依赖关系
|
||||
|
||||
```
|
||||
RNS1.0(基础设施+契约)
|
||||
├── RNS1.1(任务+绩效)──┐
|
||||
├── RNS1.2(客户+助教)──┼── RNS1.4(CHAT+联调)
|
||||
└── RNS1.3(三看板)─────┘
|
||||
```
|
||||
|
||||
- RNS1.0 必须最先完成(阻塞所有后续)
|
||||
- RNS1.1 / RNS1.2 / RNS1.3 可并行(无相互依赖)
|
||||
- RNS1.4 依赖 RNS1.1-1.3 全部完成
|
||||
|
||||
---
|
||||
|
||||
## 与 NS1 原始 spec 的关系
|
||||
|
||||
NS1 原始 spec(`NS1-xcx-backend-api.md`)保留为历史参考文档,其中:
|
||||
- 一~七章:技术架构和接口设计(被 RNS1.0 T0-5 契约重写替代)
|
||||
- 八½:前置审查发现 R1-R8(决策仍有效,分散到各子 spec 执行)
|
||||
- 八¾:看板筛选交叉矩阵(仍为权威参考,RNS1.3 直接引用)
|
||||
- 九:原始任务清单(被本拆分计划替代)
|
||||
|
||||
两份走查报告保留在 `docs/reports/` 作为需求追溯依据:
|
||||
- `storyboard-walkthrough-assistant-view.md`(助教视角,51 Gap)
|
||||
- `miniprogram-storyboard-walkthrough-gaps.md`(管理层视角,31 Gap)
|
||||
|
||||
---
|
||||
|
||||
## Gap 追溯矩阵
|
||||
|
||||
### 助教视角报告 Gap → 子 Spec 映射
|
||||
|
||||
| Gap | 描述 | 子 Spec |
|
||||
|-----|------|---------|
|
||||
| GAP-01 | globalData.authUser 缺少 role/store_name/coach_level/avatar | RNS1.0 T0-6(前端修复,/me 已返回这些字段) |
|
||||
| GAP-02 | TASK-1 performance 字段不足(4→15+) | RNS1.1 T1-1 |
|
||||
| GAP-03 | enrichTask 需要 lastVisitDays/balance/aiSuggestion | RNS1.1 T1-1 |
|
||||
| GAP-04 | pin/unpin API 端点未定义 | RNS1.1 T1-5 |
|
||||
| GAP-05 | createNote 缺少 score 参数 | RNS1.1 T1-6 |
|
||||
| GAP-06~08 | 维客线索 tag/source 格式 + aiAnalysis cache_type | RNS1.1 T1-2 |
|
||||
| GAP-09~10 | taskId/customerId 混淆 | RNS1.0 T0-6 |
|
||||
| GAP-11 | storageLevel/relationLevel 计算逻辑 | RNS1.1 T1-6(前端自行计算) |
|
||||
| GAP-12~16 | PERF-1 DateGroup/档位/lastMonthIncome/incomeItems | RNS1.1 T1-3 |
|
||||
| GAP-17 | performance→task-detail 传参不匹配 | RNS1.0 T0-6 |
|
||||
| GAP-18 | performance 无月份切换 | RNS1.1 T1-6(F8) |
|
||||
| GAP-19~22 | PERF-2 avatarChar/courseTypeClass/分页重置/Banner | RNS1.1 T1-4 + T1-6 |
|
||||
| GAP-23~30 | CUST-1 五大缺失模块 | RNS1.2 T2-1~T2-3 |
|
||||
| GAP-31 | customer-detail 跳转未传 customerId | RNS1.0 T0-6 |
|
||||
| GAP-32~35 | CUST-2 扩展字段 + 按月查询 | RNS1.2 T2-4 |
|
||||
| GAP-36~37 | notes 触底加载 + tagType 语义 | RNS1.4 T4-5 |
|
||||
| GAP-38~44 | COACH-1 六大缺失模块 | RNS1.2 T2-5~T2-6 |
|
||||
| GAP-45~51 | CHAT 模块全部 Gap | RNS1.4 T4-1~T4-3 |
|
||||
|
||||
### 管理层视角报告 Gap → 子 Spec 映射
|
||||
|
||||
| Gap | 描述 | 子 Spec |
|
||||
|-----|------|---------|
|
||||
| G1 | BOARD-3 响应结构完全不匹配 | RNS1.3 T3-3~T3-5 |
|
||||
| G2 | BOARD-1 字段严重不足 | RNS1.3 T3-1 |
|
||||
| G3 | BOARD-2 字段严重不足 | RNS1.3 T3-2 |
|
||||
| G4 | COACH-1 缺少多个核心区域 | RNS1.2 T2-5~T2-6 |
|
||||
| G5 | CUST-1 缺少管理层核心数据 | RNS1.2 T2-1~T2-3 |
|
||||
| G6~G10 | BOARD-3 各子模块缺失 + 环比格式 | RNS1.3 T3-3~T3-5 |
|
||||
| G11 | customer-detail→customer-service-records 未传 customerId | RNS1.0 T0-6 |
|
||||
| G12 | coach-detail 任务项用 name 跳转 | RNS1.0 T0-6 |
|
||||
| G13~G14 | api.ts 返回类型不匹配 | RNS1.0 T0-5(契约重写时同步更新类型) |
|
||||
| G15~G26 | 各接口中等严重度缺陷 | 分散到对应子 Spec |
|
||||
| G27~G31 | 低严重度设计决策 | 分散到对应子 Spec |
|
||||
|
||||
### 前端修复项 → 子 Spec 映射
|
||||
|
||||
| 修复项 | 描述 | 子 Spec |
|
||||
|--------|------|---------|
|
||||
| F1~F6 | 看板筛选变更不触发重新请求 | RNS1.3 T3-7 |
|
||||
| F7 | task-list status 筛选 UI 未实现 | RNS1.1 T1-6(低优先级,可后续) |
|
||||
| F8 | performance 无月份切换 | RNS1.1 T1-6 |
|
||||
| F9 | performance-records 月份切换未重置分页 | RNS1.1 T1-6 |
|
||||
| F10 | customer-service-records 本地筛选改 API 请求 | RNS1.4 T4-5 |
|
||||
| F11 | notes 无触底加载 | RNS1.4 T4-5 |
|
||||
813
docs/prd/Neo_Specs/miniprogram-storyboard-walkthrough-gaps.md
Normal file
813
docs/prd/Neo_Specs/miniprogram-storyboard-walkthrough-gaps.md
Normal file
@@ -0,0 +1,813 @@
|
||||
# 小程序管理层视角 Storyboard 走查报告 — API 需求 Gap 分析
|
||||
|
||||
> 走查日期:2026-03-18
|
||||
> 走查角色:管理层/店长
|
||||
> 走查路线:3 个看板 → 助教详情 → 客户详情 → 服务记录
|
||||
> 对比基准:前端内联 mock 数据 vs API 契约 (`API-contract.md`) vs 八¾节 (`NS1-xcx-backend-api.md`)
|
||||
|
||||
---
|
||||
|
||||
## 场景 1:助教看板 → 助教详情 → 客户详情
|
||||
|
||||
### 1.1 board-coach(`pages/board-coach/board-coach.ts`)
|
||||
|
||||
#### API 调用
|
||||
- `fetchBoardCoaches({ skill, sort, time })` → `CoachItem[]`
|
||||
- 页面 `onLoad()` → `loadData()` → `fetchBoardCoaches()`
|
||||
- 下拉刷新 `onPullDownRefresh()` → `loadData()`
|
||||
- ⚠️ 筛选变更(`onSortChange`/`onSkillChange`/`onTimeChange`)仅更新 data 状态,**不调用 `loadData()`**(八¾节 F1 已记录)
|
||||
|
||||
#### 前端期望字段(从内联 MOCK_COACHES 的 CoachItem 接口提取,共 18 个字段)
|
||||
|
||||
| 字段 | 类型 | 说明 | 数据源 |
|
||||
|------|------|------|--------|
|
||||
| `id` | string | 助教 ID | dim_assistant |
|
||||
| `name` | string | 助教姓名 | dim_assistant |
|
||||
| `initial` | string | 姓名首字(头像占位) | 前端可计算 |
|
||||
| `avatarGradient` | string | 头像渐变色 (blue/green/pink/amber/violet/cyan) | 前端可计算或后端分配 |
|
||||
| `level` | string | 等级 key: star/senior/middle/junior | dim_assistant |
|
||||
| `levelClass` | string | 等级样式类 | 前端可映射(LEVEL_CLASS 常量) |
|
||||
| `skills` | `Array<{text: string, cls: string}>` | 技能列表含 emoji 和样式类 | cfg 表 |
|
||||
| `topCustomers` | `string[]` | Top 客户列表含 emoji 前缀(如 `'💖 王先生'`) | dws_member_assistant_relation_index |
|
||||
| `perfHours` | number | 当期定档工时 | dws_assistant_salary_calc |
|
||||
| `perfHoursBefore` | number? | 上期定档工时(可选) | dws_assistant_salary_calc (上期) |
|
||||
| `perfGap` | string? | 距升档差距描述(如 `"距升档 13.8h"`,已达标时不返回) | 后端计算 |
|
||||
| `perfReached` | boolean | 是否已达标 | 后端计算 |
|
||||
| `salary` | number | 工资总额(元) | dws_assistant_salary_calc |
|
||||
| `salaryPerfHours` | number | 定档工时 | 同 perfHours |
|
||||
| `salaryPerfBefore` | number? | 上期定档工时 | 同 perfHoursBefore |
|
||||
| `svAmount` | number | 客源储值总额(元) | dws_member_consumption_summary |
|
||||
| `svCustomerCount` | number | 储值客户数 | dws_member_consumption_summary |
|
||||
| `svConsume` | number | 储值消耗额(元) | dws_member_consumption_summary |
|
||||
| `taskRecall` | number | 召回任务完成数 | biz.coach_tasks |
|
||||
| `taskCallback` | number | 回访任务完成数 | biz.coach_tasks |
|
||||
|
||||
#### 筛选参数映射
|
||||
|
||||
| 筛选项 | 前端参数 | 后端参数 | 查询影响 |
|
||||
|--------|---------|---------|---------|
|
||||
| 排序维度 | `sort`: perf_desc/perf_asc/salary_desc/salary_asc/sv_desc/task_desc | `sort` | 决定排序字段和方向 + 卡片模板(dimType) |
|
||||
| 技能筛选 | `skill`: all/chinese/snooker/mahjong/karaoke | `skill` | WHERE 过滤助教技能类型 |
|
||||
| 时间范围 | `time`: month/quarter/last_month/last_3m/last_quarter/last_6m | `time` | 决定统计日期区间 |
|
||||
|
||||
交叉约束:`time=last_6m` + `sort=sv_desc` 不兼容(TIME_OPTIONS 注释标注)
|
||||
|
||||
#### 页面跳转
|
||||
- 点击助教卡片 `onCoachTap` → `/pages/coach-detail/coach-detail?id={coachId}`
|
||||
- Tab 切换 → `board-finance`(switchTab)/ `board-customer`(navigateTo)
|
||||
|
||||
#### API 契约 vs Mock 数据 vs 八¾节 差异
|
||||
|
||||
| 差异项 | API 契约 (BOARD-1) | 前端 Mock (CoachItem) | 八¾节 (B1) | 严重度 |
|
||||
|--------|--------------------|-----------------------|------------|--------|
|
||||
| 字段结构 | 扁平简单:`id, name, avatar, level, level_class, skills(string[]), total_hours, total_income, customer_count, satisfaction` | 丰富结构:4 维度 20 个字段,skills 为对象数组,含 topCustomers | 与 Mock 一致,详细列出 4 维度字段 | 🔴 高 |
|
||||
| `initial` 字段 | ❌ 无 | ✅ 有 | ✅ 有 | 🟠 中 |
|
||||
| `avatarGradient` 字段 | ❌ 无(仅 `avatar` URL) | ✅ 有 | ✅ 有 | 🟠 中 |
|
||||
| `skills` 类型 | `string[]` | `Array<{text, cls}>` | `Array<{text, cls}>` | 🔴 高 |
|
||||
| `topCustomers` 字段 | ❌ 无 | ✅ 有 | ✅ 有 | 🔴 高 |
|
||||
| 4 维度专属字段 | ❌ 全部缺失(仅 total_hours/total_income/customer_count/satisfaction) | ✅ 完整(perf/salary/sv/task 各 2-4 个字段) | ✅ 完整 | 🔴 高 |
|
||||
| `sort` 参数格式 | `sort=field:direction` | `sort=perf_desc` 等枚举 | `sort=perf_desc` 等枚举 | 🟠 中 |
|
||||
| 分页 | 无分页定义 | 无分页(全量返回) | 无分页 | 🟡 低 |
|
||||
|
||||
#### spec 未记录的 Gap
|
||||
1. 🔴 API 契约 BOARD-1 响应字段与前端实际需求严重不匹配 — 契约仅定义 10 个通用字段,前端需要 20 个含 4 维度专属字段
|
||||
2. 🔴 `skills` 类型不一致 — 契约为 `string[]`,前端需要 `Array<{text, cls}>`(含 emoji 和样式类)
|
||||
3. 🔴 `topCustomers` 字段契约完全缺失 — 前端每个助教卡片展示 Top 3 客户
|
||||
4. 🟠 `sort` 参数格式不一致 — 契约用 `field:direction`,前端用枚举值 `perf_desc`
|
||||
5. 🟠 `initial`/`avatarGradient` 字段契约缺失 — 前端用于头像渲染,需确认由前端计算还是后端返回
|
||||
|
||||
---
|
||||
|
||||
### 1.2 coach-detail(`pages/coach-detail/coach-detail.ts`)
|
||||
|
||||
#### API 调用
|
||||
- `fetchCoachDetail(coachId)` → `CoachCard | null`(api.ts 返回类型过于简单)
|
||||
- 页面 `onLoad({ id })` → `loadData(id)` → `fetchCoachDetail(id)`
|
||||
- 实际使用内联 mock 数据组装 CoachDetail 对象
|
||||
|
||||
#### 前端期望字段(从 mockCoachDetail + 各 mock 数组提取)
|
||||
|
||||
**CoachDetail 基础信息:**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `id` | string | 助教 ID |
|
||||
| `name` | string | 助教姓名 |
|
||||
| `avatar` | string | 头像 URL |
|
||||
| `level` | string | 等级(中文:星级/高级/中级/初级) |
|
||||
| `skills` | string[] | 技能列表 |
|
||||
| `workYears` | number | 工龄 |
|
||||
| `customerCount` | number | 客户数 |
|
||||
| `hireDate` | string | 入职日期 |
|
||||
|
||||
**performance 绩效指标:**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `performance.monthlyHours` | number | 本月定档业绩工时 |
|
||||
| `performance.monthlySalary` | number | 本月工资(预估) |
|
||||
| `performance.customerBalance` | number | 客源储值余额 |
|
||||
| `performance.tasksCompleted` | number | 本月任务完成数 |
|
||||
| `performance.perfCurrent` | number | 当前绩效值 |
|
||||
| `performance.perfTarget` | number | 目标绩效值 |
|
||||
|
||||
**income 收入明细(本月/上月 Tab 切换):**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `income.thisMonth` | `IncomeItem[]` | 本月收入明细(基础课时费/激励课时费/充值提成/酒水提成) |
|
||||
| `income.lastMonth` | `IncomeItem[]` | 上月收入明细 |
|
||||
| IncomeItem: `label` | string | 收入项名称 |
|
||||
| IncomeItem: `amount` | string | 金额(格式化,如 `"¥3,500"`) |
|
||||
| IncomeItem: `color` | string | 颜色标识 |
|
||||
|
||||
**任务执行区域(管理层视角核心):**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `visibleTasks` | `TaskItem[]` | 可见任务(active 状态) |
|
||||
| `hiddenTasks` | `TaskItem[]` | 隐藏任务(inactive 状态,被顶替但未过期) |
|
||||
| `abandonedTasks` | `AbandonedTask[]` | 已放弃任务 |
|
||||
| TaskItem: `typeLabel` | string | 任务类型标签(高优先召回/优先召回/关系构建/客户回访) |
|
||||
| TaskItem: `typeClass` | string | 样式类(high-priority/priority/relationship/callback) |
|
||||
| TaskItem: `customerName` | string | 客户姓名 |
|
||||
| TaskItem: `noteCount` | number | 备注数量 |
|
||||
| TaskItem: `pinned` | boolean | 是否置顶 |
|
||||
| TaskItem: `notes` | `Array<{pinned?, text, date}>` | 备注列表(可选) |
|
||||
| AbandonedTask: `customerName` | string | 客户姓名 |
|
||||
| AbandonedTask: `reason` | string | 放弃原因 |
|
||||
|
||||
**客户关系 TOP20(管理层关注指标):**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| TopCustomer: `id` | string | 客户 ID |
|
||||
| TopCustomer: `name` | string | 客户姓名 |
|
||||
| TopCustomer: `initial` | string | 姓名首字 |
|
||||
| TopCustomer: `avatarGradient` | string | 头像渐变色 |
|
||||
| TopCustomer: `heartEmoji` | string | 爱心 emoji(❤️/💛/🤍) |
|
||||
| TopCustomer: `score` | string | 关系指数 |
|
||||
| TopCustomer: `scoreColor` | string | 分数颜色(success/warning/gray) |
|
||||
| TopCustomer: `serviceCount` | number | 服务次数 |
|
||||
| TopCustomer: `balance` | string | 余额(格式化) |
|
||||
| TopCustomer: `consume` | string | 消费额(格式化) |
|
||||
|
||||
**近期服务明细:**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| ServiceRecord: `customerId` | string? | 客户 ID |
|
||||
| ServiceRecord: `customerName` | string | 客户姓名 |
|
||||
| ServiceRecord: `initial` | string | 姓名首字 |
|
||||
| ServiceRecord: `avatarGradient` | string | 头像渐变色 |
|
||||
| ServiceRecord: `type` | string | 课程类型(基础课/激励课) |
|
||||
| ServiceRecord: `typeClass` | string | 样式类(basic/incentive) |
|
||||
| ServiceRecord: `table` | string | 台桌号 |
|
||||
| ServiceRecord: `duration` | string | 时长(格式化) |
|
||||
| ServiceRecord: `income` | string | 收入(格式化) |
|
||||
| ServiceRecord: `date` | string | 日期时间 |
|
||||
| ServiceRecord: `perfHours` | string? | 定档工时(可选) |
|
||||
|
||||
**历史月份统计(管理层评估助教表现):**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| HistoryMonth: `month` | string | 月份标签(本月/上月/4月...) |
|
||||
| HistoryMonth: `estimated` | boolean | 是否预估 |
|
||||
| HistoryMonth: `customers` | string | 客户数(格式化,如 `"22人"`) |
|
||||
| HistoryMonth: `hours` | string | 工时(格式化,如 `"87.5h"`) |
|
||||
| HistoryMonth: `salary` | string | 工资(格式化) |
|
||||
| HistoryMonth: `callbackDone` | number | 回访完成数 |
|
||||
| HistoryMonth: `recallDone` | number | 召回完成数 |
|
||||
|
||||
**备注列表:**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| NoteItem: `id` | string | 备注 ID |
|
||||
| NoteItem: `content` | string | 备注内容 |
|
||||
| NoteItem: `timestamp` | string | 时间戳 |
|
||||
| NoteItem: `score` | number | 评分 |
|
||||
| NoteItem: `customerName` | string | 关联客户/管理员名 |
|
||||
| NoteItem: `tagLabel` | string | 标签 |
|
||||
| NoteItem: `createdAt` | string | 创建时间(格式化) |
|
||||
|
||||
**绩效进度条数据(前端需要后端提供):**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `tierNodes` | number[] | 档位节点数组(如 `[0, 100, 130, 160, 190, 220]`) |
|
||||
| `maxHours` | number | 最大工时(进度条满值) |
|
||||
|
||||
#### 筛选参数映射
|
||||
- 无筛选项,单次加载全部数据
|
||||
- 收入明细有本月/上月 Tab 切换(纯前端切换,数据一次性返回)
|
||||
|
||||
#### 页面跳转
|
||||
- 点击任务项 `onTaskItemTap` → `/pages/customer-detail/customer-detail?name={customerName}`(⚠️ 用 name 而非 id)
|
||||
- 点击客户卡片 `onCustomerTap` → `/pages/customer-detail/customer-detail?id={customerId}`
|
||||
- 点击服务记录 `onSvcCardTap` → `/pages/customer-detail/customer-detail?id={customerId}`
|
||||
- 查看更多服务记录 `onViewMoreRecords` → `/pages/performance-records/performance-records?coachId={coachId}`
|
||||
- 问问助手 `onStartChat` → `/pages/chat/chat?coachId={coachId}`
|
||||
|
||||
#### API 契约 vs Mock 数据 vs 八¾节 差异
|
||||
|
||||
| 差异项 | API 契约 (COACH-1) | 前端 Mock | 八¾节 | 严重度 |
|
||||
|--------|--------------------|-----------|----|--------|
|
||||
| 返回类型 | api.ts 返回 `CoachCard`(仅 id/name/avatar/level/keyMetrics) | 实际需要 CoachDetail(含 performance/income/tasks/topCustomers/serviceRecords/historyMonths/notes) | 仅提到基本信息+任务分组+Top客户+服务记录 | 🔴 高 |
|
||||
| `income` 本月/上月明细 | ❌ 无 | ✅ 有(4 项收入分类 × 2 月) | ❌ 无 | 🔴 高 |
|
||||
| `topCustomers` TOP20 | 契约仅 `top_customers: Array<{id, name, avatar, total_spend, visit_count}>` | Mock 含 20 条,每条含 heartEmoji/score/scoreColor/serviceCount/balance/consume | ❌ 未详细定义字段 | 🔴 高 |
|
||||
| `historyMonths` 历史月份 | ❌ 完全缺失 | ✅ 有(5 个月,含 estimated/customers/hours/salary/callbackDone/recallDone) | ❌ 完全缺失 | 🔴 高 |
|
||||
| `tierNodes` 档位节点 | ❌ 缺失 | ✅ 有(进度条组件需要) | ❌ 缺失 | 🟠 中 |
|
||||
| 任务分组 | `visible_tasks`/`hidden_tasks`/`abandoned_tasks` | 同,但 TaskItem 结构含 notes 子数组 | ✅ 已确认 active/inactive/abandoned 映射 | 🟡 低 |
|
||||
| 备注列表 | ❌ 未在 COACH-1 定义 | ✅ 有(含 score 字段) | ❌ 未提及 | 🟠 中 |
|
||||
|
||||
#### spec 未记录的 Gap
|
||||
1. 🔴 COACH-1 响应缺少 `income` 收入明细(本月/上月 4 项分类)— 管理层评估助教收入结构的核心数据
|
||||
2. 🔴 COACH-1 响应缺少 `historyMonths` 历史月份统计 — 管理层评估助教长期表现趋势
|
||||
3. 🔴 `topCustomers` 字段结构不完整 — 契约仅 5 个字段,前端需要 10 个字段(含 heartEmoji/scoreColor/balance/consume)
|
||||
4. 🟠 COACH-1 响应缺少 `tierNodes`/`maxHours` — 绩效进度条组件需要档位节点数据
|
||||
5. 🟠 COACH-1 响应缺少备注列表 — 管理层需要查看助教相关备注
|
||||
6. 🟠 `onTaskItemTap` 用 `name` 而非 `id` 跳转客户详情 — 前端 Bug,name 不唯一
|
||||
7. 🟡 api.ts 中 `fetchCoachDetail` 返回类型为 `CoachCard`(mock-data.ts 中的简单类型),与实际需要的 CoachDetail 不匹配
|
||||
|
||||
---
|
||||
|
||||
## 场景 2:客户看板 → 客户详情 → 客户服务记录
|
||||
|
||||
### 2.1 board-customer(`pages/board-customer/board-customer.ts`)
|
||||
|
||||
#### API 调用
|
||||
- `fetchBoardCustomers({ dimension, project })` → `CustomerItem[]`
|
||||
- 页面 `onLoad()` → `loadData()` → `fetchBoardCustomers()`
|
||||
- 下拉刷新 `onPullDownRefresh()` → `loadData()`
|
||||
- ⚠️ 筛选变更(`onDimensionChange`/`onProjectChange`)仅更新 data 状态,**不调用 `loadData()`**(八¾节 F2 已记录)
|
||||
- ⚠️ 缺少分页参数 `page`/`pageSize`(八¾节 F3 已记录)
|
||||
|
||||
#### 前端期望字段(从内联 MOCK_CUSTOMERS 的 CustomerItem 接口提取)
|
||||
|
||||
**所有维度共享基础字段(每个 item 都返回):**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `id` | string | 客户 member_id |
|
||||
| `name` | string | 客户姓名 |
|
||||
| `initial` | string | 姓名首字 |
|
||||
| `avatarCls` | string | 头像样式类(avatar--amber/pink/blue 等) |
|
||||
| `assistants` | `AssistantInfo[]` | 关联助教列表 |
|
||||
|
||||
**AssistantInfo 结构:**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `name` | string | 助教姓名 |
|
||||
| `cls` | string | 样式类(assistant--assignee/abandoned/normal) |
|
||||
| `heartScore` | number | 爱心分 0-10 |
|
||||
| `badge` | string? | 标记(跟/弃) |
|
||||
| `badgeCls` | string? | 标记样式类 |
|
||||
|
||||
**8 维度专属字段(按 dimension 参数返回对应字段):**
|
||||
|
||||
| 维度 | 专属字段 | 类型 | 说明 |
|
||||
|------|---------|------|------|
|
||||
| recall | `idealDays` | number | 理想到店间隔(天) |
|
||||
| | `elapsedDays` | number | 已过天数 |
|
||||
| | `overdueDays` | number | 超期天数 |
|
||||
| | `visits30d` | number | 近30天到店次数 |
|
||||
| | `balance` | string | 余额(格式化) |
|
||||
| | `recallIndex` | string | 召回指数 |
|
||||
| potential | `potentialTags` | `Array<{text, theme}>` | 潜力标签 |
|
||||
| | `spend30d` | string | 近30天消费 |
|
||||
| | `avgVisits` | string | 月均到店 |
|
||||
| | `avgSpend` | string | 次均消费 |
|
||||
| balance | `balance` | string | 当前余额 |
|
||||
| | `lastVisit` | string | 最近到店 |
|
||||
| | `monthlyConsume` | string | 月均消耗 |
|
||||
| | `availableMonths` | string | 可用月数 |
|
||||
| recharge | `lastRecharge` | string | 最后充值日期 |
|
||||
| | `rechargeAmount` | string | 充值金额 |
|
||||
| | `recharges60d` | string | 近60天充值次数 |
|
||||
| | `currentBalance` | string | 当前余额 |
|
||||
| recent | `daysAgo` | number | 距今天数 |
|
||||
| | `visitFreq` | string | 到店频率 |
|
||||
| | `idealDays` | number | 理想间隔 |
|
||||
| | `visits30d` | number | 近30天到店 |
|
||||
| | `avgSpend` | string | 次均消费 |
|
||||
| spend60 | `spend60d` | string | 近60天消费总额 |
|
||||
| | `visits60d` | string | 近60天到店次数 |
|
||||
| | `highSpendTag` | boolean | 是否高消费标签 |
|
||||
| | `avgSpend` | string | 次均消费 |
|
||||
| freq60 | `visits60d` | string | 近60天到店次数 |
|
||||
| | `avgInterval` | string | 平均到店间隔 |
|
||||
| | `weeklyVisits` | `Array<{val, pct}>` | 8周到店柱状图 |
|
||||
| | `spend60d` | string | 近60天消费 |
|
||||
| loyal | `intimacy` | string | 亲密度指数 |
|
||||
| | `topCoachName` | string | 最亲密助教姓名 |
|
||||
| | `topCoachHeart` | number | 最亲密助教爱心分 |
|
||||
| | `topCoachScore` | string | 最亲密助教关系指数 |
|
||||
| | `coachName` | string | 主助教姓名 |
|
||||
| | `coachRatio` | string | 主助教占比 |
|
||||
| | `coachDetails` | `Array<{name, cls, heartScore, badge?, avgDuration, serviceCount, coachSpend, relationIdx}>` | 助教服务明细表 |
|
||||
|
||||
#### 筛选参数映射
|
||||
|
||||
| 筛选项 | 前端参数 | 后端参数 | 查询影响 |
|
||||
|--------|---------|---------|---------|
|
||||
| 维度切换 | `dimension`: recall/potential/balance/recharge/recent/spend60/freq60/loyal | `dimension` | 决定查询的 FDW 表和排序字段 |
|
||||
| 项目筛选 | `project`: all/chinese/snooker/mahjong/karaoke | `project` | WHERE 过滤客户偏好项目 |
|
||||
|
||||
无交叉约束,8×5=40 种有效组合。
|
||||
|
||||
#### 页面跳转
|
||||
- 点击客户卡片 `onCustomerTap` → `/pages/customer-detail/customer-detail?id={customerId}`
|
||||
- Tab 切换 → `board-finance`(switchTab)/ `board-coach`(navigateTo)
|
||||
|
||||
#### API 契约 vs Mock 数据 vs 八¾节 差异
|
||||
|
||||
| 差异项 | API 契约 (BOARD-2) | 前端 Mock (CustomerItem) | 八¾节 (B2) | 严重度 |
|
||||
|--------|--------------------|-----------------------|------------|--------|
|
||||
| 字段结构 | 扁平简单:`id, name, avatar, tags(string[]), total_spend, visit_count, last_visit, relation_index` | 丰富结构:8 维度 40+ 专属字段 + assistants 对象数组 | 与 Mock 一致 | 🔴 高 |
|
||||
| `assistants` 字段 | ❌ 无 | ✅ 有(含 name/cls/heartScore/badge/badgeCls) | ✅ 有 | 🔴 高 |
|
||||
| `avatarCls` 字段 | ❌ 无(仅 `avatar` URL) | ✅ 有 | ✅ 有 | 🟠 中 |
|
||||
| `initial` 字段 | ❌ 无 | ✅ 有 | ✅ 有 | 🟠 中 |
|
||||
| 8 维度专属字段 | ❌ 全部缺失 | ✅ 完整 | ✅ 完整 | 🔴 高 |
|
||||
| `weeklyVisits` 柱状图 | ❌ 无 | ✅ 有(freq60 维度,8 周数据) | ✅ 有 | 🔴 高 |
|
||||
| `coachDetails` 明细表 | ❌ 无 | ✅ 有(loyal 维度,助教服务明细) | ✅ 有 | 🔴 高 |
|
||||
| `potentialTags` 标签 | ❌ 无 | ✅ 有(potential 维度,含 text+theme) | ✅ 有 | 🟠 中 |
|
||||
| 分页 | ✅ 有(page/pageSize) | ❌ 无(全量返回 3 条 mock) | ✅ 已确认 20 条懒加载 | 🟡 低 |
|
||||
|
||||
#### spec 未记录的 Gap
|
||||
1. 🔴 API 契约 BOARD-2 响应字段与前端实际需求严重不匹配 — 契约仅 8 个通用字段,前端需要 40+ 维度专属字段
|
||||
2. 🔴 `assistants` 关联助教列表契约完全缺失 — 每个客户卡片展示关联助教及其爱心分/跟弃状态
|
||||
3. 🔴 `weeklyVisits` 8 周柱状图数据契约缺失 — freq60 维度的核心可视化数据
|
||||
4. 🔴 `coachDetails` 助教服务明细表契约缺失 — loyal 维度展示每个助教的详细服务数据
|
||||
5. 🟠 `potentialTags` 标签结构契约缺失 — potential 维度展示客户潜力标签
|
||||
|
||||
---
|
||||
|
||||
### 2.2 customer-detail(`pages/customer-detail/customer-detail.ts`)
|
||||
|
||||
#### API 调用
|
||||
- `fetchCustomerDetail(customerId)` → `MockCustomerDetail | null`
|
||||
- 页面 `onLoad()` → `loadDetail()` → `fetchCustomerDetail(id)`
|
||||
- ⚠️ `loadDetail()` 中 id 获取方式有 Bug:`(this as any).__route__?.split('?')[1]` 无法正确解析 query 参数
|
||||
|
||||
#### 前端期望字段(从内联 data 提取,管理层视角)
|
||||
|
||||
**客户基础信息:**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `detail.id` | string | 客户 ID |
|
||||
| `detail.name` | string | 客户姓名 |
|
||||
| `detail.avatarChar` | string | 头像字符 |
|
||||
| `detail.phone` | string | 完整手机号(前端控制脱敏显示) |
|
||||
| `detail.balance` | string | 余额(格式化) |
|
||||
| `detail.consumption60d` | string | 近60天消费(格式化) |
|
||||
| `detail.idealInterval` | string | 理想到店间隔 |
|
||||
| `detail.daysSinceVisit` | string | 距上次到店天数 |
|
||||
|
||||
**AI 洞察(管理层视角特有):**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `aiInsight.summary` | string | AI 分析摘要 |
|
||||
| `aiInsight.strategies` | `Array<{color, text}>` | 策略建议列表 |
|
||||
|
||||
**维客线索(clues):**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `clues[].category` | string | 线索大类(含换行符格式化) |
|
||||
| `clues[].categoryColor` | string | 分类颜色 |
|
||||
| `clues[].text` | string | 线索文本(含 emoji) |
|
||||
| `clues[].source` | string | 来源(系统/助教名) |
|
||||
| `clues[].detail` | string? | 详情(可选,展开显示) |
|
||||
|
||||
**关联助教 coachTasks(管理层关注多助教对同一客户的服务):**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `coachTasks[].name` | string | 助教姓名 |
|
||||
| `coachTasks[].level` | string | 等级 |
|
||||
| `coachTasks[].levelColor` | string | 等级颜色 |
|
||||
| `coachTasks[].taskType` | string | 任务类型标签 |
|
||||
| `coachTasks[].taskColor` | string | 任务颜色 |
|
||||
| `coachTasks[].bgClass` | string | 背景样式类 |
|
||||
| `coachTasks[].status` | string | 状态(normal/pinned/abandoned) |
|
||||
| `coachTasks[].lastService` | string | 最后服务时间 |
|
||||
| `coachTasks[].metrics` | `Array<{label, value, color?}>` | 指标列表(近60天次数/总时长/次均时长) |
|
||||
|
||||
**喜好助教 favoriteCoaches:**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `favoriteCoaches[].emoji` | string | 爱心 emoji |
|
||||
| `favoriteCoaches[].name` | string | 助教姓名 |
|
||||
| `favoriteCoaches[].relationIndex` | string | 关系指数 |
|
||||
| `favoriteCoaches[].indexColor` | string | 指数颜色 |
|
||||
| `favoriteCoaches[].bgClass` | string | 背景样式类 |
|
||||
| `favoriteCoaches[].stats` | `Array<{label, value, color?}>` | 统计(基础/激励/上课/充值) |
|
||||
|
||||
**消费记录 consumptionRecords(3 种类型,管理层关注消费结构):**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `ConsumptionRecord.id` | string | 记录 ID |
|
||||
| `ConsumptionRecord.type` | `"table" \| "shop" \| "recharge"` | 消费类型 |
|
||||
| `ConsumptionRecord.date` | string | 日期 |
|
||||
| `ConsumptionRecord.tableName` | string? | 台桌名(table 类型) |
|
||||
| `ConsumptionRecord.startTime` | string? | 开始时间 |
|
||||
| `ConsumptionRecord.endTime` | string? | 结束时间 |
|
||||
| `ConsumptionRecord.duration` | string? | 时长 |
|
||||
| `ConsumptionRecord.tableFee` | number? | 台费(优惠后) |
|
||||
| `ConsumptionRecord.tableOrigPrice` | number? | 台费原价 |
|
||||
| `ConsumptionRecord.coaches` | `Array<{name, level, levelColor, courseType, hours, perfHours?, fee}>` | 助教服务明细 |
|
||||
| `ConsumptionRecord.foodAmount` | number? | 食品酒水金额(优惠后) |
|
||||
| `ConsumptionRecord.foodOrigPrice` | number? | 食品酒水原价 |
|
||||
| `ConsumptionRecord.totalAmount` | number? | 总金额 |
|
||||
| `ConsumptionRecord.totalOrigPrice` | number? | 总原价 |
|
||||
| `ConsumptionRecord.payMethod` | string? | 支付方式 |
|
||||
| `ConsumptionRecord.rechargeAmount` | number? | 充值金额(recharge 类型) |
|
||||
|
||||
**备注列表:**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `sortedNotes[].id` | string | 备注 ID |
|
||||
| `sortedNotes[].tagLabel` | string | 标签(管理员/助教名) |
|
||||
| `sortedNotes[].createdAt` | string | 创建时间 |
|
||||
| `sortedNotes[].content` | string | 备注内容 |
|
||||
|
||||
#### 页面跳转
|
||||
- 查看服务记录 `onViewServiceRecords` → `/pages/customer-service-records/customer-service-records`(⚠️ 未传 customerId 参数!)
|
||||
- 问问助手 `onStartChat` → `/pages/chat/chat`(⚠️ 未传 customerId 参数!)
|
||||
|
||||
#### API 契约 vs Mock 数据 差异
|
||||
|
||||
| 差异项 | API 契约 (CUST-1) | 前端 Mock | 严重度 |
|
||||
|--------|--------------------|-----------| --------|
|
||||
| `aiInsight` AI 洞察 | ❌ 完全缺失 | ✅ 有(summary + strategies) | 🔴 高 |
|
||||
| `coachTasks` 关联助教任务 | ❌ 完全缺失 | ✅ 有(4 个助教,含 metrics) | 🔴 高 |
|
||||
| `favoriteCoaches` 喜好助教 | ❌ 完全缺失 | ✅ 有(含 stats 统计) | 🔴 高 |
|
||||
| `clues` 维客线索 | 契约有 `retention_clues` 但结构不同 | 前端用 category/categoryColor/text/source/detail | 🟠 中 |
|
||||
| `consumptionRecords` 结构 | 契约为扁平 `{id, date, type, type_class, amount, table, duration}` | 前端为嵌套结构(含 coaches 子数组、tableFee/foodAmount 分项) | 🔴 高 |
|
||||
| `balance`/`consumption60d`/`idealInterval`/`daysSinceVisit` | ❌ 缺失 | ✅ 有 | 🟠 中 |
|
||||
| `avatarChar` | ❌ 无(仅 avatar URL) | ✅ 有 | 🟡 低 |
|
||||
| 备注列表 | ❌ 未在 CUST-1 定义 | ✅ 有 | 🟠 中 |
|
||||
|
||||
#### spec 未记录的 Gap
|
||||
1. 🔴 CUST-1 缺少 `aiInsight` AI 洞察 — 管理层查看客户时的核心决策辅助信息(来源:biz.ai_cache app4_analysis)
|
||||
2. 🔴 CUST-1 缺少 `coachTasks` 关联助教任务区域 — 管理层关注多助教对同一客户的服务情况
|
||||
3. 🔴 CUST-1 缺少 `favoriteCoaches` 喜好助教 — 展示客户与各助教的关系指数和服务统计
|
||||
4. 🔴 CUST-1 消费记录结构严重不匹配 — 契约为扁平结构,前端需要嵌套结构(含助教明细、台费/食品分项、原价/优惠后价格)
|
||||
5. 🟠 `onViewServiceRecords` 跳转未传 `customerId` — 前端 Bug
|
||||
6. 🟠 `onStartChat` 跳转未传 `customerId` — 前端 Bug
|
||||
7. 🟠 `loadDetail()` 中 id 解析方式有 Bug — 应从 `onLoad(options)` 的 options 中获取
|
||||
|
||||
---
|
||||
|
||||
### 2.3 customer-service-records(`pages/customer-service-records/customer-service-records.ts`)
|
||||
|
||||
#### API 调用
|
||||
- `fetchCustomerRecords({ customerId })` → `{ records, hasMore }`
|
||||
- 页面 `onLoad({ customerId, id })` → `loadData(id)` → `fetchCustomerRecords({ customerId: id })`
|
||||
- ⚠️ 首次加载拉取全部记录,月份切换仅本地过滤(八¾节 F10/L4 已记录)
|
||||
|
||||
#### 前端期望字段
|
||||
|
||||
**页面头部信息:**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `customerName` | string | 客户姓名 |
|
||||
| `customerPhone` | string | 脱敏手机号 |
|
||||
| `customerPhoneFull` | string | 完整手机号 |
|
||||
| `totalServiceCount` | number | 累计服务次数 |
|
||||
| `relationIndex` | string | 关系指数 |
|
||||
|
||||
**ServiceRecord 结构(转换后):**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `table` | string | 台桌号 |
|
||||
| `type` | string | 课程类型标签 |
|
||||
| `typeClass` | `'basic' \| 'vip' \| 'tip' \| 'recharge'` | 样式类 |
|
||||
| `recordType` | `'course' \| 'recharge'` | 记录类型 |
|
||||
| `duration` | number | 折算后小时数 |
|
||||
| `durationRaw` | number | 折算前小时数 |
|
||||
| `income` | number | 到手金额 |
|
||||
| `isEstimate` | boolean | 是否预估 |
|
||||
| `drinks` | string | 饮品描述 |
|
||||
| `date` | string | 显示日期+时间段 |
|
||||
|
||||
#### 筛选参数映射
|
||||
|
||||
| 筛选项 | 前端参数 | 后端参数 | 查询影响 |
|
||||
|--------|---------|---------|---------|
|
||||
| 月份切换 | `currentYear`/`currentMonth` | `year`/`month` | 按月筛选记录(当前为本地过滤) |
|
||||
| 客户 ID | `customerId` | `customerId` | WHERE 过滤 |
|
||||
|
||||
#### 页面跳转
|
||||
- 无跳转(详情页,仅返回上一页)
|
||||
|
||||
#### API 契约 vs Mock 数据 差异
|
||||
|
||||
| 差异项 | API 契约 (CUST-2) | 前端 Mock | 严重度 |
|
||||
|--------|--------------------|-----------| --------|
|
||||
| 源数据结构 | 契约返回 ServiceRecord 格式 | Mock 使用 ConsumptionRecord(来自 mock-data.ts),前端转换为 ServiceRecord | 🟠 中 |
|
||||
| `customerPhone`/`customerPhoneFull` | 契约有 `customer_phone` | Mock 硬编码 | 🟡 低 |
|
||||
| `tables` 台桌列表 | 契约有 `tables: Array<{id, name}>` | Mock 无(前端模拟台号) | 🟡 低 |
|
||||
| `relationIndex` | 契约有 `relation_index` | Mock 硬编码 `"0.85"` | 🟡 低 |
|
||||
| 月度统计 | ❌ 契约无 | ✅ 前端本地计算 `monthCount`/`monthHours`/`monthRelation` | 🟠 中 |
|
||||
|
||||
#### spec 未记录的 Gap
|
||||
1. 🟠 CUST-2 响应缺少月度统计汇总 — 前端需要 `monthCount`/`monthHours`,当前本地计算,联调后应由后端返回
|
||||
2. 🟠 首次加载全量 vs 按月分页 — 当前设计一次拉全部记录本地过滤,数据量大时需改为按月请求
|
||||
|
||||
---
|
||||
|
||||
## 场景 3:财务看板(最复杂)
|
||||
|
||||
### 3.1 board-finance(`pages/board-finance/board-finance.ts`)
|
||||
|
||||
#### API 调用
|
||||
- `fetchBoardFinance()` → `BoardFinanceData`(⚠️ 当前签名仅 `{ date?: string }`,缺少 time/area/compare 参数)
|
||||
- 页面 `onLoad()` → `fetchBoardFinance()` → 设置 `pageState: 'normal'`
|
||||
- ⚠️ 筛选变更(`onTimeChange`/`onAreaChange`/`toggleCompare`)仅更新 data 状态,**不调用 API**(八¾节 F4/F5 已记录)
|
||||
- ⚠️ 所有 6 个板块数据完全内联在 `data` 中,`fetchBoardFinance` 返回的 `_data` 未使用
|
||||
|
||||
#### 前端期望字段(从内联 data 提取,6 个板块完整结构)
|
||||
|
||||
**板块 1:经营一览 (overview) — 8 个指标 + 8 个环比值**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `overview.occurrence` | string | 发生额/正价(格式化,如 `"¥823,456"`) |
|
||||
| `overview.occurrenceCompare` | string | 环比百分比(如 `"12.5%"`) |
|
||||
| `overview.discount` | string | 总优惠(负值,如 `"-¥113,336"`) |
|
||||
| `overview.discountCompare` | string | 环比 |
|
||||
| `overview.discountRate` | string | 优惠率(如 `"13.8%"`) |
|
||||
| `overview.discountRateCompare` | string | 环比 |
|
||||
| `overview.confirmedRevenue` | string | 成交/确认收入 |
|
||||
| `overview.confirmedCompare` | string | 环比 |
|
||||
| `overview.cashIn` | string | 实收/现金流入 |
|
||||
| `overview.cashInCompare` | string | 环比 |
|
||||
| `overview.cashOut` | string | 现金支出 |
|
||||
| `overview.cashOutCompare` | string | 环比 |
|
||||
| `overview.cashBalance` | string | 现金结余 |
|
||||
| `overview.cashBalanceCompare` | string | 环比 |
|
||||
| `overview.balanceRate` | string | 结余率 |
|
||||
| `overview.balanceRateCompare` | string | 环比 |
|
||||
|
||||
**板块 2:预收资产 (recharge) — 储值卡 + 赠送卡矩阵**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `recharge.actualIncome` | string | 储值卡充值实收 |
|
||||
| `recharge.actualCompare` | string | 环比 |
|
||||
| `recharge.firstCharge` | string | 首充 |
|
||||
| `recharge.firstChargeCompare` | string | 环比 |
|
||||
| `recharge.renewCharge` | string | 续费 |
|
||||
| `recharge.renewChargeCompare` | string | 环比 |
|
||||
| `recharge.consumed` | string | 消耗 |
|
||||
| `recharge.consumedCompare` | string | 环比 |
|
||||
| `recharge.cardBalance` | string | 储值卡总余额 |
|
||||
| `recharge.cardBalanceCompare` | string | 环比 |
|
||||
| `recharge.giftRows` | `Array<{label, total, totalCompare, wine, wineCompare, table, tableCompare, coupon, couponCompare}>` | 赠送卡矩阵(3行:新增/消费/余额 × 4列:合计/酒水卡/台费卡/抵用券) |
|
||||
| `recharge.allCardBalance` | string | 全类别会员卡余额合计 |
|
||||
| `recharge.allCardBalanceCompare` | string | 环比 |
|
||||
|
||||
赠送卡矩阵结构(3×4 = 12 个数据单元 + 12 个环比值 = 24 个字段):
|
||||
- 行:新增 / 消费 / 余额
|
||||
- 列:合计(total) / 酒水卡(wine) / 台费卡(table) / 抵用券(coupon)
|
||||
|
||||
**板块 3:应计收入确认 (revenue) — 4 个子表**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `revenue.structureRows` | `Array<{id, name, desc?, amount, discount, booked, bookedCompare, isSub?}>` | 收入结构表(9行,含子行标记) |
|
||||
| `revenue.priceItems` | `Array<{name, value, compare}>` | 正价明细(4项) |
|
||||
| `revenue.totalOccurrence` | string | 正价合计 |
|
||||
| `revenue.totalOccurrenceCompare` | string | 环比 |
|
||||
| `revenue.discountItems` | `Array<{name, desc?, value, compare}>` | 优惠明细(4项) |
|
||||
| `revenue.confirmedTotal` | string | 确认收入合计 |
|
||||
| `revenue.confirmedTotalCompare` | string | 环比 |
|
||||
| `revenue.channelItems` | `Array<{name, desc?, value, compare}>` | 渠道明细(3项) |
|
||||
|
||||
收入结构表行结构:
|
||||
- 开台与包厢(含 A区/B区/C区/团建区/麻将区 5 个子行)
|
||||
- 助教基础课
|
||||
- 助教激励课
|
||||
- 食品酒水
|
||||
|
||||
**板块 4:现金流入 (cashflow)**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `cashflow.consumeItems` | `Array<{name, desc, value, compare, isDown}>` | 消费收款(3项:纸币现金/线上收款/团购平台) |
|
||||
| `cashflow.rechargeItems` | `Array<{name, desc, value, compare}>` | 充值收款(1项:会员充值到账) |
|
||||
| `cashflow.total` | string | 现金流入合计 |
|
||||
| `cashflow.totalCompare` | string | 环比 |
|
||||
|
||||
**板块 5:现金流出 (expense) — 4 个子分组**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `expense.operationItems` | `Array<{name, value, compare, isDown}>` | 经营支出(3项:食品饮料/耗材/报销) |
|
||||
| `expense.fixedItems` | `Array<{name, value, compare, isFlat}>` | 固定支出(4项:房租/水电/物业/人员工资) |
|
||||
| `expense.coachItems` | `Array<{name, value, compare, isDown}>` | 助教分成(4项:基础课/激励课/充值提成/额外奖金) |
|
||||
| `expense.platformItems` | `Array<{name, value, compare}>` | 平台服务费(3项:汇来米/美团/抖音) |
|
||||
| `expense.total` | string | 现金流出合计 |
|
||||
| `expense.totalCompare` | string | 环比 |
|
||||
|
||||
**板块 6:助教分析 (coachAnalysis) — 基础课 + 激励课两个子表**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `coachAnalysis.basic.totalPay` | string | 基础课总收入 |
|
||||
| `coachAnalysis.basic.totalPayCompare` | string | 环比 |
|
||||
| `coachAnalysis.basic.totalShare` | string | 基础课总分成 |
|
||||
| `coachAnalysis.basic.totalShareCompare` | string | 环比 |
|
||||
| `coachAnalysis.basic.avgHourly` | string | 平均时薪 |
|
||||
| `coachAnalysis.basic.avgHourlyCompare` | string | 环比 |
|
||||
| `coachAnalysis.basic.rows` | `Array<{level, pay, payCompare, share, shareCompare, hourly, hourlyCompare, hourlyFlat?, payDown?, shareDown?}>` | 按等级分行(初级/中级/高级/星级) |
|
||||
| `coachAnalysis.incentive.*` | 同 basic | 激励课(结构完全相同) |
|
||||
|
||||
每行字段:
|
||||
- `level`: string — 等级名
|
||||
- `pay`: string — 收入
|
||||
- `payCompare`: string — 收入环比
|
||||
- `payDown`: boolean? — 收入是否下降
|
||||
- `share`: string — 分成
|
||||
- `shareCompare`: string — 分成环比
|
||||
- `shareDown`: boolean? — 分成是否下降
|
||||
- `hourly`: string — 时薪
|
||||
- `hourlyCompare`: string — 时薪环比
|
||||
- `hourlyFlat`: boolean? — 时薪是否持平
|
||||
|
||||
#### 筛选参数映射
|
||||
|
||||
| 筛选项 | 前端参数 | 后端参数 | 查询影响 |
|
||||
|--------|---------|---------|---------|
|
||||
| 时间范围 | `selectedTime`: month/lastMonth/week/lastWeek/quarter3/quarter/lastQuarter/half6 | `time` | 决定统计日期区间 |
|
||||
| 区域筛选 | `selectedArea`: all/hall/hallA/hallB/hallC/mahjong/teamBuilding | `area` | WHERE 过滤区域;area≠all 时预收资产板块隐藏 |
|
||||
| 环比开关 | `compareEnabled`: true/false | `compare`: 0/1 | 控制是否返回环比数据 |
|
||||
|
||||
时间范围→日期区间映射(后端需实现):
|
||||
- `month` → 当月1日 ~ 当月末日
|
||||
- `lastMonth` → 上月1日 ~ 上月末日
|
||||
- `week` → 本周一 ~ 本周日
|
||||
- `lastWeek` → 上周一 ~ 上周日
|
||||
- `quarter3` → 前3个月(不含本月)
|
||||
- `quarter` → 本季度第1天 ~ 本季度末日
|
||||
- `lastQuarter` → 上季度
|
||||
- `half6` → 最近6个月(不含本月)
|
||||
|
||||
#### 目录导航 (sectionNav)
|
||||
- 6 个板块:经营一览/预收资产/应计收入确认/现金流入/现金流出/助教分析
|
||||
- 每个板块有 emoji + 标题 + sectionId
|
||||
- 吸顶头显示当前板块 emoji + 标题 + 描述
|
||||
- 板块描述(`_sectionDescs`):6 段固定文案
|
||||
|
||||
#### 帮助图标 (helpTip)
|
||||
- 7 个指标解释:occurrence/discount/confirmed/cashIn/cashOut/balance/rechargeActual/firstCharge/renewCharge/consume/cardBalance/allCardBalance
|
||||
- 每个含 `title` + `content`(多行文本)
|
||||
|
||||
#### API 契约 vs Mock 数据 vs 八¾节 差异
|
||||
|
||||
| 差异项 | API 契约 (BOARD-3) | 前端 Mock (内联 data) | 八¾节 (B3) | 严重度 |
|
||||
|--------|--------------------|-----------------------|------------|--------|
|
||||
| 响应结构 | 扁平 `metrics: Array<{key, label, value, unit, trend, compareValue, sub_items}>` | 6 个板块独立对象,深度嵌套结构 | 仅定义了参数,未定义完整响应结构 | 🔴 高 |
|
||||
| 板块数量 | 未区分板块(单一 metrics 数组) | 6 个独立板块(overview/recharge/revenue/cashflow/expense/coachAnalysis) | 提到 6 个板块但未定义结构 | 🔴 高 |
|
||||
| 环比数据格式 | `trend: 'up'\|'down'\|'flat'` + `compareValue?: string` | 每个值旁有独立的 `xxxCompare` 字段(百分比字符串) + `isDown`/`isFlat` 布尔标记 | 已确认需补充 `compareValue` 字段 | 🔴 高 |
|
||||
| 赠送卡矩阵 | ❌ 完全缺失 | ✅ 3×4 矩阵(24 个字段) | ❌ 未详细定义 | 🔴 高 |
|
||||
| 收入结构表 | ❌ 缺失 | ✅ 9 行含子行标记 | ❌ 未详细定义 | 🔴 高 |
|
||||
| 助教分析子表 | ❌ 缺失 | ✅ 基础课+激励课各 4 行(按等级) | ❌ 未详细定义 | 🔴 高 |
|
||||
| 现金流出 4 子分组 | ❌ 缺失 | ✅ 经营支出/固定支出/助教分成/平台服务费 | ❌ 未详细定义 | 🔴 高 |
|
||||
| `time`/`area`/`compare` 参数 | 八¾节末尾补充了参数定义 | 前端未传参(Bug) | ✅ 已定义 | 🟠 中(八¾节已补充) |
|
||||
| `isDown`/`isFlat` 标记 | ❌ 无 | ✅ 有(控制箭头颜色方向) | ❌ 无 | 🟠 中 |
|
||||
| 帮助图标内容 | ❌ 完全缺失 | ✅ 12 个指标解释 | ❌ 未提及 | 🟡 低 |
|
||||
|
||||
#### spec 未记录的 Gap
|
||||
1. 🔴 BOARD-3 响应结构与前端实际需求完全不匹配 — 契约为扁平 metrics 数组,前端需要 6 个独立板块的深度嵌套结构
|
||||
2. 🔴 赠送卡矩阵(3×4=12 单元 + 12 环比)完全未定义 — 预收资产板块的核心数据
|
||||
3. 🔴 收入结构表(9 行含子行层级)完全未定义 — 应计收入确认板块的核心数据
|
||||
4. 🔴 助教分析子表(基础课+激励课各 4 行按等级)完全未定义
|
||||
5. 🔴 现金流出 4 个子分组(经营/固定/助教分成/平台服务费)完全未定义
|
||||
6. 🔴 环比数据格式不匹配 — 契约仅 `trend` + `compareValue`,前端需要每个值旁独立的 `xxxCompare` 字段 + `isDown`/`isFlat` 布尔标记
|
||||
7. 🟠 `isDown`/`isFlat` 布尔标记未定义 — 前端用于控制环比箭头方向和颜色
|
||||
8. 🟡 帮助图标内容(12 个指标解释文案)未定义 — 可硬编码在前端,但后端可提供配置化
|
||||
|
||||
---
|
||||
|
||||
## 场景 4:跨页面数据关联
|
||||
|
||||
### 4.1 board-coach → coach-detail → customer-detail → customer-service-records
|
||||
|
||||
**参数传递链:**
|
||||
1. `board-coach` 点击助教卡片 → `coach-detail?id={coachId}`
|
||||
2. `coach-detail` 点击客户卡片 → `customer-detail?id={customerId}`
|
||||
3. `coach-detail` 点击任务项 → `customer-detail?name={customerName}`(⚠️ 用 name 而非 id)
|
||||
4. `customer-detail` 查看服务记录 → `customer-service-records`(⚠️ 未传 customerId)
|
||||
5. `coach-detail` 查看更多服务记录 → `performance-records?coachId={coachId}`
|
||||
6. `coach-detail` 问问助手 → `chat?coachId={coachId}`
|
||||
|
||||
**跨页面 Gap:**
|
||||
- 🔴 步骤 3 用 `name` 跳转,name 不唯一,应改为 `id`
|
||||
- 🔴 步骤 4 未传 `customerId`,`customer-service-records` 无法知道查哪个客户
|
||||
- 🟠 `customer-detail` 的 `onStartChat` 未传 `customerId`,chat 页面无法关联客户上下文
|
||||
|
||||
### 4.2 board-customer → customer-detail → chat
|
||||
|
||||
**参数传递链:**
|
||||
1. `board-customer` 点击客户卡片 → `customer-detail?id={customerId}`
|
||||
2. `customer-detail` 问问助手 → `chat`(⚠️ 未传参数)
|
||||
|
||||
**跨页面 Gap:**
|
||||
- 🟠 `chat` 页面无法获取客户上下文(customerId/customerName)
|
||||
|
||||
### 4.3 board-finance → coach-detail 入口
|
||||
|
||||
**分析结果:**
|
||||
- board-finance 的助教分析板块(coachAnalysis)展示按等级汇总的数据,**无跳转到 coach-detail 的入口**
|
||||
- 这是合理的设计 — 财务看板关注的是汇总数据而非个体
|
||||
|
||||
---
|
||||
|
||||
## 汇总 Gap 清单(按严重度排序)
|
||||
|
||||
### 🔴 高严重度(阻塞联调,必须在后端开发前解决)
|
||||
|
||||
| # | 类别 | Gap 描述 | 涉及接口 | 涉及页面 |
|
||||
|---|------|---------|---------|---------|
|
||||
| G1 | 契约缺陷 | **BOARD-3 响应结构与前端完全不匹配** — 契约为扁平 metrics 数组,前端需要 6 个独立板块的深度嵌套结构(overview/recharge/revenue/cashflow/expense/coachAnalysis),含 200+ 个字段 | BOARD-3 | board-finance |
|
||||
| G2 | 契约缺陷 | **BOARD-1 响应字段严重不足** — 契约仅 10 个通用字段,前端需要 20 个含 4 维度专属字段(perf/salary/sv/task),且 skills 类型不匹配(string[] vs Array<{text,cls}>) | BOARD-1 | board-coach |
|
||||
| G3 | 契约缺陷 | **BOARD-2 响应字段严重不足** — 契约仅 8 个通用字段,前端需要 40+ 维度专属字段(8 个维度各有独立字段集),且缺少 assistants 关联助教列表 | BOARD-2 | board-customer |
|
||||
| G4 | 契约缺陷 | **COACH-1 响应缺少多个核心区域** — 缺少 income 收入明细(本月/上月)、historyMonths 历史月份统计、tierNodes 档位节点、备注列表;topCustomers 字段结构不完整(契约 5 字段 vs 前端 10 字段) | COACH-1 | coach-detail |
|
||||
| G5 | 契约缺陷 | **CUST-1 响应缺少管理层核心数据** — 缺少 aiInsight AI 洞察、coachTasks 关联助教任务、favoriteCoaches 喜好助教;消费记录结构严重不匹配(契约扁平 vs 前端嵌套含助教明细) | CUST-1 | customer-detail |
|
||||
| G6 | 契约缺陷 | **BOARD-3 赠送卡矩阵完全未定义** — 预收资产板块的 3×4 矩阵(新增/消费/余额 × 合计/酒水卡/台费卡/抵用券)= 24 个数据字段 + 24 个环比字段 | BOARD-3 | board-finance |
|
||||
| G7 | 契约缺陷 | **BOARD-3 收入结构表完全未定义** — 9 行含子行层级(开台与包厢含 5 个区域子行 + 助教基础课 + 助教激励课 + 食品酒水),每行含 amount/discount/booked/bookedCompare | BOARD-3 | board-finance |
|
||||
| G8 | 契约缺陷 | **BOARD-3 助教分析子表完全未定义** — 基础课+激励课各含汇总行 + 4 个等级行(初级/中级/高级/星级),每行含 pay/share/hourly 及各自环比 | BOARD-3 | board-finance |
|
||||
| G9 | 契约缺陷 | **BOARD-3 现金流出 4 子分组完全未定义** — 经营支出(3项)/固定支出(4项)/助教分成(4项)/平台服务费(3项) | BOARD-3 | board-finance |
|
||||
| G10 | 契约缺陷 | **环比数据格式不匹配** — 契约仅 `trend` + `compareValue`,前端需要每个值旁独立的 `xxxCompare` 百分比字段 + `isDown`/`isFlat` 布尔标记 | BOARD-3 | board-finance |
|
||||
| G11 | 前端 Bug | **customer-detail 跳转 customer-service-records 未传 customerId** — 服务记录页无法知道查哪个客户 | CUST-2 | customer-detail → customer-service-records |
|
||||
| G12 | 前端 Bug | **coach-detail 任务项跳转用 name 而非 id** — `onTaskItemTap` 传 `name={customerName}`,name 不唯一 | — | coach-detail → customer-detail |
|
||||
| G13 | 类型不匹配 | **api.ts 中 fetchCoachDetail 返回类型为 CoachCard** — 实际需要完整的 CoachDetail(含 performance/income/tasks/topCustomers/serviceRecords/historyMonths/notes),类型定义严重不足 | COACH-1 | coach-detail |
|
||||
| G14 | 类型不匹配 | **mock-data.ts 中的类型与页面内联 mock 完全脱节** — BoardFinanceData 仅含 4 个 metrics,实际页面需要 6 个板块 200+ 字段;CustomerCard/CoachCard 过于简单 | 全部看板 | 全部看板页 |
|
||||
|
||||
### 🟠 中严重度(影响功能完整性,联调前需解决)
|
||||
|
||||
| # | 类别 | Gap 描述 | 涉及接口 | 涉及页面 |
|
||||
|---|------|---------|---------|---------|
|
||||
| G15 | 契约缺陷 | **BOARD-1 sort 参数格式不一致** — 契约用 `field:direction`,前端用枚举值 `perf_desc` | BOARD-1 | board-coach |
|
||||
| G16 | 契约缺陷 | **BOARD-1/2 缺少 initial/avatarGradient/avatarCls 字段** — 前端用于头像渲染,需确认由前端计算还是后端返回 | BOARD-1/2 | board-coach/board-customer |
|
||||
| G17 | 契约缺陷 | **CUST-1 缺少 balance/consumption60d/idealInterval/daysSinceVisit** — 客户详情页头部展示的核心指标 | CUST-1 | customer-detail |
|
||||
| G18 | 契约缺陷 | **CUST-1 维客线索结构不匹配** — 契约用 tag/tag_color/emoji/text/source/desc,前端用 category/categoryColor/text/source/detail | CUST-1 | customer-detail |
|
||||
| G19 | 契约缺陷 | **CUST-1 缺少备注列表** — 客户详情页展示关联备注 | CUST-1 | customer-detail |
|
||||
| G20 | 契约缺陷 | **COACH-1 缺少备注列表** — 助教详情页展示关联备注 | COACH-1 | coach-detail |
|
||||
| G21 | 契约缺陷 | **CUST-2 缺少月度统计汇总** — 前端需要 monthCount/monthHours,当前本地计算 | CUST-2 | customer-service-records |
|
||||
| G22 | 契约缺陷 | **BOARD-3 isDown/isFlat 布尔标记未定义** — 前端用于控制环比箭头方向和颜色 | BOARD-3 | board-finance |
|
||||
| G23 | 前端 Bug | **customer-detail 的 onStartChat 未传 customerId** — chat 页面无法关联客户上下文 | CHAT | customer-detail → chat |
|
||||
| G24 | 前端 Bug | **customer-detail 的 loadDetail() 中 id 解析方式有 Bug** — 应从 onLoad(options) 获取,而非 `__route__` 解析 | CUST-1 | customer-detail |
|
||||
| G25 | 设计缺陷 | **BOARD-2 potentialTags 标签结构契约缺失** — potential 维度展示客户潜力标签(含 text+theme) | BOARD-2 | board-customer |
|
||||
| G26 | 设计缺陷 | **COACH-1 tierNodes/maxHours 缺失** — 绩效进度条组件需要档位节点数据,当前硬编码 `[0, 100, 130, 160, 190, 220]` | COACH-1 | coach-detail |
|
||||
|
||||
### 🟡 低严重度(不阻塞联调,可后续优化)
|
||||
|
||||
| # | 类别 | Gap 描述 | 涉及接口 | 涉及页面 |
|
||||
|---|------|---------|---------|---------|
|
||||
| G27 | 设计决策 | **BOARD-1 无分页定义** — 当前全量返回,助教数量少(<50)可接受 | BOARD-1 | board-coach |
|
||||
| G28 | 设计决策 | **BOARD-3 帮助图标内容未定义** — 12 个指标解释文案,可硬编码在前端 | BOARD-3 | board-finance |
|
||||
| G29 | 设计决策 | **customer-service-records 首次加载全量 vs 按月分页** — 数据量小时可接受,量大时需改为按月请求 | CUST-2 | customer-service-records |
|
||||
| G30 | 类型不匹配 | **mock-data.ts ConsumptionRecord 结构过于简单** — 仅 6 个字段,customer-detail 内联 mock 有 16 个字段(含嵌套 coaches 数组) | CUST-1 | customer-detail |
|
||||
| G31 | 契约缺陷 | **CUST-2 tables 台桌列表** — 契约定义了但前端未使用(前端模拟台号) | CUST-2 | customer-service-records |
|
||||
|
||||
---
|
||||
|
||||
## 行动建议
|
||||
|
||||
### 优先级 1:更新 API 契约(阻塞后端开发)
|
||||
|
||||
以下接口的契约需要**完全重写**,以匹配前端实际需求:
|
||||
|
||||
1. **BOARD-3 财务看板** — 从扁平 metrics 改为 6 板块嵌套结构,补充赠送卡矩阵、收入结构表、助教分析子表、现金流出子分组的完整字段定义
|
||||
2. **BOARD-1 助教看板** — 从 10 个通用字段扩展为 20 个含 4 维度专属字段,修正 skills 类型,补充 topCustomers
|
||||
3. **BOARD-2 客户看板** — 从 8 个通用字段扩展为 40+ 维度专属字段,补充 assistants/weeklyVisits/coachDetails/potentialTags
|
||||
4. **COACH-1 助教详情** — 补充 income/historyMonths/tierNodes/备注列表,扩展 topCustomers 字段
|
||||
5. **CUST-1 客户详情** — 补充 aiInsight/coachTasks/favoriteCoaches/备注列表,重写消费记录为嵌套结构
|
||||
|
||||
### 优先级 2:修复前端 Bug(联调前必须完成)
|
||||
|
||||
1. G11: customer-detail → customer-service-records 传 customerId
|
||||
2. G12: coach-detail onTaskItemTap 改用 id 而非 name
|
||||
3. G23: customer-detail → chat 传 customerId
|
||||
4. G24: customer-detail loadDetail() 修复 id 解析
|
||||
5. F1-F6: 看板筛选变更触发重新请求(八¾节已记录)
|
||||
|
||||
### 优先级 3:统一 mock-data.ts 类型定义
|
||||
|
||||
当前 `mock-data.ts` 中的类型(BoardFinanceData/CustomerCard/CoachCard/CustomerDetail)与页面内联 mock 数据严重脱节。建议:
|
||||
- 将页面内联的完整接口定义(CoachItem/CustomerItem/CoachDetail 等)提取到 `mock-data.ts` 或独立的 `types.ts`
|
||||
- 更新 `api.ts` 中各函数的返回类型
|
||||
- 确保类型定义与更新后的 API 契约一致
|
||||
826
docs/prd/Neo_Specs/storyboard-walkthrough-assistant-view.md
Normal file
826
docs/prd/Neo_Specs/storyboard-walkthrough-assistant-view.md
Normal file
@@ -0,0 +1,826 @@
|
||||
# 小程序 Storyboard 走查报告 — 助教视角(小燕)
|
||||
|
||||
> 走查日期:2026-03-18
|
||||
> 走查角色:助教"小燕",已登录,status=approved
|
||||
> 对照文档:`docs/miniprogram-dev/API-contract.md`(契约)、`docs/prd/Neo_Specs/NS1-xcx-backend-api.md`(spec)
|
||||
> 目标:提取 spec 未记录的接口需求细节
|
||||
|
||||
---
|
||||
|
||||
## 场景 1:登录 → 任务列表(app.ts)
|
||||
|
||||
### API 调用
|
||||
- `checkAuthStatus()` → `GET /api/xcx/me` → 返回 `ApiUserInfo`
|
||||
- 参数来源:token 从 `wx.getStorageSync('token')` 读取
|
||||
- 成功后根据 `data.status` 路由:`approved` → `reLaunch('/pages/task-list/task-list')`
|
||||
|
||||
### 前端期望字段(/api/xcx/me 响应)
|
||||
| 字段 | 类型 | 说明 | 契约定义 |
|
||||
|------|------|------|---------|
|
||||
| `user_id` | number | 用户 ID | ✅ |
|
||||
| `status` | enum | `new/pending/approved/rejected/disabled` | ✅ |
|
||||
| `nickname` | string | 昵称 | ✅ |
|
||||
| `role` | string | 角色 | ✅ REQ-4 |
|
||||
| `store_name` | string | 门店名 | ✅ REQ-4 |
|
||||
| `coach_level` | string? | 助教等级 | ✅ REQ-4 |
|
||||
| `avatar` | string? | 头像 URL | ✅ REQ-4 |
|
||||
|
||||
### globalData 结构
|
||||
```typescript
|
||||
globalData: {
|
||||
token?: string
|
||||
refreshToken?: string
|
||||
authUser?: { userId: number, status: string, nickname: string }
|
||||
}
|
||||
```
|
||||
|
||||
### spec 未记录的 Gap
|
||||
- **GAP-01**:`globalData.authUser` 未存储 `role`、`store_name`、`coach_level`、`avatar`,但下游页面(task-list banner、performance banner、performance-records banner)需要这些字段。前端需扩展 `globalData.authUser` 或各页面单独请求 `/me`。
|
||||
|
||||
---
|
||||
|
||||
## 场景 2:任务列表页(task-list.ts)
|
||||
|
||||
### API 调用
|
||||
- `fetchTasks(params?)` → `GET /api/xcx/tasks?status=&page=&pageSize=`
|
||||
- 返回:`{ tasks: Task[], performance: PerformanceData, total: number, hasMore: boolean }`
|
||||
- 页面加载时无参数调用(获取全部任务)
|
||||
|
||||
### 前端期望字段 — Task(任务卡片)
|
||||
| 字段 | 类型 | 说明 | 契约 TASK-1 |
|
||||
|------|------|------|------------|
|
||||
| `id` | string | 任务 ID | ✅ |
|
||||
| `customerName` | string | 客户姓名 | ✅ |
|
||||
| `customerAvatar` | string | 客户头像 | ✅ |
|
||||
| `taskType` | enum | `callback/priority_recall/relationship/high_priority` | ✅ |
|
||||
| `taskTypeLabel` | string | 类型中文标签 | ✅ |
|
||||
| `deadline` | string | 截止日期 ISO 8601 | ✅ |
|
||||
| `heartScore` | number | 爱心评分 0-10 | ✅ |
|
||||
| `hobbies` | string[] | 爱好标签 | ✅ |
|
||||
| `isPinned` | boolean | 是否置顶 | ✅ |
|
||||
| `hasNote` | boolean | 是否有备注 | ✅ |
|
||||
| `status` | enum | `pending/completed/abandoned` | ✅ |
|
||||
|
||||
### 前端期望字段 — PerformanceData(绩效概览卡片)
|
||||
| 字段 | 类型 | 说明 | 契约 TASK-1 performance | 前端实际使用 |
|
||||
|------|------|------|------------------------|-------------|
|
||||
| `monthlyIncome` | number | 月收入 | ❌ 契约仅有 `total_income` | `buildPerfData()` 用 |
|
||||
| `incomeChange` | number | 收入变化百分比 | ❌ 契约无 | `incomeTrend` 展示 |
|
||||
| `currentTier` | string | 当前档位名称 | ❌ 契约无 | `bannerTitle` |
|
||||
| `nextTierGap` | number | 距下一档差距 | ❌ 契约无 | `remainHours` |
|
||||
| `todayServiceCount` | number | 今日服务次数 | ❌ 契约无 | 未使用 |
|
||||
| `weekServiceCount` | number | 本周服务次数 | ❌ 契约无 | 未使用 |
|
||||
| `monthServiceCount` | number | 本月服务次数 | ❌ 契约无 | 未使用 |
|
||||
|
||||
### 前端期望字段 — buildPerfData() 需要后端返回的绩效字段
|
||||
| 字段 | 类型 | 说明 | 契约/spec 定义 |
|
||||
|------|------|------|---------------|
|
||||
| `tierNodes` | number[] | 档位节点数组,如 `[0,100,130,160,190,220]` | ❌ 未定义 |
|
||||
| `totalHours` | number | 当月总工时(折算后) | 契约有 `total_hours` |
|
||||
| `basicHours` | number | 基础课时 | ❌ 未定义 |
|
||||
| `bonusHours` | number | 激励课时 | ❌ 未定义 |
|
||||
| `currentTier` | number | 当前档位索引 | ❌ 未定义 |
|
||||
| `nextTierHours` | number | 下一档位工时阈值 | ❌ 未定义 |
|
||||
| `tierCompleted` | boolean | 是否已达标(满档) | ❌ 未定义 |
|
||||
| `bonusMoney` | number | 升档奖金 | ❌ 未定义 |
|
||||
| `incomeFormatted` | string | 格式化收入 | 契约有 `total_income` |
|
||||
| `incomeTrend` | string | 收入趋势(如 `↓368`) | ❌ 未定义 |
|
||||
| `incomeTrendDir` | enum | `up/down` | ❌ 未定义 |
|
||||
| `incomeMonth` | string | 当前月份标签 | 契约有 `month_label` |
|
||||
| `prevMonth` | string | 上月标签 | ❌ 未定义 |
|
||||
|
||||
### enrichTask() 需要后端返回的扩展字段
|
||||
| 字段 | 类型 | 说明 | 契约/spec 定义 |
|
||||
|------|------|------|---------------|
|
||||
| `lastVisitDays` | number | 距上次到店天数 | ❌ 前端 mock 计算,后端需返回 |
|
||||
| `balance` | number | 客户余额 | ❌ 未定义在 TASK-1 |
|
||||
| `aiSuggestion` | string | AI 建议摘要 | ❌ 未定义在 TASK-1 |
|
||||
|
||||
### 页面跳转
|
||||
| 操作 | 目标 | 参数 |
|
||||
|------|------|------|
|
||||
| 点击任务卡片 | `/pages/task-detail/task-detail` | `?id={taskId}` |
|
||||
| 点击绩效卡片 | `/pages/performance/performance` | 无参数 |
|
||||
| 长按→AI助手 | `/pages/ai-chat/ai-chat` | `?taskId={id}&customerName={name}` |
|
||||
|
||||
### 写操作
|
||||
| 操作 | API | 请求体 | 成功后行为 |
|
||||
|------|-----|--------|-----------|
|
||||
| 长按→置顶/取消置顶 | `POST /tasks/{id}/pin` 或 `/unpin` | 无 | 本地更新 `isPinned`,重新分组 |
|
||||
| 长按→放弃 | `POST /tasks/{id}/abandon` | `{ reason: string }` | 本地移至 abandonedTasks |
|
||||
| 长按→取消放弃 | `POST /tasks/{id}/restore` | 无 | 本地移至 normalTasks |
|
||||
| 长按→添加备注 | `POST /notes` | `{ content, taskId?, customerId? }` | Toast 提示 |
|
||||
|
||||
### spec 未记录的 Gap
|
||||
- **GAP-02**:契约 TASK-1 `performance` 仅有 4 个字段(`total_hours/total_income/total_customers/month_label`),但前端 `buildPerfData()` 需要 15+ 个字段(`tierNodes`、`basicHours`、`bonusHours`、`currentTier`、`nextTierHours`、`tierCompleted`、`bonusMoney`、`incomeTrend`、`incomeTrendDir`、`prevMonth` 等)。这是最大的 Gap。
|
||||
- **GAP-03**:`enrichTask()` 需要 `lastVisitDays`(距上次到店天数)、`balance`(客户余额)、`aiSuggestion`(AI 建议),这 3 个字段在 TASK-1 契约中未定义。后端需在任务列表 item 中附带这些字段,或前端改为进入详情页后再加载。
|
||||
- **GAP-04**:`pin/unpin` 操作的 API 端点在契约中未定义。spec 提到已实现 `pin/unpin`,但 `api.ts` 中无对应函数,前端仅做本地状态更新。需确认 API 路径(`POST /tasks/{id}/pin` 和 `POST /tasks/{id}/unpin`)。
|
||||
- **GAP-05**:`createNote` 的 `score` 参数 — 前端备注弹窗 `onNoteConfirm` 传递 `{ score, content }`,但 `api.ts` 的 `createNote` 签名仅有 `{ content, taskId?, customerId? }`,缺少 `score` 字段。
|
||||
|
||||
---
|
||||
|
||||
## 场景 3:任务详情页(task-detail.ts)
|
||||
|
||||
### API 调用
|
||||
- `fetchTaskDetail(taskId)` → `GET /api/xcx/tasks/{taskId}`
|
||||
- 参数来源:`options.id`(从 task-list 跳转传入)
|
||||
- `deleteNote(noteId)` → `DELETE /api/xcx/notes/{noteId}`
|
||||
|
||||
### 前端期望字段 — TaskDetail(完整清单)
|
||||
|
||||
#### 基础信息(继承 Task)
|
||||
同 TASK-1 item 字段,契约已覆盖。
|
||||
|
||||
#### 维客线索 retentionClues(内联 mock,8 条)
|
||||
| 字段 | 类型 | 说明 | 契约 TASK-2 |
|
||||
|------|------|------|------------|
|
||||
| `tag` | string | 线索大类标签(如 `"客户\n基础"`) | ❌ 契约用 `tag` 但格式不同 |
|
||||
| `tagColor` | enum | `primary/success/purple/error` | ✅ `tag_color` |
|
||||
| `emoji` | string | 表情符号 | ✅ |
|
||||
| `text` | string | 线索摘要 | ✅ |
|
||||
| `source` | string | 来源(如 `"By:系统"`、`"By:小燕"`) | ⚠️ 契约有 `source` 但格式不同 |
|
||||
| `desc` | string? | 详细描述(展开后显示) | ✅ |
|
||||
| `expanded` | boolean | 展开状态(前端本地) | — |
|
||||
|
||||
#### 话术参考 talkingPoints(内联 mock,5 条)
|
||||
| 字段 | 类型 | 说明 | 契约 TASK-2 |
|
||||
|------|------|------|------------|
|
||||
| `talkingPoints` | string[] | 话术文本数组 | ✅ |
|
||||
|
||||
#### 服务记录 serviceRecords(内联 mock,4 条)
|
||||
| 字段 | 类型 | 说明 | 契约 TASK-2 |
|
||||
|------|------|------|------------|
|
||||
| `table` | string | 台桌号 | ✅ |
|
||||
| `type` | string | 课程类型标签 | ✅ |
|
||||
| `typeClass` | enum | `basic/vip/tip/recharge/incentive` | ✅ `type_class` |
|
||||
| `recordType` | enum? | `course/recharge` | ✅ `record_type` |
|
||||
| `duration` | number | 折算后课时(小时) | ✅ |
|
||||
| `durationRaw` | number? | 折算前课时(小时) | ✅ `duration_raw` |
|
||||
| `income` | number | 收入(元) | ✅ |
|
||||
| `isEstimate` | boolean? | 是否预估金额 | ✅ `is_estimate` |
|
||||
| `drinks` | string | 酒水描述 | ✅ |
|
||||
| `date` | string | 日期(格式化后) | ✅ |
|
||||
|
||||
#### 服务汇总 serviceSummary
|
||||
| 字段 | 类型 | 说明 | 契约 TASK-2 |
|
||||
|------|------|------|------------|
|
||||
| `totalHours` | number | 总工时 | ✅ |
|
||||
| `totalIncome` | number | 总收入 | ✅ |
|
||||
| `avgIncome` | number | 平均收入 | ✅ |
|
||||
|
||||
#### AI 分析 aiAnalysis
|
||||
| 字段 | 类型 | 说明 | 契约 TASK-2 |
|
||||
|------|------|------|------------|
|
||||
| `summary` | string | AI 分析摘要 | ✅ |
|
||||
| `suggestions` | string[] | AI 建议列表 | ✅ |
|
||||
|
||||
#### 备注 notes(内联 mock,5 条)
|
||||
| 字段 | 类型 | 说明 | 契约 TASK-2 |
|
||||
|------|------|------|------------|
|
||||
| `id` | string | 备注 ID | ✅ |
|
||||
| `content` | string | 备注内容 | ✅ |
|
||||
| `tagType` | enum | `customer/coach/system` | ✅ `tag_type` |
|
||||
| `tagLabel` | string | 标签文案 | ✅ `tag_label` |
|
||||
| `createdAt` | string | 创建时间 | ✅ `created_at` |
|
||||
| `score` | number? | 满意度评分 0-10 | ✅ |
|
||||
|
||||
### 页面跳转
|
||||
| 操作 | 目标 | 参数 |
|
||||
|------|------|------|
|
||||
| 问问助手 | `/pages/chat/chat` | `?customerId={detail.id}` |
|
||||
| 查看全部服务记录 | `/pages/customer-service-records/customer-service-records` | `?customerId={detail.id}` |
|
||||
|
||||
### 写操作
|
||||
| 操作 | API | 请求体 | 成功后行为 |
|
||||
|------|-----|--------|-----------|
|
||||
| 添加备注 | `POST /notes` | `{ content }` | 本地 prepend 到 sortedNotes |
|
||||
| 删除备注 | `DELETE /notes/{noteId}` | 无 | 本地 filter 移除 |
|
||||
| 放弃任务 | `POST /tasks/{id}/abandon` | `{ reason }` | 更新 `detail.status='abandoned'` |
|
||||
| 取消放弃 | `POST /tasks/{id}/restore` | 无 | 更新 `detail.status='pending'` |
|
||||
|
||||
### spec 未记录的 Gap
|
||||
- **GAP-06**:维客线索的 `tag` 字段格式不一致 — 前端内联 mock 使用 `"客户\n基础"` 格式(含换行符),契约定义为普通 string。后端需明确 tag 的取值枚举和格式。
|
||||
- **GAP-07**:维客线索的 `source` 字段格式不一致 — 前端显示 `"By:系统"`、`"By:小燕"`,但 mock-data.ts 中 `RetentionClue.source` 定义为 `'manual' | 'ai_consumption' | 'ai_note'`。前端内联 mock 与类型定义不匹配,后端需明确返回格式。
|
||||
- **GAP-08**:`aiAnalysis` 数据来源 — spec §3.2 提到来自 `biz.ai_cache`(app4/app5/app6/app7),但契约未说明 `cache_type` 映射关系。需明确:`ai_analysis.summary` 来自哪个 `cache_type`?`talking_points` 来自哪个 `cache_type`?
|
||||
- **GAP-09**:task-detail 跳转 chat 时传 `customerId={detail.id}`,但 `detail.id` 实际是 **taskId** 不是 customerId。前端需要一个 `customerId` 字段(TASK-2 响应中需包含 `customer_id`)。
|
||||
- **GAP-10**:task-detail 跳转 customer-service-records 时同样传 `customerId={detail.id}`,存在同样的 taskId/customerId 混淆问题。TASK-2 响应需包含 `customer_id`。
|
||||
- **GAP-11**:`storageLevel`(储值等级,如"非常多")和 `relationLevel/relationLevelText/relationColor`(关系等级)在前端本地计算,但后端未定义这些字段。后端是否需要返回,还是前端根据 `heartScore` 自行计算?
|
||||
|
||||
---
|
||||
|
||||
## 场景 4:绩效概览页(performance.ts)
|
||||
|
||||
### API 调用
|
||||
- `fetchPerformanceOverview({ year, month })` → `GET /api/xcx/performance?year=&month=`
|
||||
- 参数来源:`new Date()` 取当前年月
|
||||
- 返回类型:`PerformanceData`(mock-data.ts 定义)
|
||||
|
||||
### 前端期望字段 — 完整清单(对比契约 PERF-1)
|
||||
|
||||
#### Banner 数据(内联 mock,非 API 返回)
|
||||
| 字段 | 类型 | 说明 | 契约 PERF-1 | 数据源 |
|
||||
|------|------|------|------------|--------|
|
||||
| `coachName` | string | 助教姓名 `'小燕'` | ❌ 未定义 | 应从 globalData.authUser 或 /me |
|
||||
| `coachRole` | string | 角色 `'助教'` | ❌ 未定义 | 同上 |
|
||||
| `storeName` | string | 门店名 `'广州朗朗桌球'` | ❌ 未定义 | 同上 |
|
||||
| `monthlyIncome` | string | 本月收入 `'¥6,206'` | ⚠️ 契约有 `total_income` 但是 number | 需格式化 |
|
||||
| `lastMonthIncome` | string | 上月收入 `'¥16,880'` | ❌ 未定义 | 需后端额外返回 |
|
||||
|
||||
#### 收入档位(内联 mock)
|
||||
| 字段 | 类型 | 说明 | 契约 PERF-1 |
|
||||
|------|------|------|------------|
|
||||
| `currentTier.basicRate` | number | 当前档基础费率 80 | ❌ 未定义 |
|
||||
| `currentTier.incentiveRate` | number | 当前档激励费率 95 | ❌ 未定义 |
|
||||
| `nextTier.basicRate` | number | 下一档基础费率 90 | ❌ 未定义 |
|
||||
| `nextTier.incentiveRate` | number | 下一档激励费率 114 | ❌ 未定义 |
|
||||
| `upgradeHoursNeeded` | number | 距升档所需工时 15 | ❌ 未定义 |
|
||||
| `upgradeBonus` | number | 升档奖金 800 | ❌ 未定义 |
|
||||
|
||||
#### 收入明细 incomeItems(内联 mock,4 项)
|
||||
| 项目 | icon | desc 格式 | 契约 PERF-1 |
|
||||
|------|------|----------|------------|
|
||||
| 基础课 | 🎱 | `80元/h × 75h` | ⚠️ 契约有 `income_items` 但字段不同 |
|
||||
| 激励课 | ⭐ | `95.05元/h × 10h` | 同上 |
|
||||
| 充值激励 | 💰 | `客户充值返佣` | 同上 |
|
||||
| TOP3 销冠奖 | 🏆 | `全店业绩前三名奖励` | 同上 |
|
||||
|
||||
契约 `income_items` 定义:`Array<{ label, amount, icon }>`,缺少 `desc` 字段。
|
||||
|
||||
#### 服务记录 thisMonthRecords(内联 mock,按日期分组)
|
||||
| 字段 | 类型 | 说明 | 契约 PERF-1 |
|
||||
|------|------|------|------------|
|
||||
| `date` | string | 日期标签 `'2月7日'` | ❌ 契约 `this_month_records` 结构完全不同 |
|
||||
| `totalHours` | string | 当日总工时 `'4.0h'` | ❌ |
|
||||
| `totalIncome` | string | 当日总收入 `'¥350'` | ❌ |
|
||||
| `records[].customerName` | string | 客户名 | ⚠️ 契约有 `customer_name` |
|
||||
| `records[].avatarChar` | string | 头像首字 | ❌ |
|
||||
| `records[].avatarColor` | string | 头像颜色 | ❌ |
|
||||
| `records[].timeRange` | string | 时间段 `'20:00-22:00'` | ❌ |
|
||||
| `records[].hours` | string | 工时 `'2.0h'` | ⚠️ 契约有 `hours` 但是 number |
|
||||
| `records[].courseType` | string | 课程类型 | ❌ |
|
||||
| `records[].courseTypeClass` | string | 样式类 | ❌ |
|
||||
| `records[].location` | string | 台号 | ❌ |
|
||||
| `records[].income` | string | 收入 `'¥160'` | ⚠️ 契约有 `income` 但是 number |
|
||||
|
||||
#### 新客列表 newCustomers
|
||||
| 字段 | 类型 | 说明 | 契约 PERF-1 |
|
||||
|------|------|------|------------|
|
||||
| `name` | string | 客户名 | ✅ |
|
||||
| `avatarChar` | string | 头像首字 | ❌ 契约有 `avatar_char` |
|
||||
| `avatarColor` | string | 头像颜色 | ❌ 未定义 |
|
||||
| `lastService` | string | 最后服务日期 | ❌ 未定义 |
|
||||
| `count` | number | 服务次数 | ❌ 未定义 |
|
||||
|
||||
#### 常客列表 regularCustomers
|
||||
| 字段 | 类型 | 说明 | 契约 PERF-1 |
|
||||
|------|------|------|------------|
|
||||
| `name` | string | 客户名 | ✅ |
|
||||
| `avatarChar` | string | 头像首字 | ❌ 契约有 `avatar_char` |
|
||||
| `avatarColor` | string | 头像颜色 | ❌ 未定义 |
|
||||
| `hours` | number | 总工时 | ❌ 未定义 |
|
||||
| `income` | string | 总收入 | ❌ 未定义 |
|
||||
| `count` | number | 到店次数 | ⚠️ 契约有 `visits` |
|
||||
|
||||
### 页面跳转
|
||||
| 操作 | 目标 | 参数 |
|
||||
|------|------|------|
|
||||
| 查看全部记录 | `/pages/performance-records/performance-records` | 无参数 |
|
||||
| 点击客户卡片 | `/pages/task-detail/task-detail` | `?customerName={name}` |
|
||||
| 点击服务记录 | `/pages/task-detail/task-detail` | `?customerName={name}&taskId={taskId}` |
|
||||
| 点击收入概览 | `/pages/performance-records/performance-records` | 无参数 |
|
||||
|
||||
### spec 未记录的 Gap
|
||||
- **GAP-12**:契约 PERF-1 `this_month_records` 是扁平数组 `Array<{ customer_name, hours, income, date }>`,但前端需要 **按日期分组的 DateGroup 结构**(含 `date`、`totalHours`、`totalIncome`、`records[]`),且每条记录需要 `timeRange`、`courseType`、`courseTypeClass`、`location` 等字段。契约需大幅扩展。
|
||||
- **GAP-13**:收入档位数据(`currentTier`、`nextTier`、`upgradeHoursNeeded`、`upgradeBonus`)在契约 PERF-1 中完全未定义。这些数据来自 `dws_assistant_salary_calc`,后端需返回。
|
||||
- **GAP-14**:`lastMonthIncome`(上月收入)在契约中未定义,前端 Banner 需要展示。
|
||||
- **GAP-15**:`incomeItems` 的 `desc` 字段(如 `"80元/h × 75h"`)在契约中未定义。后端需返回费率和工时的拆分数据,或直接返回格式化的 desc 字符串。
|
||||
- **GAP-16**:`newCustomers` 缺少 `lastService`(最后服务日期)和 `count`(服务次数)字段;`regularCustomers` 缺少 `hours`(总工时)和 `income`(总收入)字段。
|
||||
- **GAP-17**:跳转 task-detail 时传 `customerName` 而非 `taskId`,但 task-detail 页面 `onLoad` 读取的是 `options.id`。参数传递不匹配,联调时会导致页面无法加载正确数据。
|
||||
- **GAP-18**:页面无月份切换功能(F8),API 支持 `year/month` 参数但页面固定当前月。
|
||||
|
||||
---
|
||||
|
||||
## 场景 5:绩效明细页(performance-records.ts)
|
||||
|
||||
### API 调用
|
||||
- `fetchPerformanceRecords({ year, month })` → `GET /api/xcx/performance/records?year=&month=&page=&pageSize=`
|
||||
- 参数来源:`currentYear`/`currentMonth`(页面 data,支持月份切换)
|
||||
- Banner 数据从 `getApp().globalData.authUser` 读取
|
||||
|
||||
### 前端期望字段 — summary
|
||||
| 字段 | 类型 | 说明 | 契约 PERF-2 |
|
||||
|------|------|------|------------|
|
||||
| `totalCount` | number | 总笔数 32 | ✅ `total_count` |
|
||||
| `totalHours` | number | 总工时 59.0 | ✅ `total_hours` |
|
||||
| `totalHoursRaw` | number | 折算前总工时 63.5 | ✅ `total_hours_raw` |
|
||||
| `totalIncome` | number | 总收入 4720 | ✅ `total_income` |
|
||||
|
||||
### 前端期望字段 — dateGroups(按日期分组)
|
||||
| 字段 | 类型 | 说明 | 契约 PERF-2 |
|
||||
|------|------|------|------------|
|
||||
| `date` | string | 日期标签 `'2月7日'` | ✅ |
|
||||
| `totalHours` | number | 当日总工时 | ✅ |
|
||||
| `totalIncome` | number | 当日总收入 | ✅ |
|
||||
| `records[].id` | string | 记录 ID | ✅ |
|
||||
| `records[].customerName` | string | 客户名 | ✅ |
|
||||
| `records[].avatarChar` | string | 头像首字 | ❌ 未定义 |
|
||||
| `records[].avatarColor` | string | 头像颜色 | ❌ 未定义 |
|
||||
| `records[].timeRange` | string | 时间段 | ✅ |
|
||||
| `records[].hours` | number | 折算后工时 | ✅ |
|
||||
| `records[].hoursRaw` | number? | 折算前工时 | ✅ |
|
||||
| `records[].courseType` | string | 课程类型 | ✅ |
|
||||
| `records[].courseTypeClass` | string | 样式类 | ✅ |
|
||||
| `records[].location` | string | 台号 | ✅ |
|
||||
| `records[].income` | number | 收入 | ✅ |
|
||||
|
||||
### courseTypeClass 完整枚举
|
||||
前端使用的样式类:`tag-basic`(基础课)、`tag-vip`(包厢课)、`tag-tip`(打赏课)。
|
||||
契约定义的 `course_type_class`:未明确枚举值。
|
||||
|
||||
### spec 未记录的 Gap
|
||||
- **GAP-19**:`avatarChar`(头像首字)和 `avatarColor`(头像颜色)在契约 PERF-2 中未定义。前端通过 `nameToAvatarColor()` 工具函数从姓名生成,但后端是否需要返回?建议前端自行计算。
|
||||
- **GAP-20**:`courseTypeClass` 的完整枚举值未在契约中明确。前端使用 `tag-basic`/`tag-vip`/`tag-tip`,但后端返回的是 `basic`/`vip`/`tip` 还是带 `tag-` 前缀?需统一。
|
||||
- **GAP-21**:月份切换时未重置分页(F9 bug),`switchMonth()` 中 `page` 未重置为 1。
|
||||
- **GAP-22**:Banner 字段 `coachName`/`coachLevel`/`storeName` 从 `globalData.authUser` 读取,但 `authUser` 当前未存储 `coachLevel` 和 `storeName`(见 GAP-01)。
|
||||
|
||||
---
|
||||
|
||||
## 场景 6:客户详情页(customer-detail.ts)
|
||||
|
||||
### API 调用
|
||||
- `fetchCustomerDetail(customerId)` → `GET /api/xcx/customers/{customerId}`
|
||||
- 参数来源:页面路由参数(但当前代码从 `__route__` 解析,有 bug)
|
||||
|
||||
### 前端期望字段 — 完整清单
|
||||
|
||||
#### 客户基本信息(内联 mock)
|
||||
| 字段 | 类型 | 说明 | 契约 CUST-1 |
|
||||
|------|------|------|------------|
|
||||
| `id` | string | 客户 ID | ✅ |
|
||||
| `name` | string | 客户名 | ✅ |
|
||||
| `avatarChar` | string | 头像首字 | ❌ 未定义 |
|
||||
| `phone` | string | 手机号(完整) | ⚠️ 契约有 `phone`(脱敏)和 `phone_full`(完整) |
|
||||
| `balance` | string | 余额 `'8,600'` | ❌ 未定义 |
|
||||
| `consumption60d` | string | 近60天消费 `'2,800'` | ❌ 未定义 |
|
||||
| `idealInterval` | string | 理想到店间隔 `'7天'` | ❌ 未定义 |
|
||||
| `daysSinceVisit` | string | 距上次到店 `'12天'` | ❌ 未定义 |
|
||||
|
||||
#### AI 洞察 aiInsight(内联 mock)
|
||||
| 字段 | 类型 | 说明 | 契约 CUST-1 |
|
||||
|------|------|------|------------|
|
||||
| `summary` | string | AI 分析摘要 | ❌ 未定义 |
|
||||
| `strategies` | Array<{color, text}> | 策略建议列表 | ❌ 未定义 |
|
||||
|
||||
#### 维客线索 clues(内联 mock,7 条)
|
||||
| 字段 | 类型 | 说明 | 契约 CUST-1 |
|
||||
|------|------|------|------------|
|
||||
| `category` | string | 线索大类(含换行符) | ⚠️ 契约说"同 TASK-2 格式" |
|
||||
| `categoryColor` | string | 颜色 | ⚠️ |
|
||||
| `text` | string | 含 emoji 的摘要 | ⚠️ |
|
||||
| `source` | string | 来源 `'系统'`/`'小燕'` | ⚠️ |
|
||||
| `detail` | string? | 详细描述 | ⚠️ |
|
||||
|
||||
#### 关联助教 coachTasks(内联 mock,4 位助教)
|
||||
| 字段 | 类型 | 说明 | 契约 CUST-1 |
|
||||
|------|------|------|------------|
|
||||
| `name` | string | 助教名 | ❌ 完全未定义 |
|
||||
| `level` | string | 等级 `senior/middle/junior` | ❌ |
|
||||
| `levelColor` | string | 等级颜色 | ❌ |
|
||||
| `taskType` | string | 任务类型标签 | ❌ |
|
||||
| `taskColor` | string | 任务颜色 | ❌ |
|
||||
| `bgClass` | string | 背景样式类 | ❌ |
|
||||
| `status` | string | 状态 `normal/pinned/abandoned` | ❌ |
|
||||
| `lastService` | string | 最后服务 `'02-20 21:30 · 2.5h'` | ❌ |
|
||||
| `metrics` | Array<{label,value,color}> | 指标(近60天次数/总时长/次均时长) | ❌ |
|
||||
|
||||
#### 最亲密助教 favoriteCoaches(内联 mock,2 位)
|
||||
| 字段 | 类型 | 说明 | 契约 CUST-1 |
|
||||
|------|------|------|------------|
|
||||
| `emoji` | string | 爱心 emoji | ❌ 完全未定义 |
|
||||
| `name` | string | 助教名 | ❌ |
|
||||
| `relationIndex` | string | 关系指数 `'9.2'` | ❌ |
|
||||
| `indexColor` | string | 指数颜色 | ❌ |
|
||||
| `bgClass` | string | 背景样式类 | ❌ |
|
||||
| `stats` | Array<{label,value,color}> | 统计(基础/激励/上课/充值) | ❌ |
|
||||
|
||||
#### 消费记录 consumptionRecords(内联 mock,3 条,3 种类型)
|
||||
| 字段 | 类型 | 说明 | 契约 CUST-1 |
|
||||
|------|------|------|------------|
|
||||
| `id` | string | 记录 ID | ✅ |
|
||||
| `type` | enum | `table/shop/recharge` | ⚠️ 契约有 `type` 但枚举不同 |
|
||||
| `date` | string | 日期 | ✅ |
|
||||
| `tableName` | string? | 台桌名 | ❌ 契约有 `table` |
|
||||
| `startTime` | string? | 开始时间 | ❌ 未定义 |
|
||||
| `endTime` | string? | 结束时间 | ❌ 未定义 |
|
||||
| `duration` | string? | 时长 `'3h 20min'` | ⚠️ 契约有 `duration` 但是 number |
|
||||
| `tableFee` | number? | 台费 | ❌ 未定义 |
|
||||
| `tableOrigPrice` | number? | 台费原价 | ❌ 未定义 |
|
||||
| `coaches` | Array<{name,level,levelColor,courseType,hours,perfHours?,fee}> | 助教服务明细 | ❌ 完全未定义 |
|
||||
| `foodAmount` | number? | 酒水金额 | ❌ 未定义 |
|
||||
| `foodOrigPrice` | number? | 酒水原价 | ❌ 未定义 |
|
||||
| `totalAmount` | number? | 总金额 | ⚠️ 契约有 `amount` |
|
||||
| `totalOrigPrice` | number? | 总原价 | ❌ 未定义 |
|
||||
| `payMethod` | string? | 支付方式 | ❌ 未定义 |
|
||||
| `rechargeAmount` | number? | 充值金额 | ❌ 未定义 |
|
||||
|
||||
#### 备注 sortedNotes(内联 mock,3 条)
|
||||
| 字段 | 类型 | 说明 | 契约 CUST-1 |
|
||||
|------|------|------|------------|
|
||||
| `id` | string | 备注 ID | ❌ 契约无备注字段 |
|
||||
| `tagLabel` | string | 标签 | ❌ |
|
||||
| `createdAt` | string | 创建时间 | ❌ |
|
||||
| `content` | string | 内容 | ❌ |
|
||||
|
||||
### 页面跳转
|
||||
| 操作 | 目标 | 参数 |
|
||||
|------|------|------|
|
||||
| 查看服务记录 | `/pages/customer-service-records/customer-service-records` | 无参数(⚠️ 未传 customerId) |
|
||||
| 问问助手 | `/pages/chat/chat` | 无参数(⚠️ 未传 customerId) |
|
||||
|
||||
### spec 未记录的 Gap
|
||||
- **GAP-23**:`balance`(余额)、`consumption60d`(近60天消费)、`idealInterval`(理想到店间隔)、`daysSinceVisit`(距上次到店天数)在契约 CUST-1 中完全未定义。这些是客户详情页 Banner 的核心展示字段。
|
||||
- **GAP-24**:`aiInsight`(AI 洞察:summary + strategies)在契约 CUST-1 中完全未定义。数据来源应为 `biz.ai_cache`,但 `cache_type` 未明确(可能是 `app4_analysis` 或新增类型)。
|
||||
- **GAP-25**:`coachTasks`(关联助教任务列表)在契约 CUST-1 中完全未定义。这是一个复杂的数据结构,需要从 `biz.coach_tasks` + `fdw_etl.v_dwd_assistant_service_log` 聚合。每位助教需返回:任务类型、状态、最后服务时间、近60天服务次数/总时长/次均时长。
|
||||
- **GAP-26**:`favoriteCoaches`(最亲密助教)在契约 CUST-1 中完全未定义。需要从 `fdw_etl.v_dws_member_assistant_relation_index` 获取关系指数,并聚合基础课时/激励课时/上课次数/充值金额。
|
||||
- **GAP-27**:消费记录的 `coaches` 子数组(助教服务明细,含 `perfHours` 折算工时)在契约中完全未定义。这需要从 `fdw_etl.v_dwd_assistant_service_log` 按结算单关联查询。
|
||||
- **GAP-28**:消费记录的 `tableFee`/`tableOrigPrice`/`foodAmount`/`foodOrigPrice`/`totalOrigPrice`/`payMethod` 等拆分字段在契约中未定义。契约仅有 `amount`(总金额)。
|
||||
- **GAP-29**:消费记录的 `type` 枚举不一致 — 前端使用 `table/shop/recharge`(3 种),契约使用 `type_class`(未明确枚举)。
|
||||
- **GAP-30**:备注列表在契约 CUST-1 中未定义。前端需要展示客户相关的备注。
|
||||
- **GAP-31**:跳转 customer-service-records 和 chat 时未传 `customerId` 参数,联调时目标页面无法获取客户 ID。
|
||||
|
||||
---
|
||||
|
||||
## 场景 7:客户服务记录页(customer-service-records.ts)
|
||||
|
||||
### API 调用
|
||||
- `fetchCustomerRecords({ customerId, year?, month?, table? })` → `GET /api/xcx/customers/{customerId}/records`
|
||||
- 参数来源:`options.customerId` 或 `options.id`
|
||||
- 当前实现:首次加载拉取全部记录,月份切换仅本地过滤
|
||||
|
||||
### 前端期望字段 — ServiceRecord
|
||||
| 字段 | 类型 | 说明 | 契约 CUST-2 |
|
||||
|------|------|------|------------|
|
||||
| `table` | string | 台桌号 | ✅ |
|
||||
| `type` | string | 课程类型标签 | ✅ |
|
||||
| `typeClass` | enum | `basic/vip/tip/recharge` | ✅ `type_class` |
|
||||
| `recordType` | enum | `course/recharge` | ❌ 未定义 |
|
||||
| `duration` | number | 折算后小时 | ✅ |
|
||||
| `durationRaw` | number | 折算前小时 | ✅ `duration_raw` |
|
||||
| `income` | number | 到手金额 | ✅ |
|
||||
| `isEstimate` | boolean | 是否预估 | ❌ 未定义 |
|
||||
| `drinks` | string | 饮品描述 | ✅ |
|
||||
| `date` | string | 显示日期 | ✅ |
|
||||
|
||||
### 页面头部字段
|
||||
| 字段 | 类型 | 说明 | 契约 CUST-2 |
|
||||
|------|------|------|------------|
|
||||
| `customerName` | string | 客户名 | ✅ |
|
||||
| `customerPhone` | string | 脱敏手机号 | ✅ `customer_phone` |
|
||||
| `customerPhoneFull` | string | 完整手机号 | ❌ 未定义(需从 CUST-1 获取) |
|
||||
| `relationIndex` | string | 关系指数 | ✅ |
|
||||
| `totalServiceCount` | number | 累计服务次数 | ❌ 未定义 |
|
||||
| `monthCount` | string | 当月次数 | ❌ 需前端计算 |
|
||||
| `monthHours` | string | 当月工时 | ❌ 需前端计算 |
|
||||
| `monthRelation` | string | 当月关系指数 | ❌ 未定义 |
|
||||
|
||||
### spec 未记录的 Gap
|
||||
- **GAP-32**:`recordType`(`course/recharge`)和 `isEstimate`(是否预估)在契约 CUST-2 中未定义。
|
||||
- **GAP-33**:`customerPhoneFull`(完整手机号)在 CUST-2 响应中未定义,前端需要点击查看完整号码。
|
||||
- **GAP-34**:`totalServiceCount`(累计服务次数)在 CUST-2 中未定义。
|
||||
- **GAP-35**:月份切换采用本地筛选(F10),联调后数据量大时需改为按月请求 API。
|
||||
|
||||
---
|
||||
|
||||
## 场景 8:备注列表页(notes.ts)
|
||||
|
||||
### API 调用
|
||||
- `fetchNotes({ page?, pageSize? })` → `GET /api/xcx/notes?page=&pageSize=`
|
||||
- `deleteNote(noteId)` → `DELETE /api/xcx/notes/{noteId}`
|
||||
|
||||
### 前端期望字段 — Note
|
||||
| 字段 | 类型 | 说明 | 契约 NOTE-1 |
|
||||
|------|------|------|------------|
|
||||
| `id` | string | 备注 ID | ✅ |
|
||||
| `content` | string | 备注内容 | ✅ |
|
||||
| `tagType` | enum | `customer/coach/system` | ✅ `tag_type` |
|
||||
| `tagLabel` | string | 标签文案 | ✅ `tag_label` |
|
||||
| `createdAt` | string | 创建时间 | ✅ `created_at` |
|
||||
| `score` | number? | 满意度评分 | ✅ |
|
||||
| `timeLabel` | string | 格式化时间(前端计算) | — |
|
||||
|
||||
### 写操作
|
||||
| 操作 | API | 成功后行为 |
|
||||
|------|-----|-----------|
|
||||
| 删除备注 | `DELETE /notes/{noteId}` | 本地 filter 移除 + Toast |
|
||||
|
||||
### spec 未记录的 Gap
|
||||
- **GAP-36**:无触底加载逻辑(F11),API 支持分页但 `onReachBottom()` 未实现。
|
||||
- **GAP-37**:`tagType` 枚举 — mock 数据中有 `'system'` 类型,但契约 NOTE-1 仅定义 `customer/coach/system`。需确认 `system` 类型的备注由谁创建、如何展示。
|
||||
|
||||
---
|
||||
|
||||
## 场景 9:助教详情页(coach-detail.ts)
|
||||
|
||||
### API 调用
|
||||
- `fetchCoachDetail(coachId)` → `GET /api/xcx/coaches/{coachId}`
|
||||
- 参数来源:`options.id`
|
||||
- 返回类型:`CoachCard`(api.ts 定义),但前端实际需要远超 `CoachCard` 的数据
|
||||
|
||||
### 前端期望字段 — CoachDetail(完整清单)
|
||||
|
||||
#### 基本信息
|
||||
| 字段 | 类型 | 说明 | 契约 COACH-1 |
|
||||
|------|------|------|-------------|
|
||||
| `id` | string | 助教 ID | ✅ |
|
||||
| `name` | string | 助教名 | ✅ |
|
||||
| `avatar` | string | 头像 | ✅ |
|
||||
| `level` | string | 等级 `'星级'` | ✅ |
|
||||
| `skills` | string[] | 技能 | ✅ |
|
||||
| `workYears` | number | 工龄 3 | ❌ 未定义 |
|
||||
| `customerCount` | number | 客户数 68 | ❌ 未定义 |
|
||||
| `hireDate` | string | 入职日期 | ❌ 未定义 |
|
||||
| `store_name` | string | 门店名 | ✅ |
|
||||
|
||||
#### 绩效指标 performance(6 个指标)
|
||||
| 字段 | 类型 | 说明 | 契约 COACH-1 |
|
||||
|------|------|------|-------------|
|
||||
| `monthlyHours` | number | 本月工时 87.5 | ❌ 未定义 |
|
||||
| `monthlySalary` | number | 本月工资 6950 | ❌ 未定义 |
|
||||
| `customerBalance` | number | 客源储值余额 86200 | ❌ 未定义 |
|
||||
| `tasksCompleted` | number | 任务完成数 38 | ❌ 未定义 |
|
||||
| `perfCurrent` | number | 当前绩效 80 | ❌ 未定义 |
|
||||
| `perfTarget` | number | 目标绩效 100 | ❌ 未定义 |
|
||||
|
||||
#### 绩效指标卡片 perfCards(4 张)
|
||||
| 卡片 | 需要字段 | 契约 |
|
||||
|------|---------|------|
|
||||
| 本月定档业绩 | `monthlyHours` + 折算前工时 | ❌ |
|
||||
| 本月工资(预估) | `monthlySalary` | ❌ |
|
||||
| 客源储值余额 | `customerBalance` + `customerCount` | ❌ |
|
||||
| 本月任务完成 | `tasksCompleted` + 覆盖客户数 | ❌ |
|
||||
|
||||
#### 进度条数据
|
||||
| 字段 | 类型 | 说明 | 契约 |
|
||||
|------|------|------|------|
|
||||
| `tierNodes` | number[] | 档位节点 `[0,100,130,160,190,220]` | ❌ 未定义 |
|
||||
|
||||
#### 收入明细 income(本月/上月各 4 项)
|
||||
| 项目 | 说明 | 契约 |
|
||||
|------|------|------|
|
||||
| 基础课时费 | `¥3,500` | ❌ 未定义 |
|
||||
| 激励课时费 | `¥1,800` | ❌ 未定义 |
|
||||
| 充值提成 | `¥1,200` | ❌ 未定义 |
|
||||
| 酒水提成 | `¥450` | ❌ 未定义 |
|
||||
|
||||
#### 任务执行
|
||||
| 字段 | 类型 | 说明 | 契约 COACH-1 |
|
||||
|------|------|------|-------------|
|
||||
| `visibleTasks` | TaskItem[] | 可见任务(active) | ⚠️ 契约说"同 TASK-1 item 格式"但实际需要更多字段 |
|
||||
| `hiddenTasks` | TaskItem[] | 隐藏任务(inactive) | ⚠️ |
|
||||
| `abandonedTasks` | AbandonedTask[] | 已放弃任务 | ⚠️ |
|
||||
|
||||
TaskItem 需要的字段(超出 TASK-1 item):
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `typeLabel` | string | 任务类型标签 |
|
||||
| `typeClass` | string | 样式类 `high-priority/priority/relationship/callback` |
|
||||
| `customerName` | string | 客户名 |
|
||||
| `noteCount` | number | 备注数量 |
|
||||
| `pinned` | boolean | 是否置顶 |
|
||||
| `notes` | Array<{pinned?,text,date}> | 备注列表(含置顶标记) |
|
||||
|
||||
AbandonedTask 字段:
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `customerName` | string | 客户名 |
|
||||
| `reason` | string | 放弃原因 |
|
||||
|
||||
#### 客户关系 TOP20 — TopCustomer
|
||||
| 字段 | 类型 | 说明 | 契约 COACH-1 |
|
||||
|------|------|------|-------------|
|
||||
| `id` | string | 客户 ID | ✅ |
|
||||
| `name` | string | 客户名 | ✅ |
|
||||
| `initial` | string | 姓名首字 | ❌ |
|
||||
| `avatarGradient` | string | 头像渐变色 | ❌ |
|
||||
| `heartEmoji` | string | 爱心 emoji `❤️/💛/🤍` | ❌ |
|
||||
| `score` | string | 关系指数 `'9.5'` | ❌ 契约有 `total_spend`/`visit_count` 但无 score |
|
||||
| `scoreColor` | string | 指数颜色 | ❌ |
|
||||
| `serviceCount` | number | 服务次数 | ⚠️ 契约有 `visit_count` |
|
||||
| `balance` | string | 余额 `'¥8,600'` | ❌ 未定义 |
|
||||
| `consume` | string | 消费 `'¥12,800'` | ⚠️ 契约有 `total_spend` |
|
||||
|
||||
#### 近期服务明细 — ServiceRecord
|
||||
| 字段 | 类型 | 说明 | 契约 COACH-1 |
|
||||
|------|------|------|-------------|
|
||||
| `customerId` | string? | 客户 ID | ❌ |
|
||||
| `customerName` | string | 客户名 | ⚠️ |
|
||||
| `initial` | string | 姓名首字 | ❌ |
|
||||
| `avatarGradient` | string | 头像渐变色 | ❌ |
|
||||
| `type` | string | 课程类型 | ⚠️ |
|
||||
| `typeClass` | string | 样式类 | ⚠️ |
|
||||
| `table` | string | 台号 | ⚠️ |
|
||||
| `duration` | string | 时长 `'2.5h'` | ⚠️ |
|
||||
| `income` | string | 收入 `'¥200'` | ⚠️ |
|
||||
| `date` | string | 日期时间 | ⚠️ |
|
||||
| `perfHours` | string? | 折算工时 `'2h'` | ❌ 未定义 |
|
||||
|
||||
#### 历史月份统计 — HistoryMonth
|
||||
| 字段 | 类型 | 说明 | 契约 COACH-1 |
|
||||
|------|------|------|-------------|
|
||||
| `month` | string | 月份标签 `'本月'`/`'上月'`/`'4月'` | ❌ 完全未定义 |
|
||||
| `estimated` | boolean | 是否预估 | ❌ |
|
||||
| `customers` | string | 客户数 `'22人'` | ❌ |
|
||||
| `hours` | string | 工时 `'87.5h'` | ❌ |
|
||||
| `salary` | string | 工资 `'¥6,950'` | ❌ |
|
||||
| `callbackDone` | number | 回访完成数 | ❌ |
|
||||
| `recallDone` | number | 召回完成数 | ❌ |
|
||||
|
||||
### 页面跳转
|
||||
| 操作 | 目标 | 参数 |
|
||||
|------|------|------|
|
||||
| 点击任务项 | `/pages/customer-detail/customer-detail` | `?name={customerName}` |
|
||||
| 点击客户卡片 | `/pages/customer-detail/customer-detail` | `?id={customerId}` |
|
||||
| 点击服务记录 | `/pages/customer-detail/customer-detail` | `?id={customerId}` |
|
||||
| 查看更多服务记录 | `/pages/performance-records/performance-records` | `?coachId={coachId}` |
|
||||
| 问问助手 | `/pages/chat/chat` | `?coachId={coachId}` |
|
||||
|
||||
### spec 未记录的 Gap
|
||||
- **GAP-38**:契约 COACH-1 严重不完整。前端需要 `performance`(6 个绩效指标)、`income`(本月/上月各 4 项收入明细)、`tierNodes`(档位节点数组),契约均未定义。
|
||||
- **GAP-39**:`TopCustomer` 需要 `heartEmoji`、`score`(关系指数)、`scoreColor`、`balance`(余额)、`consume`(消费),契约仅有 `total_spend`/`visit_count`。
|
||||
- **GAP-40**:`ServiceRecord.perfHours`(折算工时)在契约中未定义。
|
||||
- **GAP-41**:`HistoryMonth`(历史月份统计)在契约中完全未定义。需要返回最近 N 个月的汇总数据(客户数、工时、工资、回访/召回完成数)。
|
||||
- **GAP-42**:`TaskItem.notes`(任务关联的备注列表,含置顶标记)在契约中未定义。契约的 `visible_tasks` 说"同 TASK-1 item 格式",但前端需要额外的 `noteCount`、`pinned`、`notes[]` 字段。
|
||||
- **GAP-43**:`AbandonedTask.reason`(放弃原因)在契约中未定义。
|
||||
- **GAP-44**:`workYears`(工龄)、`hireDate`(入职日期)在契约中未定义。
|
||||
|
||||
---
|
||||
|
||||
## 场景 10:对话页面(chat.ts + chat-history.ts)
|
||||
|
||||
### chat.ts — API 调用
|
||||
- `fetchChatMessages(customerId)` → `GET /api/xcx/chat/{chatId}/messages`
|
||||
- ⚠️ 注意:前端传入的是 `customerId`(或 `'default'`),但 API 路径参数是 `chatId`。参数语义不匹配。
|
||||
- `sendChatMessage(chatId, content)` → `POST /api/xcx/chat/{chatId}/messages`
|
||||
- 页面参数:`options.customerId`
|
||||
|
||||
### chat.ts — 前端期望字段 — ChatMessage
|
||||
| 字段 | 类型 | 说明 | 契约 CHAT-2 |
|
||||
|------|------|------|------------|
|
||||
| `id` | string | 消息 ID | ✅ |
|
||||
| `role` | enum | `user/assistant` | ✅ |
|
||||
| `content` | string | 消息内容 | ✅ |
|
||||
| `timestamp` | string | 时间戳 | ⚠️ 契约用 `created_at` |
|
||||
| `referenceCard` | object? | 引用卡片 | ❌ 完全未定义 |
|
||||
| `referenceCard.type` | enum | `customer/record` | ❌ |
|
||||
| `referenceCard.title` | string | 卡片标题 | ❌ |
|
||||
| `referenceCard.summary` | string | 卡片摘要 | ❌ |
|
||||
| `referenceCard.data` | Record<string,string> | 键值对数据 | ❌ |
|
||||
|
||||
### chat.ts — SSE 流式回复
|
||||
- 前端 `simulateStreamOutput()` 模拟流式输出(逐字显示)
|
||||
- 联调时需替换为真实 SSE 连接(`POST /api/xcx/chat/stream`)
|
||||
- spec R3 已确认保留 SSE 流式端点
|
||||
|
||||
### chat.ts — 页面参数
|
||||
| 来源页面 | 传递参数 | 用途 |
|
||||
|---------|---------|------|
|
||||
| task-detail | `?customerId={taskId}` | ⚠️ 实际传的是 taskId |
|
||||
| customer-detail | 无参数 | ⚠️ 未传 customerId |
|
||||
| coach-detail | `?coachId={coachId}` | 助教 ID |
|
||||
| chat-history | `?historyId={id}` | 历史对话 ID |
|
||||
|
||||
### chat-history.ts — API 调用
|
||||
- `fetchChatHistory({ page?, pageSize? })` → `GET /api/xcx/chat/history`
|
||||
- 返回:`{ items: ChatHistoryItem[], total, hasMore }`
|
||||
|
||||
### chat-history.ts — 前端期望字段 — ChatHistoryItem
|
||||
| 字段 | 类型 | 说明 | 契约 CHAT-1 |
|
||||
|------|------|------|------------|
|
||||
| `id` | string | 对话 ID | ✅ |
|
||||
| `title` | string | 对话标题 | ❌ 契约无 `title` |
|
||||
| `lastMessage` | string | 最后消息 | ✅ `last_message` |
|
||||
| `timestamp` | string | 时间戳 | ⚠️ 契约用 `last_time` |
|
||||
| `customerName` | string? | 关联客户名 | ✅ |
|
||||
|
||||
### chat-history.ts — 页面跳转
|
||||
| 操作 | 目标 | 参数 |
|
||||
|------|------|------|
|
||||
| 点击对话记录 | `/pages/chat/chat` | `?historyId={id}` |
|
||||
|
||||
### spec 未记录的 Gap
|
||||
- **GAP-45**:`ChatMessage.referenceCard`(引用卡片)在契约 CHAT-2 中完全未定义。前端需要展示客户概览卡片(含键值对数据),后端需在消息中附带结构化引用数据。
|
||||
- **GAP-46**:`ChatMessage.timestamp` vs 契约 `created_at` — 字段名不一致,前端使用 `timestamp`,契约定义 `created_at`。
|
||||
- **GAP-47**:`ChatHistoryItem.title`(对话标题)在契约 CHAT-1 中未定义。契约仅有 `customer_name`/`last_message`/`last_time`/`unread_count`。
|
||||
- **GAP-48**:`ChatHistoryItem.timestamp` vs 契约 `last_time` — 字段名不一致。
|
||||
- **GAP-49**:chat 页面接收 `customerId` 参数但 API 路径需要 `chatId`。前端需要一个"根据 customerId 查找或创建对话"的机制,或 API 支持 `customerId` 作为查询参数。
|
||||
- **GAP-50**:chat 页面还接收 `historyId`(从 chat-history 跳转)和 `coachId`(从 coach-detail 跳转),但 `loadMessages()` 仅使用 `customerId`。多种入口参数的路由逻辑未实现。
|
||||
- **GAP-51**:SSE 流式端点 `POST /api/xcx/chat/stream` 在契约中未定义(spec R3 已确认需补充)。
|
||||
|
||||
---
|
||||
|
||||
## 汇总:Gap 清单
|
||||
|
||||
### 一、架构级 Gap(影响多个页面)
|
||||
|
||||
| # | Gap | 严重度 | 影响页面 | 说明 |
|
||||
|---|-----|--------|---------|------|
|
||||
| GAP-01 | globalData.authUser 缺少 role/store_name/coach_level/avatar | 🟠 中 | task-list, performance, performance-records | 多个页面 Banner 需要这些字段,当前硬编码 |
|
||||
| GAP-04 | pin/unpin API 端点未在契约中定义 | 🔴 高 | task-list | api.ts 无对应函数,前端仅本地更新 |
|
||||
| GAP-05 | createNote 缺少 score 参数 | 🟠 中 | task-list, task-detail | 前端传 score 但 api.ts 签名无此字段 |
|
||||
| GAP-09/10 | task-detail 跳转 chat/customer-service-records 时传 taskId 而非 customerId | 🔴 高 | task-detail → chat/customer-service-records | TASK-2 响应需包含 customer_id |
|
||||
| GAP-31 | customer-detail 跳转时未传 customerId | 🔴 高 | customer-detail → chat/customer-service-records | 联调时目标页面无法获取客户 ID |
|
||||
| GAP-17 | performance 跳转 task-detail 传 customerName 而非 taskId | 🟠 中 | performance → task-detail | 参数不匹配 |
|
||||
|
||||
### 二、TASK-1 扩展 Gap(任务列表绩效卡片)
|
||||
|
||||
| # | Gap | 严重度 | 说明 |
|
||||
|---|-----|--------|------|
|
||||
| GAP-02 | performance 字段严重不足(需 15+ 字段,契约仅 4 个) | 🔴 高 | tierNodes、basicHours、bonusHours、currentTier、nextTierHours、tierCompleted、bonusMoney、incomeTrend、incomeTrendDir、prevMonth 等 |
|
||||
| GAP-03 | enrichTask 需要 lastVisitDays/balance/aiSuggestion | 🟠 中 | 任务卡片扩展字段 |
|
||||
|
||||
### 三、PERF-1 Gap(绩效概览)
|
||||
|
||||
| # | Gap | 严重度 | 说明 |
|
||||
|---|-----|--------|------|
|
||||
| GAP-12 | this_month_records 需要 DateGroup 分组结构 | 🔴 高 | 契约是扁平数组,前端需按日期分组 + timeRange/courseType/location |
|
||||
| GAP-13 | 收入档位数据完全未定义 | 🔴 高 | currentTier/nextTier/upgradeHoursNeeded/upgradeBonus |
|
||||
| GAP-14 | lastMonthIncome 未定义 | 🟠 中 | Banner 需展示上月收入 |
|
||||
| GAP-15 | incomeItems.desc 未定义 | 🟠 中 | 需费率×工时的拆分描述 |
|
||||
| GAP-16 | newCustomers/regularCustomers 缺少多个字段 | 🟠 中 | lastService/count/hours/income |
|
||||
| GAP-18 | 页面无月份切换功能 | 🟡 低 | F8 前端修复项 |
|
||||
|
||||
### 四、CUST-1 Gap(客户详情 — 最大 Gap 集中区)
|
||||
|
||||
| # | Gap | 严重度 | 说明 |
|
||||
|---|-----|--------|------|
|
||||
| GAP-23 | balance/consumption60d/idealInterval/daysSinceVisit 未定义 | 🔴 高 | Banner 核心展示字段 |
|
||||
| GAP-24 | aiInsight (summary + strategies) 未定义 | 🔴 高 | AI 洞察模块 |
|
||||
| GAP-25 | coachTasks(关联助教任务列表)未定义 | 🔴 高 | 复杂数据结构,需多表聚合 |
|
||||
| GAP-26 | favoriteCoaches(最亲密助教)未定义 | 🔴 高 | 需关系指数 + 服务统计 |
|
||||
| GAP-27 | 消费记录 coaches 子数组(助教服务明细)未定义 | 🔴 高 | 含 perfHours 折算工时 |
|
||||
| GAP-28 | 消费记录拆分字段(tableFee/foodAmount/totalOrigPrice 等)未定义 | 🟠 中 | 契约仅有 amount |
|
||||
| GAP-29 | 消费记录 type 枚举不一致 | 🟠 中 | 前端 table/shop/recharge vs 契约未明确 |
|
||||
| GAP-30 | 备注列表未定义 | 🟠 中 | 客户详情需展示备注 |
|
||||
|
||||
### 五、COACH-1 Gap(助教详情 — 第二大 Gap 集中区)
|
||||
|
||||
| # | Gap | 严重度 | 说明 |
|
||||
|---|-----|--------|------|
|
||||
| GAP-38 | performance/income/tierNodes 未定义 | 🔴 高 | 6 个绩效指标 + 本月/上月各 4 项收入 + 档位节点 |
|
||||
| GAP-39 | TopCustomer 缺少 heartEmoji/score/scoreColor/balance | 🔴 高 | 客户关系 TOP20 核心字段 |
|
||||
| GAP-40 | ServiceRecord.perfHours 未定义 | 🟠 中 | 折算工时 |
|
||||
| GAP-41 | HistoryMonth 完全未定义 | 🔴 高 | 历史月份统计(5+ 个月) |
|
||||
| GAP-42 | TaskItem.notes(含置顶标记)未定义 | 🟠 中 | 任务关联备注 |
|
||||
| GAP-43 | AbandonedTask.reason 未定义 | 🟡 低 | 放弃原因 |
|
||||
| GAP-44 | workYears/hireDate 未定义 | 🟡 低 | 助教基本信息 |
|
||||
|
||||
### 六、CHAT 模块 Gap
|
||||
|
||||
| # | Gap | 严重度 | 说明 |
|
||||
|---|-----|--------|------|
|
||||
| GAP-45 | ChatMessage.referenceCard 未定义 | 🔴 高 | 引用卡片(客户概览数据) |
|
||||
| GAP-46 | timestamp vs created_at 字段名不一致 | 🟠 中 | 前端用 timestamp,契约用 created_at |
|
||||
| GAP-47 | ChatHistoryItem.title 未定义 | 🟠 中 | 对话标题 |
|
||||
| GAP-48 | ChatHistoryItem.timestamp vs last_time 不一致 | 🟠 中 | 字段名不一致 |
|
||||
| GAP-49 | customerId → chatId 映射机制缺失 | 🔴 高 | 前端传 customerId 但 API 需要 chatId |
|
||||
| GAP-50 | 多入口参数路由逻辑未实现 | 🟠 中 | historyId/coachId/customerId 三种入口 |
|
||||
| GAP-51 | SSE 流式端点未在契约中定义 | 🟠 中 | spec 已确认需补充 |
|
||||
|
||||
### 七、其他 Gap
|
||||
|
||||
| # | Gap | 严重度 | 说明 |
|
||||
|---|-----|--------|------|
|
||||
| GAP-06 | 维客线索 tag 格式不一致(含换行符) | 🟡 低 | 需统一 |
|
||||
| GAP-07 | 维客线索 source 格式不一致 | 🟡 低 | mock 用 `'By:系统'`,类型定义用 `'manual'` |
|
||||
| GAP-08 | aiAnalysis/talkingPoints 的 ai_cache cache_type 未明确 | 🟠 中 | 需明确映射 |
|
||||
| GAP-11 | storageLevel/relationLevel 计算逻辑未明确 | 🟡 低 | 前端本地计算 |
|
||||
| GAP-19 | avatarChar/avatarColor 是否后端返回 | 🟡 低 | 建议前端自行计算 |
|
||||
| GAP-20 | courseTypeClass 枚举值前缀不统一 | 🟡 低 | `tag-basic` vs `basic` |
|
||||
| GAP-21 | 月份切换未重置分页 | 🟡 低 | F9 前端 bug |
|
||||
| GAP-22 | performance-records Banner 字段来源 | 🟡 低 | 依赖 GAP-01 |
|
||||
| GAP-32 | recordType/isEstimate 未定义 | 🟡 低 | CUST-2 扩展字段 |
|
||||
| GAP-33 | customerPhoneFull 未定义 | 🟡 低 | CUST-2 需完整手机号 |
|
||||
| GAP-34 | totalServiceCount 未定义 | 🟡 低 | CUST-2 累计次数 |
|
||||
| GAP-35 | 月份切换本地筛选需改 API 请求 | 🟡 低 | F10 前端修复 |
|
||||
| GAP-36 | notes 页无触底加载 | 🟡 低 | F11 前端修复 |
|
||||
| GAP-37 | Note.tagType 'system' 类型语义 | 🟡 低 | 需确认 |
|
||||
|
||||
---
|
||||
|
||||
### 统计
|
||||
|
||||
| 严重度 | 数量 | 说明 |
|
||||
|--------|------|------|
|
||||
| 🔴 高 | 17 | 阻塞联调,必须在后端开发前解决 |
|
||||
| 🟠 中 | 19 | 影响功能完整性,需在联调前补充 |
|
||||
| 🟡 低 | 15 | 可联调后修复或前端自行处理 |
|
||||
| **合计** | **51** | |
|
||||
|
||||
### 最高优先级修复建议
|
||||
|
||||
1. **契约大幅扩展 CUST-1**:客户详情是 Gap 最集中的页面(GAP-23~30),需新增 `balance`、`consumption60d`、`idealInterval`、`daysSinceVisit`、`aiInsight`、`coachTasks`、`favoriteCoaches`、消费记录拆分字段、备注列表。
|
||||
2. **契约大幅扩展 COACH-1**:助教详情是第二大 Gap 集中区(GAP-38~44),需新增 `performance`(6 指标)、`income`(8 项)、`tierNodes`、`TopCustomer` 扩展字段、`HistoryMonth`、`ServiceRecord.perfHours`。
|
||||
3. **契约扩展 PERF-1**:绩效概览需要 DateGroup 分组结构(GAP-12)和收入档位数据(GAP-13)。
|
||||
4. **契约扩展 TASK-1 performance**:绩效卡片需要 15+ 字段(GAP-02)。
|
||||
5. **修复 customerId/taskId 混淆**:TASK-2 响应需包含 `customer_id`(GAP-09/10),customer-detail 跳转需传参(GAP-31)。
|
||||
6. **补充 CHAT 模块**:referenceCard(GAP-45)、customerId→chatId 映射(GAP-49)、SSE 端点定义(GAP-51)。
|
||||
236
docs/prd/specs/P12-gift-card-breakdown.md
Normal file
236
docs/prd/specs/P12-gift-card-breakdown.md
Normal file
@@ -0,0 +1,236 @@
|
||||
# P12:赠送卡矩阵细分数据 — gift-card-breakdown
|
||||
|
||||
> 优先级:P1(BOARD-3 财务看板功能缺陷修复)
|
||||
> 来源:RNS1 系列审计遗留项 P1-6
|
||||
> 预估工作量:中
|
||||
> 依赖:无新增依赖(DWD 层数据已就绪)
|
||||
|
||||
---
|
||||
|
||||
## 背景
|
||||
|
||||
BOARD-3 财务看板的「预收资产」板块包含一个赠送卡 3×4 矩阵(3 行:新增/消费/余额;4 列:合计/酒水卡/台费卡/抵用券)。当前矩阵除余额行的 total 列外,所有细分单元格均返回 0。
|
||||
|
||||
根因:DWS 层 `dws_finance_recharge_summary` 只存储赠送卡总额(`recharge_gift`、`gift_card_balance`),未按卡类型拆分。而 DWD 层 `dim_member_card_account` 已通过 `card_type_id` 区分三种赠送卡类型,数据源完备。
|
||||
|
||||
---
|
||||
|
||||
## 需求(Requirements)
|
||||
|
||||
### 用户故事
|
||||
|
||||
1. 作为门店管理者,我需要在财务看板中看到赠送卡按用途(酒水/台费/抵用券)拆分的余额、新增、消费数据,以便了解各类赠送卡的使用情况和资金分布。
|
||||
|
||||
### 验收标准
|
||||
|
||||
- AC1:`dws_finance_recharge_summary` 新增 6 个字段(3 种卡类型 × 余额+新增),ETL 任务正确填充
|
||||
- AC2:BOARD-3 赠送卡矩阵「余额」行的 liquor/table_fee/voucher 列显示正确数值
|
||||
- AC3:赠送卡矩阵「新增」行显示各类型赠送卡的新增金额(按充值订单的 `point_amount` 拆分)
|
||||
- AC4:赠送卡矩阵「消费」行显示各类型赠送卡的消费金额(如 DWD 层可追溯)
|
||||
- AC5:环比数据正确计算(如启用 compare 参数)
|
||||
- AC6:RLS 视图自动包含新字段(`CREATE OR REPLACE VIEW` 使用 `SELECT *` 或显式列名)
|
||||
- AC7:FDW 外部表通过 `IMPORT FOREIGN SCHEMA` 自动同步
|
||||
- AC8:`GET /api/xcx/board/finance` 接口返回的 `gift_rows` 矩阵包含正确的细分数据(非全 0)
|
||||
- AC9:小程序 `board-finance` 页面赠送卡矩阵正确渲染后端返回的细分数据(替换 mock)
|
||||
|
||||
---
|
||||
|
||||
## 数据源分析
|
||||
|
||||
### DWD 层现有数据
|
||||
|
||||
`dwd.dim_member_card_account` 通过 `card_type_id` 区分卡类型:
|
||||
|
||||
| card_type_id | 类型 | 当前记录数 | 当前余额 |
|
||||
|---|---|---|---|
|
||||
| 2793249295533893 | 储值卡 | 421 | 128,918.32 |
|
||||
| 2791990152417157 | 台费卡(赠送) | 343 | 246,267.50 |
|
||||
| 2793266846533445 | 活动抵用券(赠送) | 118 | 24,978.70 |
|
||||
| 2794699703437125 | 酒水卡(赠送) | 49 | 3,708.95 |
|
||||
| 2791987095408517 | 年卡 | 7 | 7.00 |
|
||||
| 2793306611533637 | 月卡 | 12 | 4,938.00 |
|
||||
|
||||
ETL 任务 `FinanceRechargeTask._extract_card_balances()` 已硬编码三个赠送卡 ID:
|
||||
```python
|
||||
GIFT_CARD_TYPE_IDS = [2791990152417157, 2793266846533445, 2794699703437125]
|
||||
```
|
||||
|
||||
### 矩阵数据需求
|
||||
|
||||
| 行 | 数据来源 | 说明 |
|
||||
|---|---|---|
|
||||
| 余额 | `dim_member_card_account` 按 `card_type_id` 分组 | 当日末快照,已有数据 |
|
||||
| 新增 | `dwd_recharge_order.point_amount` 按会员卡类型拆分 | 需要 JOIN `dim_member_card_account` 确定卡类型 |
|
||||
| 消费 | `dwd_settlement_head.gift_card_amount` | 总额已有,但无法按卡类型拆分(结算单不记录具体使用哪种赠送卡) |
|
||||
|
||||
### 关键约束
|
||||
|
||||
- **消费行拆分可能不可行**:`dwd_settlement_head.gift_card_amount` 是赠送卡消费总额,飞球上游 API 不提供按卡类型拆分的消费明细。需确认是否有 `dwd_settlement_head_ex` 或其他扩展表包含此信息。
|
||||
- **新增行拆分**:充值订单 `dwd_recharge_order` 的 `tenant_member_card_id` 可关联 `dim_member_card_account.tenant_member_id`,从而确定充值到哪种卡类型。
|
||||
- **card_type_id 硬编码**:当前 ETL 已硬编码,本次扩展沿用同一套 ID。后续可考虑迁移至配置表(feiqiu-data-rules 规则 6 精神)。
|
||||
|
||||
---
|
||||
|
||||
## 设计要点
|
||||
|
||||
### DDL 变更(`dws_finance_recharge_summary`)
|
||||
|
||||
新增 6 个字段:
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `gift_liquor_balance` | NUMERIC(14,2) | 酒水卡余额(当日末) |
|
||||
| `gift_table_fee_balance` | NUMERIC(14,2) | 台费卡余额(当日末) |
|
||||
| `gift_voucher_balance` | NUMERIC(14,2) | 抵用券余额(当日末) |
|
||||
| `gift_liquor_recharge` | NUMERIC(14,2) | 酒水卡新增充值(赠送部分) |
|
||||
| `gift_table_fee_recharge` | NUMERIC(14,2) | 台费卡新增充值(赠送部分) |
|
||||
| `gift_voucher_recharge` | NUMERIC(14,2) | 抵用券新增充值(赠送部分) |
|
||||
|
||||
> 消费行拆分字段暂不新增(待确认上游数据可行性)。消费行 total 可通过 `gift_card_balance` 变化量 + 新增量反推(`consumed = prev_balance + recharge - current_balance`),但精度依赖余额快照的连续性。
|
||||
|
||||
### ETL 任务修改
|
||||
|
||||
修改 `FinanceRechargeTask._extract_card_balances()`:
|
||||
- 将 `gift_balance` 拆分为 3 个细分余额
|
||||
- 新增 `_extract_gift_recharge_breakdown()` 方法,通过 `dwd_recharge_order JOIN dim_member_card_account` 按卡类型拆分赠送金额
|
||||
|
||||
### 后端接口修改
|
||||
|
||||
接口路径:`GET /api/xcx/board/finance`(BOARD-3 财务看板)
|
||||
- 路由:`apps/backend/app/routers/xcx_board.py` → `get_finance_board()`
|
||||
- 权限:`view_board_finance`
|
||||
|
||||
涉及文件与修改点:
|
||||
|
||||
| 文件 | 修改点 |
|
||||
|---|---|
|
||||
| `apps/backend/app/services/fdw_queries.py` | `get_finance_recharge()` — SQL 新增 6 个字段的 SUM,填充 `gift_rows` 矩阵的余额行和新增行细分数据 |
|
||||
| `apps/backend/app/services/fdw_queries.py` | `_empty_recharge_data()` — 空默认值同步新增字段 |
|
||||
| `apps/backend/app/services/board_service.py` | `_build_recharge()` — 环比计算已覆盖 `gift_rows` 所有 cell,无需额外修改(自动适配) |
|
||||
| `apps/backend/app/schemas/xcx_board.py` | `GiftCell` / `GiftRow` / `RechargePanel` — schema 无需修改(已预留 `liquor`/`table_fee`/`voucher` 字段) |
|
||||
|
||||
> 消费行:如果无法直接拆分,使用 `余额变化量反推法` 或保持 total only。
|
||||
|
||||
### 小程序页面影响
|
||||
|
||||
页面路径:`apps/miniprogram/miniprogram/pages/board-finance/`
|
||||
|
||||
| 文件 | 当前状态 | 修改点 |
|
||||
|---|---|---|
|
||||
| `board-finance.wxml` (L316-370) | 赠送卡矩阵已渲染 `recharge.giftRows`,3 行 × 4 列(酒水卡/台费卡/抵用券) | 无需修改(模板已就绪,数据绑定字段已对齐) |
|
||||
| `board-finance.ts` | 当前使用 mock 数据(`giftRows` 硬编码) | 联调时替换 mock 为真实 API 调用(非本 SPEC 范围,属于联调阶段) |
|
||||
| `board-finance.wxss` (L643-717) | 赠送卡表格样式已完成 | 无需修改 |
|
||||
|
||||
字段映射关系(后端 → 小程序):
|
||||
|
||||
| 后端字段 | 小程序绑定 |
|
||||
|---|---|
|
||||
| `GiftRow.total.value` | `item.total` |
|
||||
| `GiftRow.liquor.value` | `item.wine` |
|
||||
| `GiftRow.table_fee.value` | `item.table` |
|
||||
| `GiftRow.voucher.value` | `item.coupon` |
|
||||
| `GiftRow.*.compare` | `item.*Compare` |
|
||||
|
||||
> 小程序页面模板和样式已在 RNS1 阶段完成,本次只需后端返回正确数据即可自动渲染。联调阶段需将 mock 数据替换为真实 API 调用。
|
||||
|
||||
### 管理后台
|
||||
|
||||
admin-web 无涉及赠送卡/充值统计的页面,不受影响。
|
||||
|
||||
### RLS 视图 & FDW
|
||||
|
||||
- `app.v_dws_finance_recharge_summary` 使用 `CREATE OR REPLACE VIEW`,需要更新列列表
|
||||
- FDW 使用 `IMPORT FOREIGN SCHEMA`,需要重新导入(幂等脚本已支持)
|
||||
|
||||
---
|
||||
|
||||
## 数据流向
|
||||
|
||||
```
|
||||
DWD 层
|
||||
├─ dwd_recharge_order.point_amount ──→ JOIN dim_member_card_account ──→ 按 card_type_id 拆分赠送金额
|
||||
├─ dim_member_card_account.balance ──→ 按 card_type_id 分组求和 ──→ 3 种赠送卡余额
|
||||
└─ dwd_settlement_head.gift_card_amount ──→ 总额(无法按卡类型拆分)
|
||||
|
||||
↓ ETL(FinanceRechargeTask)
|
||||
|
||||
DWS 层:dws_finance_recharge_summary(+6 字段)
|
||||
|
||||
↓ RLS 视图 + FDW
|
||||
|
||||
业务库:app.v_dws_finance_recharge_summary
|
||||
|
||||
↓ fdw_queries.get_finance_recharge()
|
||||
|
||||
FastAPI:GET /api/xcx/board/finance → RechargePanel.gift_rows
|
||||
|
||||
↓ 小程序渲染
|
||||
|
||||
board-finance 页面:赠送卡矩阵 3×4 显示正确数值
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 待确认项
|
||||
|
||||
1. **消费行拆分**:`dwd_settlement_head_ex` 或其他表是否包含赠送卡消费的卡类型明细?如果没有,消费行只能显示 total,细分列保持 0 或使用反推法。
|
||||
2. **card_type_id 2791987095408517 和 2793306611533637**:已确认为年卡和月卡,不纳入赠送卡矩阵。
|
||||
3. **环比需求**:赠送卡矩阵的每个 cell 是否需要环比数据(compare 字段)?当前 schema `GiftCell` 已预留 `compare: str | None`。
|
||||
|
||||
---
|
||||
|
||||
## 任务清单
|
||||
|
||||
### 数据层(ETL + DDL)
|
||||
|
||||
- [ ] T1:DDL 迁移 — `dws_finance_recharge_summary` 新增 6 个字段
|
||||
- 迁移脚本:`db/etl_feiqiu/migrations/2026-xx-xx_add_gift_breakdown_fields.sql`
|
||||
- DDL 基线同步:`docs/database/ddl/etl_feiqiu__dws.sql`
|
||||
- [ ] T2:ETL 修改 — `_extract_card_balances()` 拆分赠送卡余额
|
||||
- 文件:`apps/etl/connectors/feiqiu/tasks/dws/finance_recharge_task.py`
|
||||
- 返回值新增 `gift_liquor_balance`、`gift_table_fee_balance`、`gift_voucher_balance`
|
||||
- [ ] T3:ETL 新增 — `_extract_gift_recharge_breakdown()` 拆分赠送卡新增
|
||||
- 文件:同 T2
|
||||
- SQL:`dwd_recharge_order JOIN dim_member_card_account` 按 `card_type_id` 分组
|
||||
- [ ] T4:ETL transform — `transform()` 方法写入新增 6 个字段到 record dict
|
||||
|
||||
### 数据库视图层
|
||||
|
||||
- [ ] T5:RLS 视图更新 — `app.v_dws_finance_recharge_summary` 重建
|
||||
- 迁移脚本:`db/etl_feiqiu/migrations/` 或 `db/zqyy_app/migrations/`
|
||||
- [ ] T6:FDW 外部表同步 — `IMPORT FOREIGN SCHEMA` 重新导入
|
||||
|
||||
### 后端接口层
|
||||
|
||||
- [ ] T7:后端修改 — `fdw_queries.get_finance_recharge()`
|
||||
- 文件:`apps/backend/app/services/fdw_queries.py`
|
||||
- SQL 新增 6 个字段的 SUM
|
||||
- 填充 `gift_rows` 矩阵:余额行 + 新增行的 liquor/table_fee/voucher
|
||||
- [ ] T8:后端修改 — `_empty_recharge_data()` 空默认值同步
|
||||
|
||||
### 小程序页面层
|
||||
|
||||
- [ ] T9:小程序联调 — `board-finance.ts` 替换 mock 数据为真实 API 调用
|
||||
- 文件:`apps/miniprogram/miniprogram/pages/board-finance/board-finance.ts`
|
||||
- 当前状态:mock 数据硬编码,模板和样式已就绪
|
||||
- 注意:字段映射 `liquor→wine`、`table_fee→table`、`voucher→coupon` 需在数据转换层处理
|
||||
|
||||
### 验证与文档
|
||||
|
||||
- [ ] T10:验证 — 跑数后对比 `dim_member_card_account` 余额与 DWS 汇总值
|
||||
- [ ] T11:BD 手册更新 — `BD_manual_dws_finance_recharge_summary.md`
|
||||
|
||||
### 涉及文件汇总
|
||||
|
||||
| 模块 | 文件路径 | 操作 |
|
||||
|---|---|---|
|
||||
| DDL | `db/etl_feiqiu/migrations/2026-xx-xx_add_gift_breakdown_fields.sql` | 新增 |
|
||||
| DDL 基线 | `docs/database/ddl/etl_feiqiu__dws.sql` | 修改 |
|
||||
| ETL | `apps/etl/connectors/feiqiu/tasks/dws/finance_recharge_task.py` | 修改 |
|
||||
| RLS 视图 | `db/etl_feiqiu/migrations/` 或 `db/zqyy_app/migrations/` | 新增 |
|
||||
| 后端 | `apps/backend/app/services/fdw_queries.py` | 修改 |
|
||||
| 小程序 | `apps/miniprogram/miniprogram/pages/board-finance/board-finance.ts` | 修改 |
|
||||
| Schema | `apps/backend/app/schemas/xcx_board.py` | 无需修改(已预留) |
|
||||
| 路由 | `apps/backend/app/routers/xcx_board.py` | 无需修改 |
|
||||
| 服务 | `apps/backend/app/services/board_service.py` | 无需修改(环比自动适配) |
|
||||
| BD 手册 | `apps/etl/connectors/feiqiu/docs/database/DWS/main/BD_manual_dws_finance_recharge_summary.md` | 修改 |
|
||||
Reference in New Issue
Block a user