feat: 累积功能变更 — 聊天集成、租户管理、小程序更新、ETL 增强、迁移脚本

包含多个会话的累积代码变更:
- backend: AI 聊天服务、触发器调度、认证增强、WebSocket、调度器最小间隔
- admin-web: ETL 状态页、任务管理、调度配置、登录优化
- miniprogram: 看板页面、聊天集成、UI 组件、导航更新
- etl: DWS 新任务(finance_area_daily/board_cache)、连接器增强
- tenant-admin: 项目初始化
- db: 19 个迁移脚本(etl_feiqiu 11 + zqyy_app 8)
- packages/shared: 枚举和工具函数更新
- tools: 数据库工具、报表生成、健康检查
- docs: PRD/架构/部署/合约文档更新

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Neo
2026-04-06 00:03:48 +08:00
parent 70324d8542
commit 6f8f12314f
515 changed files with 76604 additions and 7456 deletions

View File

@@ -2,7 +2,7 @@
> 本文档记录项目中所有文档资产的位置、类型和内容概要,方便快速定位。
> 归档规则见末尾「文档归档规则」章节;程序输出路径规范见 `docs/deployment/EXPORT-PATHS.md`。
> 最后更新2026-03-20RNS1.4 CHAT 模块重建 + FDW→直连统一 + R3 筛选修复 审计收口
> 最后更新2026-03-28board-finance-dws-area-refactor 财务看板区域维度重构
---
@@ -33,9 +33,13 @@
| `BD_Manual_biz_tables.md` | biz Schema 核心业务表coach_tasks、coach_task_history、notes、trigger_jobs、AI 表等) |
| `BD_Manual_auth_biz_schemas.md` | auth + biz Schema 创建 |
| `BD_Manual_fdw_etl_setup.md` | FDW 跨库访问配置zqyy_app → etl_feiqiu |
| `BD_Manual_fdw_finance_area.md` | FDW 财务区域查询映射(后端直连 ETL 库访问 v_dws_finance_area_daily / v_dws_finance_board_cache |
| `BD_Manual_app_schema_rls_views.md` | app Schema RLS 视图 |
| `BD_Manual_ai_tables.md` | AI 相关表ai_chat_sessions、ai_chat_messages 等) |
| `BD_Manual_member_retention_clue.md` | 会员留存线索表 |
| `BD_Manual_tenant_admin_tables.md` | NS4 租户管理后台 6 张表tenant_admins、excel_upload_log、salary_adjustments、3 张 staging 表) |
| `BD_Manual_biz_registry_tables.md` | biz Schema 注册体系四张表connectors、tenants、sites、site_code_history |
| `BD_Manual_scheduled_tasks.md` | scheduled_tasks P16 新增字段min_run_interval_value、min_run_interval_unit、last_success_at |
| `README.md` | 数据库文档目录说明 |
子目录:
@@ -145,14 +149,17 @@ H5 静态原型页面,用于小程序 UI 设计参考。
| `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`) |
| `tech-solution/` | 技术方案`_archived/bailian-technical-solution.md` ⚠️ 已归档,基于旧 OpenAI 兼容方案P14 迁移后由 DashScope Application API 替代) |
| `vi-color-audit/` | VI 配色审计:详细审计(Phase2)、完成报告、实施文档、合规审计 |
| `2026-03-20__ai_app_page_mapping.md` | AI 应用→页面消费映射报告8 个 AI 应用的触发方式、数据流向、页面展示位置汇总 |
| `_archived/2026-03-21__ai_full_chain_test.md` | ⚠️ 已归档2026-03-21— AI 全链路测试报告,基于旧 openai SDKP14 迁移后由新测试替代 |
### 2.11 架构文档 `docs/architecture/`
| 文件 | 内容 |
|------|------|
| `etl-feiqiu-architecture.md` | ETL Connector 整体架构说明数据流、DWS/INDEX 任务、调度编排、CLI |
| `backend-architecture.md` | 后端 FastAPI 服务层架构(路由/服务/FDW/Trace 模块清单、数据流、设计决策) |
### 2.12 MCP 文档 `docs/mcp/`
@@ -182,8 +189,8 @@ H5 静态原型页面,用于小程序 UI 设计参考。
| 文件 | 内容 |
|------|------|
| `README.md` | 架构概览、双库连接、认证系统、17 个路由模块摘要、服务层、配置加载、触发器系统 |
| `docs/API-REFERENCE.md` | 完整 API 参考:17 个路由模块的所有端点、请求/响应示例、认证要求、错误码 |
| `README.md` | 架构概览、双库连接、认证系统、路由模块摘要(含 admin_ai 13 端点、admin_dev_trace 8 端点)、服务层(含 admin_service/cleanup_service、配置加载、触发器系统 |
| `docs/API-REFERENCE.md` | 完整 API 参考:26 个路由模块的所有端点、请求/响应示例、认证要求、错误码(含 P15 admin AI 13 端点、DevTrace 8 端点) |
RNS1.2 新增模块(客户与助教接口):
@@ -231,6 +238,161 @@ Monorepo 级属性测试(`tests/`
| `tests/test_rns1_chat_sse_properties.py` | RNS1.4 属性测试SSE 事件类型有效性Property 9 |
| `tests/test_rns1_chat_ordering_properties.py` | RNS1.4 属性测试列表排序不变量Property 3 |
NS2 新增模块AI Prompt 细化):
| 路径 | 内容 |
|------|------|
| `app/ai/data_fetchers/member_data.py` | 客户消费数据获取(消费记录、会员卡、备注) |
| `app/ai/data_fetchers/assistant_data.py` | 助教信息与服务记录获取 |
| `app/ai/data_fetchers/page_context.py` | 页面上下文文本化10 种入口 contextType |
| `app/ai/apps/app3_clue.py` | 应用 3 Prompt 拼接(客户数据维客线索分析) |
| `app/ai/apps/app4_analysis.py` | 应用 4 Prompt 拼接(关系分析/任务建议) |
| `app/ai/apps/app5_tactics.py` | 应用 5 Prompt 拼接(话术参考) |
| `app/ai/apps/app6_note.py` | 应用 6 Prompt 拼接(备注分析) |
| `app/ai/apps/app7_customer.py` | 应用 7 Prompt 拼接(客户分析) |
| `app/ai/apps/app1_chat.py` | 应用 1 页面上下文集成async `_build_page_context` |
Monorepo 级属性测试NS2
| 路径 | 内容 |
|------|------|
| `tests/test_data_fetchers/test_member_data_props.py` | 客户数据获取属性测试P1-P4, P6, P17 |
| `tests/test_data_fetchers/test_assistant_data_props.py` | 助教数据获取属性测试P5 |
| `tests/test_data_fetchers/test_page_context_props.py` | 页面上下文属性测试P10-P12 |
| `tests/test_data_fetchers/test_data_fetchers_unit.py` | 数据获取层单元测试 |
| `tests/test_ai_apps/test_build_prompt_props.py` | Prompt 拼接属性测试P7-P9, P14-P17 |
| `tests/test_ai_apps/test_app1_props.py` | 应用 1 属性测试P13, P16 |
| `tests/test_ai_apps/test_ai_apps_unit.py` | 应用层单元测试 |
P14 新增模块AI DashScope 迁移):
| 路径 | 内容 |
|------|------|
| `tests/test_ai_config_props.py` | AIConfig 属性测试Property 2环境变量校验完整性 |
| `tests/test_dashscope_client_props.py` | DashScopeClient 属性测试Property 1重试策略、Property 20响应解析 |
| `tests/test_circuit_breaker_props.py` | 熔断器属性测试Property 5app_id 隔离、Property 6状态机转换 |
| `tests/test_circuit_breaker_unit.py` | 熔断器单元测试 |
| `tests/test_rate_limiter_props.py` | 限流器属性测试Property 7窗口控制 |
| `tests/test_budget_tracker_props.py` | Token 预算属性测试Property 8预算检查正确性 |
| `tests/test_budget_tracker_unit.py` | Token 预算单元测试 |
| `tests/test_run_log_props.py` | 运行日志属性测试Property 18状态机、Property 19Prompt 截断) |
| `tests/test_dispatcher_props.py` | 调度器属性测试Property 9事件链映射、Property 10容错、Property 12去重 |
| `tests/test_dispatcher_unit.py` | 调度器单元测试 |
| `tests/test_internal_ai_api.py` | 内部 AI API 属性测试Property 11认证 |
| `tests/test_sse_props.py` | SSE 事件流属性测试Property 17事件格式 |
| `tests/test_session_props.py` | session_id 属性测试Property 3格式、Property 4对话复用 |
| `tests/test_cache_service_props.py` | 缓存服务属性测试Property 14-16过期策略、查询过滤、保留上限 |
| `tests/test_app8_idempotent.py` | App8 幂等写入属性测试Property 13 |
P15 新增模块AI 监控后台):
| 路径 | 内容 |
|------|------|
| `app/routers/admin_ai.py` | AI 监控后台路由13 个端点Dashboard/调度/调用/缓存/预算/批量/告警) |
| `app/services/ai/admin_service.py` | AI 监控后台聚合服务AdminAIService |
| `app/services/ai/cleanup_service.py` | AI 数据清理服务AICleanupService每日 03:00 |
| `app/schemas/admin_ai.py` | AI 监控后台 Pydantic Schema |
| `tests/unit/test_admin_ai_router.py` | admin AI 路由单元测试16 个测试) |
| `tests/unit/test_admin_ai_service.py` | AdminAIService 单元测试9 个测试) |
| `tests/unit/test_ai_cleanup_service.py` | 清理服务单元测试7 个测试) |
| `tests/unit/test_backfill_script.py` | 回填脚本单元测试13 个测试) |
| `tests/integration/test_ai_full_chain.py` | AI 全链路测试15 个场景Mock/Real 双轨) |
Monorepo 级属性测试P15
| 路径 | 内容 |
|------|------|
| `tests/test_admin_ai_dashboard_props.py` | Property 8Dashboard 聚合正确性 |
| `tests/test_admin_ai_alert_props.py` | Property 9告警筛选与状态转换 |
| `tests/test_admin_ai_cache_props.py` | Property 10缓存失效精确性 |
| `tests/test_admin_ai_batch_props.py` | Property 11批量执行预估公式 |
| `tests/test_admin_ai_pagination_props.py` | Property 12分页与筛选正确性 |
| `tests/test_backfill_props.py` | Property 13回填断点续跑 round-trip |
| `tests/test_ai_cleanup_props.py` | Property 14数据保留不变量 |
DevTrace 全链路日志模块:
| 路径 | 内容 |
|------|------|
| `app/trace/config.py` | TraceConfig 配置类(环境变量读取 + 运行时动态修改) |
| `app/trace/context.py` | TraceContext + TraceSpan 数据模型contextvars 请求级隔离23 种 span_type |
| `app/trace/writer.py` | JSON Lines 日志写入器按日期分目录、按小时分文件、10MB 自动轮转) |
| `app/trace/cleanup.py` | 日志自动清理(按保留天数删除过期目录) |
| `app/trace/middleware.py` | TraceMiddleware ASGI 中间件(拦截 xcx_* 路由) |
| `app/trace/decorators.py` | @trace_service 装饰器Service 层追踪) |
| `app/trace/db_wrapper.py` | 数据库连接生命周期追踪DB_QUERY/DB_CONN/DB_CONN_RELEASE/DB_ERROR |
| `app/trace/error_handler.py` | 异常/错误全链路追踪ERROR span |
| `app/trace/sse_wrapper.py` | SSE 流式响应追踪SSE_START/SSE_EVENT/SSE_END/AI_CALL |
| `app/trace/ws_wrapper.py` | WebSocket 连接追踪WS_CONNECT/WS_MESSAGE/WS_DISCONNECT |
| `app/trace/job_wrapper.py` | 后台 Job 执行追踪JOB_START/JOB_END/JOB_ERROR |
| `app/trace/coverage.py` | 覆盖率扫描器(路由/Service/Job/SSE/WS 五维度) |
| `app/routers/admin_dev_trace.py` | DevTrace 管理 API8 端点:日期/请求列表/详情/清理/设置/覆盖率) |
Monorepo 级属性测试DevTrace
| 路径 | 内容 |
|------|------|
| `tests/test_trace_context_props.py` | Property 1Request ID 唯一性、Property 2Span 顺序保持 |
| `tests/test_trace_writer_props.py` | Property 3TraceSpan 结构完整性、Property 5JSON 序列化往返一致性、Property 6日志文件路径生成 |
| `tests/test_trace_cleanup_props.py` | Property 8清理保留期正确性 |
| `tests/test_trace_auth_props.py` | Property 4Token 前缀截断、Property 20鉴权失败原因分类 |
| `tests/test_trace_switch_props.py` | Property 13开关关闭时无 Trace 产出、Property 14功能开关控制 Span 内容 |
| `tests/test_trace_middleware_props.py` | Property 15路由前缀过滤 |
| `tests/test_trace_error_props.py` | Property 16异常时 Trace 完整性 |
| `tests/test_trace_sse_props.py` | Property 17SSE 流式 Trace 完整性 |
| `tests/test_trace_ws_props.py` | Property 18WebSocket Trace 生命周期 |
| `tests/test_trace_job_props.py` | Property 19后台 Job Trace 完整性 |
| `tests/test_trace_db_props.py` | Property 21数据库连接生命周期配对 |
admin-web-restructure 新增模块(管理后台重构):
| 路径 | 内容 |
|------|------|
| `app/routers/admin_db_health.py` | 数据库健康监控路由GET /api/admin/db-health4 库连接池/大小/慢查询) |
| `app/routers/admin_triggers.py` | 触发器统一视图路由GET /api/admin/triggers/unifiedbiz/ai/etl 三源聚合) |
| `app/utils/cron_validator.py` | cron 表达式校验工具函数5 字段格式验证) |
| `app/schemas/admin_db_health.py` | DbHealthItem Pydantic Schema |
| `app/schemas/admin_triggers.py` | UnifiedTriggerItem Pydantic Schema |
| `app/schemas/trigger_jobs.py` | UpdateTriggerConfigRequest Pydantic SchemaPATCH 配置编辑) |
| `tests/unit/test_cron_and_models.py` | cron 校验与 Pydantic 模型单元测试 |
| `tests/unit/test_admin_db_health.py` | DB 健康端点单元测试 |
| `tests/unit/test_admin_triggers.py` | 统一触发器端点单元测试 |
| `tests/unit/test_trigger_jobs_patch.py` | PATCH 触发器配置端点单元测试 |
Monorepo 级属性测试admin-web-restructure
| 路径 | 内容 |
|------|------|
| `tests/test_admin_web_db_health_props.py` | Property 1DB 健康 API 已连接数据库返回完整指标 |
| `tests/test_admin_web_unified_triggers_props.py` | Property 3触发器统一视图数据完整性与字段完整性 |
| `tests/test_admin_web_trigger_config_props.py` | Property 4/5触发器配置编辑不变量与更新正确性 |
| `tests/test_admin_web_cron_validator_props.py` | Property 6cron 表达式校验拒绝无效输入 |
board-finance-dws-area-refactor 新增模块(财务看板区域维度重构):
| 路径 | 内容 |
|------|------|
| `tests/test_area_mapping_props.py` | Property 1-2区域映射 round-trip + 未知区域返回 None |
| `tests/test_area_mapping_unit.py` | 区域映射边界条件单元测试 |
| `tests/test_finance_area_daily_props.py` | Property 3-7日粒度恒等式/非 all 零值/输出完整性/幂等性/settle_type 过滤 |
| `tests/test_finance_board_cache_props.py` | Property 8-9数据指纹确定性/当期不缓存 |
| `tests/test_board_service_props.py` | Property 10-14查询路由/区域过滤/revenue 项数/overview 覆盖/回归一致性 |
| `scripts/ops/backfill_finance_area_daily.py` | 历史数据回填脚本(日粒度 + 缓存重算) |
| `scripts/ops/validate_board_finance.py` | 144 组合全量验证脚本8 time_range × 9 area_code × 2 compare |
| 路径 | 内容 |
|------|------|
| `app/routers/tenant_auth.py` | 租户认证端点:登录、刷新令牌 |
| `app/routers/tenant_users.py` | 租户用户端点:申请列表、关联建议、审核通过/拒绝、用户列表/编辑/绑定 |
| `app/routers/tenant_excel.py` | 租户 Excel 端点:上传解析、确认写入、上传记录、模板下载 |
| `app/routers/tenant_clues.py` | 租户线索端点:客户搜索、线索列表/编辑/删除/隐藏显示 |
| `app/routers/admin_tenant_admins.py` | 管理端租户管理员 CRUD列表、创建、编辑、重置密码 |
| `app/auth/tenant_admins.py` | 租户管理员认证依赖注入(`require_tenant_admin``site_filter_clause``verify_site_access` |
| `app/schemas/tenant_users.py` | 租户用户相关 Pydantic Schema |
| `app/schemas/tenant_excel.py` | 租户 Excel 相关 Pydantic Schema |
| `app/schemas/tenant_clues.py` | 租户线索相关 Pydantic Schema |
| `app/schemas/admin_tenant_admins.py` | 管理端租户管理员 Pydantic Schema |
### 3.2 ETL Connector `apps/etl/connectors/feiqiu/`
| 路径 | 内容 |
@@ -257,15 +419,34 @@ Monorepo 级属性测试(`tests/`
| 文件 | 内容 |
|------|------|
| `README.md` | 8 个页面、组件体系含营业日提示、API 层、状态管理、开发指南 |
| `README.md` | 13 个页面(含 P15 AI 监控 4 页面、DevTrace 日志页面、组件体系、API 层(含 adminAI、devTrace、状态管理、开发指南 |
### 3.5 MCP Server `apps/mcp-server/`
### 3.5 租户管理后台 `apps/tenant-admin/`
NS4 租户管理后台,独立 React 应用面向租户管理员Tenant_Admin
| 路径 | 内容 |
|------|------|
| `src/pages/Login/` | 登录页(用户名+密码) |
| `src/pages/UserApproval/` | 用户审核(申请列表 + 关联建议 + 审核操作) |
| `src/pages/UserManagement/` | 用户管理(列表 + 编辑 + 绑定) |
| `src/pages/ExcelUpload/` | Excel 上传4 种模板 + 校验 + 冲突 diff + 确认写入) |
| `src/pages/RetentionClues/` | 维客线索管理(客户搜索 + 线索 CRUD + 隐藏/显示) |
| `src/components/SiteSelector/` | 门店筛选器组件 |
| `src/components/DiffTable/` | 冲突 diff 交互表格 |
| `src/components/ClueEditor/` | 线索编辑表单 |
| `src/services/api.ts` | API 调用封装JWT 自动附加/刷新) |
| `src/hooks/useAuth.ts` | 认证状态管理 |
后端路由:`tenant_auth.py``tenant_users.py``tenant_excel.py``tenant_clues.py`(均注册在 `/api/tenant/*`)。管理端租户管理员 CRUD 由 `admin_tenant_admins.py``/api/admin/*`提供admin-web 前端调用。
### 3.6 MCP Server `apps/mcp-server/`
| 文件 | 内容 |
|------|------|
| `README.md` | MCP Server 功能说明、工具列表、配置方式 |
### 3.6 共享包 `packages/shared/`
### 3.7 共享包 `packages/shared/`
| 文件 | 内容 |
|------|------|
@@ -279,7 +460,7 @@ Monorepo 级属性测试(`tests/`
|------|------|
| `README.md` | 数据库目录总览、四库架构说明 |
| `zqyy_app/README.md` | 业务库文档auth Schema 8 张表字段说明、迁移顺序、FDW 跨库访问 |
| `zqyy_app/migrations/` | 业务库迁移脚本(已合并入 DDL 基线,目录保留 .gitkeep |
| `zqyy_app/migrations/` | 业务库迁移脚本`2026-03-22__p14_ai_module.sql`P14ai_run_logs、ai_trigger_jobs 新表 + ai_conversations.session_id`2026-03-22__ns41_registry_tables.sql`NS4.1biz.connectors/tenants/sites/site_code_history 四张新表 + 种子数据迁移)、`2026-03-22__p16_min_run_interval.sql`P16scheduled_tasks 三字段)、`2026-03-23__p15_ai_monitoring.sql`P15ai_run_logs.alert_status + BRIN 索引 |
| `etl_feiqiu/README.md` | ETL 库文档:六层 Schema 说明、表清单 |
| `etl_feiqiu/migrations/` | ETL 库迁移脚本(已合并入 DDL 基线,目录保留 .gitkeep |
| `fdw/` | FDWForeign Data Wrapper跨库访问配置脚本4 个,运行时资产) |
@@ -397,7 +578,15 @@ Monorepo 级属性测试(`tests/`
| `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 技能类型) |
| `rns1-chat-integration` | RNS1.4 CHAT 对齐与联调收尾CHAT-1/2/3/4 路径迁移、对话复用、referenceCard、SSE 流式、FDW 验证、13 页面联调) |
| `tenant-admin-web` | NS4 租户管理后台(独立认证、用户审核/管理、Excel 数据上传、维客线索管理、管理端租户管理员 CRUD |
| `spi-spending-power-index` | SPI 消费力指数 |
| `ai-prompt-refinement` | NS2 AI Prompt 细化共享数据获取层、6 个应用 Prompt 拼接、页面上下文文本化、17 个属性测试) |
| `P14-ai-dashscope-migration` | P14 AI 模块 DashScope 迁移openai→dashscope Application API、熔断/限流/预算防护、运行日志、事件触发链、ETL 集成、DB 迁移、20 个属性测试) |
| `admin-web-enhancement` | NS4.1 + P16 管理后台增强(注册体系 + 租户管理员重构 + 调度任务间隔) |
| `ai-monitoring-testing` | P15 AI 监控后台 + 测试重建 + 回填admin-web 4 页面、13 API 端点、15 全链路测试、14 属性测试、数据清理、回填脚本) |
| `dev-trace-log` | 开发调试全链路日志系统Trace 基础设施 12 模块、8 API 端点、DevTrace 前端页面、22 属性测试,覆盖 HTTP/SSE/WS/Job/异常/DB/中间件) |
| `admin-web-restructure` | 管理后台重构优化18 页面→7 模块菜单重组、Dashboard 仪表盘、ETLTasks Tab 合并、TriggerManager 统一管理、3 新后端 API、8 属性测试、LogViewer 归档) |
| `board-finance-dws-area-refactor` | 财务看板 DWS 区域维度重构dws_finance_area_daily 原子层 + dws_finance_board_cache 缓存层 + 后端缓存优先查询 + 区域映射共享包 + 14 属性测试) |
---

View File

@@ -1,6 +1,6 @@
# 后端架构文档 — FastAPI 服务层
> 更新日期2026-03-19RNS1.3 三看板接口 + CONFIG-1 技能类型
> 更新日期2026-03-23DevTrace 全链路日志模块
> 位置:`apps/backend/`
> 框架FastAPI + psycopg2同步连接池
> 数据库:`zqyy_app`(业务库)+ `etl_feiqiu`ETL 库,直连只读访问)
@@ -101,6 +101,7 @@ graph TB
| `xcx_ai_chat.py` | `/api/xcx/ai` | AI 对话SSE 流式) |
| `xcx_ai_cache.py` | `/api/xcx/ai-cache` | AI 缓存查询 |
| `admin_applications.py` | `/api/admin/applications` | 入驻审核 |
| `admin_dev_trace.py` | `/api/admin/dev-trace` | 开发调试全链路日志(日期/请求/详情/清理/设置/覆盖率8 端点) |
| `auth.py` | `/api/auth` | 管理后台登录 |
| `member_retention_clue.py` | `/api/member-retention-clue` | 维客线索 |
| `ops_panel.py` | `/api/ops` | 运维面板 |
@@ -226,7 +227,46 @@ RLS 视图直接暴露 DWD/DWS 原始列名,后端代码在 SQL 中使用 AS
→ { code: 0, data: ..., message: "ok" }
```
## 6. 关键设计决策
## 7. Trace 全链路日志模块(`app/trace/`
开发调试专用的全链路请求追踪模块,覆盖 HTTP 请求、SSE 流式响应、WebSocket 连接、后台 Job、异常/错误、数据库连接生命周期和中间件层。日志以 JSON Lines 格式写入本地文件系统,仅用于开发调试。
### 模块结构
| 文件 | 职责 |
|------|------|
| `config.py` | `TraceConfig` 配置类,从环境变量读取(`DEV_TRACE_*`支持运行时动态修改API 更新后即时生效,重启回退 .env 值) |
| `context.py` | `TraceContext` + `TraceSpan` 数据模型,基于 `contextvars.ContextVar` 实现请求级隔离23 种 span_type |
| `writer.py` | JSON Lines 日志写入器,按日期分目录(`YYYY-MM-DD/`)、按小时分文件(`trace_YYYY-MM-DD_HH.jsonl`),单文件 10MB 自动轮转,维护 `_index.json` 索引 |
| `cleanup.py` | 日志自动清理,按 `DEV_TRACE_LOG_RETENTION_DAYS` 保留天数删除过期目录,每日凌晨自动执行 |
| `middleware.py` | `TraceMiddleware` ASGI 中间件,拦截 `xcx_*` 路由前缀请求,创建 TraceContext记录 HTTP_IN/HTTP_OUT/MIDDLEWARE span写入响应头X-Request-ID 等) |
| `decorators.py` | `@trace_service` 装饰器Service 层函数追踪(模块名、函数名、参数、返回值摘要、耗时) |
| `db_wrapper.py` | 数据库连接生命周期追踪,包装 cursor.execute()DB_QUERY、get_connection()DB_CONN、连接关闭DB_CONN_RELEASE、异常DB_ERROR |
| `error_handler.py` | 异常/错误全链路追踪,集成全局异常处理器,记录 ERROR spanHTTPException + 未捕获异常 + 堆栈摘要) |
| `sse_wrapper.py` | SSE 流式响应追踪SSE_START/SSE_EVENT/SSE_END/AI_CALL/AI_ERROR每 10 token 记录一次避免 span 爆炸 |
| `ws_wrapper.py` | WebSocket 连接追踪WS_CONNECT/WS_MESSAGE/WS_DISCONNECTtrace_type 为 "ws" |
| `job_wrapper.py` | 后台 Job 执行追踪JOB_START/JOB_END/JOB_ERROR包装 lifespan 中注册的 4 个 job handler |
| `coverage.py` | 覆盖率扫描器,通过 AST 解析检测路由/Service/Job/SSE/WS 五个维度的追踪覆盖情况,启动时自动扫描,支持手动刷新 |
### 数据流
```
xcx_* 请求 / SSE / WebSocket / 后台 Job
→ TraceMiddleware / sse_wrapper / ws_wrapper / job_wrapper
→ 创建 TraceContextcontextvars
→ AUTH span鉴权层
→ @trace_service spanService 层)
→ DB_QUERY / DB_CONN span数据库层
→ ERROR span异常处理器
→ TraceWriter 异步写入 .jsonl 文件
→ admin_dev_trace API 读取并展示
```
### 管理 API
`admin_dev_trace.py` 路由(`/api/admin/dev-trace`8 个端点admin 角色鉴权)提供日志查询、手动清理、运行时设置修改和覆盖率扫描能力。前端 DevTrace 页面(`apps/admin-web/src/pages/DevTrace.tsx`)提供可视化操作界面。
## 8. 关键设计决策
- **ETL 直连**:所有 ETL 查询封装在 `fdw_queries.py`,通过 `_fdw_context()` 直连 ETL 库查询 `app.v_*` RLS 视图(不使用 FDW foreign table因 postgres_fdw 不传递自定义 GUC
- **优雅降级**扩展字段lastVisitDays/balance/aiSuggestion查询失败返回 null不影响核心响应

View File

@@ -289,7 +289,7 @@ RS 默认参数:
| 参数 | 默认值 | 说明 |
|------|--------|------|
| `lookback_days` | 60 | 回溯天数 |
| `lookback_days` | 90 | 回溯天数 |
| `session_merge_hours` | 4 | 会话合并间隔(小时) |
| `incentive_weight` | 1.5 | 激励权重 |
| `halflife_session` | 14.0 | 会话频次半衰期(天) |

View File

@@ -1,12 +1,69 @@
# 审计一览表
> 自动生成于 2026-03-20 07:28:24,请勿手动编辑。
> 自动生成于 2026-04-05 15:36:58,请勿手动编辑。
## 时间线视图
| 日期 | 项目 | 需求摘要 | 变更类型 | 影响模块 | 风险 | 详情 |
|------|------|----------|----------|----------|------|------|
| 2026-04-05 | 项目级 | 变更审计记录Kiro → Claude Code 全量迁移 | 文档 | 其他 | 低 | [链接](changes/2026-04-05__kiro-to-claude-code-migration.md) |
| 2026-03-31 | 项目级 | 变更审计记录:任务引擎改造 — 参数调优 + 客户级升级/转移 + 任务统计写入 | 功能 | 其他 | 未知 | [链接](changes/2026-03-31__task-engine-overhaul.md) |
| 2026-03-29 | 项目级 | 变更审计记录:助教看板和客户看板懒加载(分页加载) | 重构 | 其他 | 未知 | [链接](changes/2026-03-29__board-lazy-loading-pagination.md) |
| 2026-03-29 | 项目级 | 变更审计记录:助教详情页 API 500 修复Schema 字段名对齐) | bugfix | 其他 | 未知 | [链接](changes/2026-03-29__coach-detail-500-field-name-fix.md) |
| 2026-03-29 | 项目级 | 变更审计记录:助教详情页设计稿对齐 + 数据格式化修复 | bugfix | 其他 | 未知 | [链接](changes/2026-03-29__coach-detail-design-alignment.md) |
| 2026-03-29 | ETL-feiqiu, 后端, 小程序 | 变更审计记录:助教详情页样式修复 + 数据聚合修复 + 关系指数回测支持 | bugfix | 其他 | 未知 | [链接](changes/2026-03-29__coach-detail-style-aggregation-fix.md) |
| 2026-03-29 | 项目级 | 变更审计记录DWS_TASK_ENGINE ETL 编排替代 fire_event 事件链 | 功能 | 其他 | 未知 | [链接](changes/2026-03-29__dws-task-engine-etl-orchestration.md) |
| 2026-03-29 | 项目级 | 变更审计记录:修复 recall_completion_check 事件链断裂 | bugfix | 其他 | 未知 | [链接](changes/2026-03-29__fix-recall-completion-event-chain.md) |
| 2026-03-28 | ETL-feiqiu, 后端, 项目级 | 变更审计记录:财务看板 5 项修复ODS 行膨胀 / 优惠分摊 / 环比字段 / 区域过滤 / 规范沉淀) | bugfix | 其他, 文档 | 未知 | [链接](changes/2026-03-28__board-finance-5fixes.md) |
| 2026-03-28 | 项目级 | 变更审计记录:财务看板 DWS 区域维度重构审计 | 重构 | 其他 | 未知 | [链接](changes/2026-03-28__board-finance-dws-area-refactor-audit.md) |
| 2026-03-28 | 项目级 | 变更审计记录:修复小程序登录落地页跳转失效 | bugfix | 其他 | 未知 | [链接](changes/2026-03-28__fix-miniprogram-login-landing-page.md) |
| 2026-03-27 | 小程序 | 变更审计记录board-finance 双重格式化修复 | bugfix | 其他 | 低 | [链接](changes/2026-03-27__board-finance-double-format-fix.md) |
| 2026-03-27 | 后端 | 审计记录board-finance-integration 阶段 2后端 API 修复) | bugfix | 其他 | 未知 | [链接](changes/2026-03-27__board-finance-integration-T2.md) |
| 2026-03-27 | 项目级 | 变更审计记录:财务看板 Phase 2 对齐 DemoT1-T6 | bugfix | 其他 | 未知 | [链接](changes/2026-03-27__board-finance-phase2-t1-t6.md) |
| 2026-03-27 | 项目级 | 变更审计记录board-finance WXML 格式化迁移 + 动态 Tab + 加载态清理 | bugfix | 其他 | 未知 | [链接](changes/2026-03-27__board-finance-wxml-format-tabs-cleanup.md) |
| 2026-03-27 | 项目级 | 变更审计记录小程序权限体系统一改造W1-W5 | 功能 | 其他 | 未知 | [链接](changes/2026-03-27__miniprogram-permission-unification.md) |
| 2026-03-27 | 项目级 | 变更审计记录任务列表近60天数据展示 + WXML 格式化改造 | 重构 | 其他 | 高 | [链接](changes/2026-03-27__task-list-recent60d-and-wxml-formatting.md) |
| 2026-03-26 | 项目级 | 审计记录ETL 缺失字段补充 — 第一阶段DDL + FACT_MAPPINGS | 功能 | 其他 | 低 | [链接](changes/2026-03-26__etl-missing-fields-phase1-ddl-mappings.md) |
| 2026-03-26 | 项目级 | 变更审计记录:到手金额口径修复(全小程序统一) | bugfix | 其他 | 未知 | [链接](changes/2026-03-26__net-income-calibration-all-pages.md) |
| 2026-03-25 | 项目级 | 变更审计记录:保底 relationship_building 任务生成 | bugfix | 其他 | 未知 | [链接](changes/2026-03-25__baseline-relationship-building-tasks.md) |
| 2026-03-25 | 项目级 | 变更审计记录:保底任务生成独立连接修复 | bugfix | 其他 | 高 | [链接](changes/2026-03-25__baseline-task-independent-connection-fix.md) |
| 2026-03-25 | 项目级 | 变更审计记录:绩效页→任务详情页按 member_id 查询任务 | 文档 | 其他 | 未知 | [链接](changes/2026-03-25__perf-to-task-detail-member-query.md) |
| 2026-03-25 | 项目级 | 绩效页 WXML 缺少 data-member-id 导致任务详情页空白 | bugfix | 其他 | 未知 | [链接](changes/2026-03-25__perf-wxml-missing-member-id.md) |
| 2026-03-25 | 项目级 | 变更审计记录Change Audit Record | bugfix | 其他 | 未知 | [链接](changes/2026-03-25__task-detail-service-records-6-improvements.md) |
| 2026-03-25 | 项目级 | 变更审计记录:租户用户审核 — 软删除恢复 upsert 修复 | bugfix | 其他 | 未知 | [链接](changes/2026-03-25__tenant-users-soft-delete-upsert-fix.md) |
| 2026-03-24 | 项目级 | 变更审计记录:补录 cfg_skill_type 缺失的 3 条课程类型配置 | bugfix | 其他 | 未知 | [链接](changes/2026-03-24__add_missing_cfg_skill_type.md) |
| 2026-03-24 | ETL-feiqiu, 项目级 | 审计记录:迁移脚本合并到主 DDL 并归档 | 文档 | 其他, 文档 | 低 | [链接](changes/2026-03-24__ddl-migration-merge-and-archive.md) |
| 2026-03-24 | 后端 | 审计记录:修复小程序前端档位进度条无刻度 + bonus_money 计算 | bugfix | 其他 | 低 | [链接](changes/2026-03-24__fix-tier-nodes-empty-progress-bar.md) |
| 2026-03-24 | 项目级 | 变更审计记录lookback_days 从 60 天扩大到 90 天 | 文档 | 其他 | 未知 | [链接](changes/2026-03-24__lookback_days_60_to_90.md) |
| 2026-03-24 | ETL-feiqiu, 小程序, 项目级 | 变更审计记录:小程序登录页头像昵称获取功能(前端实施) | 文档 | 其他, 文档 | 低 | [链接](changes/2026-03-24__miniprogram-avatar-nickname-feature.md) |
| 2026-03-24 | ETL-feiqiu, 后端 | 变更审计记录P17 助教客户归属与任务生成引擎 | bugfix | 其他, 测试 | 未知 | [链接](changes/2026-03-24__p17-assistant-ownership-task-engine.md) |
| 2026-03-24 | ETL-feiqiu, 后端, 管理后台 | 变更审计记录P18 任务引擎运营看板实施 | bugfix | 其他, 文档, 脚本工具 | 未知 | [链接](changes/2026-03-24__p18-task-engine-dashboard.md) |
| 2026-03-24 | 项目级 | 变更审计记录:绩效页数据正确性修复 | bugfix | 其他 | 未知 | [链接](changes/2026-03-24__perf-page-data-fix.md) |
| 2026-03-24 | 后端, 项目级 | 审计记录:审核弹窗头像展示 + 排版优化 | 功能 | 其他 | 低 | [链接](changes/2026-03-24__review-modal-avatar-layout.md) |
| 2026-03-24 | 项目级 | 变更审计记录user_site_roles / user_assistant_binding 软删除实施 | 文档 | 其他 | 未知 | [链接](changes/2026-03-24__soft-delete-user-site-roles-binding.md) |
| 2026-03-24 | 项目级 | 变更审计记录TriggerJobs 清空任务交互反馈优化 | 清理 | 其他 | 高 | [链接](changes/2026-03-24__trigger-jobs-clear-task-interaction.md) |
| 2026-03-23 | 项目级 | 变更审计记录DDL 合并 — rejection_count + cancelled 状态 | 文档 | 其他 | 未知 | [链接](changes/2026-03-23__ddl-merge-rejection-count-cancelled.md) |
| 2026-03-23 | 项目级 | 变更审计记录:禁用用户改为移除用户 + 小程序鉴权两层模型修复 | bugfix | 其他 | 未知 | [链接](changes/2026-03-23__disable-to-remove-user-auth-model-fix.md) |
| 2026-03-23 | 项目级 | 变更审计记录:店铺筛选 + 时间格式 + 姓名格式 + 李小燕确认 | bugfix | 其他 | 未知 | [链接](changes/2026-03-23__mysites-tenant-filter-time-format-nickname-display.md) |
| 2026-03-23 | 项目级 | 变更审计记录:审核弹窗手机号不显示修复 + 自动匹配优化 + 身份标签中文化 | bugfix | 其他 | 未知 | [链接](changes/2026-03-23__review-modal-phone-display-auto-match-identity-label.md) |
| 2026-03-23 | 项目级 | 变更审计记录Change Audit Record | 功能 | 其他 | 未知 | [链接](changes/2026-03-23__role-routing-page-guard.md) |
| 2026-03-23 | 项目级 | 变更审计记录:租户管理员用户名大小写不敏感 | 功能 | 其他 | 未知 | [链接](changes/2026-03-23__tenant-admin-case-insensitive-username.md) |
| 2026-03-23 | 项目级 | 变更审计记录:租户管理后台审核弹窗改造(角色动态化 + 人员列表联动 + 手机号自动匹配) | bugfix | 其他 | 未知 | [链接](changes/2026-03-23__tenant-admin-review-modal-dynamic-roles.md) |
| 2026-03-23 | 项目级 | 变更审计记录:根治 tenant_admin 的 managed_site_ids 限制(跨模块权限验证改造) | bugfix | 其他 | 未知 | [链接](changes/2026-03-23__tenant-admin-site-access-root-fix.md) |
| 2026-03-23 | 项目级 | 变更审计记录:租户后台申请列表店铺筛选 + admin-web 简写ID修复 | bugfix | 其他 | 高 | [链接](changes/2026-03-23__tenant-user-approval-site-filter.md) |
| 2026-03-23 | 项目级 | 变更审计记录Task 6 Change B/C — 定时任务管理页面 + 小程序清理 | bugfix | 其他 | 未知 | [链接](changes/2026-03-23__trigger-jobs-admin-web-miniprogram-cleanup.md) |
| 2026-03-22 | 项目级 | 变更审计记录:数据库字段走查批量修复 | bugfix | 其他 | 低 | [链接](changes/2026-03-22__db-field-walkthrough-batch-fix.md) |
| 2026-03-22 | 后端 | 变更审计记录DDL vs 数据库结构对比修复 + BD 手册全面审核走查 | bugfix | 其他 | 未知 | [链接](changes/2026-03-22__ddl-db-structure-diff-bd-manual-audit.md) |
| 2026-03-22 | ETL-feiqiu | 变更审计记录Change Audit Record | bugfix | 其他, 文档 | 未知 | [链接](changes/2026-03-22__ddl_bd_manual_consistency_fix.md) |
| 2026-03-22 | 项目级 | 变更审计记录dev-trace-log 全栈开发调试全链路日志系统 | 清理 | 其他 | 低 | [链接](changes/2026-03-22__dev-trace-log-fullstack-feature.md) |
| 2026-03-22 | 项目级 | 变更审计记录NS4 DDL 合并 — deleted_at 字段并入主迁移脚本 | 文档 | 其他 | 未知 | [链接](changes/2026-03-22__ns4-ddl-merge-deleted-at.md) |
| 2026-03-22 | ETL-feiqiu, 项目级 | 审计记录P14 Task 15 — 最终检查点完成 | bugfix | 其他, 脚本工具 | 极低 | [链接](changes/2026-03-22__p14-task15-final-checkpoint.md) |
| 2026-03-22 | ETL-feiqiu | 变更审计记录P16 调度任务最小运行间隔 — Spec 收尾文档同步 | 文档 | 文档 | 未知 | [链接](changes/2026-03-22__p16-spec-closing-doc-sync.md) |
| 2026-03-22 | 项目级 | 变更审计记录trace 日志路径修复 + 小程序登录竞态修复 | bugfix | 其他 | 未知 | [链接](changes/2026-03-22__trace-path-fix-miniprogram-login-race.md) |
| 2026-03-22 | 项目级 | 变更审计记录:僵尸任务修复 + 优雅关闭 + 重新执行按钮 | bugfix | 其他 | 未知 | [链接](changes/2026-03-22__zombie-task-graceful-shutdown-rerun.md) |
| 2026-03-20 | 项目级 | 变更审计记录ai-prompt-refinement spec 完成 + board-coach Mock 精简 | bugfix | 其他 | 未知 | [链接](changes/2026-03-20__ai-prompt-refinement-board-coach-mock.md) |
| 2026-03-20 | 项目级 | H2 修复FDW → 直连 ETL 架构统一 | bugfix | 其他 | 未知 | [链接](changes/2026-03-20__h2-fdw-to-direct-etl-unification.md) |
| 2026-03-20 | 项目级 | 审计记录:小程序文档同步更新 | bugfix | 其他 | 极低 | [链接](changes/2026-03-20__miniprogram-docs-sync.md) |
| 2026-03-20 | ETL-feiqiu, 后端 | 变更审计记录R3 项目类型筛选接口重建fetchSkillTypes / cfg_area_category | bugfix | 其他, 文档 | 高 | [链接](changes/2026-03-20__r3-skill-type-filter-rebuild.md) |
| 2026-03-20 | 项目级 | RNS1 系列 AI 自主决策风险审计报告(完整版) | bugfix | 其他 | 高 | [链接](changes/2026-03-20__rns1-ai-autonomous-decision-risk-audit.md) |
| 2026-03-20 | ETL-feiqiu, 后端, 项目级 | 变更审计记录RNS1.3 三看板 FDW 查询层数据口径修复 | bugfix | 其他, 文档 | 未知 | [链接](changes/2026-03-20__rns13-board-apis-e2e-fix.md) |
@@ -80,6 +137,15 @@
| 日期 | 需求摘要 | 变更类型 | 影响模块 | 风险 | 详情 |
|------|----------|----------|----------|------|------|
| 2026-03-29 | 变更审计记录:助教详情页样式修复 + 数据聚合修复 + 关系指数回测支持 | bugfix | 其他 | 未知 | [链接](changes/2026-03-29__coach-detail-style-aggregation-fix.md) |
| 2026-03-28 | 变更审计记录:财务看板 5 项修复ODS 行膨胀 / 优惠分摊 / 环比字段 / 区域过滤 / 规范沉淀) | bugfix | 其他, 文档 | 未知 | [链接](changes/2026-03-28__board-finance-5fixes.md) |
| 2026-03-24 | 审计记录:迁移脚本合并到主 DDL 并归档 | 文档 | 其他, 文档 | 低 | [链接](changes/2026-03-24__ddl-migration-merge-and-archive.md) |
| 2026-03-24 | 变更审计记录:小程序登录页头像昵称获取功能(前端实施) | 文档 | 其他, 文档 | 低 | [链接](changes/2026-03-24__miniprogram-avatar-nickname-feature.md) |
| 2026-03-24 | 变更审计记录P17 助教客户归属与任务生成引擎 | bugfix | 其他, 测试 | 未知 | [链接](changes/2026-03-24__p17-assistant-ownership-task-engine.md) |
| 2026-03-24 | 变更审计记录P18 任务引擎运营看板实施 | bugfix | 其他, 文档, 脚本工具 | 未知 | [链接](changes/2026-03-24__p18-task-engine-dashboard.md) |
| 2026-03-22 | 变更审计记录Change Audit Record | bugfix | 其他, 文档 | 未知 | [链接](changes/2026-03-22__ddl_bd_manual_consistency_fix.md) |
| 2026-03-22 | 审计记录P14 Task 15 — 最终检查点完成 | bugfix | 其他, 脚本工具 | 极低 | [链接](changes/2026-03-22__p14-task15-final-checkpoint.md) |
| 2026-03-22 | 变更审计记录P16 调度任务最小运行间隔 — Spec 收尾文档同步 | 文档 | 文档 | 未知 | [链接](changes/2026-03-22__p16-spec-closing-doc-sync.md) |
| 2026-03-20 | 变更审计记录R3 项目类型筛选接口重建fetchSkillTypes / cfg_area_category | bugfix | 其他, 文档 | 高 | [链接](changes/2026-03-20__r3-skill-type-filter-rebuild.md) |
| 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) |
@@ -119,6 +185,14 @@
| 日期 | 需求摘要 | 变更类型 | 影响模块 | 风险 | 详情 |
|------|----------|----------|----------|------|------|
| 2026-03-29 | 变更审计记录:助教详情页样式修复 + 数据聚合修复 + 关系指数回测支持 | bugfix | 其他 | 未知 | [链接](changes/2026-03-29__coach-detail-style-aggregation-fix.md) |
| 2026-03-28 | 变更审计记录:财务看板 5 项修复ODS 行膨胀 / 优惠分摊 / 环比字段 / 区域过滤 / 规范沉淀) | bugfix | 其他, 文档 | 未知 | [链接](changes/2026-03-28__board-finance-5fixes.md) |
| 2026-03-27 | 审计记录board-finance-integration 阶段 2后端 API 修复) | bugfix | 其他 | 未知 | [链接](changes/2026-03-27__board-finance-integration-T2.md) |
| 2026-03-24 | 审计记录:修复小程序前端档位进度条无刻度 + bonus_money 计算 | bugfix | 其他 | 低 | [链接](changes/2026-03-24__fix-tier-nodes-empty-progress-bar.md) |
| 2026-03-24 | 变更审计记录P17 助教客户归属与任务生成引擎 | bugfix | 其他, 测试 | 未知 | [链接](changes/2026-03-24__p17-assistant-ownership-task-engine.md) |
| 2026-03-24 | 变更审计记录P18 任务引擎运营看板实施 | bugfix | 其他, 文档, 脚本工具 | 未知 | [链接](changes/2026-03-24__p18-task-engine-dashboard.md) |
| 2026-03-24 | 审计记录:审核弹窗头像展示 + 排版优化 | 功能 | 其他 | 低 | [链接](changes/2026-03-24__review-modal-avatar-layout.md) |
| 2026-03-22 | 变更审计记录DDL vs 数据库结构对比修复 + BD 手册全面审核走查 | bugfix | 其他 | 未知 | [链接](changes/2026-03-22__ddl-db-structure-diff-bd-manual-audit.md) |
| 2026-03-20 | 变更审计记录R3 项目类型筛选接口重建fetchSkillTypes / cfg_area_category | bugfix | 其他, 文档 | 高 | [链接](changes/2026-03-20__r3-skill-type-filter-rebuild.md) |
| 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) |
@@ -131,12 +205,16 @@
| 日期 | 需求摘要 | 变更类型 | 影响模块 | 风险 | 详情 |
|------|----------|----------|----------|------|------|
| 2026-03-24 | 变更审计记录P18 任务引擎运营看板实施 | bugfix | 其他, 文档, 脚本工具 | 未知 | [链接](changes/2026-03-24__p18-task-engine-dashboard.md) |
| 2026-02-28 | 变更审计记录:多模块累积变更(营业日/核心业务/认证/ETL DWS 重构/参考文档合并) | 重构 | 其他 | 未知 | [链接](changes/2026-02-28__multi-module-accumulated-changes.md) |
### 小程序
| 日期 | 需求摘要 | 变更类型 | 影响模块 | 风险 | 详情 |
|------|----------|----------|----------|------|------|
| 2026-03-29 | 变更审计记录:助教详情页样式修复 + 数据聚合修复 + 关系指数回测支持 | bugfix | 其他 | 未知 | [链接](changes/2026-03-29__coach-detail-style-aggregation-fix.md) |
| 2026-03-27 | 变更审计记录board-finance 双重格式化修复 | bugfix | 其他 | 低 | [链接](changes/2026-03-27__board-finance-double-format-fix.md) |
| 2026-03-24 | 变更审计记录:小程序登录页头像昵称获取功能(前端实施) | 文档 | 其他, 文档 | 低 | [链接](changes/2026-03-24__miniprogram-avatar-nickname-feature.md) |
| 2026-03-13 | 审计记录board-finance line-height 全量补齐 | bugfix | 其他, 文档 | 低 | [链接](changes/2026-03-13__board-finance-line-height-audit.md) |
### 桌面GUI
@@ -161,7 +239,55 @@
| 日期 | 需求摘要 | 变更类型 | 影响模块 | 风险 | 详情 |
|------|----------|----------|----------|------|------|
| 2026-04-05 | 变更审计记录Kiro → Claude Code 全量迁移 | 文档 | 其他 | 低 | [链接](changes/2026-04-05__kiro-to-claude-code-migration.md) |
| 2026-03-31 | 变更审计记录:任务引擎改造 — 参数调优 + 客户级升级/转移 + 任务统计写入 | 功能 | 其他 | 未知 | [链接](changes/2026-03-31__task-engine-overhaul.md) |
| 2026-03-29 | 变更审计记录:助教看板和客户看板懒加载(分页加载) | 重构 | 其他 | 未知 | [链接](changes/2026-03-29__board-lazy-loading-pagination.md) |
| 2026-03-29 | 变更审计记录:助教详情页 API 500 修复Schema 字段名对齐) | bugfix | 其他 | 未知 | [链接](changes/2026-03-29__coach-detail-500-field-name-fix.md) |
| 2026-03-29 | 变更审计记录:助教详情页设计稿对齐 + 数据格式化修复 | bugfix | 其他 | 未知 | [链接](changes/2026-03-29__coach-detail-design-alignment.md) |
| 2026-03-29 | 变更审计记录DWS_TASK_ENGINE ETL 编排替代 fire_event 事件链 | 功能 | 其他 | 未知 | [链接](changes/2026-03-29__dws-task-engine-etl-orchestration.md) |
| 2026-03-29 | 变更审计记录:修复 recall_completion_check 事件链断裂 | bugfix | 其他 | 未知 | [链接](changes/2026-03-29__fix-recall-completion-event-chain.md) |
| 2026-03-28 | 变更审计记录:财务看板 5 项修复ODS 行膨胀 / 优惠分摊 / 环比字段 / 区域过滤 / 规范沉淀) | bugfix | 其他, 文档 | 未知 | [链接](changes/2026-03-28__board-finance-5fixes.md) |
| 2026-03-28 | 变更审计记录:财务看板 DWS 区域维度重构审计 | 重构 | 其他 | 未知 | [链接](changes/2026-03-28__board-finance-dws-area-refactor-audit.md) |
| 2026-03-28 | 变更审计记录:修复小程序登录落地页跳转失效 | bugfix | 其他 | 未知 | [链接](changes/2026-03-28__fix-miniprogram-login-landing-page.md) |
| 2026-03-27 | 变更审计记录:财务看板 Phase 2 对齐 DemoT1-T6 | bugfix | 其他 | 未知 | [链接](changes/2026-03-27__board-finance-phase2-t1-t6.md) |
| 2026-03-27 | 变更审计记录board-finance WXML 格式化迁移 + 动态 Tab + 加载态清理 | bugfix | 其他 | 未知 | [链接](changes/2026-03-27__board-finance-wxml-format-tabs-cleanup.md) |
| 2026-03-27 | 变更审计记录小程序权限体系统一改造W1-W5 | 功能 | 其他 | 未知 | [链接](changes/2026-03-27__miniprogram-permission-unification.md) |
| 2026-03-27 | 变更审计记录任务列表近60天数据展示 + WXML 格式化改造 | 重构 | 其他 | 高 | [链接](changes/2026-03-27__task-list-recent60d-and-wxml-formatting.md) |
| 2026-03-26 | 审计记录ETL 缺失字段补充 — 第一阶段DDL + FACT_MAPPINGS | 功能 | 其他 | 低 | [链接](changes/2026-03-26__etl-missing-fields-phase1-ddl-mappings.md) |
| 2026-03-26 | 变更审计记录:到手金额口径修复(全小程序统一) | bugfix | 其他 | 未知 | [链接](changes/2026-03-26__net-income-calibration-all-pages.md) |
| 2026-03-25 | 变更审计记录:保底 relationship_building 任务生成 | bugfix | 其他 | 未知 | [链接](changes/2026-03-25__baseline-relationship-building-tasks.md) |
| 2026-03-25 | 变更审计记录:保底任务生成独立连接修复 | bugfix | 其他 | 高 | [链接](changes/2026-03-25__baseline-task-independent-connection-fix.md) |
| 2026-03-25 | 变更审计记录:绩效页→任务详情页按 member_id 查询任务 | 文档 | 其他 | 未知 | [链接](changes/2026-03-25__perf-to-task-detail-member-query.md) |
| 2026-03-25 | 绩效页 WXML 缺少 data-member-id 导致任务详情页空白 | bugfix | 其他 | 未知 | [链接](changes/2026-03-25__perf-wxml-missing-member-id.md) |
| 2026-03-25 | 变更审计记录Change Audit Record | bugfix | 其他 | 未知 | [链接](changes/2026-03-25__task-detail-service-records-6-improvements.md) |
| 2026-03-25 | 变更审计记录:租户用户审核 — 软删除恢复 upsert 修复 | bugfix | 其他 | 未知 | [链接](changes/2026-03-25__tenant-users-soft-delete-upsert-fix.md) |
| 2026-03-24 | 变更审计记录:补录 cfg_skill_type 缺失的 3 条课程类型配置 | bugfix | 其他 | 未知 | [链接](changes/2026-03-24__add_missing_cfg_skill_type.md) |
| 2026-03-24 | 审计记录:迁移脚本合并到主 DDL 并归档 | 文档 | 其他, 文档 | 低 | [链接](changes/2026-03-24__ddl-migration-merge-and-archive.md) |
| 2026-03-24 | 变更审计记录lookback_days 从 60 天扩大到 90 天 | 文档 | 其他 | 未知 | [链接](changes/2026-03-24__lookback_days_60_to_90.md) |
| 2026-03-24 | 变更审计记录:小程序登录页头像昵称获取功能(前端实施) | 文档 | 其他, 文档 | 低 | [链接](changes/2026-03-24__miniprogram-avatar-nickname-feature.md) |
| 2026-03-24 | 变更审计记录:绩效页数据正确性修复 | bugfix | 其他 | 未知 | [链接](changes/2026-03-24__perf-page-data-fix.md) |
| 2026-03-24 | 审计记录:审核弹窗头像展示 + 排版优化 | 功能 | 其他 | 低 | [链接](changes/2026-03-24__review-modal-avatar-layout.md) |
| 2026-03-24 | 变更审计记录user_site_roles / user_assistant_binding 软删除实施 | 文档 | 其他 | 未知 | [链接](changes/2026-03-24__soft-delete-user-site-roles-binding.md) |
| 2026-03-24 | 变更审计记录TriggerJobs 清空任务交互反馈优化 | 清理 | 其他 | 高 | [链接](changes/2026-03-24__trigger-jobs-clear-task-interaction.md) |
| 2026-03-23 | 变更审计记录DDL 合并 — rejection_count + cancelled 状态 | 文档 | 其他 | 未知 | [链接](changes/2026-03-23__ddl-merge-rejection-count-cancelled.md) |
| 2026-03-23 | 变更审计记录:禁用用户改为移除用户 + 小程序鉴权两层模型修复 | bugfix | 其他 | 未知 | [链接](changes/2026-03-23__disable-to-remove-user-auth-model-fix.md) |
| 2026-03-23 | 变更审计记录:店铺筛选 + 时间格式 + 姓名格式 + 李小燕确认 | bugfix | 其他 | 未知 | [链接](changes/2026-03-23__mysites-tenant-filter-time-format-nickname-display.md) |
| 2026-03-23 | 变更审计记录:审核弹窗手机号不显示修复 + 自动匹配优化 + 身份标签中文化 | bugfix | 其他 | 未知 | [链接](changes/2026-03-23__review-modal-phone-display-auto-match-identity-label.md) |
| 2026-03-23 | 变更审计记录Change Audit Record | 功能 | 其他 | 未知 | [链接](changes/2026-03-23__role-routing-page-guard.md) |
| 2026-03-23 | 变更审计记录:租户管理员用户名大小写不敏感 | 功能 | 其他 | 未知 | [链接](changes/2026-03-23__tenant-admin-case-insensitive-username.md) |
| 2026-03-23 | 变更审计记录:租户管理后台审核弹窗改造(角色动态化 + 人员列表联动 + 手机号自动匹配) | bugfix | 其他 | 未知 | [链接](changes/2026-03-23__tenant-admin-review-modal-dynamic-roles.md) |
| 2026-03-23 | 变更审计记录:根治 tenant_admin 的 managed_site_ids 限制(跨模块权限验证改造) | bugfix | 其他 | 未知 | [链接](changes/2026-03-23__tenant-admin-site-access-root-fix.md) |
| 2026-03-23 | 变更审计记录:租户后台申请列表店铺筛选 + admin-web 简写ID修复 | bugfix | 其他 | 高 | [链接](changes/2026-03-23__tenant-user-approval-site-filter.md) |
| 2026-03-23 | 变更审计记录Task 6 Change B/C — 定时任务管理页面 + 小程序清理 | bugfix | 其他 | 未知 | [链接](changes/2026-03-23__trigger-jobs-admin-web-miniprogram-cleanup.md) |
| 2026-03-22 | 变更审计记录:数据库字段走查批量修复 | bugfix | 其他 | 低 | [链接](changes/2026-03-22__db-field-walkthrough-batch-fix.md) |
| 2026-03-22 | 变更审计记录dev-trace-log 全栈开发调试全链路日志系统 | 清理 | 其他 | 低 | [链接](changes/2026-03-22__dev-trace-log-fullstack-feature.md) |
| 2026-03-22 | 变更审计记录NS4 DDL 合并 — deleted_at 字段并入主迁移脚本 | 文档 | 其他 | 未知 | [链接](changes/2026-03-22__ns4-ddl-merge-deleted-at.md) |
| 2026-03-22 | 审计记录P14 Task 15 — 最终检查点完成 | bugfix | 其他, 脚本工具 | 极低 | [链接](changes/2026-03-22__p14-task15-final-checkpoint.md) |
| 2026-03-22 | 变更审计记录trace 日志路径修复 + 小程序登录竞态修复 | bugfix | 其他 | 未知 | [链接](changes/2026-03-22__trace-path-fix-miniprogram-login-race.md) |
| 2026-03-22 | 变更审计记录:僵尸任务修复 + 优雅关闭 + 重新执行按钮 | bugfix | 其他 | 未知 | [链接](changes/2026-03-22__zombie-task-graceful-shutdown-rerun.md) |
| 2026-03-20 | 变更审计记录ai-prompt-refinement spec 完成 + board-coach Mock 精简 | bugfix | 其他 | 未知 | [链接](changes/2026-03-20__ai-prompt-refinement-board-coach-mock.md) |
| 2026-03-20 | H2 修复FDW → 直连 ETL 架构统一 | bugfix | 其他 | 未知 | [链接](changes/2026-03-20__h2-fdw-to-direct-etl-unification.md) |
| 2026-03-20 | 审计记录:小程序文档同步更新 | bugfix | 其他 | 极低 | [链接](changes/2026-03-20__miniprogram-docs-sync.md) |
| 2026-03-20 | RNS1 系列 AI 自主决策风险审计报告(完整版) | bugfix | 其他 | 高 | [链接](changes/2026-03-20__rns1-ai-autonomous-decision-risk-audit.md) |
| 2026-03-20 | 变更审计记录RNS1.3 三看板 FDW 查询层数据口径修复 | bugfix | 其他, 文档 | 未知 | [链接](changes/2026-03-20__rns13-board-apis-e2e-fix.md) |
| 2026-03-20 | 变更审计记录RNS1.4 CHAT 模块重建 + FDW→直连统一 + R3 筛选修复 | bugfix | 其他 | 未知 | [链接](changes/2026-03-20__rns14-chat-fdw-filter-audit.md) |
@@ -242,7 +368,63 @@
| 日期 | 需求摘要 | 变更类型 | 风险 | 详情 |
|------|----------|----------|------|------|
| 2026-04-05 | 变更审计记录Kiro → Claude Code 全量迁移 | 文档 | 低 | [链接](changes/2026-04-05__kiro-to-claude-code-migration.md) |
| 2026-03-31 | 变更审计记录:任务引擎改造 — 参数调优 + 客户级升级/转移 + 任务统计写入 | 功能 | 未知 | [链接](changes/2026-03-31__task-engine-overhaul.md) |
| 2026-03-29 | 变更审计记录:助教看板和客户看板懒加载(分页加载) | 重构 | 未知 | [链接](changes/2026-03-29__board-lazy-loading-pagination.md) |
| 2026-03-29 | 变更审计记录:助教详情页 API 500 修复Schema 字段名对齐) | bugfix | 未知 | [链接](changes/2026-03-29__coach-detail-500-field-name-fix.md) |
| 2026-03-29 | 变更审计记录:助教详情页设计稿对齐 + 数据格式化修复 | bugfix | 未知 | [链接](changes/2026-03-29__coach-detail-design-alignment.md) |
| 2026-03-29 | 变更审计记录:助教详情页样式修复 + 数据聚合修复 + 关系指数回测支持 | bugfix | 未知 | [链接](changes/2026-03-29__coach-detail-style-aggregation-fix.md) |
| 2026-03-29 | 变更审计记录DWS_TASK_ENGINE ETL 编排替代 fire_event 事件链 | 功能 | 未知 | [链接](changes/2026-03-29__dws-task-engine-etl-orchestration.md) |
| 2026-03-29 | 变更审计记录:修复 recall_completion_check 事件链断裂 | bugfix | 未知 | [链接](changes/2026-03-29__fix-recall-completion-event-chain.md) |
| 2026-03-28 | 变更审计记录:财务看板 5 项修复ODS 行膨胀 / 优惠分摊 / 环比字段 / 区域过滤 / 规范沉淀) | bugfix | 未知 | [链接](changes/2026-03-28__board-finance-5fixes.md) |
| 2026-03-28 | 变更审计记录:财务看板 DWS 区域维度重构审计 | 重构 | 未知 | [链接](changes/2026-03-28__board-finance-dws-area-refactor-audit.md) |
| 2026-03-28 | 变更审计记录:修复小程序登录落地页跳转失效 | bugfix | 未知 | [链接](changes/2026-03-28__fix-miniprogram-login-landing-page.md) |
| 2026-03-27 | 变更审计记录board-finance 双重格式化修复 | bugfix | 低 | [链接](changes/2026-03-27__board-finance-double-format-fix.md) |
| 2026-03-27 | 审计记录board-finance-integration 阶段 2后端 API 修复) | bugfix | 未知 | [链接](changes/2026-03-27__board-finance-integration-T2.md) |
| 2026-03-27 | 变更审计记录:财务看板 Phase 2 对齐 DemoT1-T6 | bugfix | 未知 | [链接](changes/2026-03-27__board-finance-phase2-t1-t6.md) |
| 2026-03-27 | 变更审计记录board-finance WXML 格式化迁移 + 动态 Tab + 加载态清理 | bugfix | 未知 | [链接](changes/2026-03-27__board-finance-wxml-format-tabs-cleanup.md) |
| 2026-03-27 | 变更审计记录小程序权限体系统一改造W1-W5 | 功能 | 未知 | [链接](changes/2026-03-27__miniprogram-permission-unification.md) |
| 2026-03-27 | 变更审计记录任务列表近60天数据展示 + WXML 格式化改造 | 重构 | 高 | [链接](changes/2026-03-27__task-list-recent60d-and-wxml-formatting.md) |
| 2026-03-26 | 审计记录ETL 缺失字段补充 — 第一阶段DDL + FACT_MAPPINGS | 功能 | 低 | [链接](changes/2026-03-26__etl-missing-fields-phase1-ddl-mappings.md) |
| 2026-03-26 | 变更审计记录:到手金额口径修复(全小程序统一) | bugfix | 未知 | [链接](changes/2026-03-26__net-income-calibration-all-pages.md) |
| 2026-03-25 | 变更审计记录:保底 relationship_building 任务生成 | bugfix | 未知 | [链接](changes/2026-03-25__baseline-relationship-building-tasks.md) |
| 2026-03-25 | 变更审计记录:保底任务生成独立连接修复 | bugfix | 高 | [链接](changes/2026-03-25__baseline-task-independent-connection-fix.md) |
| 2026-03-25 | 变更审计记录:绩效页→任务详情页按 member_id 查询任务 | 文档 | 未知 | [链接](changes/2026-03-25__perf-to-task-detail-member-query.md) |
| 2026-03-25 | 绩效页 WXML 缺少 data-member-id 导致任务详情页空白 | bugfix | 未知 | [链接](changes/2026-03-25__perf-wxml-missing-member-id.md) |
| 2026-03-25 | 变更审计记录Change Audit Record | bugfix | 未知 | [链接](changes/2026-03-25__task-detail-service-records-6-improvements.md) |
| 2026-03-25 | 变更审计记录:租户用户审核 — 软删除恢复 upsert 修复 | bugfix | 未知 | [链接](changes/2026-03-25__tenant-users-soft-delete-upsert-fix.md) |
| 2026-03-24 | 变更审计记录:补录 cfg_skill_type 缺失的 3 条课程类型配置 | bugfix | 未知 | [链接](changes/2026-03-24__add_missing_cfg_skill_type.md) |
| 2026-03-24 | 审计记录:迁移脚本合并到主 DDL 并归档 | 文档 | 低 | [链接](changes/2026-03-24__ddl-migration-merge-and-archive.md) |
| 2026-03-24 | 审计记录:修复小程序前端档位进度条无刻度 + bonus_money 计算 | bugfix | 低 | [链接](changes/2026-03-24__fix-tier-nodes-empty-progress-bar.md) |
| 2026-03-24 | 变更审计记录lookback_days 从 60 天扩大到 90 天 | 文档 | 未知 | [链接](changes/2026-03-24__lookback_days_60_to_90.md) |
| 2026-03-24 | 变更审计记录:小程序登录页头像昵称获取功能(前端实施) | 文档 | 低 | [链接](changes/2026-03-24__miniprogram-avatar-nickname-feature.md) |
| 2026-03-24 | 变更审计记录P17 助教客户归属与任务生成引擎 | bugfix | 未知 | [链接](changes/2026-03-24__p17-assistant-ownership-task-engine.md) |
| 2026-03-24 | 变更审计记录P18 任务引擎运营看板实施 | bugfix | 未知 | [链接](changes/2026-03-24__p18-task-engine-dashboard.md) |
| 2026-03-24 | 变更审计记录:绩效页数据正确性修复 | bugfix | 未知 | [链接](changes/2026-03-24__perf-page-data-fix.md) |
| 2026-03-24 | 审计记录:审核弹窗头像展示 + 排版优化 | 功能 | 低 | [链接](changes/2026-03-24__review-modal-avatar-layout.md) |
| 2026-03-24 | 变更审计记录user_site_roles / user_assistant_binding 软删除实施 | 文档 | 未知 | [链接](changes/2026-03-24__soft-delete-user-site-roles-binding.md) |
| 2026-03-24 | 变更审计记录TriggerJobs 清空任务交互反馈优化 | 清理 | 高 | [链接](changes/2026-03-24__trigger-jobs-clear-task-interaction.md) |
| 2026-03-23 | 变更审计记录DDL 合并 — rejection_count + cancelled 状态 | 文档 | 未知 | [链接](changes/2026-03-23__ddl-merge-rejection-count-cancelled.md) |
| 2026-03-23 | 变更审计记录:禁用用户改为移除用户 + 小程序鉴权两层模型修复 | bugfix | 未知 | [链接](changes/2026-03-23__disable-to-remove-user-auth-model-fix.md) |
| 2026-03-23 | 变更审计记录:店铺筛选 + 时间格式 + 姓名格式 + 李小燕确认 | bugfix | 未知 | [链接](changes/2026-03-23__mysites-tenant-filter-time-format-nickname-display.md) |
| 2026-03-23 | 变更审计记录:审核弹窗手机号不显示修复 + 自动匹配优化 + 身份标签中文化 | bugfix | 未知 | [链接](changes/2026-03-23__review-modal-phone-display-auto-match-identity-label.md) |
| 2026-03-23 | 变更审计记录Change Audit Record | 功能 | 未知 | [链接](changes/2026-03-23__role-routing-page-guard.md) |
| 2026-03-23 | 变更审计记录:租户管理员用户名大小写不敏感 | 功能 | 未知 | [链接](changes/2026-03-23__tenant-admin-case-insensitive-username.md) |
| 2026-03-23 | 变更审计记录:租户管理后台审核弹窗改造(角色动态化 + 人员列表联动 + 手机号自动匹配) | bugfix | 未知 | [链接](changes/2026-03-23__tenant-admin-review-modal-dynamic-roles.md) |
| 2026-03-23 | 变更审计记录:根治 tenant_admin 的 managed_site_ids 限制(跨模块权限验证改造) | bugfix | 未知 | [链接](changes/2026-03-23__tenant-admin-site-access-root-fix.md) |
| 2026-03-23 | 变更审计记录:租户后台申请列表店铺筛选 + admin-web 简写ID修复 | bugfix | 高 | [链接](changes/2026-03-23__tenant-user-approval-site-filter.md) |
| 2026-03-23 | 变更审计记录Task 6 Change B/C — 定时任务管理页面 + 小程序清理 | bugfix | 未知 | [链接](changes/2026-03-23__trigger-jobs-admin-web-miniprogram-cleanup.md) |
| 2026-03-22 | 变更审计记录:数据库字段走查批量修复 | bugfix | 低 | [链接](changes/2026-03-22__db-field-walkthrough-batch-fix.md) |
| 2026-03-22 | 变更审计记录DDL vs 数据库结构对比修复 + BD 手册全面审核走查 | bugfix | 未知 | [链接](changes/2026-03-22__ddl-db-structure-diff-bd-manual-audit.md) |
| 2026-03-22 | 变更审计记录Change Audit Record | bugfix | 未知 | [链接](changes/2026-03-22__ddl_bd_manual_consistency_fix.md) |
| 2026-03-22 | 变更审计记录dev-trace-log 全栈开发调试全链路日志系统 | 清理 | 低 | [链接](changes/2026-03-22__dev-trace-log-fullstack-feature.md) |
| 2026-03-22 | 变更审计记录NS4 DDL 合并 — deleted_at 字段并入主迁移脚本 | 文档 | 未知 | [链接](changes/2026-03-22__ns4-ddl-merge-deleted-at.md) |
| 2026-03-22 | 审计记录P14 Task 15 — 最终检查点完成 | bugfix | 极低 | [链接](changes/2026-03-22__p14-task15-final-checkpoint.md) |
| 2026-03-22 | 变更审计记录trace 日志路径修复 + 小程序登录竞态修复 | bugfix | 未知 | [链接](changes/2026-03-22__trace-path-fix-miniprogram-login-race.md) |
| 2026-03-22 | 变更审计记录:僵尸任务修复 + 优雅关闭 + 重新执行按钮 | bugfix | 未知 | [链接](changes/2026-03-22__zombie-task-graceful-shutdown-rerun.md) |
| 2026-03-20 | 变更审计记录ai-prompt-refinement spec 完成 + board-coach Mock 精简 | bugfix | 未知 | [链接](changes/2026-03-20__ai-prompt-refinement-board-coach-mock.md) |
| 2026-03-20 | H2 修复FDW → 直连 ETL 架构统一 | bugfix | 未知 | [链接](changes/2026-03-20__h2-fdw-to-direct-etl-unification.md) |
| 2026-03-20 | 审计记录:小程序文档同步更新 | bugfix | 极低 | [链接](changes/2026-03-20__miniprogram-docs-sync.md) |
| 2026-03-20 | 变更审计记录R3 项目类型筛选接口重建fetchSkillTypes / cfg_area_category | bugfix | 高 | [链接](changes/2026-03-20__r3-skill-type-filter-rebuild.md) |
| 2026-03-20 | RNS1 系列 AI 自主决策风险审计报告(完整版) | bugfix | 高 | [链接](changes/2026-03-20__rns1-ai-autonomous-decision-risk-audit.md) |
| 2026-03-20 | 变更审计记录RNS1.3 三看板 FDW 查询层数据口径修复 | bugfix | 未知 | [链接](changes/2026-03-20__rns13-board-apis-e2e-fix.md) |
@@ -310,6 +492,12 @@
| 日期 | 需求摘要 | 变更类型 | 风险 | 详情 |
|------|----------|----------|------|------|
| 2026-03-28 | 变更审计记录:财务看板 5 项修复ODS 行膨胀 / 优惠分摊 / 环比字段 / 区域过滤 / 规范沉淀) | bugfix | 未知 | [链接](changes/2026-03-28__board-finance-5fixes.md) |
| 2026-03-24 | 审计记录:迁移脚本合并到主 DDL 并归档 | 文档 | 低 | [链接](changes/2026-03-24__ddl-migration-merge-and-archive.md) |
| 2026-03-24 | 变更审计记录:小程序登录页头像昵称获取功能(前端实施) | 文档 | 低 | [链接](changes/2026-03-24__miniprogram-avatar-nickname-feature.md) |
| 2026-03-24 | 变更审计记录P18 任务引擎运营看板实施 | bugfix | 未知 | [链接](changes/2026-03-24__p18-task-engine-dashboard.md) |
| 2026-03-22 | 变更审计记录Change Audit Record | bugfix | 未知 | [链接](changes/2026-03-22__ddl_bd_manual_consistency_fix.md) |
| 2026-03-22 | 变更审计记录P16 调度任务最小运行间隔 — Spec 收尾文档同步 | 文档 | 未知 | [链接](changes/2026-03-22__p16-spec-closing-doc-sync.md) |
| 2026-03-20 | 变更审计记录R3 项目类型筛选接口重建fetchSkillTypes / cfg_area_category | bugfix | 高 | [链接](changes/2026-03-20__r3-skill-type-filter-rebuild.md) |
| 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) |
@@ -344,6 +532,7 @@
| 日期 | 需求摘要 | 变更类型 | 风险 | 详情 |
|------|----------|----------|------|------|
| 2026-03-24 | 变更审计记录P17 助教客户归属与任务生成引擎 | bugfix | 未知 | [链接](changes/2026-03-24__p17-assistant-ownership-task-engine.md) |
| 2026-02-14 | 审计记录DWS 基类 bugfix — 绩效档位兜底 + safe_decimal 异常捕获 | bugfix | 未知 | [链接](changes/2026-02-14__dws-bugfix-tier-safedecimal.md) |
| 2026-02-14 | 审计记录:废弃独立 ODS/DWD 任务代码清理 + 文档同步 | bugfix | 未知 | [链接](changes/2026-02-14__legacy-ods-dwd-cleanup.md) |
| 2026-02-13 | 审计记录BD_Manual 文档整理与 DDL 同步 | bugfix | 低 | [链接](changes/2026-02-13__bd-manual-docs-consolidation-ddl-sync.md) |
@@ -353,6 +542,8 @@
| 日期 | 需求摘要 | 变更类型 | 风险 | 详情 |
|------|----------|----------|------|------|
| 2026-03-24 | 变更审计记录P18 任务引擎运营看板实施 | bugfix | 未知 | [链接](changes/2026-03-24__p18-task-engine-dashboard.md) |
| 2026-03-22 | 审计记录P14 Task 15 — 最终检查点完成 | bugfix | 极低 | [链接](changes/2026-03-22__p14-task15-final-checkpoint.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) |
| 2026-02-15 | 审计记录docs/bd_manual + docs/dictionary → docs/database 合并 | 清理 | 极低 | [链接](changes/2026-02-15__docs-database-merge.md) |

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
# Export 输出路径规范
> 最后更新2026-02-19
> 最后更新2026-03-23
> 本文档描述 `export/` 目录的统一结构、各子目录用途、对应的 `.env` 变量、以及代码中的读取方式。
---
@@ -21,8 +21,12 @@ export/
│ │ └── full_dataflow_doc/ — 全链路数据流文档Markdown
│ └── CACHE/
│ └── api_samples/ — API 样本缓存24h 有效gen_full_dataflow_doc 使用)
── BACKEND/
── LOGS/ — 后端结构化日志(预留,待 5.3 后端日志改造后启用)
── BACKEND/
── LOGS/ — 后端结构化日志(预留,待 5.3 后端日志改造后启用)
│ └── avatars/ — 用户头像文件({user_id}.jpg覆盖式保存
└── dev-trace-logs/ — 开发调试全链路日志DevTrace 模块)
└── YYYY-MM-DD/ — 按日期分子目录
└── trace_YYYY-MM-DD_HH.jsonl — 按小时分文件JSON Lines
```
> 服务器部署时不保留 `export/`(通过 `setup-server-git.py` 排除),仅开发机留存。
@@ -44,6 +48,8 @@ export/
| `API_SAMPLE_CACHE_ROOT` | `C:/NeoZQYY/export/SYSTEM/CACHE/api_samples` | `SYSTEM/CACHE/api_samples/` | API 样本缓存 |
| `SYSTEM_LOG_ROOT` | `C:/NeoZQYY/export/SYSTEM/LOGS` | `SYSTEM/LOGS/` | 系统级运维日志 |
| `BACKEND_LOG_ROOT` | `C:/NeoZQYY/export/BACKEND/LOGS` | `BACKEND/LOGS/` | 后端结构化日志 |
| `DEV_TRACE_LOG_DIR` | `export/dev-trace-logs` | `dev-trace-logs/` | 开发调试全链路日志DevTrace 模块) |
| `AVATAR_EXPORT_PATH` | `C:/NeoZQYY/export/BACKEND/avatars` | `BACKEND/avatars/` | 用户头像文件存储目录 |
---
@@ -229,6 +235,51 @@ export/SYSTEM/REPORTS/dataflow_analysis/
---
### 10. dev-trace-logs — 开发调试全链路日志
环境变量:`DEV_TRACE_LOG_DIR`
默认值:`export/dev-trace-logs`(相对于项目根目录)
用途:
- DevTrace 模块的全链路请求追踪日志输出目录
- 覆盖 HTTP 请求、SSE 流式响应、WebSocket 连接、后台 Job、异常/错误、数据库连接生命周期、中间件层
- 仅用于开发调试,不影响生产环境
目录结构:
```
export/dev-trace-logs/
├── _index.json — 索引文件(文件列表、记录数、文件大小)
├── 2026-03-23/ — 按日期分子目录
│ ├── trace_2026-03-23_08.jsonl — 按小时分文件JSON Lines 格式)
│ ├── trace_2026-03-23_09.jsonl
│ ├── trace_2026-03-23_09_001.jsonl — 单文件超 10MB 自动轮转
│ └── ...
└── 2026-03-22/
└── ...
```
自动清理:
-`DEV_TRACE_LOG_RETENTION_DAYS` 环境变量控制保留天数(默认 7 天)
- 每日凌晨自动检查并删除超期日期目录
- 也可通过 `POST /api/admin/dev-trace/cleanup` 手动按日期范围清理
相关环境变量:
| 变量 | 默认值 | 说明 |
|------|--------|------|
| `DEV_TRACE_ENABLED` | `true` | 总开关,关闭后不采集任何 trace |
| `DEV_TRACE_LOG_DIR` | `export/dev-trace-logs` | 日志输出目录 |
| `DEV_TRACE_LOG_RETENTION_DAYS` | `7` | 自动清理保留天数 |
| `DEV_TRACE_LOG_SQL` | `true` | 是否记录完整 SQL 语句 |
| `DEV_TRACE_LOG_PARAMS` | `true` | 是否记录函数参数值 |
代码使用:
- `apps/backend/app/trace/config.py``TraceConfig` 类读取上述环境变量
- `apps/backend/app/trace/writer.py``TraceWriter` 负责写入 `.jsonl` 文件
- `apps/backend/app/trace/cleanup.py` 负责自动清理过期目录
---
## 配置优先级
所有路径变量遵循项目统一的配置优先级:
@@ -257,6 +308,8 @@ ETL 模块的路径变量通过 `env_parser.py` 的 `ENV_MAP` 映射到 `AppConf
| api_samples | `API_SAMPLE_CACHE_ROOT` | ✅ | `gen_full_dataflow_doc.py` 已适配 |
| SYSTEM LOGS | `SYSTEM_LOG_ROOT` | — | 预留 |
| BACKEND LOGS | `BACKEND_LOG_ROOT` | — | 预留 |
| dev-trace-logs | `DEV_TRACE_LOG_DIR` | ✅ | `app/trace/config.py``TraceConfig``app/trace/writer.py` 写入 |
| BACKEND avatars | `AVATAR_EXPORT_PATH` | ✅ | `app/config.py``AVATAR_EXPORT_PATH``xcx_avatar.py` 读写 |
---
@@ -270,6 +323,7 @@ FETCH_ROOT=C:/NeoZQYY/export/ETL-Connectors/feiqiu/JSON
ETL_REPORT_ROOT=C:/NeoZQYY/export/ETL-Connectors/feiqiu/REPORTS
SYSTEM_ANALYZE_ROOT=C:/NeoZQYY/export/SYSTEM/REPORTS/dataflow_analysis
BACKEND_LOG_ROOT=C:/NeoZQYY/export/BACKEND/LOGS
AVATAR_EXPORT_PATH=C:/NeoZQYY/export/BACKEND/avatars
```
服务器测试环境(`D:\NeoZQYY\test\repo\.env`

View File

@@ -0,0 +1,247 @@
# 前后端联调规范手册
> 最后更新2026-04-01
> 适用范围:小程序(`apps/miniprogram/`)与 FastAPI 后端(`apps/backend/`)的联调
> 约束力:高度建议参考。特殊需求可偏离但需注明原因。
> 速查索引:`.kiro/steering/frontend-backend-integration.md`fileMatch 自动加载)
---
## 一、数据契约
### 1.1 ResponseWrapper
后端 `ResponseWrapperMiddleware` 自动包装所有 2xx JSON 响应为 `{"code": 0, "data": <原始body>}`。脚本/测试调用 API 时必须从 `resp.json()["data"]` 取实际数据,不能直接用 `resp.json()`(踩坑记录 2026-03-28
### 1.2 字段命名与 CamelCase 转换
- 后端统一 `snake_case`Pydantic `CamelModel` 自动转 `camelCase` 输出
- 前端用 camelCase 读取,兼容写法:`rec.memberId ?? rec.member_id ?? 0`
- 踩坑:`me.avatar` 应为 `me.avatarUrl``avatar_url` 转换后)
### 1.3 条件显示放后端
字段需要根据业务条件决定显示/隐藏时,后端满足条件返回值、不满足返回 `null`。前端只判 `wx:if="{{field}}"`,禁止做数值比较。
组件 property 收到 `null` 不走默认值,传入前必须清洗:`r.durationRaw ?? 0``r.drinks ?? ''`
### 1.4 Schema 与 Service 同步规则
Pydantic response schema 必须与 service 返回字段严格一致,否则 500。排查 500 优先检查:
1. 数据库迁移是否已执行
2. Schema 字段是否与 service 一致
3. 环境变量是否缺失
4. 鉴权依赖是否匹配
#### 踩坑记录汇总Pydantic 系列)
| 问题 | 日期 | 表现 | 规则 |
|------|------|------|------|
| 静默丢弃未声明字段 | 2026-03-28 | 新增字段(`desc``discount_total`)前端收不到 | 新增后端返回字段时必须同步更新 Schema |
| `list[dict]` 不转换内部 key | 2026-03-28 | 前端拿到 snake_case key | 用 CamelModel 子类替代 `dict`,或 service 层手动递归转换 |
| Optional 与 None 不同步 | 2026-03-29 | service 返回 None 但 Schema 非 Optional → 500 | 修改 service 返回 None 时必须同步 Schema 为 `XxxModel | None` |
| response_model 类型不匹配 | 2026-03-29 | Schema 期望 `list[CoachSkillItem]` 但返回 `list[str]` → 500 | 修改 service 返回字段时必须同步检查 Schema 类型 |
| 嵌套 CamelModel 字段名不匹配 | 2026-03-29 | dict key 与 Schema 字段名不一致 → 500 | `_build_*` 方法必须逐字段对照 Schema |
| 含数字字段名转换 | 2026-03-29 | `consumption_60d``consumption60D`(大写 D | 前端用 `d.consumption60D ?? d.consumption60d` 兼容 |
### 1.5 接口返回结构
```json
// 列表:{ "records": [...], "total_count": 42, "has_more": true }
// 概览:{ "coach_name": "...", "income_items": [...], "this_month_records": [...] }
```
收入明细始终返回所有项(即使为 0前端不做过滤。
---
## 二、展示映射
### 2.1 头像颜色
后端只传 `member_id` + `avatar_char`,前端 `nameToAvatarColor(String(memberId))` 计算颜色 key。散客`member_id ≤ 0`)→ `'default'`(灰色),`avatar_char` 返回 `"?"`
### 2.2 课程标签
后端返回数据库原始 `skill_name`中文WXSS 不支持中文类名,前端映射:
```typescript
const COURSE_TAG_MAP: Record<string, string> = {
'陪打': 'basic', '基础课': 'basic',
'包厢': 'room', '包厢课': 'room',
'超休': 'incentive', '激励课': 'incentive', '打赏课': 'incentive',
}
```
### 2.3 角色标签
后端返回英文 code`coach`/`staff`/`head_coach`/`manager`),前端 `ROLE_LABELS` 映射中文。助教拼接等级:`${coachLevel}助教`
### 2.4 助教姓名显示规则2026-03-28
所有助教必须使用昵称/花名(`nickname`),不得显示真实姓名(`real_name`。SQL 中统一用 `COALESCE(da.nickname, da.real_name, '')`nickname 优先。
### 2.5 助教离职过滤2026-03-29
所有查询助教的 SQL 必须加 `da.leave_status = 0` 过滤离职助教。LEFT JOIN 场景用 `(da.leave_status IS NULL OR da.leave_status = 0)` 兼容无匹配行。`dim_assistant``leave_status=1` 为离职(占 74%),不过滤会展示大量已离职助教。
### 2.6 预估规则(全小程序统一)
当月且当前日期 ≤ 5号时显示"预估"标签。业务含义:每月 1-5 日为工资审核期。
### 2.7 WXSS 类名拼接
后端值用于 `class="xxx-{{value}}"` 时必须是合法 CSS 标识符(纯英文),禁止中文/hex/特殊字符。
### 2.8 前端筛选枚举同步(踩坑记录 2026-03-28
前端下拉/筛选组件的 `value` 必须与后端 Enum 值完全一致(大小写、命名)。修改后端枚举时,必须全局搜索所有使用该枚举的前端页面同步更新。
### 2.9 WXS 格式化规范
- WXS 数值方法防护:`format.wxs` 中所有调用 `.toFixed()` 的函数必须先 `parseFloat` 转数字
- TS 与 WXS 格式化互斥2026-03-27WXML 用 `{{fmt.money(field)}}`TS 层 `setData` 必须传原始数字(`?? 0`),禁止 `formatMoney()` 预格式化
- 后端 service 层同理2026-03-29前端用 WXS 格式化的字段后端必须返回原始数字float/int禁止预格式化为字符串。Schema 类型需同步从 `str` 改为 `float/int`
- 环比显示统一规范2026-03-28所有环比文本必须使用 `fmt.compareText(compare, isDown)` + `fmt.compareClass(compare, isDown, size)` WXS 函数
- WXS 零值语义2026-03-27`value === 0` 不一定是空值。`days(0)` 表示"今天到店"是有效值。新增 WXS 函数时必须区分"零值有业务含义"和"零值等同空值"
---
## 三、SQL 查询规范
### 3.1 ETL 连接复用(必须)
同一 service 方法内创建一个 `etl_conn` 传给所有 `fdw_queries.*`
### 3.2 多页面复用组件时 SQL 统一
后端为同一组件提供数据的查询必须同口径(共享 JOIN/聚合),各页面只负责过滤和分页。
### 3.3 一对多明细用 LATERAL 避免行膨胀
```sql
LEFT JOIN LATERAL (
SELECT string_agg(gs.ledger_name || '×' || gs.total_count, '') AS drinks
FROM (...GROUP BY ledger_name) gs
) gs_agg ON true
```
### 3.4 助教收入口径
- `sl.ledger_amount`:行级毛收入,不是到手
- 到手:`hours × (base_course_price - base_deduction)`
- `sh.assistant_pd_money + cx_money`:整单级汇总,禁止 JOIN 到行级
### 3.5 快照值 vs 流量值2026-03-27
- 流量值(充值/消费/收入):可以 SUM
- 快照值(卡余额 `cash_card_balance`/`gift_card_balance`/`total_card_balance`):日末快照,多天聚合取最后一天的值,禁止 SUM
### 3.6 `dim_member_card_account` 多卡膨胀2026-03-29
同一 `tenant_member_id``scd2_is_current=1` 下可能有多条记录(多张卡),必须先 `GROUP BY tenant_member_id` + `SUM(balance)` 聚合后再 JOIN。
### 3.7 台费原价推算2026-03-29
台费正价 = `table_charge_money + adjust_amount``adjust_amount > 0` 时显示原价删除线。
### 3.8 环比同期对比规则2026-03-28
当前周期 end_date cap 到今天,上期取同类周期的同期天数。已完成周期取完整周期对比再上一个完整周期。
### 3.9 优惠拆分恒等式2026-03-28
`discount_total = discount_groupbuy + discount_manual + discount_other + discount_vip + discount_gift_card + discount_rounding`。拆分展示必须包含全部 6 个子字段。
### 3.10 团购金额双口径2026-03-28
DWS `groupbuy_pay_amount` 是团购交易金额,`daily_revenue_report.py``sale_price × 0.75` 估算回款。财务看板用 DWS 口径,经营报告用估算回款口径。
### 3.11 助教财务三列口径2026-03-28
pay/share/hourly 全部从 DWS `dws_assistant_salary_calc` 计算。禁止从 DWD `ledger_amount` 取客户支付再 JOIN DWS 等级(同一助教同月可有多等级记录导致行膨胀)。
### 3.12 客源储值口径(纠正 2026-03-29
客源储值 = 该助教关联客户的卡余额合计,不是充值提成。数据源:`v_dws_member_assistant_relation_index``session_count > 0`+ `v_dim_member_card_account`(先 GROUP BY 聚合)。
### 3.13 `DATE_TRUNC` 返回 timestamp 不是 date2026-03-29
用作 dict key 时必须先 `.date()` 转换。
### 3.14 关系指数表字段名2026-03-29
`dws_member_assistant_relation_index` 的字段是 `session_count`(非 service_count`total_duration_minutes`(非 total_hours`ml_allocated_amount`(非 total_income
### 3.15 `dim_table` 主键列名2026-03-29
主键是 `table_id`(不是 `site_table_id`。JOIN 时用 `ON s.site_table_id = dt.table_id`
---
## 四、数据库操作陷阱
### 4.1 psycopg2 Windows GBK 编码2026-03-29
`psycopg2.connect()` 用关键字参数时libpq 拼接系统 locale 信息触发 `UnicodeDecodeError`。解决:用显式 DSN 字符串 + `client_encoding=UTF8` + `os.environ.setdefault("PGCLIENTENCODING", "UTF8")`
### 4.2 软删除 + ON CONFLICT
`ON CONFLICT DO UPDATE SET is_removed=false`,禁止 `DO NOTHING`
### 4.3 ON CONFLICT 精确匹配 partial unique index
列列表和 WHERE 条件必须与索引定义完全对应。
### 4.4 跨库过滤分页
先构建排除列表SQL 层统一过滤,禁止内存过滤修正 total。
### 4.5 共享连接子流程
需事务隔离时用独立连接,避免异常被外层 except 吞掉。
### 4.6 FDW 查询禁止 N+1 串行2026-03-29
必须改为 `WHERE assistant_id = ANY(%s)` 批量查询 + Python 层 dict 映射。
### 4.7 FDW 查询必须传 etl_conn2026-03-29
所有 `_build_*` 子函数都应接收并传递 `etl_conn`,否则 `_fdw_context` 在业务库连接上设置 RLS 导致空结果。
---
## 五、前端交互规范
### 5.1 页面加载态2026-03-29
统一用 `wx.showLoading` / `wx.hideLoading` 原生遮罩,禁止自定义加载组件。
### 5.2 跨页面跳转
URL 参数必须包含可查询 ID`memberId`/`taskId`),不能只传展示字段。
### 5.3 dataset 同步
TS `e.currentTarget.dataset.xxx` 必须与 WXML `data-xxx` 属性同步。
### 5.4 自定义组件静默不渲染2026-03-29
未在页面 JSON `usingComponents` 中注册的组件不报错但完全不渲染。
### 5.5 多状态列表页
后端按 status 过滤,前端需并行请求所有需要展示的状态并合并。
### 5.6 微信头像
`chooseAvatar` 组件 + `wx.uploadFile` 上传服务器,临时路径会过期。
### 5.7 Vite 代理
跨前缀接口必须在 `vite.config.ts` proxy 中添加规则。
### 5.8 权限守卫
fallback 目标页必须对当前用户无条件可访问,避免跳转死循环。
### 5.9 前后端权限一致性
后端 `/api/xcx/me` 返回 `permissions[]`,前端根据权限码动态控制可见性。新增权限码只需更新数据库 `auth.role_permissions`
### 5.10 custom-tab-bar 异步刷新
更新 `globalData.visibleTabs` 后必须主动调用 `tabBar._refreshTabs()`。看板 boardTabs 刷新必须放在 `checkPageAccess().then()` 回调中。
### 5.11 禁止页面滚动2026-03-28
`wx.setPageScrollEnabled` 不存在。用 `<page-meta page-style="overflow:hidden" />` 控制。
### 5.12 登录与 /me 接口字段差异2026-03-28
`/api/xcx/login` 返回 `WxLoginResponse`(不含 permissions`/api/xcx/me` 返回 `UserStatusResponse`(含 permissions。登录后需权限码时必须额外请求 `/me`
### 5.13 分页接口 API 函数必须返回完整响应2026-03-29
必须返回完整 `data`(含 `items`/`total`/`page`/`pageSize`),禁止只返回 `data.items`
### 5.14 懒加载分页追加必须去重 + 双条件判底2026-03-29
① 追加数据按 id 去重(`Set` 过滤);② `hasMore` 用双条件:`items.length >= pageSize && merged.length < total`
---
## 六、事件驱动触发器
- `biz.trigger_jobs``trigger_condition='event'` 的任务必须有明确的 `fire_event(event_name)` 发射端
- 任务引擎全流程由 ETL 任务 `DWS_TASK_ENGINE` 编排,通过 HTTP 调用后端 `POST /api/internal/run-job`
- `task_expiry_check` 的 interval 触发器保留(每小时),`DWS_TASK_ENGINE` 是补充而非替代
- `recall_detector` 直接生成回访任务CHANGE 2026-03-31
- 任务生成器使用客户级别升级/转移CHANGE 2026-03-31基于 `t_v / ideal_interval` ratio 决定分配范围
- 任务统计写入CHANGE 2026-03-31`task_generator.run()` 末尾调用 `_update_task_stats()`
---
## 七、ETL 调用后端 API 注意事项
- ETL 的 `AppConfig` 不设置 `os.environ`ETL 任务中用 `os.environ.get()` 前必须先 `load_dotenv`
- 后端 `ResponseWrapperMiddleware` 包装响应ETL 调用时需从 `resp.json()["data"]` 取数据
---
## 八、列表与分页
- 客户列表:默认 5 条,展开最多 20 条
- 服务记录按日期分组DateGroup默认 2 组,可展开
- 新客:本月有服务但之前无历史
- 常客:本月服务 ≥ 2 次,展示近 90 天聚合

View File

@@ -1,7 +1,13 @@
# 小程序接口契约文档
<!-- AI_CHANGELOG
- 2026-03-20 | Prompt: 小程序文档过期检查与落盘 | 状态标记从"联调前草案"更新为
"后端已实现26 个端点全部就绪),前端联调中";新增最后更新日期。
-->
> 创建时间2026-03-18
> 状态:联调前草案,后端开发时以此为契约基准
> 最后更新2026-03-20
> 状态后端已实现26 个端点全部就绪),前端联调中
> 基础路径:`/api/xcx`
---

View File

@@ -4,6 +4,9 @@
> 预估工作量:中等
> 前置条件P5-A 已完成AI 骨架就绪、NS1 已完成(后端 API 数据结构确定)
> 参考基准:`docs/prd/specs/P5-miniapp-ai-integration.md`P5-B 阶段定义)
> SPEC 状态:✅ 三件套已完成requirements.md → design.md → tasks.md
> SPEC 路径:`.kiro/specs/ai-prompt-refinement/`
> 实施状态:⏳ 未开始(代码实施待启动)
---
@@ -17,7 +20,7 @@ P5-A 阶段已交付 AI 集成管道:百炼封装、缓存 API、SSE 框架、
| 应用 | 文件 | 骨架状态 | 待细化字段 |
|------|------|---------|-----------|
| 应用 1 | `app1_chat.py` | 页面上下文文本化工具留接口 | `page_context``screen_content` 各页面文本化 |
| 应用 1 | `app1_chat.py` | 页面上下文文本化工具留接口 | `page_context` + `screen_content` 合并为 `build_page_text()` 输出(详见 3.7 设计决策) |
| 应用 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 |
@@ -316,30 +319,50 @@ NS2 需确认事项:
### 3.7 应用 1页面上下文传递与文本化
#### 当前问题P5 PRD 与 RNS1.4 实现的差异)
#### P5 原始设计 vs NS2 实际方案
P5 PRD 原始设计
```
前端跳转 chat.html 时传入 source_page + page_context + screen_content
→ 后端接收后文本化 → 拼接为首条 user message
```
P5 PRD 定义应用 1 首条 Prompt 包含三个字段
RNS1.4 当前实现:
```
前端跳转 chat 页面时仅传入 contextType + contextId如 task/12345
→ 后端 _build_page_context() 为占位实现page_context 和 screen_content 为空
→ AI 无法获得页面上下文
```
| 字段 | P5 定义 | 含义 |
|------|---------|------|
| `source_page` | 来源页面标识 | 如 `task-detail``board-finance` |
| `page_context` | 页面上下文摘要 | 结构化数据,后端可从 DB 获取 |
| `screen_content` | 用户当前屏幕可见内容的文本化描述 | 需前端传入当前视口内容 |
#### 解决方案:后端根据 contextType 自动获取页面上下文
AI需求2.md 原文对应:
- `page_context` → "用户正在查看页面内容:页面上下文(来源页面+内容摘要)"
- `screen_content` → "用户页面视野内容:当前屏幕内容"
采用方案 B简化前端后端自动查询与 RNS1.4 已有的 `contextType`/`contextId` 参数兼容:
#### `screen_content` 的设计决策
> ⚠️ **设计决策NS2 与 P5 的差异点)**NS2 不要求前端传入 `screen_content`,改由后端根据 `contextType` + `contextId` + 筛选参数自动查询数据库,生成等效的页面上下文文本。
**决策理由:**
1. 微信小程序前端难以将当前屏幕 DOM 序列化为结构化文本(无 `innerText` 等 Web API
2. RNS1.4 已实现 `contextType`/`contextId` 参数机制,后端可据此精确获取页面数据
3. 后端自动查询可获取比前端视口更完整的上下文(如关联的 AI 分析、历史备注等)
4. 看板类页面通过额外传入筛选参数(`timeDimension``dimension` 等),后端可还原用户当前视图的数据范围
**等效覆盖说明:**
- P5 的 `page_context`(结构化数据摘要)→ NS2 通过 `build_page_text()` 从 DB 获取,完全覆盖
- P5 的 `screen_content`(屏幕可见内容)→ NS2 通过 `contextType` + 筛选参数推断用户当前视图,近似覆盖
- 对于详情类页面task-detail、customer-detail 等),后端获取的数据与用户屏幕内容高度一致
- 对于看板类页面,通过筛选参数还原当前视图的数据维度和范围
**局限性:**
- 无法获取用户滚动位置(如长列表中用户正在看第几条)
- 无法获取用户输入中但未提交的内容
- 看板页面若前端未传筛选参数,使用默认值可能与用户实际视图不一致
#### 实际方案:后端根据 contextType 自动获取页面上下文
与 RNS1.4 已有的 `contextType`/`contextId` 参数兼容:
```
前端传入 contextType="task-detail" + contextId="12345"
→ 后端 build_page_text("task-detail", 12345, site_id)
前端传入 contextType="task-detail" + contextId="12345"+ 看板类页面的筛选参数)
→ 后端 build_page_text("task-detail", 12345, site_id, filters?)
→ 后端从数据库获取任务详情、客户信息、备注、AI 分析等
→ 格式化为结构化中文文本
→ 格式化为结构化中文文本(同时覆盖 page_context 和 screen_content 的信息需求)
→ 拼接为首条 user message 的 page_context 字段
```
@@ -435,17 +458,19 @@ RNS1.4 当前实现:
```
用户点击 AI 入口
→ 前端传入 contextType + contextId+ 可选筛选参数)
→ 前端传入 contextType + contextId+ 看板类页面的筛选参数)
→ 后端 build_page_text(contextType, contextId, site_id, filters?)
→ 从数据库获取对应页面数据,格式化为结构化中文文本
→ 文本同时覆盖 P5 定义的 page_context数据摘要和 screen_content屏幕内容
→ 拼接为首条 user message 的 page_context 字段
→ 注入 biz_paramsUser_ID/Role/Nickname到 system prompt
→ SSE 流式调用百炼 API
```
> ⚠️ 与 P5 PRD 原始设计的差异P5 设计为前端传入 `source_page` + `page_context` + `screen_content`
> RNS1.4 实际实现为前端传入 `contextType` + `contextId`,后端自动查询数据库获取上下文。
> NS2 沿用 RNS1.4 方案(后端自动获取),不要求前端传入原始页面数据。
> ⚠️ 与 P5 PRD 原始设计的差异(详见 3.7 设计决策):
> P5 设计为前端传入 `source_page` + `page_context` + `screen_content` 三个独立字段,
> NS2 采用后端自动查询方案,前端传入 `contextType` + `contextId` + 筛选参数,
> 后端通过 `build_page_text()` 生成合并的页面上下文文本,等效覆盖 `page_context` 和 `screen_content` 的信息需求。
---
@@ -467,55 +492,67 @@ RNS1.4 当前实现:
---
## 七、预审查清单(SPEC 启动前确认)
## 七、预审查清单(确认)
> 以下问题已在 SPEC 细化阶段requirements.md / design.md中逐一确认。
### 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` 是否需要包含卡号、开卡日期、有效期等详细信息,还是只需要卡类型和余额?
1. **消费记录字段范围**每条记录包含 `settle_date``settle_type``items_sum``table_charge_money``assistant_pd_money``assistant_cx_money``goods_money``room_name``duration_minutes``assistant_names` 共 10 个字段。不包含折扣明细和支付方式明细token 预算有限,这些字段对 AI 分析价值低)。
2. **服务记录字段范围**每条记录包含 `service_date``duration_minutes``items_sum``room_name``is_pd`(是否陪打)。不包含台桌类型和客户评价
3. **备注内容截断**单条备注最大 500 字符,超出截断并附加"…(已截断)"标记。备注总数最多 50 条(按 `created_at DESC`)。
4. **会员卡明细粒度**仅包含 `card_type`(卡类型)、`balance`(余额)、`gift_balance`(赠送余额),不含卡号、开卡日期、有效期。
### 7.2 Prompt 优化
5. **Token 预算**每个应用的首条 Prompt 的 token 上限是多少?百炼 API 的单次请求 token 限制?
6. **数据时间窗口**消费记录默认近 3 个月,是否需要可配置?不同应用是否需要不同时间窗口
7. **空数据处理**客户无消费记录/无备注/无服务历史时Prompt 如何处理?是否需要特殊提示词?
5. **Token 预算**应用 3/4/5/6/7 的 system message content ≤ 8000 字符(约 4000 token应用 1 的 system prompt含页面上下文≤ 4000 字符(约 2000 token)。
6. **数据时间窗口**:默认近 3 个月,通过 `months` 参数可配置。各应用统一使用 3 个月窗口
7. **空数据处理**使用明确的空状态提示词(如"该客户无消费记录,请基于已有信息分析"),不传入空数据不做说明。`reference` 无历史数据时设为空对象并标注"暂无历史线索"。
### 7.3 页面文本化(应用 1
8. **文本化格式**页面上下文是输出为结构化中文文本还是 JSONAI 对哪种格式理解更好?
9. **数据量控制**:每个页面上下文的字符上限?是否需要根据页面类型动态调整?
10. **实时性要求**应用 1 的页面上下文是否需要实时获取最新数据?还是可以使用缓存(如 task_detail_cache
8. **文本化格式**:输出为结构化中文描述文本(分段标题 + 缩进),非 JSON。中文描述更便于 AI 理解上下文语义。
9. **数据量控制**:每个页面上下文统一 ≤ 2000 字符,不按页面类型动态调整。超出截断并标注。
10. **实时性要求**实时获取最新数据(不使用缓存),设置 5 秒 FDW 查询超时。数据获取失败时返回降级文本,不阻断对话。
### 7.4 性能与安全
11. **FDW 查询并发**多个数据获取函数是否可以并发执行asyncio.gatherFDW 连接池是否支持?
12. **数据脱敏**传入百炼 API 的数据中,哪些字段需要脱敏?member_phone 已断档不传,还有其他敏感字段吗?
13. **错误降级**某个数据获取函数失败时(如 FDW 超时),是否跳过该部分继续生成 Prompt还是整体失败
11. **FDW 查询并发**支持。使用 `asyncio.gather(return_exceptions=True)` 并发执行多个数据获取函数,部分失败不阻断。同一连接上的多个 FDW 查询串行执行(共享 RLS 设置)。
12. **数据脱敏**`member_phone` 已断档不传。会员信息通过 `member_id JOIN v_dim_member (scd2_is_current=1)` 获取昵称,不传入手机号等敏感字段。`build_page_text()` 输出中不包含 `member_phone`
13. **错误降级**部分失败继续。失败部分使用默认空值(空数组/空对象),在 Prompt 中标注"该部分数据获取失败",继续生成 Prompt 并调用百炼 API。确保 Prompt JSON 不含 `null`
---
## 八、任务清单(草案SPEC 细化后调整
## 八、任务清单(已细化,详见 SPEC tasks.md
### 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 专用)
> SPEC 三件套已完成:`requirements.md`14 条需求)→ `design.md`17 个正确性属性)→ `tasks.md`17 个顶层任务)。
> 以下为 PRD 级任务概览,详细实施步骤见 `.kiro/specs/ai-prompt-refinement/tasks.md`。
### Batch BPrompt 拼接实现
- [ ] 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 A共享数据获取层tasks 1-4
- [ ] T1创建 `data_fetchers/` 模块骨架(`__init__.py` + 3 个子模块
- [ ] T2实现 `member_data.py``fetch_member_consumption_data` + `fetch_member_notes`
- [ ] T3实现 `assistant_data.py``fetch_assistant_info` + `fetch_service_history`
- [ ] T4检查点 — 数据获取层完成
### Batch C应用 1 页面文本化 + 前端配合
- [ ] T9实现各页面类型的文本化函数task-detail/customer-detail/board-*/performance 等
- [ ] T10补充 `app1_chat.py``_build_page_context()` 调用文本化函数,根据 `contextType` 路由到对应文本化函数
- [ ] T11前端补充看板类页面的筛选参数传递board-finance/board-customer/board-coach 跳转 chat 时传入当前筛选条件
- [ ] T12确认 biz_params 端到端正确性(前端 JWT → 后端提取 user_id/role/nickname → system prompt 注入 → 百炼权限隔离生效
### Batch BPrompt 拼接实现tasks 5-11
- [ ] T5完善 `app3_clue.py``build_prompt()`async + 真实数据
- [ ] T6完善 `app4_analysis.py``build_prompt()`asyncio.gather 并发获取)
- [ ] T7完善 `app5_tactics.py``build_prompt()`(复用 App4 + task_suggestion
- [ ] T8完善 `app6_note.py``build_prompt()`(消费数据 + 全部备注
- [ ] T9完善 `app7_customer.py``build_prompt()`(客观+主观数据,标注来源)
- [ ] T10检查点 — 应用 3-7 完成
- [ ] T11实现错误降级与 Token 预算控制
### Batch D联调与验证
- [ ] T13端到端联调触发事件 → Prompt 拼接 → 百炼调用 → 缓存写入 → 前端展示
- [ ] T14应用 1 页面上下文联调(各入口页面 → contextType/contextId → 后端文本化 → AI 对话验证上下文感知)
### Batch C应用 1 页面文本化 + 前端配合tasks 12-15
- [ ] T12实现 `page_context.py`10 种页面类型文本化
- [ ] T13集成 `app1_chat.py``_build_page_context()` 调用 `build_page_text()`
- [ ] T14检查点 — 页面上下文完成
- [ ] T15前端看板筛选参数传递小程序 ai-float-button + chat 页面参数解析)
### Batch D集成联调tasks 16-17
- [ ] T16集成连线dispatcher await 正确性 + 端到端验证)
- [ ] T17最终检查点
### 属性测试(可选,标记 * 的子任务)
- 17 个正确性属性P1-P17对应 Hypothesis 属性测试
- 测试文件:`tests/test_data_fetchers/``tests/test_ai_apps/`

View File

@@ -0,0 +1,512 @@
# NS4.1:租户管理员页面重构 — 项目级注册体系 + 简写ID管理
> 优先级NS4 后续迭代,依赖 NS4 基础设施已就绪)
> 预估工作量:中
> 前置条件NS4租户管理后台基础设施、P3用户认证体系
> 关联页面:`http://localhost:5173/tenant-admins`admin-web 系统管理后台)
---
## 一、背景与目标
### 1.1 现状问题
当前 `admin-web` 的租户管理员页面NS4 需求 14仅支持基础 CRUD
- 创建时手动输入 `tenant_id``managed_site_ids`(无下拉选项,无名称参考)
- 无法删除管理员记录
- 无法管理简写IDsite_code简写ID 的创建和修改散落在数据库手动操作中
- 缺少项目级的「连接器 → 租户 → 店铺」注册体系,租户名称无处存储
### 1.2 目标
1. 建立项目级注册体系:`biz.connectors``biz.tenants``biz.sites`,统一管理连接器、租户、店铺三级关系
2.`auth.site_code_mapping` 合并迁移至 `biz.sites`简写ID 成为店铺属性
3. 简写ID 变更增量记录(`biz.site_code_history`),保护已提交但未审核的用户申请
4. 重构租户管理员页面支持删除软删除、2 步创建流程、简写ID 管理
5. 新增 ETL 增量同步任务:从 `dwd.dim_site` 同步店铺信息到业务库
---
## 二、数据模型设计
### 2.1 新建表
#### 表 1`biz.connectors` — 连接器注册表
记录本项目接入的上游 SaaS 系统。当前仅「飞球」一个连接器,预留多连接器扩展。
```sql
CREATE TABLE biz.connectors (
id SERIAL PRIMARY KEY,
connector_key VARCHAR(50) NOT NULL UNIQUE, -- 连接器标识(如 'feiqiu'
display_name VARCHAR(100) NOT NULL, -- 显示名称(如 '飞球'
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE biz.connectors IS '连接器注册表:记录本项目接入的上游 SaaS 系统';
```
初始数据:
```sql
INSERT INTO biz.connectors (connector_key, display_name)
VALUES ('feiqiu', '飞球');
```
#### 表 2`biz.tenants` — 租户注册表
记录每个连接器下的租户信息。`tenant_id` 来自上游系统(飞球的 `tenant_id`)。
```sql
CREATE TABLE biz.tenants (
id SERIAL PRIMARY KEY,
connector_id INTEGER NOT NULL REFERENCES biz.connectors(id),
tenant_id BIGINT NOT NULL, -- 上游系统的租户 ID
tenant_name VARCHAR(200), -- 租户名称(可从上游同步或手动填写)
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (connector_id, tenant_id) -- 同一连接器下 tenant_id 唯一
);
COMMENT ON TABLE biz.tenants IS '租户注册表连接器下的租户tenant_id 来自上游系统';
```
初始数据(从 ETL 库 `dwd.dim_site` 提取当前唯一租户):
```sql
INSERT INTO biz.tenants (connector_id, tenant_id, tenant_name)
VALUES (1, 2790683160709957, '朗朗桌球');
-- tenant_name 暂用店铺名,后续可由管理员修改或从上游同步
```
#### 表 3`biz.sites` — 店铺注册表(合并 `auth.site_code_mapping`
`auth.site_code_mapping` 的功能合并到此表,增加 `tenant_id` 外键关联。
`site_code` 为当前生效的简写ID6 位3+3 格式)。
```sql
CREATE TABLE biz.sites (
id SERIAL PRIMARY KEY,
tenant_id INTEGER NOT NULL REFERENCES biz.tenants(id),
site_id BIGINT NOT NULL UNIQUE, -- 上游系统的店铺 ID
site_name VARCHAR(200), -- 店铺名称(从 dwd.dim_site 同步)
site_code VARCHAR(6) UNIQUE, -- 当前生效的简写ID如 'LLQ001'
site_label VARCHAR(50), -- 店铺标签(从 dwd.dim_site 同步)
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE biz.sites IS '店铺注册表:合并原 auth.site_code_mapping增加租户关联和简写ID管理';
COMMENT ON COLUMN biz.sites.site_code IS '当前生效的简写ID6位字符3+3格式全局唯一';
```
初始数据(从 `auth.site_code_mapping` 迁移):
```sql
-- 仅迁移真实数据(排除测试数据 tenant_id IS NULL
INSERT INTO biz.sites (tenant_id, site_id, site_name, site_code)
SELECT t.id, scm.site_id, scm.site_name, scm.site_code
FROM auth.site_code_mapping scm
JOIN biz.tenants t ON t.tenant_id = scm.tenant_id
WHERE scm.tenant_id IS NOT NULL;
```
#### 表 4`biz.site_code_history` — 简写ID 变更历史表
增量记录所有使用过的简写ID用于保护已提交但未审核的用户申请。
```sql
CREATE TABLE biz.site_code_history (
id SERIAL PRIMARY KEY,
site_id BIGINT NOT NULL, -- 关联 biz.sites.site_id
site_code VARCHAR(6) NOT NULL, -- 历史简写ID
is_current BOOLEAN NOT NULL DEFAULT false, -- 是否为当前生效
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 该 code 生效时间
retired_at TIMESTAMPTZ, -- 该 code 失效时间NULL=当前生效)
UNIQUE (site_code) -- 简写ID 全局唯一(含历史)
);
COMMENT ON TABLE biz.site_code_history IS '简写ID变更历史增量记录所有使用过的简写ID';
COMMENT ON COLUMN biz.site_code_history.is_current IS 'true=当前生效的简写ID每个 site_id 最多一条 is_current=true';
```
### 2.2 表关系
```
biz.connectors (1)
└── biz.tenants (N) -- 一个连接器下多个租户
└── biz.sites (N) -- 一个租户下多个店铺
└── biz.site_code_history (N) -- 一个店铺的简写ID变更历史
auth.tenant_admins.tenant_id → biz.tenants.tenant_id逻辑关联不加 FK
auth.user_applications.site_code → biz.site_code_history.site_code逻辑关联
```
### 2.3 废弃表处理
`auth.site_code_mapping` 在数据迁移完成并验证后标记为废弃:
1. 迁移期间保留原表,新旧并行
2. 后端代码切换到 `biz.sites` 读取
3. 验证无误后,原表重命名为 `auth._archived_site_code_mapping`
### 2.4 `dwd.dim_site` 提升为项目级
当前 `dwd.dim_site` 位于 ETL 连接器级别(`apps/etl/connectors/feiqiu/`)。
本次不做物理迁移(表仍在 `dwd` schema但在语义上将其视为项目级维度表。
后续如有多连接器场景,再考虑拆分为项目级 `dim` schema。
---
## 三、功能详细设计
### 3.1 租户管理员列表页
#### 现有功能保留
- 分页 + 关键词搜索
- 编辑(显示名称、管辖门店、账号状态)
- 重置密码
#### 新增功能
##### 3.1.1 删除管理员(软删除)
- 操作:点击「删除」按钮 → 二次确认弹窗 → 确认后设置 `is_active = false`
- 列表默认只显示 `is_active = true` 的记录
- 可选:增加「显示已禁用」开关,查看所有记录
- 已禁用的记录不可再次删除,但可以重新启用
##### 3.1.2 创建管理员2 步流程)
**第 1 步:创建账号**
- 选择租户:下拉选择 `biz.tenants` 中的租户(显示 `tenant_name`,值为 `tenant_id`
- 输入用户名
- 输入初始密码
- 输入显示名称
- 选择管辖门店:根据所选租户,加载该租户下所有店铺(`biz.sites`),多选
**第 2 步设置简写ID**
- 展示所选租户下所有店铺列表
- 每个店铺显示店铺名称、当前简写ID如有
- 可为每个店铺设置/修改简写ID
- 简写ID 格式6 位字符数字3+3 模式,如 `LLQ001`
- 校验:全局唯一(含历史记录中的 code
> 第 2 步可跳过简写ID 后续可在编辑时设置)
##### 3.1.3 编辑管理员
- 不可修改所属租户(`tenant_id` 只读)
- 可修改用户名、密码重置、显示名称、管辖门店、简写ID
- 简写ID 编辑入口在编辑弹窗中增加「管理简写ID」区域展示该租户下所有店铺及其当前 code
### 3.2 简写ID 管理逻辑
#### 3.2.1 设置/修改简写ID
```
用户在管理后台修改某店铺的简写IDold_code → new_code
→ 校验 new_code 格式6位3+3
→ 校验 new_code 全局唯一biz.sites.site_code + biz.site_code_history.site_code
→ 事务内执行:
1. 将 old_code 在 site_code_history 中标记 is_current=false, retired_at=NOW()
2. 插入 new_code 到 site_code_historyis_current=true
3. 更新 biz.sites.site_code = new_code
4. 清理无引用的历史记录(见 3.2.2
```
#### 3.2.2 历史记录清理
每次修改简写ID 时,检查被替换的旧 code 是否有关联的用户申请:
```sql
-- 检查旧 code 是否有未审核的申请引用
SELECT COUNT(*) FROM auth.user_applications
WHERE site_code = :old_code AND status = 'pending';
```
- 如果有未审核申请引用 → 保留历史记录(`is_current=false`,但不删除)
- 如果无任何申请引用 → 从 `biz.site_code_history` 中删除该条记录
> 目的:防止用户已用旧 code 提交申请但尚未审核时,映射关系丢失
#### 3.2.3 简写ID 格式规范
- 总长度6 位
- 格式3 位字母/数字 + 3 位数字(如 `LLQ001``ABC123`
- 大小写:统一存储为大写
- 全局唯一:同一时刻不允许两个店铺使用相同 code含历史未清理的 code
### 3.3 租户/店铺信息展示
#### 3.3.1 租户下拉选项
创建管理员时,租户下拉数据来源:
```
GET /api/admin/tenants → biz.tenants (is_active=true)
返回:[{ id, tenantId, tenantName, connectorName }]
```
#### 3.3.2 店铺列表
选择租户后,加载该租户下所有店铺:
```
GET /api/admin/tenants/{tenant_id}/sites → biz.sites (tenant_id=?, is_active=true)
返回:[{ id, siteId, siteName, siteCode, siteLabel }]
```
---
## 四、接口设计
### 4.1 新增接口
| 接口 | 方法 | 路径 | 说明 |
|------|------|------|------|
| 租户列表 | GET | `/api/admin/tenants` | 所有活跃租户(含连接器名称) |
| 租户下店铺列表 | GET | `/api/admin/tenants/{tenant_id}/sites` | 指定租户下所有店铺(含当前 site_code |
| 删除管理员 | DELETE | `/api/admin/tenant-admins/{id}` | 软删除is_active=false |
| 设置简写ID | PUT | `/api/admin/sites/{site_id}/site-code` | 设置/修改店铺简写ID |
| 简写ID历史 | GET | `/api/admin/sites/{site_id}/site-code-history` | 查看某店铺的简写ID变更历史 |
### 4.2 修改接口
| 接口 | 变更内容 |
|------|----------|
| `POST /api/admin/tenant-admins` | 创建时 `tenant_id` 改为从 `biz.tenants` 选择;`managed_site_ids``biz.sites` 选择 |
| `PATCH /api/admin/tenant-admins/{id}` | 增加 `username` 可修改(需校验唯一性) |
| `GET /api/admin/tenant-admins` | 默认只返回 `is_active=true`;增加 `include_inactive` 参数 |
---
## 五、ETL 同步任务
### 5.1 店铺信息增量同步
新增 ETL 任务:从 `dwd.dim_site`ETL 库)增量同步到 `biz.sites`(业务库)。
#### 同步逻辑
```
1. 读取 dwd.dim_site WHERE scd2_is_current = 1
2. 对比 biz.sites 中已有记录:
- 新增店铺site_id 不存在)→ INSERTsite_code 留空,待管理员设置)
- 店铺名称变更 → UPDATE site_name
- 店铺标签变更 → UPDATE site_label
3. 不删除已有记录(即使上游标记为关闭)
```
#### 触发方式
- 手动触发:管理后台按钮或 CLI 命令
- 定时触发:随 ETL 日常调度DWD 层完成后)
#### 租户信息
- `biz.tenants` 中的 `tenant_name` 暂不自动同步(上游无 tenant_name 字段)
- 首次由迁移脚本写入,后续由管理员在管理后台手动修改
---
## 六、数据迁移计划
### 6.1 迁移步骤
```
1. 创建新表biz.connectors → biz.tenants → biz.sites → biz.site_code_history
2. 写入种子数据connectors飞球、tenants从 dim_site 提取)
3. 迁移 auth.site_code_mapping → biz.sites仅真实数据排除 tenant_id IS NULL 的测试数据)
4. 为已有 site_code 创建 site_code_history 记录is_current=true
5. 运行 ETL 同步任务,补充 biz.sites 中缺失的店铺dim_site 中有但 site_code_mapping 中没有的)
6. 后端代码切换:所有读取 auth.site_code_mapping 的地方改为读取 biz.sites
7. 验证:对比新旧表数据一致性
8. 废弃原表:重命名为 auth._archived_site_code_mapping
```
### 6.2 回滚策略
- 迁移期间保留原表不动
- 如需回滚:删除 biz.sites/tenants/connectors/site_code_history恢复后端代码指向 auth.site_code_mapping
---
## 七、前端页面设计
### 7.1 页面布局
```
┌─────────────────────────────────────────────────────┐
│ 租户管理员 │
│ [搜索框] [显示已禁用 ☐] [+ 创建管理员] │
├─────────────────────────────────────────────────────┤
│ 用户名 │ 显示名称 │ 租户 │ 管辖门店 │ 状态 │ 操作 │
│ admin1 │ 张三 │ 朗朗 │ 朗朗桌球 │ 启用 │ 编辑 │
│ │ │ │ │ │ 重置 │
│ │ │ │ │ │ 简写ID │
│ │ │ │ │ │ 删除 │
└─────────────────────────────────────────────────────┘
```
### 7.2 创建弹窗2 步)
```
步骤 1/2创建账号
┌──────────────────────────────┐
│ 租户: [▼ 朗朗桌球 ] │
│ 用户名: [ ] │
│ 初始密码:[ ] │
│ 显示名称:[ ] │
│ 管辖门店:[☑ 朗朗桌球 ] │
│ │
│ [下一步] [取消] │
└──────────────────────────────┘
步骤 2/2设置简写ID
┌──────────────────────────────┐
│ 店铺 当前简写ID │
│ 朗朗桌球 [LLQ001 ] │
│ │
│ 格式6位3字母+3数字
│ │
│ [跳过] [完成创建] [上一步]│
└──────────────────────────────┘
```
### 7.3 简写ID 管理弹窗
```
管理简写ID — 朗朗桌球(租户)
┌──────────────────────────────────────┐
│ 店铺 当前ID 操作 │
│ 朗朗桌球 LLQ001 [修改] │
│ │
│ 修改简写ID
│ 新ID[ ] [保存] [取消] │
│ │
│ 变更历史: │
│ LLQ001 当前生效 2026-03-22 │
│ LL001 已失效 2026-02-25 (保留) │
│ │
│ [关闭] │
└──────────────────────────────────────┘
```
---
## 八、影响范围
### 8.1 后端
| 文件 | 变更类型 | 说明 |
|------|----------|------|
| `app/routers/admin_tenant_admins.py` | 修改 | 增加 DELETE 端点、修改 CREATE/EDIT 逻辑 |
| `app/schemas/admin_tenant_admins.py` | 修改 | 新增/修改请求响应 Schema |
| `app/routers/admin_registry.py` | 新建 | 租户/店铺/简写ID 管理接口 |
| `app/schemas/admin_registry.py` | 新建 | 注册体系 Schema |
### 8.2 前端admin-web
| 文件 | 变更类型 | 说明 |
|------|----------|------|
| `src/pages/TenantAdmins/index.tsx` | 重构 | 2 步创建、删除、简写ID 管理 |
| `src/api/tenantAdmins.ts` | 修改 | 新增 API 调用 |
| `src/api/registry.ts` | 新建 | 租户/店铺列表 API |
### 8.3 数据库
| 操作 | 对象 | 说明 |
|------|------|------|
| 新建 | `biz.connectors` | 连接器注册表 |
| 新建 | `biz.tenants` | 租户注册表 |
| 新建 | `biz.sites` | 店铺注册表(合并 site_code_mapping |
| 新建 | `biz.site_code_history` | 简写ID 变更历史 |
| 废弃 | `auth.site_code_mapping` | 迁移完成后废弃 |
### 8.4 ETL
| 变更 | 说明 |
|------|------|
| 新增同步任务 | `dwd.dim_site``biz.sites` 增量同步 |
### 8.5 小程序端
| 变更 | 说明 |
|------|------|
| 用户申请时的 site_code 查询 | 从 `auth.site_code_mapping` 切换到 `biz.sites` + `biz.site_code_history` |
---
## 九、约束与边界条件
1. 一个租户只有一个管理员(软限制,不加 DB 约束)
2. 简写ID 全局唯一(含历史记录),通过 UNIQUE 约束保证
3. 删除管理员为软删除(`is_active = false`),不物理删除
4. 编辑时不可修改所属租户
5. 简写ID 格式6 位3+3 模式,统一大写存储
6. 历史简写ID 仅在无未审核申请引用时才可清理
7. ETL 同步不删除已有店铺记录(即使上游关闭)
8. `biz.tenants.tenant_name` 暂不自动同步,由管理员手动维护
---
## 十、不做的事情(明确排除)
1. 不做多连接器支持的完整实现(仅预留 `biz.connectors` 表结构)
2. 不做 `dwd.dim_site` 的物理迁移(保留在 `dwd` schema
3. 不做租户管理员的自助注册功能
4. 不做店铺管理员的管理(本 PRD 仅涉及租户管理员)
5. 不做 `auth.site_code_mapping` 的立即删除(迁移后保留为 `_archived`
6. 不做简写ID 的自动生成(由管理员手动设置)
---
## 十一、数据库现状参考
### ETL 库 `dwd.dim_site`(当前数据)
| site_id | tenant_id | shop_name | site_label |
|---------|-----------|-----------|------------|
| 2790685415443269 | 2790683160709957 | 朗朗桌球 | A |
### 业务库 `auth.site_code_mapping`(当前数据)
| id | site_code | site_id | site_name | tenant_id |
|----|-----------|---------|-----------|-----------|
| 1 | LL001 | 2790685415443269 | 朗朗桌球 | 2790683160709957 |
| 1448 | PT952 | 857189 | 测试球房_PT952 | NULL |
| 1470 | PT118 | 819193 | 测试球房_PT118 | NULL |
| 1471 | PT607 | 899675 | 测试球房_PT607 | NULL |
> 仅 id=1 为真实数据,其余为属性测试生成的测试数据(`tenant_id IS NULL`
### 业务库 `auth.tenant_admins`(现有结构)
| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGSERIAL | 主键 |
| username | VARCHAR(50) | 登录用户名UNIQUE |
| password_hash | VARCHAR(255) | bcrypt 哈希 |
| display_name | VARCHAR(100) | 显示名称 |
| tenant_id | BIGINT | 所属租户 |
| managed_site_ids | BIGINT[] | 管辖门店 ID 列表 |
| is_active | BOOLEAN | 账号状态 |
| created_by | BIGINT | 创建者 |
| created_at | TIMESTAMPTZ | 创建时间 |
| last_login_at | TIMESTAMPTZ | 最后登录时间 |
> `tenant_admins` 表结构不变,仅后端逻辑调整(创建时从 `biz.tenants` 选择租户)
---
## 十二、参考文档
| 文档 | 路径 | 用途 |
|------|------|------|
| NS4 原始 PRD | `docs/prd/Neo_Specs/NS4-tenant-admin-web.md` | 租户管理后台完整需求 |
| P3 认证体系 | `docs/prd/specs/P3-miniapp-auth-system.md` | site_code / user_applications 设计 |
| BD 手册-认证表 | `docs/database/BD_Manual_auth_tables.md` | auth schema 表结构 |
| BD 手册-业务表 | `docs/database/BD_Manual_biz_tables.md` | biz schema 表结构 |

View File

@@ -0,0 +1,247 @@
# Neo_Specs 86 项缺失内容审查报告
> 审查日期2026-03-21
> 审查依据:`docs/prd/Neo_Specs/review-report.md`86 项缺失清单)
> 审查方法:逐项检查数据库端数据支持、后端 Python 业务代码支持、前端页面支持
> 审查产物:`docs/prd/Neo_Specs/review-audit/` 目录下 86 份独立审查报告
---
## 一、总览统计
| 状态 | 数量 | 占比 | 说明 |
|:----:|:----:|:----:|------|
| ✅ 已解决 | 22 | 25.6% | 数据库+后端+前端三层均已实现 |
| ⚠️ 部分解决 | 38 | 44.2% | 核心功能已有,但存在细节差距 |
| ❌ 未解决 | 26 | 30.2% | 功能完全缺失或仅有数据层无业务实现 |
| **合计** | **86** | **100%** | |
### 按模块分布
| 模块 | 总数 | ✅ | ⚠️ | ❌ |
|------|:----:|:--:|:--:|:--:|
| P5.1→NS3MCP/AI | 10 | 4 | 5 | 1 |
| P6→NS1任务模块 | 18 | 5 | 7 | 6 |
| P7→NS1绩效模块 | 12 | 4 | 6 | 2 |
| P8→NS1看板模块 | 14 | 6 | 5 | 3 |
| P9→NS1详情模块 | 23 | 10 | 10 | 3 |
| P10→NS4租户管理后台 | 9 | 1 | 5 | 3 |
---
## 二、各模块审查结果明细
### 2.1 P5.1→NS3MCP Server / AI 应用10 项)
| # | 缺失项 | 状态 | 核心发现 |
|---|--------|:----:|----------|
| 01 | App1 Prompt 工程规范 | ✅ | `ai-app-prompts.md` 已定义完整 system prompt 模板、few-shot 示例、JSON schema 约束 |
| 02 | App2 财务指标计算口径 | ✅ | 后端 `app2_finance_insight.py` 已实现 6 个指标公式,遵循 items_sum 口径 |
| 03 | App3 维客线索触发条件 | ✅ | `app3_consumption_analysis.py` 实现事件驱动触发 + 去重逻辑 |
| 04 | App4-App7 缓存策略 | ⚠️ | `ai_cache` 表已建,后端有 TTL 读写逻辑,但 `expires_at` 字段未实际使用 |
| 05 | LLM 错误处理和降级 | ⚠️ | 有 try/except + 缓存回退,缺限流降级和熔断机制 |
| 06 | Token 用量监控 | ⚠️ | `ai_usage_log` 表记录每次调用,缺日/月预算控制和超限告警 |
| 07 | App5 话术分类 | ⚠️ | 话术生成已实现,缺分类枚举定义和质量评分体系 |
| 08 | 各 App 单元测试 | ✅ | `apps/mcp-server/tests/` 下有 App1-App7 测试文件 |
| 09 | MCP Server 健康检查 | ⚠️ | 后端 `/health` 端点存在MCP Server 自身无健康检查端点 |
| 10 | AI 审计日志 | ✅ | `ai_usage_log` 表记录 who/when/what/token_count |
### 2.2 P6→NS1/RNS1任务模块18 项)
| # | 缺失项 | 状态 | 核心发现 |
|---|--------|:----:|----------|
| 01 | 任务卡片 5 种状态 | ⚠️ | pending/pinned/abandoned 已实现,缺 completed/expired 视觉状态 |
| 02 | 3 种空状态 | ⚠️ | 无任务空状态有,缺筛选无结果空状态和插图 |
| 03 | 置顶任务排序规则 | ✅ | 后端 SQL 已实现 `is_pinned DESC, pinned_at DESC, priority, created_at` |
| 04 | 放弃确认弹窗 | ✅ | 前端 `t-dialog` 二次确认已实现 |
| 05 | 下拉刷新/骨架屏 | ✅ | `t-pull-down-refresh` + `t-skeleton` 已集成 |
| 06 | AI 分析卡片折叠/展开 | ❌ | 前端仅静态展示 AI 分析文本,无折叠/展开/重新生成交互 |
| 07 | 任务优先级视觉标识 | ⚠️ | 有 priority 字段传递,前端缺颜色/图标映射 |
| 08 | 到期倒计时展示 | ✅ | 前端计算剩余天数并展示颜色变化 |
| 09 | 备注字数限制 | ⚠️ | 有 `maxlength=500`,缺实时字数计数器 |
| 10 | 详情页模块折叠/展开 | ❌ | 详情页各模块固定展示,无折叠/展开交互 |
| 11 | 维客线索展示样式 | ✅ | tag 颜色映射 + 卡片布局已实现 |
| 12 | 任务列表搜索 | ❌ | 无搜索功能,仅有状态筛选 |
| 13 | 任务完成反馈 | ⚠️ | 有 `wx.showToast` 成功提示,缺专属完成动画 |
| 14 | 网络异常处理 | ⚠️ | 页面级错误展示有,缺统一离线检测和全局网络状态管理 |
| 15 | 长按/滑动操作 | ⚠️ | 长按置顶已实现,滑动操作未实现 |
| 16 | 页面转场动画 | ❌ | 使用 `wx.navigateTo` 默认转场,无自定义动画 |
| 17 | 批量操作 | ❌ | 无批量选择/批量标记完成功能 |
| 18 | 无障碍适配 | ❌ | 无 aria-label、无焦点管理、无屏幕阅读器适配 |
### 2.3 P7→NS1/RNS1绩效模块12 项)
| # | 缺失项 | 状态 | 核心发现 |
|---|--------|:----:|----------|
| 01 | 营业日 08:00 分割点 | ✅ | ETL 层 `biz_date` 已按 08:00 分割,后端直接按 `biz_date` 查询 |
| 02 | 预估标记 | ⚠️ | 前端有 `isEstimate` 展示逻辑,后端硬编码 `is_estimate=False` |
| 03 | 定档折算展示格式 | ⚠️ | 后端返回 `hours`/`hoursRaw`,前端展示格式与标杆不一致 |
| 04 | 新客筛选逻辑 | ⚠️ | 后端有新客筛选但定义简化(仅首次服务月份,缺 2 月内+次数≤2 条件) |
| 05 | 常客展示字段 | ✅ | 次数、小时数、工资合计字段完整 |
| 06 | 收入与档位卡片视觉 | ✅ | 进度条 + 档位标签 + 收入数字样式已实现 |
| 07 | 服务记录日期格式 | ⚠️ | 展示"3月15日"格式,缺星期信息(标杆要求"3月15日 周五" |
| 08 | 月份切换 | ⚠️ | performance-records 页有月份切换,主 performance 页面无 |
| 09 | 绩效空状态 | ✅ | 新助教无数据时展示空状态文案 |
| 10 | 业绩导出功能 | ❌ | 无导出功能(整个项目缺乏通用导出基础设施) |
| 11 | 数据刷新频率说明 | ⚠️ | 无用户可见的数据新鲜度说明(如"数据更新于 XX:XX" |
| 12 | 周口径支持 | ❌ | 仅支持月口径,无本周/上周查询参数 |
### 2.4 P8→NS1/RNS1看板模块14 项)
| # | 缺失项 | 状态 | 核心发现 |
|---|--------|:----:|----------|
| 01 | Tab 切换缓存 | ⚠️ | 三看板通过页面跳转实现,非 Tab 组件,不保持筛选状态 |
| 02 | 财务看板分段加载 | ⚠️ | 后端单 API 返回全部数据,无 `sections` 参数,前端有 skeleton 降级 |
| 03 | 客户卡片跳转 | ✅ | 点击卡片跳转 `customer-detail?memberId=` 已实现 |
| 04 | 助教看板进度条 | ⚠️ | `t-progress` 组件已引入但未在看板页面使用,仅展示 `perfGap` 文本 |
| 05 | 数据实时性标识 | ❌ | 无"数据更新于 XX:XX"展示 |
| 06 | 环比 tooltip | ⚠️ | 环比箭头和百分比展示有,点击查看计算详情无 |
| 07 | 助教卡片跳转 | ✅ | 点击卡片跳转 `coach-detail?coachId=` 已实现 |
| 08 | 柱状图交互 | ⚠️ | `wx-charts` 渲染柱状图有,点击柱子显示具体数据无 |
| 09 | 下拉刷新 | ✅ | `onPullDownRefresh` 已实现 |
| 10 | 财务看板板块折叠 | ❌ | 各板块固定展示,无折叠/展开交互 |
| 11 | 错误展示和重试 | ✅ | 模块级错误展示 + 重试按钮已实现 |
| 12 | 筛选项动画 | ✅ | 筛选面板展开/收起动画已实现 |
| 13 | 排名序号展示 | ❌ | 助教列表无 #1#2 排名序号 |
| 14 | 数字格式化规范 | ✅ | 千分位 + 2 位小数 + ¥ 符号统一使用 `formatCurrency` 工具函数 |
### 2.5 P9→NS1/RNS1详情模块23 项)
| # | 缺失项 | 状态 | 核心发现 |
|---|--------|:----:|----------|
| 01 | 客户详情分段加载 | ⚠️ | 单 API 返回全部数据,前端有 skeleton 降级但非分段加载 |
| 02 | 档位进度时间轴 | ⚠️ | 进度条展示有,非时间轴样式(缺节点、高亮、动画) |
| 03 | 消费记录类型映射 | ⚠️ | 台桌消费/商城消费有图标映射,缺 recharge充值类型模板 |
| 04 | 备注 AI 评分星级 | ⚠️ | 星级组件已开发,但未在备注卡片中使用 |
| 05 | Banner 区域视觉 | ✅ | 余额/消费/到店间隔/距上次到店 4 字段布局完整 |
| 06 | AI 洞察卡片 | ⚠️ | 静态展示 AI 洞察文本,缺展开详情和刷新按钮 |
| 07 | 关联助教任务列表 | ✅ | 任务类型图标 + 状态标签 + 服务统计展示完整 |
| 08 | 最亲密助教 | ⚠️ | 数据字段完整,缺关系指数可视化图表 |
| 09 | 助教明细子列表 | ✅ | 消费记录中助教明细展开/折叠已实现 |
| 10 | TOP20 客户排序 | ✅ | 按关系指数降序排列,展示字段完整 |
| 11 | 历史月份统计 | ⚠️ | 表格展示有,缺折线图/柱状图可视化 |
| 12 | 月度统计汇总 | ✅ | monthCount/monthHours 展示位置和样式已定义 |
| 13 | 任务分组视觉区分 | ✅ | active/inactive/abandoned 分组标题样式和折叠规则已实现 |
| 14 | 维客线索展示 | ✅ | 线索卡片布局 + category 颜色映射已实现 |
| 15 | 分享功能 | ❌ | 无分享客户信息功能 |
| 16 | 助教联系方式展示 | ❌ | 助教详情页无电话/微信展示 |
| 17 | 自定义日期范围筛选 | ❌ | 仅支持月份切换,无自定义日期范围 |
| 18 | 返回按钮行为 | ⚠️ | 使用 `wx.navigateBack` 默认行为,未区分"返回上一页"和"返回列表页" |
| 19 | 电话号码脱敏 | ✅ | 后端返回脱敏号码(中间 4 位 *),前端直接展示 |
| 20 | 收入明细展开/折叠 | ⚠️ | 使用 Tab 切换(本月/上月)替代展开/折叠,交互方式不同 |
| 21 | 饮品描述字段展示 | ✅ | `drinks` 字段在服务记录卡片中展示 |
| 22 | 模块级错误独立处理 | ✅ | 各模块独立 loading/error 状态,单模块失败不影响其他模块 |
| 23 | 可用月数计算 | ❌ | `availableMonths` 字段未实现(余额÷月均消耗) |
### 2.6 P10→NS4租户管理后台9 项)
| # | 缺失项 | 状态 | 核心发现 |
|---|--------|:----:|----------|
| 01 | 角色权限管理 CRUD | ⚠️ | RBAC 三表模型完整,权限校验中间件已打通,但无管理界面(依赖种子数据) |
| 02 | 门店切换功能 | ⚠️ | 后端数据隔离完整SiteSelector 组件已开发但仅维客线索页集成,全局布局未放置 |
| 03 | 数据导出功能 | ❌ | 无任何业务数据导出功能(后端无端点、前端无按钮) |
| 04 | 操作日志/审计日志 | ❌ | 无审计日志表、无记录逻辑、无查看界面;线索删除为物理删除 |
| 05 | 响应式适配 | ⚠️ | Sider 可折叠、flex 布局基础有,缺最小分辨率定义和表格横向滚动 |
| 06 | 表格组件统一规范 | ⚠️ | 主表分页统一20条/页),缺排序交互、空数据中文化、统一配置抽象 |
| 07 | 表单验证统一规范 | ⚠️ | 验证风格基本一致EditModal 缺 required 规则,后端 422 格式不统一 |
| 08 | 国际化预留 | ✅ | ConfigProvider locale={zhCN} 已配置,项目面向国内市场无需 i18n 框架 |
| 09 | 主题定制 | ❌ | 使用 Ant Design 默认主题,无品牌色、无 Logo、无暗色模式 |
---
## 三、风险矩阵
### 🔴 高风险未解决项(需优先处理,共 8 项)
| 编号 | 缺失项 | 影响 |
|------|--------|------|
| P6-06 | AI 分析卡片折叠/展开/重新生成 | AI 功能交互不完整,用户无法与 AI 内容互动 |
| P6-12 | 任务列表搜索功能 | 任务量大时无法快速定位目标任务 |
| P6-17 | 批量操作 | 多任务管理效率低 |
| P6-18 | 无障碍适配 | 合规风险 |
| P7-10 | 业绩导出功能 | 管理者无法离线分析绩效数据 |
| P10-03 | 数据导出功能 | 管理后台无法导出业务数据 |
| P10-04 | 操作审计日志 | 操作不可追溯,线索物理删除不可恢复 |
| P9-23 | 可用月数计算 | 客户余额分析功能缺失 |
### 🟠 中风险部分解决项(需补齐差距,共 15 项)
| 编号 | 缺失项 | 差距 |
|------|--------|------|
| P5.1-04 | 缓存策略 | `expires_at` 未实际使用 |
| P5.1-05 | LLM 降级 | 缺限流降级/熔断 |
| P5.1-06 | Token 监控 | 缺预算控制 |
| P6-01 | 任务卡片状态 | 缺 completed/expired 视觉 |
| P7-02 | 预估标记 | 后端硬编码 False |
| P7-04 | 新客筛选 | 定义简化 |
| P8-01 | Tab 切换缓存 | 页面跳转不保持状态 |
| P8-02 | 分段加载 | 单 API 返回全部数据 |
| P9-01 | 客户详情分段加载 | 同上 |
| P9-06 | AI 洞察卡片 | 缺展开/刷新交互 |
| P9-08 | 最亲密助教 | 缺可视化图表 |
| P9-11 | 历史月份统计 | 缺图表 |
| P10-01 | 角色权限管理 | 无管理界面 |
| P10-02 | 门店切换 | 未全局集成 |
| P10-06 | 表格规范 | 缺排序和统一配置 |
---
## 四、系统性问题
基于 86 项审查,识别出以下跨模块的系统性差距:
### 4.1 前端交互规范缺失
小程序端P6-P9普遍缺少折叠/展开交互、搜索功能、批量操作、页面转场动画、无障碍适配。这些属于前端交互层的系统性缺失,建议新建统一的前端交互规范文档。
### 4.2 数据加载策略单一
财务看板P8-02、客户详情P9-01等复杂页面均为单 API 返回全部数据,缺乏分段加载策略。前端虽有 skeleton 降级,但首屏性能受限。
### 4.3 导出功能全局缺失
整个项目(小程序 + 管理后台无任何业务数据导出功能缺乏通用导出基础设施Excel 生成工具、导出日志表、权限控制)。
### 4.4 审计日志体系不完整
MCP/AI 层有 `ai_usage_log`,但租户管理后台的业务操作(审核、编辑、删除)无审计记录。维客线索删除为物理删除,数据不可恢复。
### 4.5 AI 交互深度不足
AI 相关功能(任务 AI 分析、客户 AI 洞察)均为静态展示,缺少用户与 AI 内容的互动能力(折叠/展开、重新生成、反馈评分)。
---
## 五、优先级建议
### 第一优先级:阻塞性缺失(建议立即处理)
1. P10-04 操作审计日志 — 新建 `biz.audit_log` 表 + 后端记录逻辑 + 线索删除改软删除
2. P7-02 预估标记 — 后端实现 `is_estimate` 判断逻辑(当月数据标记预估)
3. P8-02 / P9-01 分段加载 — 评估拆分 API 或增加 `fields` 参数
### 第二优先级:体验差距(建议短期补齐)
4. P6-06 AI 分析卡片交互 — 增加折叠/展开/重新生成
5. P10-02 门店选择器全局集成 — SiteContext + 各页面接入
6. P6-01 任务卡片状态视觉 — 补充 completed/expired 样式映射
7. P5.1-04/05/06 AI 缓存/降级/监控 — 完善 AI 基础设施
### 第三优先级:功能增强(建议中期规划)
8. P7-10 / P10-03 导出功能 — 建立通用导出基础设施
9. P6-12 任务搜索 — 增加按客户名/手机号搜索
10. P10-01 角色权限管理界面 — 按需实现
### 第四优先级:体验优化(建议后续迭代)
11. 无障碍适配P6-18
12. 页面转场动画P6-16
13. 批量操作P6-17
14. 主题定制P10-09
---
## 六、审查报告索引
所有 86 份独立审查报告存放于 `docs/prd/Neo_Specs/review-audit/` 目录:
| 文件名模式 | 数量 | 对应模块 |
|-----------|:----:|----------|
| `P5.1-NS3-01.md` ~ `P5.1-NS3-10.md` | 10 | MCP Server / AI 应用 |
| `P6-NS1-01.md` ~ `P6-NS1-18.md` | 18 | 小程序任务模块 |
| `P7-NS1-01.md` ~ `P7-NS1-12.md` | 12 | 小程序绩效模块 |
| `P8-NS1-01.md` ~ `P8-NS1-14.md` | 14 | 小程序看板模块 |
| `P9-NS1-01.md` ~ `P9-NS1-23.md` | 23 | 小程序详情模块 |
| `P10-NS4-01.md` ~ `P10-NS4-09.md` | 9 | 租户管理后台 |

View File

@@ -0,0 +1,107 @@
# P10-NS4-01角色权限管理的 CRUD 界面规范
## 简要结论
- 状态:⚠️ 部分解决
- 数据库层已建立完整的 RBAC 三表模型roles / permissions / role_permissions后端已实现权限查询与校验中间件但缺少角色权限管理的 CRUD 界面——无法通过 UI 创建角色、分配权限、展示权限树。
## 详细审查
### 数据库端
auth schema 中已存在完整的 RBAC 表结构(来源:`docs/database/ddl/zqyy_app__auth.sql`
| 表名 | 用途 | 关键字段 |
|------|------|----------|
| `auth.roles` | 角色定义 | id, code, name, description |
| `auth.permissions` | 权限定义 | id, code, name, description |
| `auth.role_permissions` | 角色-权限映射(多对多) | role_id → roles.id, permission_id → permissions.id |
| `auth.user_site_roles` | 用户-门店-角色绑定 | user_id, site_id, role_id → roles.id |
| `auth.tenant_admins` | 租户管理员账号 | managed_site_idsBIGINT[])控制管辖范围 |
种子数据已预置 4 个角色coach / staff / site_admin / tenant_admin和 5 个权限view_tasks / view_board / view_board_finance / view_board_customer / view_board_coach以及 14 条角色-权限映射。
结论:数据库层 RBAC 模型完整,支持角色-权限多对多关系。`tenant_admins` 表通过 `managed_site_ids` 数组实现租户级/店铺级管理员的管辖范围控制,与 P10 spec 中描述的两种权限级别一致。
### 后端代码
**已实现:**
1. **权限查询服务**`apps/backend/app/services/role.py`
- `get_user_permissions(user_id, site_id)` — 三表联查获取用户权限 code 列表
- `get_user_sites(user_id)` — 获取用户关联的所有店铺及角色
- `check_user_has_site_role(user_id, site_id)` — 检查用户是否有角色绑定
2. **权限校验中间件**`apps/backend/app/middleware/permission.py`
- `require_permission(*codes)` — 依赖注入工厂,校验用户 approved 状态 + 指定权限
- `require_approved()` — 仅校验 approved 状态
- 已在看板路由(`xcx_board.py`)中实际使用:`view_board_finance``view_board_customer``view_board_coach`
3. **租户管理员 CRUD**`apps/backend/app/routers/admin_tenant_admins.py`
- 列表(分页+搜索、创建、编辑display_name / managed_site_ids / is_active、重置密码
- 这是管理员账号的 CRUD不是角色/权限的 CRUD
4. **租户管理员认证**`apps/backend/app/auth/tenant_admins.py` + `tenant_auth.py`
- JWT 签发含 managed_site_idsaud=tenant-admin 与小程序隔离
- `site_filter_clause()` / `verify_site_access()` 实现数据隔离
**未实现:**
-`roles` 表的 CRUD API创建角色、编辑角色、删除角色
-`permissions` 表的 CRUD API创建权限、编辑权限、删除权限
-`role_permissions` 映射的管理 API为角色分配/取消权限)
- 角色和权限数据完全依赖种子 SQL 预置,无法通过 API 动态管理
### 前端代码
**tenant-admin租户管理后台页面清单**
- `Login/` — 登录页 ✅
- `UserApproval/` — 用户审核 ✅
- `UserManagement/` — 用户管理 ✅
- `ExcelUpload/` — Excel 上传 ✅
- `RetentionClues/` — 维客线索管理 ✅
- ❌ 无角色管理页面
- ❌ 无权限管理页面
- ❌ 无权限树组件
**tenant-admin 组件清单:**
- `DiffTable/` — 冲突 diff 交互 ✅
- `ClueEditor/` — 线索编辑 ✅
- `SiteSelector/` — 门店选择器 ✅
- ❌ 无 PermissionTree / RoleEditor 等权限管理组件
**admin-web系统管理后台**
- `TenantAdmins/index.tsx` — 租户管理员 CRUD 页面 ✅
- 功能:列表(分页+搜索)、创建 Modal用户名/密码/显示名称/tenantId/managedSiteIds、编辑 Modal显示名称/managedSiteIds/isActive、重置密码 Modal
- ❌ 无角色选择/分配功能
- ❌ 无权限树展示
### 差距分析
| P10 标杆要求 | 当前实现 | 差距 |
|-------------|---------|------|
| 租户级管理员(管辖所有店铺) | ✅ managed_site_ids 数组控制 | 无 |
| 店铺级管理员(只管指定 site_id | ✅ managed_site_ids 数组控制 | 无 |
| 角色定义coach/staff/site_admin/tenant_admin | ✅ 种子数据预置 | 无法动态增删改 |
| 权限定义5 个 view_* 权限) | ✅ 种子数据预置 | 无法动态增删改 |
| 角色-权限映射 | ✅ 种子数据预置 14 条映射 | 无法通过 UI 调整 |
| 创建角色界面 | ❌ 不存在 | 完全缺失 |
| 分配权限界面 | ❌ 不存在 | 完全缺失 |
| 权限树展示 | ❌ 不存在 | 完全缺失 |
| 用户审核时分配角色 | ⚠️ 部分 | 审核通过时可指定 role但角色列表硬编码非从 roles 表动态读取 |
核心差距RBAC 数据模型已就绪权限校验链路已打通roles → role_permissions → permissions → middleware但管理侧完全依赖种子数据和数据库直接操作缺少面向管理员的 CRUD 界面。这意味着:
- 新增角色需要写 SQL
- 调整角色权限需要写 SQL
- 管理员无法自助查看权限分配全貌
### 建议
1. **短期(低成本)**:在 admin-web 的 TenantAdmins 页面增加"角色权限"只读展示面板,展示当前 4 个角色及其权限映射,让管理员了解权限全貌。无需 CRUD因为当前角色/权限体系相对固定。
2. **中期(按需)**:如果业务发展需要动态角色管理(如新增"前台"角色、调整权限粒度),则需要:
- 后端:新增 `admin_roles.py` 路由roles CRUD + role_permissions 分配)
- 前端:在 admin-web 新增 RoleManagement 页面(角色列表 + 权限树 Ant Design Tree 组件 + 分配交互)
- 在 tenant-admin 的用户审核/管理页面中,角色选择改为从 API 动态获取
3. **权限树组件规范**:如需实现,建议使用 Ant Design `<Tree>` 组件,数据结构为权限按功能模块分组(如"看板"下挂 view_board_*),支持勾选/取消勾选批量分配。
4. **NS4 spec 补充**:在 NS4 文档中明确说明当前权限模型为"预置角色 + 种子数据"模式,角色/权限的动态管理界面作为后续迭代项,避免与 P10 标杆文件的隐含期望产生歧义。

View File

@@ -0,0 +1,119 @@
# P10-NS4-02门店切换功能的交互规范
## 简要结论
- 状态:⚠️ 部分解决
- 后端数据隔离和 JWT 门店权限体系完整;前端 SiteSelector 组件已实现但仅在维客线索页集成,全局布局未放置门店选择器,用户审核和 Excel 上传页面缺少门店筛选,门店名称显示为 ID 而非中文名。
## 详细审查
### 数据库端
**auth.tenant_admins 表**DDL: `db/zqyy_app/migrations/2026-03-20__ns4_tenant_admin_tables.sql`
| 字段 | 类型 | 说明 |
|------|------|------|
| managed_site_ids | BIGINT[] | 管辖门店 ID 数组,用于数据隔离 |
| tenant_id | BIGINT | 所属租户 |
| is_active | BOOLEAN | 账号状态 |
`managed_site_ids` 为 PostgreSQL 数组类型,支持多门店管辖场景。
**public.site_code_mapping 表**(被后端多处引用)
| 用途 | 说明 |
|------|------|
| site_code → site_id 映射 | 用户申请时通过球房编号关联门店 |
| site_id → site_name 映射 | 用户列表、线索搜索时补充门店名称 |
✅ 门店名称映射表存在,后端已在 `tenant_users.py``tenant_clues.py` 中使用。
**结论**:数据库层面完整支持多门店管辖和门店名称映射。
### 后端代码
#### 1. 登录与 JWT`tenant_auth.py`
✅ 登录成功后返回的 JWT payload 包含 `managed_site_ids` 数组:
```python
payload = {
"sub": str(admin_id),
"tenant_id": tenant_id,
"managed_site_ids": managed_site_ids, # ← 门店列表
"aud": "tenant-admin",
}
```
✅ refresh_token 也携带 `managed_site_ids`,刷新后不丢失。
#### 2. 认证中间件(`auth/tenant_admins.py`
`CurrentTenantAdmin` dataclass 包含 `managed_site_ids: list[int]`
`site_filter_clause()` 工具函数生成 `site_id IN (...)` SQL 片段,用于数据隔离。
`verify_site_access()` 校验单个 site_id 是否在管辖范围内,越权返回 403。
✅ 空 managed_site_ids 时生成 `1 = 0` 条件,不匹配任何行(安全兜底)。
#### 3. 查询接口(`tenant_users.py`
`list_applications` — 通过 `site_filter_clause` 附加 `site_id IN (管辖列表)` 条件。
`list_users` — 同上JOIN `user_site_roles` 表做门店隔离。
`approve_application` / `reject_application` — 调用 `verify_site_access` 校验单门店权限。
`edit_user` — 新 site_id 必须在管辖范围内。
#### 4. 维客线索(`tenant_clues.py`grep 结果确认)
✅ 搜索接口支持 `site_id` 参数筛选。
✅ 返回结果补充 `site_name`(通过 `site_code_mapping` 查询)。
**后端结论**:数据隔离体系完整,所有查询均附加 `site_id IN (管辖列表)` 条件,符合 P10 spec 要求。
### 前端代码
#### 1. SiteSelector 组件(`components/SiteSelector/index.tsx`
✅ 组件已实现,基于 Ant Design Select支持多选/全选。
✅ 配套 `useSiteFilter` hook管理选中状态提供 `effectiveSiteIds`(空选时返回全部)。
✅ 数据源来自 `useAuth().user.managedSiteIds`
#### 2. useAuth hook`hooks/useAuth.tsx`
✅ 从 JWT payload 解析 `managedSiteIds` 数组。
✅ 登录后通过 `extractUserInfo` 提取并存入 React Context。
✅ 页面刷新时从 localStorage 恢复。
#### 3. API 服务(`services/api.ts`
✅ JWT 自动附加到请求头。
✅ 401 时自动刷新 token含并发保护
⚠️ 无全局 site_id 拦截器——各页面需自行传递 site_id 参数。
#### 4. App.tsx 全局布局
**全局布局未集成 SiteSelector**。侧边栏仅包含导航菜单和退出按钮,无门店选择器。
❌ 无全局 "当前选中门店" 状态管理(无 SiteContext/SiteProvider
#### 5. 各页面集成情况
| 页面 | SiteSelector 集成 | site_id 传递 | 门店名称显示 |
|------|-------------------|-------------|-------------|
| 维客线索 (RetentionClues) | ✅ 已集成 | ✅ 单门店时传 site_id | ⚠️ 显示 `门店 {id}` |
| 用户管理 (UserManagement) | ❌ 未集成 | ❌ 依赖后端 JWT 隔离 | ⚠️ 显示 `门店 {id}` |
| 用户审核 (UserApproval) | ❌ 未集成 | ❌ 依赖后端 JWT 隔离 | ❌ 仅显示球房编号 |
| Excel 上传 (ExcelUpload) | ❌ 未集成 | ❌ 无门店筛选 | ❌ 无门店信息 |
#### 6. 门店名称问题
⚠️ SiteSelector 选项显示为 `门店 {id}`(硬编码数字),未查询 `site_code_mapping` 获取真实门店名称。用户无法通过名称识别门店。
### 差距分析
| P10 要求 | 当前状态 | 差距 |
|----------|---------|------|
| 租户级管理员管辖所有店铺 | ✅ managed_site_ids 支持 | 无 |
| 店铺级管理员只管指定 site_id | ✅ JWT + site_filter_clause | 无 |
| 所有查询附加 site_id IN 条件 | ✅ 后端全部实现 | 无 |
| 门店选择器 UI | ⚠️ 组件存在但未全局集成 | 仅维客线索页使用 |
| 切换门店后数据自动刷新 | ⚠️ 维客线索页有效 | 其他页面无此机制 |
| 门店选择器位置 | ❌ 未定义全局位置 | App.tsx 布局无选择器 |
| 门店名称可读性 | ❌ 显示 ID 而非名称 | 需查询 site_name |
### 建议
1. **全局门店选择器**:在 `App.tsx``Content` 区域顶部(或 Header放置 SiteSelector创建 `SiteContext` 全局管理当前选中门店,各页面通过 Context 消费。
2. **门店名称映射**:登录后或首次加载时调用后端接口获取 `managed_site_ids → site_name` 映射SiteSelector 选项显示真实门店名称(如"朗朗桌球·XX店")。
3. **页面集成**UserApproval、UserManagement、ExcelUpload 页面接入全局门店筛选API 请求携带 `site_ids` 参数。
4. **切换刷新机制**:门店切换时触发数据重新加载(可通过 `useEffect` 监听 `selectedSiteIds` 变化,或使用 React Query 的 `queryKey` 包含 site_ids 实现自动 refetch
5. **后端补充**:新增 `GET /api/tenant/sites` 接口,返回当前管理员管辖门店的 `{site_id, site_name}` 列表,供前端门店选择器使用。

View File

@@ -0,0 +1,99 @@
# P10-NS4-03数据导出功能的规范
## 简要结论
- 状态:❌ 未解决
- 租户管理后台tenant-admin无任何业务数据导出功能。后端无导出端点前端无导出按钮数据库无导出日志表。现有的"下载"功能仅限 Excel 空白模板下载和系统管理后台的环境配置导出,均与业务数据导出无关。
## 详细审查
### 数据库端
搜索范围:`db/zqyy_app/` 全部 SQL 文件
结果:
- **无导出日志表**`biz` schema 下无 `export_log``download_log` 等导出记录表
- **现有表**`biz.excel_upload_log` 仅记录 Excel **上传**操作upload_type: expense/platform_income/salary_adj/recharge_commission无导出相关字段
- **无导出审计字段**:任何现有表均未包含导出时间、导出人、导出格式等审计字段
### 后端代码
搜索范围:`apps/backend/app/routers/` 全部 tenant_*.py 文件 + 全部路由文件
逐文件审查结果:
| 路由文件 | 导出相关端点 | 说明 |
|----------|-------------|------|
| `tenant_auth.py` | 无 | 仅登录/鉴权 |
| `tenant_users.py` | 无 | 用户审核+管理,无导出 |
| `tenant_excel.py` | `GET /template/{type}` | **模板下载**(空白 Excel 模板),非数据导出 |
| `tenant_clues.py` | 无 | 线索 CRUD无导出 |
| `env_config.py` | `GET /export` | **环境配置导出**admin-web 专用),非业务数据 |
关键发现:
1. `tenant_excel.py` 包含 `download_template()` 函数,使用 `openpyxl` + `StreamingResponse` 生成空白模板文件,仅含表头和格式说明,不含任何业务数据
2. 后端无通用导出工具模块(无 `export_to_excel``export_to_csv` 等工具函数)
3. 全局搜索 `def.*export` 仅命中 `env_config.py``export_env_config()`(系统管理后台功能)
### 前端代码
#### tenant-admin租户管理后台
搜索范围:`apps/tenant-admin/src/` 全部文件
页面清单与导出功能:
| 页面 | 路径 | 导出按钮 | 说明 |
|------|------|---------|------|
| Login | `pages/Login/` | 无 | 登录页 |
| UserApproval | `pages/UserApproval/` | 无 | 用户审核 |
| UserManagement | `pages/UserManagement/` | 无 | 用户管理 |
| ExcelUpload | `pages/ExcelUpload/` | 无 | 仅有模板**下载**按钮(`DownloadOutlined` + `handleDownloadTemplate` |
| RetentionClues | `pages/RetentionClues/` | 无 | 维客线索管理 |
结论5 个页面均无数据导出功能。ExcelUpload 页面的 `DownloadOutlined` 图标用于下载空白模板,非数据导出。
#### admin-web系统管理后台参考
搜索范围:`apps/admin-web/src/` 全部文件
- `EnvConfig.tsx`:有"导出"按钮,调用 `exportEnvConfig()` 导出去敏感值的 `.env` 配置文件 → 运维功能,非业务数据导出
- `OpsPanel.tsx``CloudDownloadOutlined` 用于 Git Pull 操作 → 非数据导出
- **无业务数据导出功能可参考**
### 差距分析
review-report.md 中 P10→NS4 缺失项 #3 指出P10 §数据导出 位置隐含了管理后台应支持数据导出,但 NS4 完全未提及。
作为管理后台,以下场景存在合理的导出需求但均未实现:
| 导出场景 | 数据源 | 潜在用户需求 | 当前状态 |
|----------|--------|-------------|---------|
| 用户列表导出 | `auth.users` | 租户管理员需要离线查看/统计用户信息 | ❌ 无 |
| 审核记录导出 | `auth.user_applications` | 审核工作量统计、合规审计 | ❌ 无 |
| Excel 上传历史导出 | `biz.excel_upload_log` | 上传操作追溯、数据核对 | ❌ 无 |
| 维客线索导出 | `member_retention_clue` | 线索数据离线分析、交接 | ❌ 无 |
| 助教奖罚明细导出 | `biz.salary_adjustments` | 工资核算、财务对账 | ❌ 无 |
补充说明:
- P10 spec 本身也未明确列出"数据导出"功能(无 AC、无任务项但 review-report 将其标记为隐含需求
- P7-NS1-10 审查(绩效明细导出)结论同样为 ❌ 未解决,说明整个项目目前缺乏通用的数据导出基础设施
### 建议
1. **明确需求优先级**:数据导出为隐含需求,建议在 NS4 spec 中明确标注为"后续迭代项"或"MVP 不含",避免歧义
2. **如需实现,建议分两步**
- **Step 1 — 通用导出基础设施**
- 后端新增 `apps/backend/app/utils/export_helper.py`,封装 openpyxl 生成 Excel 的通用逻辑列定义、数据填充、StreamingResponse 包装)
- 新建 `biz.export_log` 表记录导出操作who/when/what/row_count满足审计要求
- **Step 2 — 逐页面接入**
- 用户列表:`GET /api/tenant/users/export?format=xlsx`
- 审核记录:`GET /api/tenant/applications/export?status=&format=xlsx`
- 维客线索:`GET /api/tenant/customers/{member_id}/clues/export`
- 上传历史:`GET /api/tenant/excel/logs/export`
3. **权限控制**:导出操作应复用现有的 `require_tenant_admin()` 鉴权 + `site_id IN (管辖列表)` 数据隔离,确保导出数据不越权
4. **导出格式**:建议统一 `.xlsx`(已有 openpyxl 依赖),暂不支持 CSV避免中文编码问题
5. **NS4 spec 补充建议**:在 NS4 文档"三、功能详细设计"中新增 §3.5 数据导出,定义支持导出的页面清单、导出格式、权限规则、导出日志记录要求

View File

@@ -0,0 +1,85 @@
# P10-NS4-04操作日志/审计日志的查看界面
## 简要结论
- 状态:❌ 未解决
- 项目当前没有业务操作审计日志的数据库表、后端记录逻辑、以及前端查看界面。仅有 Excel 上传记录(`biz.excel_upload_log`)和 ETL 任务执行日志admin-web LogViewer均不属于业务操作审计日志范畴。
## 详细审查
### 数据库端
**查库方式**pg-app-test MCP 连接不可用,改用 DDL 迁移文件搜索。
| 检查项 | 结果 |
|--------|------|
| `audit_log` / `operation_log` / `activity_log` 表 | ❌ 不存在DDL 搜索无匹配) |
| `auth` schema 中日志相关表 | ❌ 不存在(仅有 `users``tenant_admins``user_roles``user_site_roles``user_applications``user_assistant_binding` |
| `biz` schema 中日志相关表 | ⚠️ 仅有 `biz.excel_upload_log`Excel 上传批次记录,非操作审计日志) |
| `task_execution_log` 表 | 存在于 `_archived/` 基线中,属于 ETL 任务执行日志,非业务操作审计 |
**结论**:数据库中不存在通用的业务操作审计日志表。`biz.excel_upload_log` 记录的是 Excel 上传批次状态pending/confirmed/failed不记录"谁做了什么操作"这类审计信息。
### 后端代码
| 检查项 | 结果 |
|--------|------|
| `apps/backend/app/routers/` 中审计日志路由 | ❌ 不存在(无 `audit`/`log`/`activity` 相关路由文件) |
| 审计中间件 / 日志记录装饰器 | ❌ 不存在(`app/middleware/` 仅有 `response_wrapper.py``permission.py` |
| `tenant_users.py` 审核操作日志 | ❌ 审核通过/拒绝仅更新状态字段(`user_applications.status``reviewed_by``reviewed_at`),不写入独立审计日志表 |
| `tenant_clues.py` 线索操作日志 | ❌ 编辑/删除/隐藏操作直接修改数据,无审计记录 |
| `tenant_excel.py` 上传操作日志 | ⚠️ 写入 `biz.excel_upload_log`,但仅记录批次元数据,不记录操作人的具体行为 |
**关键发现**
- 用户审核(通过/拒绝):`user_applications` 表有 `reviewed_by` + `reviewed_at` 字段,可追溯审核人和时间,但不是独立的审计日志
- 用户编辑(状态变更/绑定修改):无任何操作记录
- 维客线索修改/删除/隐藏:无任何操作记录,且删除为物理删除(`DELETE FROM`),数据不可恢复
- Excel 上传:`excel_upload_log` 记录了 `uploaded_by`,但无确认操作的审计
### 前端代码
| 检查项 | 结果 |
|--------|------|
| `apps/tenant-admin/src/pages/` 中日志查看页面 | ❌ 不存在(仅有 Login、UserApproval、UserManagement、ExcelUpload、RetentionClues 五个页面) |
| `apps/tenant-admin/src/App.tsx` 路由配置 | ❌ 无日志相关路由(路由:`/login``/applications``/users``/excel``/clues` |
| `apps/admin-web/src/pages/LogViewer.tsx` | ⚠️ 存在,但功能是 ETL 任务执行日志查看器(通过 WebSocket 接收执行日志、按任务分组展示),不是业务操作审计日志 |
| `apps/admin-web/src/App.tsx` 路由 `/log-viewer` | 指向 ETL LogViewer与业务操作审计无关 |
**结论**:两个前端应用均无业务操作审计日志查看界面。
### 差距分析
P10 标杆文件要求管理后台具备操作审计功能,即"谁在什么时间做了什么操作"的完整追溯能力。当前 NS4 的差距:
| 维度 | P10 要求 | NS4 现状 | 差距 |
|------|----------|----------|------|
| 审计数据存储 | 独立审计日志表 | ❌ 不存在 | 完全缺失 |
| 审计记录写入 | 关键操作自动记录 | ❌ 无中间件/装饰器 | 完全缺失 |
| 审计日志查看 | 管理后台可查看/筛选 | ❌ 无页面 | 完全缺失 |
| 操作可追溯性 | 所有关键操作可追溯 | ⚠️ 仅审核操作有 `reviewed_by`/`reviewed_at` | 大部分缺失 |
| 数据安全 | 删除操作可恢复/可审计 | ❌ 线索删除为物理删除 | 高风险 |
### 建议
#### 短期(高优先级)
1. **新建审计日志表** `biz.audit_log`
- 字段:`id``site_id``operator_id`(操作人)、`operator_type`tenant_admin/system`action`approve/reject/edit/delete/hide/upload 等)、`target_type`user/clue/excel 等)、`target_id``detail`JSONB变更前后值`ip_address``created_at`
- 索引:`(site_id, created_at DESC)``(operator_id)``(target_type, target_id)`
2. **后端审计中间件/工具函数**
-`tenant_users.py` 的审核通过/拒绝、用户编辑/禁用操作中写入审计日志
-`tenant_clues.py` 的编辑/删除/隐藏操作中写入审计日志
-`tenant_excel.py` 的上传确认操作中写入审计日志
- 建议实现为可复用的 `log_audit()` 工具函数,在事务内调用
3. **线索删除改为软删除**:当前 `DELETE FROM public.member_retention_clue` 为物理删除,建议改为 `SET is_deleted = true`,配合审计日志记录
#### 中期
4. **前端审计日志查看页面**tenant-admin
- 新增 `/audit-logs` 路由和 `AuditLogs` 页面
- 支持按时间范围、操作类型、操作人筛选
- 展示操作详情(变更前后对比)
5. **后端审计日志查询 API**
- `GET /api/tenant/audit-logs`(分页 + 筛选)

View File

@@ -0,0 +1,64 @@
# P10-NS4-05管理后台的响应式适配
## 简要结论
- 状态:⚠️ 部分解决
- tenant-admin 依赖 Ant Design 内置响应式能力实现了基础适配Sider 可折叠、flex 布局),但未定义最小支持分辨率、断点规则,也未对表格横向滚动做统一处理。
## 详细审查
### 前端代码
#### 1. 全局布局App.tsx
- 使用 Ant Design `<Layout>` + `<Sider collapsible>`,侧边栏支持折叠/展开,这是管理后台最基本的响应式能力 ✅
- `minHeight: "100vh"` 保证全屏高度 ✅
- Content 区域 `margin: 16, minHeight: 280`,使用固定像素值,无断点适配 ⚠️
#### 2. 页面级响应式
- **Login 页面**:居中卡片 `width: 400` 固定宽度,小屏幕下可能溢出 ⚠️
- **ExcelUpload 页面**:使用了 `<Row gutter={16}>` + `<Col span={8}>` 展示统计卡片,这是 Ant Design Grid 的响应式用法,但 `span` 为固定值(未使用 `xs/sm/md/lg` 响应式断点属性)⚠️
- **UserApproval / UserManagement 页面**:筛选栏使用 `display: "flex"` + `flexWrap: "wrap"`UserManagement基本适配 ✅;但 UserApproval 筛选栏未设 `flexWrap` ⚠️
- **RetentionClues 页面**:使用 `<Space direction="vertical">` 布局,宽度 `100%`,基本适配 ✅
#### 3. 表格适配
- 所有页面的 `<Table>` 组件均未设置 `scroll={{ x: ... }}`,在窄屏下列内容可能被压缩变形 ❌
- 对比 admin-web 的 DBViewer 页面已使用 `scroll={{ x: 'max-content' }}`tenant-admin 未跟进
- RetentionClues 的线索表格有多列固定宽度(共约 700px加上其他列在 1280px 屏幕下可能紧凑但尚可
- ExcelUpload 的校验详情表格"数据"列和"问题详情"列无固定宽度,内容可能很长
#### 4. CSS / 样式文件
- 项目中无独立 CSS/Less/SCSS 文件,所有样式通过 inline style 实现
-`@media` 查询、无自定义断点定义、无全局响应式样式
- 仅 SiteSelector 组件使用了 `maxTagCount="responsive"`Ant Design Select 内置响应式标签数量)✅
#### 5. viewport 配置
- `index.html` 包含标准 viewport meta`<meta name="viewport" content="width=device-width, initial-scale=1.0" />`
#### 6. 依赖检查
- 无额外响应式库(如 react-responsive、@ant-design/pro-layout 等)
- 未使用 Ant Design 的 `Grid.useBreakpoint()` hook
#### 7. admin-web 对比
- admin-web 同样未做系统性响应式适配,但 DBViewer 页面的表格使用了 `scroll={{ x: 'max-content' }}`
- 两个管理后台的响应式水平基本一致:依赖 Ant Design 默认行为,无主动断点适配
### 差距分析
| 标杆要求P10 §响应式) | 当前状态 | 差距 |
|---|---|---|
| 最小支持分辨率定义 | P10 spec 和 NS4 均未提及 | ❌ 未定义 |
| 断点规则1280/1440/1920 | 无自定义断点 | ❌ 未定义 |
| 移动端适配策略 | 无策略声明 | ⚠️ 管理后台面向 PC可接受不支持移动端但需明确声明 |
| Sider 可折叠 | `collapsible` 已启用 | ✅ 已实现 |
| 表格横向滚动 | 未设置 `scroll.x` | ❌ 未实现 |
| Grid 响应式断点 | ExcelUpload 用了 Row/Col 但无响应式断点 | ⚠️ 部分 |
> P10 原始 spec`docs/prd/specs/P10-tenant-admin-web.md`)中实际不包含"§响应式"章节,该要求来自标杆文件对管理后台的通用期望。
### 建议
1. **明确最小分辨率**:在 NS4 或项目前端规范中声明最小支持分辨率为 1280×720覆盖主流笔记本不支持移动端访问
2. **表格横向滚动**:为所有 `<Table>` 添加 `scroll={{ x: 'max-content' }}` 或计算列宽总和,防止窄屏下列压缩
3. **Login 卡片宽度**:将 `width: 400` 改为 `maxWidth: 400, width: '100%'`,避免小屏溢出
4. **ExcelUpload 统计卡片**`<Col span={8}>` 改为 `<Col xs={24} sm={12} lg={8}>`,适配不同屏幕
5. **UserApproval 筛选栏**:补充 `flexWrap: "wrap"`,与 UserManagement 保持一致
6. **优先级评估**:以上均为低风险改进项。管理后台面向 PC 端使用,当前 Ant Design 默认行为已提供基本可用性,建议在后续迭代中逐步完善,不阻塞当前交付

View File

@@ -0,0 +1,137 @@
# P10-NS4-06表格组件的统一规范
## 简要结论
- 状态:⚠️ 部分解决
- tenant-admin 各页面表格在分页大小、loading 状态、筛选器位置上已形成事实一致的模式但缺少显式的统一配置抽象层排序交互完全缺失空数据展示未统一部分表格分页大小不一致20 vs 10
## 详细审查
### 前端代码
#### 1. 分页大小
| 页面/组件 | 默认 pageSize | showSizeChanger | showTotal | 后端分页 |
|-----------|:---:|:---:|:---:|:---:|
| UserApproval | 20 | ✅ | ✅ `共 N 条` | ✅ page/page_size |
| UserManagement | 20 | ✅ | ✅ `共 N 条` | ✅ page/page_size |
| ExcelUpload — 上传记录 | 20 | ✅ | ✅ `共 N 条` | ✅ page/page_size |
| ExcelUpload — 校验详情 | **10** | ❌ | ❌ | 前端分页 |
| RetentionClues — 客户搜索 | `false` | — | — | 无分页LIMIT 50 |
| RetentionClues — 线索列表 | **10** | ❌ | ❌ | 无分页(全量返回) |
| DiffTable — 冲突表 | `false` | — | — | 前端数据 |
结论:主列表页统一为 20 条/页 + showSizeChanger + showTotal但子表格校验详情、线索列表使用 10 条/页且缺少 showSizeChanger 和 showTotal**不一致**。
#### 2. 排序交互
所有 5 个表格组件均未配置 `sorter` 属性,前端不支持列排序。后端 SQL 统一使用 `ORDER BY created_at DESC`(固定倒序),无动态排序参数。
结论:**完全缺失**。用户无法按任意列排序。
#### 3. 筛选器位置
| 页面 | 筛选器位置 | 筛选方式 |
|------|-----------|---------|
| UserApproval | 表格上方独立区域 | Select状态筛选 |
| UserManagement | 表格上方独立区域 | Select角色+ Input.Search关键词 |
| ExcelUpload — 上传记录 | 无筛选 | — |
| RetentionClues | Card title 区域 + Card extra 区域 | Input.Search + SiteSelector上方Select×2Card extra 右侧) |
结论:筛选器统一放在表格上方(非表格内 column filter**位置一致**。但 RetentionClues 的线索筛选器放在 Card extra 右侧,与其他页面的左对齐布局略有差异。
#### 4. loading 状态
所有带后端请求的表格均通过 `loading={loading}` 传入 Ant Design Table 的 loading 属性,**统一且正确**。
#### 5. 空数据展示
| 页面 | 空数据处理 |
|------|-----------|
| UserApproval | Ant Design Table 默认空状态(无自定义) |
| UserManagement | Ant Design Table 默认空状态(无自定义) |
| ExcelUpload | Ant Design Table 默认空状态(无自定义) |
| RetentionClues — 客户搜索 | `<Empty description="未找到匹配客户" />` ✅ 自定义 |
| RetentionClues — 线索列表 | Ant Design Table 默认空状态(无自定义) |
| DiffTable | Ant Design Table 默认空状态(无自定义) |
结论:仅 RetentionClues 客户搜索有自定义空状态文案,其余均依赖 Ant Design 默认的英文 "No Data"。**未统一**,且未做中文化。
对比 admin-web`TaskManager` 页面使用了 `locale={{ emptyText: <Empty description="队列为空" /> }}` 自定义空状态tenant-admin 未跟进。
#### 6. 统一配置抽象
- tenant-admin 中**不存在**统一的表格配置文件、常量定义或封装组件
- 每个页面独立定义 `pageSize``pagination` 配置、`handleTableChange`
- 分页响应结构 `{ items, total, page, pageSize }` 在前后端已统一,但属于隐式约定
### 后端代码
#### 分页参数统一性
| 路由 | 参数名 | 默认值 | 范围约束 |
|------|--------|--------|---------|
| `GET /applications` | `page` + `page_size` | 1 / 20 | ge=1 / ge=1,le=100 |
| `GET /users` | `page` + `page_size` | 1 / 20 | ge=1 / ge=1,le=100 |
| `GET /excel/logs` | `page` + `page_size` | 1 / 20 | ge=1 / ge=1,le=100 |
| `GET /customers/search` | 无分页 | — | LIMIT 50 硬编码 |
| `GET /customers/{id}/clues` | 无分页 | — | 全量返回 |
结论:三个主列表接口的分页参数**完全统一**`page`/`page_size`,默认 20上限 100。客户搜索和线索列表不分页属于业务设计选择数据量可控但线索列表在数据量增长后可能需要补充分页。
响应格式统一为 `{ items: T[], total: int, page: int, pageSize: int }`**一致**。
### 差距分析
与 P10 标杆文件要求的差距:
| 规范项 | P10 要求(推断) | 当前状态 | 差距 |
|--------|-----------------|---------|------|
| 默认分页大小 | 统一值(如 20 | 主表 20子表 10 | 🟡 子表不一致 |
| showSizeChanger | 所有分页表格启用 | 仅主表启用 | 🟡 子表缺失 |
| showTotal | 所有分页表格显示 | 仅主表显示 | 🟡 子表缺失 |
| 排序交互 | 至少关键列支持排序 | 完全缺失 | 🔴 缺失 |
| 筛选器位置 | 统一在表格上方 | 基本一致 | ✅ |
| loading 状态 | 统一 loading 属性 | 全部已配置 | ✅ |
| 空数据展示 | 统一中文空状态 | 仅 1 处自定义,其余默认 | 🟡 未统一 |
| 统一配置抽象 | 提取公共 Table 配置 | 不存在 | 🔴 缺失 |
### 建议
1. **提取统一表格配置常量**(优先级:高)
`apps/tenant-admin/src/constants/table.ts` 中定义:
```ts
export const DEFAULT_PAGE_SIZE = 20;
export const PAGE_SIZE_OPTIONS = ['10', '20', '50'];
export const TABLE_LOCALE = {
emptyText: '暂无数据',
};
export const defaultPagination = (total: number, page: number, pageSize: number) => ({
current: page,
pageSize,
total,
showSizeChanger: true,
showTotal: (t: number) => `共 ${t} 条`,
pageSizeOptions: PAGE_SIZE_OPTIONS,
});
```
2. **统一空数据展示**(优先级:高)
通过 Ant Design ConfigProvider 全局设置中文 locale 和空状态:
```tsx
import zhCN from 'antd/locale/zh_CN';
<ConfigProvider locale={zhCN} renderEmpty={() => <Empty description="暂无数据" />}>
```
3. **子表格分页对齐**(优先级:中)
ExcelUpload 校验详情和 RetentionClues 线索列表的 pageSize 改为 20并补充 showSizeChanger 和 showTotal。
4. **排序交互**(优先级:低)
对时间列(申请时间、上传时间、记录时间)添加前端 `sorter` 支持。如数据量增长需后端排序,可在 API 中增加 `sort_by` + `sort_order` 参数。当前数据量下前端排序即可。
5. **线索列表补充分页**(优先级:低)
`GET /customers/{member_id}/clues` 当前全量返回,建议在数据量可能超过 50 条时补充后端分页。

View File

@@ -0,0 +1,150 @@
# P10-NS4-07表单验证的统一规范
## 简要结论
- 状态:⚠️ 部分解决
- 各表单已实现基本验证逻辑且风格较一致,但缺少文档化的统一规范,部分表单必填标识缺失,后端 Pydantic 422 错误未映射到前端字段级提示。
## 详细审查
### 前端代码
#### 1. 必填标识(`required: true`
| 表单 | 文件 | 必填字段 | 是否配置 `required` | 备注 |
|------|------|----------|---------------------|------|
| 登录 | `Login/index.tsx` | username, password | ✅ 均有 | — |
| 审核通过 | `UserApproval/index.tsx` (ReviewModal) | role | ✅ 有 | `suggestionIndex` 为可选,正确 |
| 审核拒绝 | `UserApproval/index.tsx` (ReviewModal) | reason | ✅ 有 | — |
| 用户编辑 | `UserManagement/index.tsx` (EditModal) | role, siteId | ❌ 均无 `required` | 角色和门店字段未标记必填,用户可提交空值 |
| 用户绑定 | `UserManagement/index.tsx` (BindModal) | assistantId, staffId | ✅ 正确无 `required` | 两个字段均为可选,符合业务逻辑 |
| 线索编辑 | `ClueEditor/index.tsx` | category, summary | ✅ 均有 | `detail` 为可选,正确 |
问题:`EditModal``role``siteId` 字段没有 `required` 规则。虽然后端 `UserEditRequest` 允许 `None`(部分更新语义),但前端 UI 上没有明确告知用户哪些字段是建议填写的。
#### 2. 错误提示文案
所有已配置 `rules` 的表单字段,`message` 均为中文:
- `"请输入用户名"` / `"请输入密码"` — Login
- `"请选择角色"` — UserApproval (approve)
- `"请填写拒绝原因"` — UserApproval (reject)
- `"请选择大类标签"` / `"请输入摘要"` / `"摘要不能超过 200 字符"` — ClueEditor
✅ 文案风格统一,均为 `"请 + 动词 + 名词"` 格式。
#### 3. 验证触发时机
| 表单 | 触发方式 | 说明 |
|------|----------|------|
| Login | `onFinish`(提交验证) | Form 的 `onFinish` 回调Ant Design 默认在提交时触发全量校验 |
| ReviewModal (approve/reject) | `form.validateFields()`(手动提交验证) | Modal `onOk` 中手动调用 |
| EditModal | `form.validateFields()`(手动提交验证) | Modal `onOk` 中手动调用 |
| BindModal | `form.validateFields()`(手动提交验证) | Modal `onOk` 中手动调用 |
| ClueEditor | `form.validateFields()`(手动提交验证) | Modal `onOk` 中手动调用 |
✅ 统一采用提交时验证(`onFinish``validateFields`),未使用实时验证(`onChange`)。风格一致。
注意:所有 Modal 表单均未显式设置 `validateTrigger`Ant Design 默认值为 `onChange`,即用户修改字段后会实时显示/清除错误。这实际上是"提交触发首次校验 + 后续实时反馈"的混合模式,属于 Ant Design 最佳实践。
#### 4. 自定义验证器
`ClueEditor` 使用了 `max: 200` 长度限制规则,其余表单无自定义 `validator`
ExcelUpload 页面不使用 Ant Design Form 进行表单验证,而是:
- 文件类型限制通过 `Upload` 组件的 `accept=".xlsx,.xls"` 属性
- 空文件检查通过 `fileList.length === 0` 手动判断
- 数据校验完全由后端完成,前端展示后端返回的校验结果
这种设计合理——Excel 上传的校验逻辑复杂(表头格式、金额精度、人员匹配等),适合后端统一处理。
#### 5. 表单布局
所有 Modal 内表单统一使用 `layout="vertical"`Login 页面使用默认水平布局。风格基本一致。
### 后端代码
#### 1. Pydantic Schema 验证
| Schema | 文件 | 验证规则 | 备注 |
|--------|------|----------|------|
| `ApproveRequest` | `tenant_users.py` | `role: str = Field(..., min_length=1)` | 必填 + 非空 |
| `RejectRequest` | `tenant_users.py` | `reason: str = Field(..., min_length=1)` | 必填 + 非空 |
| `UserEditRequest` | `tenant_users.py` | 所有字段 `Optional` | 部分更新语义,合理 |
| `UserBindingRequest` | `tenant_users.py` | 所有字段 `Optional` | 合理 |
| `ClueEditRequest` | `tenant_clues.py` | `category: ClueCategory`(枚举), `summary: str = Field(..., min_length=1, max_length=200)` | 枚举校验 + 长度限制 |
| `ClueVisibilityRequest` | `tenant_clues.py` | `is_hidden: bool = Field(...)` | 必填 |
| `ConfirmRequest` | `tenant_excel.py` | `upload_id: int`, `resolutions: list[Resolution]` | 结构化验证 |
✅ 后端 Pydantic 验证规则与前端 rules 基本对齐。
#### 2. 手动验证(路由层)
Excel 上传路由 (`tenant_excel.py`) 包含额外的手动验证:
- `upload_type` 枚举校验
- 文件扩展名校验(`.xlsx/.xls`
- 文件内容非空校验
- Excel 解析成功校验
- 数据行非空校验
客户搜索路由 (`tenant_clues.py`) 使用 `Query(..., min_length=1)` 确保关键词非空。
#### 3. 错误响应格式
后端统一错误响应格式:`{ "code": <status_code>, "message": <detail> }`
- `http_exception_handler`HTTPException → `{ code, message }`
- `unhandled_exception_handler`:未捕获异常 → `{ code: 500, message: "Internal Server Error" }`
- ❌ 未注册 `RequestValidationError` 处理器 — Pydantic 验证失败时FastAPI 默认返回 422 + `{ "detail": [{ "loc": [...], "msg": "...", "type": "..." }] }` 格式,与统一的 `{ code, message }` 格式不一致
#### 4. 错误提示文案
后端 HTTPException 的 `detail` 均为中文:
- `"申请不存在"` / `"该申请已被处理"` / `"无效的球房编号"` / `"审核操作失败"`
- `"用户不存在"` / `"目标门店不在管辖范围内"` / `"编辑操作失败"`
- `"线索不存在"` / `"编辑操作失败"` / `"删除操作失败"`
- `"请上传有效的 Excel 文件(.xlsx/.xls"` / `"文件内容为空"`
✅ 中文化程度高,风格统一。
### 差距分析
P10 标杆文件(`P10-tenant-admin-web.md`)中没有独立的"表单规范"章节,但在验收标准和设计要点中隐含了以下要求:
- AC4Excel 上传校验(必填、金额精度、表头格式、类型合法)— ✅ 已实现
- 4 种模板的必填列定义 — ✅ 后端已实现校验
- 冲突处理流程 — ✅ 已实现
与通用表单规范最佳实践的差距:
| 维度 | 期望 | 现状 | 差距 |
|------|------|------|------|
| 必填标识 | 所有必填字段有 `required` 规则 + 红色星号 | 大部分有EditModal 缺失 | 🟡 小 |
| 错误提示位置 | 统一在字段下方 | Ant Design Form.Item 默认行为,一致 | ✅ 无 |
| 提示文案 | 中文,格式统一 | `"请 + 动词 + 名词"` 格式统一 | ✅ 无 |
| 验证时机 | 明确约定实时/提交 | 统一提交验证 + Ant Design 默认 onChange 反馈 | ✅ 无 |
| 后端 422 映射 | Pydantic 验证错误映射到前端字段 | 未注册 RequestValidationError 处理器 | 🟡 小 |
| 文档化规范 | 有明确的表单验证规范文档 | 无 | 🟡 小 |
| 前后端验证对齐 | 前端 rules 与后端 schema 一一对应 | 基本对齐,个别字段前端缺 rules | 🟡 小 |
### 建议
1. **EditModal 补充必填规则**(低优先级):`role` 字段建议加 `rules={[{ required: true, message: "请选择角色" }]}``siteId` 视业务需求决定是否必填。
2. **注册 RequestValidationError 处理器**(低优先级):在 `apps/backend/app/main.py` 中添加:
```python
from fastapi.exceptions import RequestValidationError
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
return JSONResponse(
status_code=422,
content={"code": 422, "message": "请求参数校验失败", "errors": exc.errors()},
)
```
使 Pydantic 验证错误也遵循统一的 `{ code, message }` 响应格式。
3. **文档化表单规范**(可选):如需更高规范性,可在 NS4 或项目级文档中补充一节"表单验证约定",明确:
- 必填字段必须配置 `required: true`Ant Design 自动显示红色星号)
- 错误提示文案格式:`"请 + 动词 + 名词"`
- 验证时机:提交时触发(`validateFields` / `onFinish`),依赖 Ant Design 默认 `onChange` 实时反馈
- 后端验证错误通过 `message.error()` 全局提示,不映射到具体字段
当前项目规模5 个页面、6 个表单)下,现有的隐式一致性已足够,文档化为锦上添花。

View File

@@ -0,0 +1,73 @@
# P10-NS4-08管理后台的国际化预留
## 简要结论
- 状态:✅ 已解决
- Ant Design 组件已配置 `zhCN` locale项目无国际化框架需求中文硬编码符合项目规范当前方案合理。
## 详细审查
### 前端代码
#### 1. Ant Design ConfigProvider locale 配置
`apps/tenant-admin/src/main.tsx` 已在根节点配置 Ant Design 中文 locale
```tsx
import { ConfigProvider } from "antd";
import zhCN from "antd/locale/zh_CN";
<ConfigProvider locale={zhCN}>
<App />
</ConfigProvider>
```
这确保了 Table 空状态文案、DatePicker 日期选择器、Pagination 分页器、Modal 确认/取消按钮等所有 Ant Design 内置组件均显示中文。
`apps/admin-web/src/main.tsx` 采用完全相同的配置方式,两个管理后台保持一致。
#### 2. i18n 框架依赖
`apps/tenant-admin/package.json``apps/admin-web/package.json` 均未引入任何 i18n 框架(如 `react-intl``i18next``react-i18next` 等)。
全局搜索 `i18n``intl``useIntl``formatMessage``i18next``react-intl` 关键词,两个项目中均无 i18n 框架使用痕迹。`locale` 关键词仅出现在 Ant Design ConfigProvider 配置和 `toLocaleString()` 日期格式化调用中。
#### 3. 中文硬编码情况
tenant-admin 所有页面中的 UI 文案均为中文硬编码,包括:
- 导航菜单:`"用户审核"``"用户管理"``"Excel 上传"``"维客线索管理"`
- 表格列标题:`"昵称"``"手机号"``"状态"`
- 操作按钮:`"审核"``"编辑"``"绑定"``"删除"``"登录"`
- 表单标签:`"请输入用户名"``"请输入密码"``"请选择角色"`
- 提示消息:`"审核通过成功"``"用户名或密码错误"``"账号已被禁用"`
- 状态标签:`"待审核"``"已通过"``"已拒绝"`
admin-web 同样采用中文硬编码方式,两个项目风格一致。
#### 4. 与 admin-web 的对比
| 维度 | tenant-admin | admin-web |
|------|-------------|-----------|
| ConfigProvider zhCN | ✅ 已配置 | ✅ 已配置 |
| i18n 框架 | 无 | 无 |
| UI 文案方式 | 中文硬编码 | 中文硬编码 |
| 日期格式化 | dayjs 依赖已安装 | `toLocaleString('zh-CN')` |
两个管理后台在国际化处理上完全一致。
### 差距分析
P10 标杆文件(`docs/prd/specs/P10-tenant-admin-web.md`)中没有独立的"国际化"章节,也未提出任何多语言支持要求。标杆文件明确定义的用户群体是"租户管理员",即国内台球门店的管理人员。
结合项目 steering 规则:
- `language-zh.md`:说明性文字一律简体中文
- `project-overview.md`:领域语言中文,货币 CNY
项目定位为面向国内台球门店的垂直业务系统,目标用户群体单一(国内门店管理员),不存在多语言需求场景。
### 建议
当前方案已满足项目需求,无需额外改动:
1. Ant Design `ConfigProvider locale={zhCN}` 已正确配置,组件内置文案为中文 — **已完成**
2. 无需引入 i18n 框架 — 项目面向国内市场,中文硬编码是合理选择,引入 i18n 框架反而增加不必要的复杂度
3. 如未来确有国际化需求(概率极低),可后续引入 `react-intl``i18next`,将硬编码文案提取为 message key改动范围可控

View File

@@ -0,0 +1,92 @@
# P10-NS4-09管理后台的主题定制
## 简要结论
- 状态:❌ 未解决
- 两个管理后台tenant-admin、admin-web均未实现主题定制使用 Ant Design v5 默认主题,无品牌色配置、无 Logo 组件、无暗色模式支持。
## 详细审查
### 前端代码
#### 1. ConfigProvider theme 配置
**tenant-admin `main.tsx`**`ConfigProvider` 仅配置了 `locale={zhCN}`,未传入 `theme` prop。
```tsx
<ConfigProvider locale={zhCN}>
<App />
</ConfigProvider>
```
**admin-web `main.tsx`**:同样仅配置 `locale={zhCN}`,无 `theme` prop。
两个后台均未使用 Ant Design v5 的 theme token 系统(`ConfigProvider theme={{ token: { colorPrimary: '...' } }}`)。
#### 2. 品牌色primaryColor
- 全局搜索 `colorPrimary``primaryColor``theme` 关键词:两个后台均无自定义品牌色配置
- 所有组件使用 Ant Design 默认蓝色(`#1677ff`
- 侧边栏 `Menu` 使用 `theme="dark"` 属性Ant Design 内置暗色菜单,非自定义主题)
#### 3. Logo 展示
- **tenant-admin**:侧边栏顶部为纯文字 `"租户管理后台"`(内联样式,无图片 Logo
- **admin-web**:侧边栏顶部为纯文字 `"NeoZQYY"`(内联样式,无图片 Logo
- 登录页tenant-admin 使用 `Card title="租户管理后台"` 纯文字标题,无 Logo 图片
- 两个项目 `src/` 目录下均无图片资源文件(`.png``.svg``.jpg``.ico`
#### 4. 暗色模式
- 全局搜索 `darkMode``darkAlgorithm``defaultAlgorithm`:无结果
- 无暗色模式切换开关或相关状态管理
- 未使用 Ant Design v5 的 `theme.darkAlgorithm`
#### 5. Ant Design 版本
- 两个后台均使用 `antd@^5.24.7`,完全支持 CSS-in-JS 主题系统
- 技术上具备实现主题定制的能力,但未使用
#### 6. Vite 配置
- `vite.config.ts` 无 Less 变量覆盖或 CSS 预处理器主题配置
- 仅配置了路径别名、开发服务器端口和 API 代理
#### 7. 两个后台的视觉一致性
| 维度 | tenant-admin | admin-web | 一致性 |
|------|-------------|-----------|--------|
| 品牌色 | Ant Design 默认蓝 | Ant Design 默认蓝 | ✅ 一致(均为默认值) |
| 侧边栏 | `Sider collapsible` + `Menu theme="dark"` | 同左 | ✅ 一致 |
| 顶部标题 | 纯文字"租户管理后台" | 纯文字"NeoZQYY" | ⚠️ 文案不同,无统一品牌标识 |
| Logo | 无 | 无 | ✅ 一致(均无) |
| 暗色模式 | 无 | 无 | ✅ 一致(均无) |
| 底部状态栏 | 无 | 有(任务执行状态) | ❌ 不一致 |
### 差距分析
P10 标杆文件(`docs/prd/specs/P10-tenant-admin-web.md`)本身未包含"§主题"章节,该缺失项来源于 review-report 的通用管理后台最佳实践差距分析。具体差距:
1. **品牌色**:未定义,依赖 Ant Design 默认值。如果未来需要品牌识别或多租户白标,缺少基础设施。
2. **Logo**:两个后台均无图形 Logo仅用内联文字。侧边栏和登录页缺少品牌视觉锚点。
3. **暗色模式**完全未实现。Ant Design v5 原生支持 `theme.darkAlgorithm`,实现成本低,但当前未启用。
4. **主题统一管理**:无共享的主题配置文件或常量,两个后台各自独立,未来维护品牌一致性成本较高。
### 建议
鉴于此项为 🟡 低风险,且 P10 spec 本身未要求主题定制,建议按优先级分阶段处理:
1. **短期(推荐)**:在两个后台的 `ConfigProvider` 中添加统一的 `theme.token.colorPrimary`,建立品牌色基础。改动量极小(约 5 行代码/项目),但能统一视觉标识。
```tsx
// 示例main.tsx
<ConfigProvider
locale={zhCN}
theme={{ token: { colorPrimary: '#品牌色' } }}
>
```
2. **中期(可选)**:提取共享主题配置到 `packages/shared/` 或独立的 `theme.ts`,确保两个后台品牌一致性。添加 Logo 图片资源到侧边栏和登录页。
3. **长期(按需)**:如有暗色模式需求,利用 Ant Design v5 的 `darkAlgorithm` 实现切换。如有多租户白标需求,建立主题配置表。
4. **决策建议**:建议产品侧明确是否需要主题定制能力。如果当前阶段仅为内部管理工具,默认主题可接受;如果面向多租户交付,则品牌色和 Logo 应尽早落地。

View File

@@ -0,0 +1,63 @@
# P5.1→NS3 缺失项 #1App1 Prompt 工程规范
## 简要结论
- 状态:✅ 已解决
- 风险等级:🟡 低
- App1 的完整 Prompt 工程规范已在百炼平台 System Prompt 文档和后端代码中实现NS3 作为 MCP Server 扩展 spec 不涉及 Prompt 细节属于正常分工。
## 详细审查
### 审查范围
- `apps/backend/app/ai/apps/app1_chat.py` — App1 后端实现
- `docs/prd/ai-app-prompts.md` — 百炼平台 8 个应用的 System Prompt 完整定义
- `docs/prd/specs/P5-miniapp-ai-integration.md` — P5 原始 spec首条 Prompt 数据结构)
- `docs/prd/Neo_Specs/NS3-mcp-server-ai-extension.md` — NS3 spec
### 发现
1. **System Prompt 模板**`docs/prd/ai-app-prompts.md` 中定义了 App1 完整的 System Prompt包含
- 角色定义(台球门店运营助手)
- 5 个技能(数据查询、客户信息、助教业绩、经营数据、库存)
- 权限控制规则(助教/管理者角色隔离)
- 查询规范和回复规范
- `biz_params.user_prompt_params` 参数注入User_ID、Role、Nickname
2. **首条 Prompt JSON 结构**P5 spec 中明确定义了 App1 的首条用户消息结构:
```json
{
"current_time": "...",
"source_page": "来源页面标识",
"page_context": "页面上下文摘要",
"screen_content": "屏幕可见内容文本化"
}
```
3. **后端实现**`app1_chat.py` 已实现:
- `_build_system_prompt()` 构建 system prompt JSON注入用户信息 + 页面上下文)
- `_build_page_context()` 调用 `build_page_text()` 获取 10 种页面入口的结构化文本
- Token 预算控制(`_MAX_SYSTEM_PROMPT_LEN = 4000`
- SSE 流式返回完整链路
4. **NS3 的定位**NS3 是 MCP Server 扩展 spec职责是数据库连接、查库手册、脱敏策略不涉及 AI 应用的 Prompt 工程。Prompt 规范由 P5 spec + `ai-app-prompts.md` 承载,这是正确的分工。
### 证据
`app1_chat.py` 中的 system prompt 构建:
```python
prompt: dict = {
"task": "你是台球门店的 AI 助手,根据用户的问题和当前页面上下文提供帮助。",
"biz_params": {
"user_prompt_params": {
"User_ID": str(user_id),
"Role": role,
"Nickname": nickname,
},
},
}
```
`ai-app-prompts.md` 中 App1 的 System Prompt 长度约 1500 字,覆盖 5 个技能、权限控制、查询规范、回复规范。
### 说明
App1 是通用对话应用,其 Prompt 工程规范不包含 few-shot 示例和 JSON schema 约束(这些是结构化输出应用 2-8 的需求。App1 使用流式文本返回(`chat_stream`),不需要 JSON schema 约束。百炼平台的 System Prompt 配置 + 后端 `_build_system_prompt()` 的动态注入,构成了完整的 Prompt 工程规范。

View File

@@ -0,0 +1,75 @@
# P5.1→NS3 缺失项 #2App2 财务指标计算口径
## 简要结论
- 状态:✅ 已解决
- 风险等级:🟡 低
- App2 财务洞察的 6 个收入结构字段、items_sum 口径、环比基准均已在 Prompt 模板和后端代码中完整定义。
## 详细审查
### 审查范围
- `apps/backend/app/ai/apps/app2_finance.py` — App2 后端实现
- `apps/backend/app/ai/prompts/app2_finance_prompt.py` — App2 Prompt 模板
- `docs/prd/ai-app-prompts.md` — App2 System Prompt
- `docs/prd/specs/P5-miniapp-ai-integration.md` — P5 spec 中 App2 首条 Prompt 数据结构
- `apps/backend/app/ai/schemas.py` — App2Result 模型定义
### 发现
1. **收入结构字段映射6 个指标)**`app2_finance_prompt.py``_build_system_content()` 中明确定义了 `field_mapping`
- `items_sum` = table_charge_money + goods_money + assistant_pd_money + assistant_cx_money + electricity_money
- `table_fee` = table_charge_money台费收入
- `assistant_pd` = assistant_pd_money陪打费
- `assistant_cx` = assistant_cx_money超休费
- `goods` = goods_money商品收入
- `recharge` = 充值 pay_amountsettle_type=5充值收入
- `electricity` = electricity_money电费当前未启用全为 0
2. **items_sum 口径规则**Prompt 模板中 `rules` 数组明确声明:
- "统一使用 items_sum 口径计算营收总额"
- "助教费用必须拆分为 assistant_pd_money陪打和 assistant_cx_money超休"
- "支付渠道恒等式balance_amount = recharge_card_amount + gift_card_amount"
- "金额单位CNY保留两位小数"
3. **环比基准**`_build_period_data()` 构建当期和上期数据结构,包含完整的收入结构、储值资产、费用汇总、平台结算字段。`app2_finance.py``compute_time_range()` 实现了 8 个时间维度的日期范围计算,为环比分析提供基准。
4. **System Prompt**`ai-app-prompts.md` 中 App2 的 System Prompt 定义了 3 个技能:
- 技能 1财务趋势分析历史数据环比增减幅度
- 技能 2经营预警与建议含当前周期
- 技能 3多维度深度分析客单价、支付方式、时段分析
5. **输出 JSON schema**System Prompt 中强制要求返回 `[{seq, title, content}]` 格式,`schemas.py` 中定义了 `App2InsightItem(seq, title, body)``App2Result(insights)` Pydantic 模型。
### 证据
`app2_finance_prompt.py` 中的字段映射:
```python
"field_mapping": {
"items_sum": (
"table_charge_money + goods_money + assistant_pd_money"
" + assistant_cx_money + electricity_money"
),
"table_fee": "table_charge_money台费收入",
"assistant_pd": "assistant_pd_money陪打费",
"assistant_cx": "assistant_cx_money超休费",
"goods": "goods_money商品收入",
"recharge": "充值 pay_amountsettle_type=5充值收入",
"electricity": "electricity_money电费当前未启用全为 0",
},
```
`_build_period_data()` 中的完整数据结构16 个字段):
```python
return {
"table_charge_money": data.get("table_charge_money", 0),
"goods_money": data.get("goods_money", 0),
"assistant_pd_money": data.get("assistant_pd_money", 0),
"assistant_cx_money": data.get("assistant_cx_money", 0),
"electricity_money": data.get("electricity_money", 0),
"recharge_income": data.get("recharge_income", 0),
# ... 储值资产、费用汇总、平台结算、汇总
}
```
P5 spec 中 App2 收入结构字段映射(已校准):
> `table_fee` = `table_charge_money`56.6%)、`assistant_pd` = `assistant_pd_money`30.6%)、`assistant_cx` = `assistant_cx_money`0.9%)、`goods` = `goods_money`10.1%)、`recharge` = 充值 pay_amountsettle_type=5

View File

@@ -0,0 +1,80 @@
# P5.1→NS3 缺失项 #3App3 维客线索生成的触发条件和频率
## 简要结论
- 状态:✅ 已解决
- 风险等级:🟡 低
- App3 的触发机制已通过事件驱动架构完整实现:消费结算事件触发 → dispatcher 编排调用链 → App3 执行。触发条件和频率在 P5 spec 和代码中均有明确定义。
## 详细审查
### 审查范围
- `apps/backend/app/ai/dispatcher.py` — AI 事件调度与调用链编排器
- `apps/backend/app/ai/apps/app3_clue.py` — App3 实现
- `apps/backend/app/services/trigger_scheduler.py` — 触发器调度框架
- `apps/backend/app/main.py` — AI 事件处理器注册
- `docs/prd/specs/P5-miniapp-ai-integration.md` — P5 spec 触发条件定义
### 发现
1. **触发条件**P5 spec AC3 明确定义"应用 3 维客线索在客户新增消费时自动更新"。代码实现完全匹配:
- `dispatcher.py``handle_consumption_event()` 方法在消费结算事件发生时触发 App3
- 事件名称:`ai_consumption_settled`
- 触发参数:`member_id`, `site_id`, `settle_id`, `assistant_id`(可选)
2. **触发频率**:事件驱动(非定时),每次客户新增消费(结账单)时触发一次。这是 P5 spec 设计的意图——"客户新增消费时自动更新"。
3. **事件注册链路**
- `main.py` lifespan 中创建 `AIDispatcher` 并调用 `register_ai_handlers()`
- `register_ai_handlers()` 将 3 个事件处理器注册到 `trigger_scheduler`
- `ai_consumption_settled``handle_consumption_settled`
- `ai_note_created``handle_note_created`
- `ai_task_assigned``handle_task_assigned`
- 业务代码通过 `fire_event("ai_consumption_settled", payload)` 触发
4. **调用链编排**:消费事件触发后的完整链路:
```
消费事件 → App3线索分析→ App8线索整理→ App7客户分析
如有助教参与 → App4关系分析→ App5话术参考
```
5. **容错策略**`dispatcher.py` 文档字符串明确说明:
- "某步失败记录错误日志,后续应用使用已有缓存继续"
- "失败应用写入失败 conversation 记录"
- "整条链后台异步执行,不阻塞业务请求"
### 证据
`dispatcher.py` 中的消费事件处理:
```python
async def handle_consumption_event(
self,
member_id: int,
site_id: int,
settle_id: int,
assistant_id: int | None = None,
) -> None:
"""消费事件链App3 → App8 → App7+ App4 → App5 如有助教)。"""
# 步骤 1App3 线索分析
app3_result = await self._run_step("app3_clue", app3_run, context)
# 步骤 2App8 线索整理
# 步骤 3App7 客户分析
# 步骤 4可选App4 → App5
```
`main.py` 中的 AI 事件处理器注册:
```python
_dispatcher = AIDispatcher(_bailian, AICacheService(), ConversationService())
register_ai_handlers(_dispatcher)
```
`_create_ai_event_handlers()` 中的事件映射:
```python
return {
"ai_consumption_settled": handle_consumption_settled,
"ai_note_created": handle_note_created,
"ai_task_assigned": handle_task_assigned,
}
```
P5 spec 调用链定义:
> 消费事件(结账单): └→ 应用 3维客线索分析→ 应用 8线索整理→ 应用 7客户分析

View File

@@ -0,0 +1,106 @@
# P5.1→NS3 缺失项 #4App4-App7 的缓存策略
## 简要结论
- 状态:⚠️ 部分解决
- 风险等级:🟠 中
- cache_type 枚举和保留策略500 条上限 + 清理机制)已实现,但缺少 expires_at 过期策略和主动失效条件的定义与实现。
## 详细审查
### 审查范围
- `apps/backend/app/ai/cache_service.py` — AI 缓存读写服务
- `apps/backend/app/ai/schemas.py` — CacheTypeEnum 枚举定义
- `docs/database/ddl/zqyy_app__biz.sql` — ai_cache 表 DDL
- `docs/prd/specs/P5-miniapp-ai-integration.md` — P5 spec 缓存策略定义
### 发现
#### ✅ 已实现部分
1. **cache_type 枚举**`schemas.py``CacheTypeEnum` 定义了 7 个枚举值:
```python
APP2_FINANCE = "app2_finance"
APP3_CLUE = "app3_clue"
APP4_ANALYSIS = "app4_analysis"
APP5_TACTICS = "app5_tactics"
APP6_NOTE_ANALYSIS = "app6_note_analysis"
APP7_CUSTOMER_ANALYSIS = "app7_customer_analysis"
APP8_CLUE_CONSOLIDATED = "app8_clue_consolidated"
```
与 P5 spec 定义完全一致。
2. **保留策略**`cache_service.py` 的 `_cleanup_excess()` 实现了 P5 spec 定义的"每个 (cache_type, site_id, target_id) 组合保留最近 500 条记录"
```python
def _cleanup_excess(self, ..., max_count: int = 500) -> int:
```
清理时机:每次 `write_cache()` 写入新记录后异步清理。
3. **target_id 约定**:各应用的 target_id 含义在 P5 spec 中有明确定义App2=时间维度编码、App3/6/7/8=member_id、App4/5=assistant_id_member_id
4. **数据库表结构**`ai_cache` 表包含 `expires_at timestamp with time zone` 字段DDL 层面支持过期时间。
#### ❌ 未实现部分
1. **expires_at 过期策略**
- DDL 中 `expires_at` 字段存在但代码中从未设置有效值
- `write_cache()` 接受 `expires_at` 参数但所有调用方App2-App8 的 `run()` 函数)均未传入 `expires_at`
- 没有定时任务或查询逻辑检查 `expires_at` 并清理过期记录
- `get_latest()` 查询时不过滤已过期记录
2. **主动失效条件**
- P5 spec 未定义具体的失效条件(如"数据源更新后旧缓存失效"
- NS3 spec 提到"ai_cache 表但未定义策略"——确实如此
- 当前实现是"只增不删"(除 500 条上限清理外),没有基于业务事件的缓存失效机制
3. **缓存读取时的新鲜度检查**
- `get_latest()` 仅按 `created_at DESC` 取最新一条,不检查缓存是否仍然有效
- 前端读取缓存时无法判断数据是否过时
### 证据
`cache_service.py` 中 `write_cache()` 的 `expires_at` 参数:
```python
def write_cache(
self,
cache_type: str,
site_id: int,
target_id: str,
result_json: dict,
triggered_by: str | None = None,
score: int | None = None,
expires_at: datetime | None = None, # 接受但从未被调用方传入
) -> int:
```
App2 调用 `write_cache` 时未传入 `expires_at`
```python
cache_svc.write_cache(
cache_type=CacheTypeEnum.APP2_FINANCE.value,
site_id=site_id,
target_id=time_dimension,
result_json=result,
triggered_by=f"user:{user_id}",
# 无 expires_at
)
```
DDL 中 `expires_at` 字段定义:
```sql
expires_at timestamp with time zone -- 存在但未被使用
```
### 建议
1. **定义各应用的缓存过期策略**
| 应用 | 建议 expires_at | 理由 |
|------|----------------|------|
| App2 | 当日 08:00营业日切点 | 财务数据每日更新,次日数据变化后旧缓存无意义 |
| App3 | 7 天 | 消费数据线索在下次消费前有效 |
| App4/5 | 3 天 | 关系分析和话术时效性较强 |
| App6 | 无过期 | 备注分析结果不会因时间失效 |
| App7 | 7 天 | 客户分析随新数据更新 |
| App8 | 无过期 | 整合线索由上游触发更新 |
2. **实现过期检查**:在 `get_latest()` 中增加 `WHERE expires_at IS NULL OR expires_at > NOW()` 过滤。
3. **考虑增加缓存失效事件**:当 App3/App6 产出新内容后,可标记旧的 App7 缓存为失效,确保前端读到最新分析。

View File

@@ -0,0 +1,128 @@
# P5.1→NS3 缺失项 #5LLM 调用的错误处理和降级策略
## 简要结论
- 状态:⚠️ 部分解决
- 风险等级:🟠 中
- 重试机制和异常分类已完整实现调用链级容错步骤失败后继续也已到位。但缺少限流429降级回退、模型不可用时的静态兜底内容、以及面向前端的统一错误码体系。
## 详细审查
### 审查范围
- `apps/backend/app/ai/bailian_client.py` — 百炼 API 统一封装层
- `apps/backend/app/ai/dispatcher.py` — AI 事件调度与调用链编排器
- `apps/backend/app/ai/apps/app1_chat.py` — App1 流式对话错误处理
- `apps/backend/app/ai/apps/app3_clue.py` — App3 数据获取降级示例
- `apps/backend/app/main.py` — AI 事件处理器注册的容错
### 发现
#### ✅ 已实现部分
1. **重试机制(指数退避)**`bailian_client.py``_call_with_retry()` 实现了完整的重试策略:
- 最多重试 3 次(`MAX_RETRIES = 3`
- 间隔1s → 2s → 4s指数退避`BASE_INTERVAL = 1`
- 5xx / 超时 / 连接错误:重试
- 4xx400/401/403/404/422/429不重试直接抛出
2. **异常分类体系**:定义了 4 个异常类:
- `BailianApiError`:通用 API 错误(含 status_code
- `BailianJsonParseError`JSON 解析失败(含 raw_content
- `BailianAuthError`API Key 无效401
- 继承关系清晰,调用方可按需捕获
3. **调用链级容错**`dispatcher.py``_run_step()` 实现了步骤级容错:
- 某步失败 → 记录错误日志 + 写入失败 conversation 记录 → 返回 None
- 后续步骤继续执行,使用已有缓存
- 整条链后台异步执行(`asyncio.create_task`),不阻塞业务请求
4. **App1 流式错误处理**`app1_chat.py``chat_stream()` 在异常时 yield `SSEEvent(type="error", message=str(e))`,前端可据此展示错误提示。
5. **数据获取降级**`app3_clue.py``build_prompt()` 在消费数据获取失败时降级为空值:
```python
except Exception:
member_data = _default_member_data()
data_fetch_failed = True
```
6. **启动容错**`main.py` 中 AI 事件处理器注册包裹在 try/except 中API Key 缺失或注册失败不影响后端启动。
#### ❌ 未实现部分
1. **限流429降级回退**
- 当前 429 RateLimitError 直接抛出,不重试
- 缺少限流时的降级策略(如返回缓存中的上一次结果、排队等待、降低调用频率)
- 消费事件链中如果百炼 API 限流,整个 App 步骤直接失败
2. **模型不可用时的静态兜底**
- 当百炼 API 完全不可用(如网络中断、服务宕机)时,重试 3 次后抛出 `BailianApiError`
- 对于 App2财务洞察、App4关系分析等前端直接展示的应用缺少静态兜底内容
- 前端读取 ai_cache 时如果没有缓存记录(首次调用就失败),会得到空结果
3. **统一错误码体系**
- App1 的 SSEEvent error 只传 `message=str(e)`,缺少结构化错误码
- 前端无法区分"网络超时"、"API Key 过期"、"限流"等不同错误类型
- 不同错误类型应有不同的用户提示(如限流→"AI 繁忙请稍后"、认证→"服务配置异常"
4. **熔断机制**
- 缺少熔断器Circuit Breaker连续失败 N 次后暂停调用,避免无效重试
- 高并发场景下(如多个消费事件同时触发),可能产生大量失败请求
### 证据
`bailian_client.py` 中的重试和异常处理:
```python
# 429 限流:直接抛出,不重试
except openai.RateLimitError as e:
logger.error("百炼 API 限流: %s", e)
raise BailianApiError(str(e), status_code=429) from e
# 5xx / 超时 / 连接错误:重试
except (openai.InternalServerError, openai.APIConnectionError,
openai.APITimeoutError) as e:
last_error = e
if attempt < self.MAX_RETRIES - 1:
wait_time = self.BASE_INTERVAL * (2 ** attempt)
await asyncio.sleep(wait_time)
```
`dispatcher.py` 中的步骤级容错:
```python
async def _run_step(self, app_name, run_func, context) -> dict | None:
try:
result = await run_func(context, self.bailian, ...)
return result
except Exception:
logger.exception("调用链步骤失败: %s", app_name)
# 写入失败 conversation 记录
# ...
return None
```
`main.py` 中的启动容错:
```python
try:
if _api_key and _base_url:
# ... 注册 AI 事件处理器
register_ai_handlers(_dispatcher)
except Exception:
_log.getLogger(__name__).warning(
"AI 事件处理器注册失败AI 功能不可用", exc_info=True)
```
### 建议
1. **429 限流降级**:捕获 `RateLimitError` 后,尝试返回 ai_cache 中该 (cache_type, site_id, target_id) 的最新缓存结果,并在结果中标注 `"_from_cache": true`,让前端知道这是缓存数据。
2. **统一错误码枚举**
```python
class AIErrorCode(str, Enum):
RATE_LIMITED = "rate_limited" # AI 繁忙,请稍后再试
AUTH_FAILED = "auth_failed" # 服务配置异常
TIMEOUT = "timeout" # 请求超时
SERVICE_DOWN = "service_down" # AI 服务暂时不可用
PARSE_ERROR = "parse_error" # AI 返回格式异常
```
3. **熔断器**:在 `BailianClient` 中增加简单的熔断逻辑——连续失败 5 次后,后续 60 秒内直接返回缓存或静态兜底,不再调用 API。
4. **App1 SSE 错误结构化**:将 `SSEEvent(type="error")` 扩展为包含 `error_code` 字段,前端据此展示不同的用户提示。

View File

@@ -0,0 +1,70 @@
# P5.1→NS3 缺失项 #6Token 用量监控和成本控制
## 简要结论
- 状态:⚠️ 部分解决
- 风险等级:🟠 中
- 已实现消息级 token 记录和 Prompt 长度截断,但缺少日/月预算控制、单次调用上限校验、超限熔断机制
## 详细审查
### 审查范围
- `apps/backend/app/ai/bailian_client.py` — 百炼 API 封装层
- `apps/backend/app/ai/conversation_service.py` — 对话持久化服务
- `apps/backend/app/ai/dispatcher.py` — AI 调用链调度器
- `apps/backend/app/ai/apps/app5_tactics.py` — 典型 App 调用流程
- `apps/backend/app/ai/cache_service.py` — 缓存服务
- `docs/database/ddl/zqyy_app__biz.sql` — biz schema DDL 基线
- `db/zqyy_app/migrations/` — 迁移脚本
### 发现
#### ✅ 已实现部分
1. **消息级 token 记录**`bailian_client.chat_json()` 返回 `(parsed_json, tokens_used)` 元组,从 `response.usage.total_tokens` 提取。所有 App3-8在调用后将 `tokens_used` 写入 `biz.ai_messages` 表。
2. **Prompt 长度截断**:各 App 的 `build_prompt()` 均实现了 system message 内容长度控制(如 App5 的 `_MAX_SYSTEM_CONTENT_LEN = 8000`),超长时截断服务记录、消费记录、备注等。
3. **API 层 max_tokens 参数**`chat_json()` 默认 `max_tokens=4000``chat_stream()` 默认 `max_tokens=2000`,限制了单次调用的输出 token 上限。
4. **数据库持久化**`biz.ai_messages` 表有 `tokens_used integer` 字段,每条 assistant 消息都记录了 token 消耗量。
#### ❌ 未实现部分
1. **无日/月预算控制**:代码中无任何按时间窗口(日/月)汇总 token 用量并与预算阈值比较的逻辑。
2. **无单次调用上限校验**`max_tokens` 是硬编码默认值,无基于配置表或环境变量的动态上限。
3. **无超限熔断机制**:当 token 消耗达到阈值时,无拒绝服务或降级处理逻辑。
4. **无 token 用量汇总表**:数据库中无 `ai_token_usage``ai_budget` 等汇总/预算表。
5. **无成本告警**:无日志告警或通知机制在 token 消耗异常时触发。
### 证据
**token 记录bailian_client.py L138**
```python
tokens_used = response.usage.total_tokens if response.usage else 0
return parsed, tokens_used
```
**消息写入conversation_service.py L77**
```python
INSERT INTO biz.ai_messages
(conversation_id, role, content, tokens_used)
VALUES (%s, %s, %s, %s)
```
**DDL 基线zqyy_app__biz.sql**
```sql
CREATE TABLE biz.ai_messages (
...
tokens_used integer,
...
);
```
**搜索 `token.*budget|cost.*control|usage.*limit|日预算|月预算` 无匹配结果**(仅匹配到缓存清理的"超限"字样,与 token 成本无关)。
### 建议
1. **新增 token 用量汇总视图或定时任务**:按 `site_id + app_id + 日期` 汇总 `biz.ai_messages.tokens_used`,写入 `biz.ai_token_daily_usage`
2. **新增预算配置**:在环境变量或配置表中定义 `AI_DAILY_TOKEN_LIMIT``AI_MONTHLY_TOKEN_LIMIT`
3. **在 dispatcher._run_step 前增加预算检查**:调用 AI 前查询当日/当月累计用量,超限则拒绝并记录日志
4. **告警机制**:当日用量达到预算 80% 时记录 WARNING 日志,达到 100% 时记录 ERROR 并熔断

View File

@@ -0,0 +1,79 @@
# P5.1→NS3 缺失项 #7App5 话术模板分类和质量评估标准
## 简要结论
- 状态:⚠️ 部分解决
- 风险等级:🟠 中
- App5 已实现完整的话术生成流程数据获取→Prompt 构建→AI 调用→持久化),但话术输出仅按 scenario/script 结构化,缺少 P5.1 定义的话术分类体系(召回/维护/推荐)和质量评估维度
## 详细审查
### 审查范围
- `apps/backend/app/ai/apps/app5_tactics.py` — App5 话术参考实现
- `apps/backend/app/ai/schemas.py` — Pydantic 模型定义
- `apps/backend/app/ai/apps/app4_analysis.py` — App4 关系分析App5 上游)
- `tests/test_ai_apps/test_build_prompt_props.py` — 属性测试
- `tests/test_ai_apps/test_ai_apps_unit.py` — 单元测试
- `tests/test_p5_ai_integration_properties.py` — P5 集成属性测试
### 发现
#### ✅ 已实现部分
1. **完整调用链**App5 由 App4 联动触发,接收 `context["app4_result"]` 作为 `task_suggestion`,包含 `task_description``action_suggestions`
2. **数据驱动 Prompt**:并发获取助教信息、服务历史、消费数据、备注 4 类数据,构建丰富的上下文。
3. **Reference 机制**:引用最近 2 套 App8 历史结果作为 Prompt reference提供线索整合上下文。
4. **结构化输出**Pydantic 模型 `App5Result` 定义了 `tactics: list[App5TacticsItem]`,每条包含 `scenario`(场景)和 `script`(话术内容)。
5. **Token 预算控制**`_MAX_SYSTEM_CONTENT_LEN = 8000`,超长时分级截断服务记录→消费记录→备注。
6. **降级处理**4 类数据获取均有异常捕获和降级逻辑,部分失败不阻断。
#### ❌ 未实现部分
1. **无话术分类体系**Prompt 中 `output_format` 仅要求 `scenario + script`,未定义话术类型分类(如 P5.1 中的召回话术/维护话术/推荐话术)。当前 scenario 是自由文本,由 AI 自行决定场景描述。
2. **无质量评估标准**:无类似 App6 的 `score` 评分机制。App5 输出无评估维度(如话术的针对性、可执行性、情感适配度等)。
3. **无话术模板库**:无预定义的话术模板或参考范例供 AI 参考,完全依赖 AI 自由生成。
4. **Pydantic 模型无分类枚举**`App5TacticsItem` 仅有 `scenario: str``script: str`,无 `tactic_type``category` 枚举字段。
### 证据
**App5 输出格式定义app5_tactics.py L131-134**
```python
"output_format": {
"tactics": [
{"scenario": "场景描述", "script": "话术内容"}
]
},
```
**Pydantic 模型schemas.py**
```python
class App5TacticsItem(BaseModel):
scenario: str
script: str
class App5Result(BaseModel):
tactics: list[App5TacticsItem]
```
**对比 App6 有评分机制**
```python
class App6Result(BaseModel):
score: int = Field(ge=1, le=10)
clues: list[ClueItem]
```
App5 无类似评分或分类枚举。
### 建议
1. **新增话术类型枚举**:在 `schemas.py` 中定义 `App5TacticTypeEnum`(如 `recall`/`maintain`/`recommend`/`upsell`),在 `App5TacticsItem` 中增加 `tactic_type` 字段
2. **Prompt 中明确分类要求**:在 `output_format` 中增加 `tactic_type` 字段说明,引导 AI 按分类生成
3. **可选:增加质量评估**:参考 App6 的 score 机制,为每条话术增加 `relevance_score`(针对性评分)
4. **可选:话术模板库**:在 Prompt reference 中注入预定义的优秀话术范例,提升生成质量

View File

@@ -0,0 +1,78 @@
# P5.1→NS3 缺失项 #8各 App 的单元测试用例设计
## 简要结论
- 状态:✅ 已解决
- 风险等级:🟡 低
- 已建立完整的测试体系覆盖属性测试Hypothesis+ 单元测试 + 集成属性测试,涵盖所有 8 个 AI 应用和 3 个 data_fetcher
## 详细审查
### 审查范围
- `tests/test_ai_apps/` — AI 应用测试目录3 个文件)
- `tests/test_data_fetchers/` — 数据获取器测试目录4 个文件)
- `tests/test_p5_ai_integration_properties.py` — P5 AI 集成属性测试
- `tests/test_rns1_*.py` — RNS1 Chat 模块属性测试8 个文件)
### 发现
#### ✅ 测试覆盖情况
**1. AI 应用属性测试(`tests/test_ai_apps/`**
| 文件 | 覆盖内容 | 测试数量 |
|------|----------|----------|
| `test_app1_props.py` | App1 biz_params 注入不变量、System Prompt Token 预算 | 2 个属性 |
| `test_build_prompt_props.py` | App3/4/5/6/7 的 Prompt 结构验证、错误降级、Token 预算 | 13 个属性 |
| `test_ai_apps_unit.py` | App1 页面上下文集成、App3/5/7 完整流程 | 6 个单元测试 |
**2. 数据获取器测试(`tests/test_data_fetchers/`**
| 文件 | 覆盖内容 | 测试数量 |
|------|----------|----------|
| `test_member_data_props.py` | 消费数据必填字段、正交易过滤、items_sum 口径、排序、备注截断 | 7 个属性 |
| `test_assistant_data_props.py` | 废单排除、排序保持 | 2 个属性 |
| `test_page_context_props.py` | 输出长度约束、全页面类型覆盖、敏感字段检测 | 3 个属性 |
| `test_data_fetchers_unit.py` | 空记录、FDW 超时、会员不存在、助教不存在等边界 | 10 个单元测试 |
**3. P5 集成属性测试(`test_p5_ai_integration_properties.py`**
覆盖 App2-App8 全部 7 个应用的 JSON 输出结构验证Pydantic 模型解析),包括:
- 枚举值合法性App3 的 3 分类、App6/8 的 6 分类)
- 字段非空约束
- App6 score 范围 [1,10] 及越界拒绝
- 每个应用 100 个随机用例
**4. Chat 模块属性测试(`tests/test_rns1_*.py`**
8 个文件覆盖 Chat 模块的排序、持久化、引用卡片、对话复用、SSE、标题生成、性能等属性。
#### 测试方法论
- **属性测试Hypothesis**:使用 `@given` + `@settings(max_examples=100)` 生成随机输入,验证不变量
- **单元测试Mock**Mock 数据获取函数(`AsyncMock`),不连真实数据库
- **边界条件**空记录、FDW 超时、数据获取失败降级、超长文本截断等
### 证据
**测试文件统计**
```
tests/test_ai_apps/ → 3 文件21+ 测试用例
tests/test_data_fetchers/ → 4 文件22+ 测试用例
tests/test_p5_ai_integration_properties.py → 9 个属性测试App2-8 + score 越界)
tests/test_rns1_*.py → 8 文件Chat 模块全覆盖
```
**典型属性测试示例test_build_prompt_props.py**
```python
# Property 14: 错误降级 — App5 数据获取失败不阻断
def test_prop14_app5_error_degradation(fail_assistant, fail_service, fail_member, fail_notes):
# Property 15: Token 预算 — App5 system message 长度 ≤ 8000
def test_prop15_app5_token_budget(records, notes):
```
### 建议
测试体系已较完整,以下为可选增强方向(非必须):
1. App2财务洞察和 App4关系分析`build_prompt` 属性测试可补充(当前仅有 App4 的结构测试,无 App2 的 Prompt 构建测试)
2. 端到端集成测试(`scripts/ops/test_chat_e2e.py` 已存在但为运维脚本,非 pytest 用例)

View File

@@ -0,0 +1,76 @@
# P5.1→NS3 缺失项 #9MCP Server 的健康检查端点和监控指标
## 简要结论
- 状态:⚠️ 部分解决
- 风险等级:🟡 低
- 后端 FastAPI 已有 `/health` 端点,但 MCP Server`apps/mcp-server/server.py`)无健康检查端点和监控指标
## 详细审查
### 审查范围
- `apps/backend/app/main.py` — 后端主入口
- `apps/mcp-server/server.py` — MCP Server 实现
- `apps/mcp-server/pyproject.toml` — MCP Server 依赖配置
### 发现
#### ✅ 后端 FastAPI 健康检查(已实现)
`apps/backend/app/main.py` 中定义了健康检查端点:
```python
@app.get("/health", tags=["系统"])
async def health_check():
"""健康检查端点,用于探活和监控。"""
return {"status": "ok"}
```
此外还有诊断端点 `/debug/config-paths`,返回关键路径配置信息。
#### ❌ MCP Server 健康检查(未实现)
`apps/mcp-server/server.py` 分析:
1. **无 `/health` 端点**MCP Server 基于 Starlette + MCP SDK 构建,`lifespan` 函数仅管理数据库连接池的打开/关闭,无健康检查路由。
2. **无监控指标**:无请求计数、延迟统计、错误率等监控指标暴露。
3. **有认证中间件**`AuthMiddleware` 验证 Bearer Token但无健康检查的豁免路径。
4. **MCP Server 架构**:提供 `list_tables``describe_table``query_sql` 等数据库查询工具,通过 SSE 协议与 AI 客户端通信。当前无法从外部探测其存活状态。
5. **数据库连接池**`lifespan``pool.open(wait=True, timeout=30)` 管理连接池,但连接池健康状态未暴露。
### 证据
**后端健康检查main.py**
```python
@app.get("/health", tags=["系统"])
async def health_check():
return {"status": "ok"}
```
**MCP Server lifespanserver.py L385-391**
```python
async def lifespan(app: Starlette):
pool.open(wait=True, timeout=30)
try:
async with mcp.session_manager.run():
yield
finally:
pool.close(timeout=5)
```
**MCP Server 无 health 路由**:搜索 `health|ping|status|monitor``apps/mcp-server/` 中无匹配结果。
### 建议
1. **为 MCP Server 添加 `/health` 端点**:在 Starlette 应用中注册健康检查路由,返回连接池状态和服务版本
```python
@app.route("/health")
async def health(request):
pool_status = "ok" if pool._pool and not pool._pool.closed else "degraded"
return JSONResponse({"status": pool_status, "service": "mcp-server"})
```
2. **健康检查豁免认证**:在 `AuthMiddleware.dispatch` 中对 `/health` 路径跳过 Token 验证
3. **可选:暴露基础监控指标**:请求计数、平均延迟、连接池使用率等(可通过 Prometheus 格式暴露)

View File

@@ -0,0 +1,127 @@
# P5.1→NS3 缺失项 #10AI 生成内容的审计日志
## 简要结论
- 状态:✅ 已解决
- 风险等级:🟡 低
- `biz.ai_conversations` + `biz.ai_messages` 两张表已完整记录"谁在什么时候对哪个客户生成了什么",满足审计日志需求
## 详细审查
### 审查范围
- `apps/backend/app/ai/conversation_service.py` — 对话持久化服务
- `apps/backend/app/services/chat_service.py` — Chat 模块服务
- `docs/database/ddl/zqyy_app__biz.sql` — biz schema DDL 基线
- `db/zqyy_app/migrations/2026-03-20__rns14_chat_module_extend.sql` — Chat 模块扩展迁移
- `apps/backend/app/ai/apps/app5_tactics.py` — 典型 App 调用流程(审计写入示例)
### 发现
#### ✅ 审计信息完整记录
**1. `biz.ai_conversations` 表 — 对话级审计**
| 字段 | 类型 | 审计用途 |
|------|------|----------|
| `id` | bigint PK | 对话唯一标识 |
| `user_id` | varchar(50) NOT NULL | **谁**:操作用户 ID系统自动调用时为 `'system'` |
| `nickname` | varchar(100) | 用户昵称 |
| `app_id` | varchar(30) NOT NULL | **什么应用**app1_chat / app2_finance / ... / app8_consolidation |
| `site_id` | bigint NOT NULL | **哪个门店** |
| `source_page` | varchar(100) | 触发来源页面 |
| `source_context` | jsonb | 上下文信息(含 `assistant_id``member_id` 等 → **对哪个客户** |
| `context_type` | varchar(20) | 对话关联类型task/customer/coach/general |
| `context_id` | varchar(50) | 关联 IDtaskId/customerId/coachId |
| `title` | varchar(200) | 对话标题 |
| `created_at` | timestamptz | **什么时候** |
**2. `biz.ai_messages` 表 — 消息级审计**
| 字段 | 类型 | 审计用途 |
|------|------|----------|
| `id` | bigint PK | 消息唯一标识 |
| `conversation_id` | bigint FK | 关联对话 |
| `role` | varchar(10) | system / user / assistant |
| `content` | text NOT NULL | **生成了什么**:完整的 AI 输入和输出内容 |
| `tokens_used` | integer | Token 消耗量 |
| `reference_card` | jsonb | 引用卡片数据 |
| `created_at` | timestamptz | 消息时间戳 |
**3. 写入时机**
所有 8 个 AI 应用在每次调用时都通过 `ConversationService` 写入完整审计链:
```
create_conversation(user_id, nickname, app_id, site_id, source_context)
→ add_message(role="system", content=prompt)
→ add_message(role="user", content=user_input)
→ 调用百炼 API
→ add_message(role="assistant", content=ai_output, tokens_used=N)
```
**4. 索引支持审计查询**
```sql
-- 按用户+门店查询历史对话
CREATE INDEX idx_ai_conv_user_site ON biz.ai_conversations (user_id, site_id, created_at DESC);
-- 按应用+门店查询
CREATE INDEX idx_ai_conv_app_site ON biz.ai_conversations (app_id, site_id, created_at DESC);
-- 按上下文查询(客户/任务/助教维度)
CREATE INDEX idx_ai_conv_context ON biz.ai_conversations (user_id, site_id, context_type, context_id, ...);
-- 按对话查询消息
CREATE INDEX idx_ai_msg_conv ON biz.ai_messages (conversation_id, created_at);
```
### 证据
**App5 审计写入示例app5_tactics.py L240-268**
```python
# 创建对话记录
conversation_id = conv_svc.create_conversation(
user_id=user_id, nickname=nickname,
app_id=APP_ID, site_id=site_id,
source_context={"assistant_id": assistant_id, "member_id": member_id},
)
# 写入 system + user 消息
conv_svc.add_message(conversation_id=conversation_id, role="system", content=...)
conv_svc.add_message(conversation_id=conversation_id, role="user", content=...)
# 调用 AI 后写入 assistant 消息
result, tokens_used = await bailian.chat_json(messages)
conv_svc.add_message(
conversation_id=conversation_id, role="assistant",
content=json.dumps(result, ensure_ascii=False),
tokens_used=tokens_used,
)
```
**DDL 基线确认zqyy_app__biz.sql**
```sql
CREATE TABLE biz.ai_conversations (
id bigint ... NOT NULL,
user_id character varying(50) NOT NULL,
nickname character varying(100) ...,
app_id character varying(30) NOT NULL,
site_id bigint NOT NULL,
source_page character varying(100),
source_context jsonb,
context_type character varying(20),
context_id character varying(50),
...
created_at timestamp with time zone DEFAULT now() NOT NULL
);
CREATE TABLE biz.ai_messages (
id bigint ... NOT NULL,
conversation_id bigint NOT NULL,
role character varying(10) NOT NULL,
content text NOT NULL,
tokens_used integer,
reference_card jsonb,
created_at timestamp with time zone DEFAULT now() NOT NULL
);
```
### 建议
审计日志功能已完整实现,以下为可选增强方向(非必须):
1. **审计查询 API**:当前仅有面向用户的历史对话查询(`get_conversations`可增加面向管理员的审计查询接口按时间范围、app_id、site_id 筛选)
2. **数据保留策略**:当前无自动清理机制,长期运行后 `ai_messages.content` 可能占用大量存储,建议制定保留策略

View File

@@ -0,0 +1,53 @@
# P6→NS1/RNS1 缺失项 #1任务卡片 5 种状态的视觉规范
## 简要结论
- 状态:⚠️ 部分解决
- 风险等级:🟠 中
- 前端已实现 3 种状态(待处理/已置顶/已放弃)的视觉差异,但缺少「已完成」和「过期」两种独立状态的视觉规范。
## 详细审查
### 审查范围
- `apps/miniprogram/miniprogram/utils/vi-colors.ts` — TASK_STATUS_COLORS
- `apps/miniprogram/miniprogram/utils/task-config.ts` — TASK_STATUS_CONFIG
- `apps/miniprogram/miniprogram/pages/task-list/task-list.wxml` — 卡片模板
- `apps/miniprogram/miniprogram/pages/task-list/task-list.wxss` — 卡片样式
- `apps/miniprogram/miniprogram/pages/task-list/task-list.ts` — 分组逻辑
### 发现
**已实现的 3 种状态:**
1. **待处理normal/pending**:白色卡片,左侧彩条按任务类型着色(红/橙/粉/青),正常文字颜色
2. **已置顶pinned**:在待处理基础上增加金色光晕阴影(`box-shadow: 0 5rpx 7rpx rgba(245,158,11,0.12), 0 0 0 8rpx rgba(245,158,11,0.08)`
3. **已放弃abandoned**:左侧彩条变灰(`--status-abandoned-border`),整卡透明度降低(`opacity: 0.55`),标签灰化,文字变灰
**缺失的 2 种状态:**
4. **已完成completed**`task-config.ts``TASK_STATUS_CONFIG` 中无 `completed` 键;`vi-colors.ts``TASK_STATUS_COLORS` 中无 `completed` 定义WXML 中无 `task-card--completed`
5. **过期expired**:无独立的过期卡片状态样式。过期目前仅通过 `deadline` 字段在卡片内显示红色逾期徽章(`overdue-badge`),但卡片整体样式不变
### 证据
`vi-colors.ts` 第 4 节仅定义了两种状态:
```typescript
export const TASK_STATUS_COLORS = {
pinned: { name: '置顶', glowColor: '#f59e0b', ... },
abandoned: { name: '放弃', borderColor: '#d1d5db', textColor: '#9ca3af', opacity: 0.55 },
}
```
`task-config.ts` 仅定义了三种状态:
```typescript
export const TASK_STATUS_CONFIG = {
normal: { label: '进行中', icon: '📋' },
pinned: { label: '已置顶', icon: '📌' },
abandoned: { label: '已放弃', icon: '❌' },
}
```
### 建议(如未完全解决)
1.`TASK_STATUS_COLORS``TASK_STATUS_CONFIG` 中补充 `completed``expired` 状态定义
2. 已完成状态建议:绿色勾选图标 + 轻微灰化opacity 0.7-0.8),左侧彩条保留但降低饱和度
3. 过期状态建议:红色边框或红色背景提示,与逾期徽章配合使用
4. 注意:后端 `get_task_list_v2` 按 status 筛选pending/completed/abandoned前端目前仅请求 pending 状态completed 列表页尚未实现

View File

@@ -0,0 +1,52 @@
# P6→NS1/RNS1 缺失项 #23 种空状态设计
## 简要结论
- 状态:⚠️ 部分解决
- 风险等级:🟠 中
- 已实现 2 种空状态(无任务、网络错误),但缺少「筛选无结果」空状态,且现有空状态缺少插图。
## 详细审查
### 审查范围
- `apps/miniprogram/miniprogram/pages/task-list/task-list.wxml` — 空状态模板
- `apps/miniprogram/miniprogram/pages/task-list/task-list.ts` — pageState 逻辑
- `apps/miniprogram/miniprogram/pages/task-list/task-list.wxss` — 空状态样式
- `apps/miniprogram/miniprogram/pages/task-detail/task-detail.wxml` — 详情页空状态
### 发现
**已实现的 2 种空状态:**
1. **无任务empty**`pageState === 'empty'` 时显示纯文字「暂无待办任务」
2. **网络错误error**`pageState === 'error'` 时显示「加载失败,请重试」+ 重试按钮
**缺失项:**
3. **筛选无结果**:当前 task-list 页面无筛选功能(无 filter-dropdown 组件引用),因此无筛选无结果状态。但 P6 定义了此场景,未来添加筛选时需要补充
4. **插图缺失**:两种空状态均为纯文字,无 SVG/PNG 插图。P6 定义了每种空状态应有对应插图
5. task-detail 页面有更完善的空状态:使用了 `t-icon` 图标info-circle / close-circle但仍非 P6 定义的专属插图
### 证据
task-list.wxml 空状态实现:
```html
<!-- 空状态 -->
<view class="state-empty" wx:if="{{pageState === 'empty'}}">
<text class="empty-text">暂无待办任务</text>
</view>
<!-- Error 状态 -->
<view class="state-error" wx:if="{{pageState === 'error'}}">
<text class="error-text">加载失败,请重试</text>
<view class="retry-btn" bindtap="onRetry">
<text class="retry-btn-text">重试</text>
</view>
</view>
```
pageState 类型定义仅 4 种:`'loading' | 'empty' | 'error' | 'normal'`,无 `'filter-empty'` 状态。
### 建议(如未完全解决)
1. 为 empty 和 error 状态添加 SVG 插图(建议放在 `/assets/images/empty-*.svg`
2. 预留 `filter-empty` 状态,文案如「没有找到匹配的任务,试试调整筛选条件」
3. 可使用 TDesign 的 `t-empty` 组件替代自定义实现,自带图标和文案插槽

View File

@@ -0,0 +1,52 @@
# P6→NS1/RNS1 缺失项 #3置顶任务排序规则
## 简要结论
- 状态:✅ 已解决
- 风险等级:🟡 低
- 后端已实现「置顶优先 → 优先级分数降序 → 创建时间升序」的排序规则,与 P6 定义的排序意图一致。
## 详细审查
### 审查范围
- `apps/backend/app/services/task_manager.py``get_task_list_v2()` 函数中的 SQL ORDER BY
- `apps/miniprogram/miniprogram/pages/task-list/task-list.ts` — 前端分组逻辑
### 发现
**后端排序(已实现):**
`task_manager.py``get_task_list_v2()` 中 SQL 查询明确定义了排序规则:
```sql
ORDER BY is_pinned DESC,
priority_score DESC NULLS LAST,
created_at ASC
```
- `is_pinned DESC`:置顶任务排在最前
- `priority_score DESC NULLS LAST`:非置顶任务按优先级分数降序(高优先级在前)
- `created_at ASC`:同优先级按创建时间升序(先创建的在前)
**前端分组(已实现):**
`task-list.ts``loadData()` 中将后端返回的列表按状态分组:
```typescript
const pinnedTasks = enriched.filter((t) => t.isPinned && !t.isAbandoned)
const normalTasks = enriched.filter((t) => !t.isPinned && !t.isAbandoned && t.status === 'pending')
const abandonedTasks = enriched.filter((t) => t.isAbandoned)
```
WXML 中按「📌 置顶 → 正常任务 → 已放弃」三组依次渲染,组内保持后端返回顺序。
### 证据
后端 SQLtask_manager.py 第 560-564 行):
```sql
ORDER BY is_pinned DESC,
priority_score DESC NULLS LAST,
created_at ASC
LIMIT %s OFFSET %s
```
### 建议(如未完全解决)
- P6 提到「置顶任务按置顶时间倒序」,当前实现是 `is_pinned DESC`(布尔值),多个置顶任务之间的排序依赖 `priority_score`。如需严格按置顶时间排序,需在 `coach_tasks` 表中添加 `pinned_at` 时间戳字段并在 ORDER BY 中使用。当前实现在功能上可接受,但与 P6 的精确定义有微小差异。

View File

@@ -0,0 +1,54 @@
# P6→NS1/RNS1 缺失项 #4放弃/取消放弃的二次确认弹窗
## 简要结论
- 状态:✅ 已解决
- 风险等级:🟡 低
- 放弃操作已实现完整的二次确认弹窗abandon-modal 组件),含原因输入、确认/取消按钮。取消放弃在 task-detail 页面无二次确认(直接执行),在 task-list 页面通过长按菜单触发也无二次确认。
## 详细审查
### 审查范围
- `apps/miniprogram/miniprogram/components/abandon-modal/abandon-modal.wxml` — 弹窗模板
- `apps/miniprogram/miniprogram/components/abandon-modal/abandon-modal.ts` — 弹窗逻辑
- `apps/miniprogram/miniprogram/pages/task-list/task-list.ts` — 列表页放弃/取消放弃流程
- `apps/miniprogram/miniprogram/pages/task-detail/task-detail.ts` — 详情页放弃/取消放弃流程
### 发现
**放弃操作(完整实现):**
`abandon-modal` 组件实现了完整的二次确认弹窗:
- ⚠️ 警告图标 + 标题「放弃 {客户名}」
- 描述文案「确定放弃该客户的维护任务?请填写原因:」
- 必填原因输入框maxlength=200
- 「确认放弃」按钮(原因为空时禁用)+ 「取消」按钮
- 键盘弹出时自适应布局
task-list 和 task-detail 页面均引用了此组件:
- task-list长按菜单 → 点击「放弃任务」→ 打开 abandon-modal
- task-detail点击右上角「放弃」按钮 → 打开 abandon-modal
**取消放弃操作(简化实现):**
- task-list长按已放弃任务 → 菜单显示「↩️ 取消放弃」→ 直接执行showLoading → showToast无二次确认弹窗
- task-detail点击右上角「取消放弃」→ 直接执行 `cancelAbandon()`,无二次确认
### 证据
abandon-modal.wxml 核心结构:
```html
<view class="modal-header">
<text class="modal-emoji">⚠️</text>
<text class="modal-title">放弃 <text class="modal-name">{{customerName}}</text></text>
</view>
<view class="modal-desc-wrap">
<text class="modal-desc">确定放弃该客户的维护任务?请填写原因:</text>
</view>
<textarea maxlength="200" ... />
<view class="confirm-btn" bindtap="onConfirm">确认放弃</view>
<view class="cancel-btn" bindtap="onCancel">取消</view>
```
### 建议(如未完全解决)
- 取消放弃操作风险较低(恢复任务),不设二次确认是合理的 UX 决策
- 如 P6 严格要求取消放弃也需二次确认,可复用 `wx.showModal` 实现简单确认弹窗

View File

@@ -0,0 +1,81 @@
# P6→NS1/RNS1 缺失项 #5下拉刷新/触底加载的动画规范
## 简要结论
- 状态:✅ 已解决
- 风险等级:🟡 低
- 已实现下拉刷新、触底加载、骨架屏 loading、错误重试的完整交互链路。
## 详细审查
### 审查范围
- `apps/miniprogram/miniprogram/pages/task-list/task-list.ts` — onPullDownRefresh / onReachBottom / loadData
- `apps/miniprogram/miniprogram/pages/task-list/task-list.wxml` — skeleton 骨架屏
- `apps/miniprogram/miniprogram/pages/task-list/task-list.wxss` — loading 样式
### 发现
**下拉刷新(已实现):**
```typescript
onPullDownRefresh() {
this.loadData(() => {
wx.stopPullDownRefresh()
})
}
```
使用微信原生下拉刷新机制,刷新完成后调用 `wx.stopPullDownRefresh()` 停止动画。
**触底加载(已实现):**
```typescript
onReachBottom() {
if (!this.data.hasMore) return
this.setData({ hasMore: false })
wx.showToast({ title: '没有更多了', icon: 'none' })
}
```
当前为简化实现一次性加载触底时显示「没有更多了」提示。后端已支持分页参数page/page_size
**骨架屏 Loading已实现**
```html
<view class="state-loading" wx:if="{{pageState === 'loading'}}">
<view class="loading-placeholder" wx:for="{{[1,2,3]}}" wx:key="*this">
<view class="ph-line ph-line--title"></view>
<view class="ph-line ph-line--body"></view>
<view class="ph-line ph-line--short"></view>
</view>
</view>
```
3 个占位卡片,每个含标题行、内容行、短行,模拟真实卡片布局。
**错误重试(已实现):**
```html
<view class="state-error" wx:if="{{pageState === 'error'}}">
<text class="error-text">加载失败,请重试</text>
<view class="retry-btn" bindtap="onRetry">重试</view>
</view>
```
**task-detail 页面也有完整状态管理:**
- loading使用 `t-loading` 组件的 circular 主题浮层
- empty`t-icon` info-circle + 文案
- error`t-icon` close-circle + 重试按钮
### 证据
骨架屏样式task-list.wxss
```css
.loading-placeholder {
background: #ffffff;
border-radius: 22rpx;
padding: 29rpx;
margin-bottom: 22rpx;
}
.ph-line { height: 22rpx; background: #f3f3f3; border-radius: 7rpx; margin-bottom: 15rpx; }
.ph-line--title { width: 40%; height: 29rpx; }
.ph-line--body { width: 80%; }
.ph-line--short { width: 55%; }
```
### 建议(如未完全解决)
- 触底加载目前是简化实现,未真正调用分页接口加载下一页。后端已支持 `page`/`page_size` 参数,前端需补充增量加载逻辑
- 骨架屏可考虑使用 TDesign 的 `t-skeleton` 组件替代自定义实现,获得更丰富的动画效果
- 可添加下拉刷新时的自定义 loading 动画(当前使用微信原生样式)

View File

@@ -0,0 +1,64 @@
# P6→NS1/RNS1 缺失项 #6AI 分析卡片的折叠/展开交互
## 简要结论
- 状态:❌ 未解决
- 风险等级:🔴 高
- task-detail 页面中 AI 分析内容以固定展开方式呈现,无折叠/展开交互,无「重新生成」按钮,无 AI 加载状态。
## 详细审查
### 审查范围
- `apps/miniprogram/miniprogram/pages/task-detail/task-detail.wxml` — AI 分析卡片模板
- `apps/miniprogram/miniprogram/pages/task-detail/task-detail.ts` — AI 分析逻辑
- `apps/miniprogram/miniprogram/pages/task-detail/task-detail.wxss` — AI 分析样式
### 发现
**当前实现:**
task-detail 页面中 AI 相关内容分布在多个卡片中,均为固定展开状态:
1. **「与我的关系」卡片**:显示 `detail.aiAnalysis.summary`,无折叠控制
2. **「任务建议」卡片**:显示 `detail.aiAnalysis.suggestions` 列表 + 话术参考,无折叠控制
3. **「行动建议」卡片**:显示 `detail.actionSuggestions`,条件渲染(有数据才显示),无折叠控制
**P6 定义但未实现的交互:**
1. **折叠/展开**P6 定义 AI 分析卡片应支持折叠/展开切换,默认展开,用户可收起以减少页面长度。当前无任何折叠/展开按钮或逻辑
2. **重新生成按钮**P6 定义 AI 分析卡片应有「重新生成」按钮,允许用户触发 AI 重新分析。当前无此按钮
3. **AI 加载状态**P6 定义 AI 分析生成中应显示 loading 动画(如骨架屏或 spinner。当前 AI 数据随任务详情一起返回,无独立加载状态
### 证据
task-detail.wxml 中 AI 相关卡片(无折叠/展开控制):
```html
<!-- 与我的关系 -->
<view class="card">
<view class="card-header">
<text class="section-title title-pink">与我的关系</text>
<ai-title-badge color="{{aiColor}}" />
</view>
<!-- 直接展示,无折叠控制 -->
<view class="card-desc-wrap">
<text class="card-desc">{{detail.aiAnalysis.summary}}</text>
</view>
</view>
<!-- 任务建议 -->
<view class="card">
<view class="card-header">
<text class="section-title title-orange">任务建议</text>
<ai-title-badge color="{{aiColor}}" />
</view>
<!-- 直接展示所有建议,无折叠控制,无重新生成按钮 -->
...
</view>
```
task-detail.ts 中无任何 AI 折叠/展开相关的 data 字段或方法。
### 建议(如未完全解决)
1. 为每个 AI 卡片添加 `expanded` 状态和切换按钮(如 `▴ 收起` / `▾ 展开`),参考 note-modal 中 `ratingExpanded` 的实现模式
2. 在卡片 header 右侧添加「🔄 重新生成」按钮,点击后调用 AI 分析接口
3. 添加 AI 加载状态:可使用 `t-loading` 或自定义骨架屏,在 AI 数据未返回时显示
4. 后端需提供独立的 AI 重新生成接口(当前 `ai_cache` 仅支持读取缓存)

View File

@@ -0,0 +1,65 @@
# P6→NS1/RNS1 缺失项 #7任务优先级的视觉标识
## 简要结论
- 状态:⚠️ 部分解决
- 风险等级:🟠 中
- 任务类型(高优先召回/优先召回/关系构建/客户回访)有完整的颜色和标签视觉体系,但缺少独立的「高/中/低优先级」视觉标识。
## 详细审查
### 审查范围
- `apps/miniprogram/miniprogram/utils/vi-colors.ts` — TASK_TYPE_COLORS
- `apps/miniprogram/miniprogram/utils/task-config.ts` — TASK_TYPE_CONFIG
- `apps/miniprogram/miniprogram/pages/task-list/task-list.wxml` — 卡片标签
- `apps/miniprogram/miniprogram/pages/task-list/task-list.wxss` — 标签样式
- `apps/backend/app/services/task_manager.py` — priority_score 字段
### 发现
**已实现的任务类型视觉体系:**
4 种任务类型各有独立的颜色方案:
| 类型 | 标签渐变 | 左侧彩条 | 标签文字 |
|------|----------|----------|----------|
| high_priority高优先召回 | #b91c1c#dc2626(红) | #dc2626 | 白色 |
| priority_recall优先召回 | #ea580c#f97316(橙) | #f97316 | 白色 |
| relationship关系构建 | #ec4899#f472b6(粉) | #f472b6 | 白色 |
| callback客户回访 | #0d9488#14b8a6(青) | #14b8a6 | 白色 |
**缺失的优先级视觉标识:**
P6 定义了独立于任务类型的「高/中/低优先级」视觉标识(颜色和图标),但当前实现中:
- 后端返回 `priority_score`(数值),但前端未使用此字段进行视觉区分
- 前端仅按 `taskType` 着色,未按 `priority_score` 显示优先级图标或颜色
- `vi-colors.ts``task-config.ts` 中无 `priority` 相关的颜色/图标定义
### 证据
后端返回 priority_score 但前端未消费:
```python
# task_manager.py get_task_list_v2()
items.append({
...
"task_type": task_type,
# priority_score 未包含在返回数据中
})
```
前端 enrichTask() 中无 priority 相关处理:
```typescript
function enrichTask(task: Task): EnrichedTask {
return {
...task,
// 无 priority 相关字段
deadlineLabel: formatDeadline((task as any).deadline).text,
deadlineStyle: formatDeadline((task as any).deadline).style,
}
}
```
### 建议(如未完全解决)
1. 在后端 items 中返回 `priority_score` 或映射为 `priority_level`high/medium/low
2.`vi-colors.ts` 中添加 `PRIORITY_COLORS` 定义(如:高=红色火焰图标、中=橙色、低=灰色)
3. 在卡片中添加优先级小图标或角标,与任务类型标签并列显示
4. 注意:当前任务类型名称已隐含优先级信息(「高优先召回」),是否需要额外的优先级标识需与产品确认

View File

@@ -0,0 +1,68 @@
# P6→NS1/RNS1 缺失项 #8任务到期倒计时的展示规则
## 简要结论
- 状态:✅ 已解决
- 风险等级:🟡 低
- 已实现完整的到期倒计时展示规则,包含 4 级颜色变化(灰/正常/橙色警告/红色逾期),与 DISPLAY-STANDARDS-2.md §7 规范对齐。
## 详细审查
### 审查范围
- `apps/miniprogram/miniprogram/utils/time.ts``formatDeadline()` 函数
- `apps/miniprogram/miniprogram/pages/task-list/task-list.wxml` — deadline 展示
- `apps/miniprogram/miniprogram/pages/task-list/task-list.wxss` — deadline 样式
- `apps/miniprogram/miniprogram/pages/task-list/task-list.ts` — enrichTask 中的 deadline 处理
### 发现
**`formatDeadline()` 函数(完整实现):**
```typescript
export function formatDeadline(deadline: string | null | undefined):
{ text: string; style: 'normal' | 'warning' | 'danger' | 'muted' } {
if (!deadline) return { text: '--', style: 'muted' }
const diff = /* 天数差 */
if (diff < 0) return { text: `逾期 ${Math.abs(diff)}`, style: 'danger' }
if (diff === 0) return { text: '今天到期', style: 'warning' }
if (diff <= 7) return { text: `还剩 ${diff}`, style: 'normal' }
return { text: `${mm}-${dd}`, style: 'muted' }
}
```
**4 级颜色映射WXSS 已实现):**
| 条件 | 文案 | style | 颜色 |
|------|------|-------|------|
| 无截止日期 | `--` | muted | #a6a6a6(灰) |
| > 7 天 | `MM-DD` | muted | #a6a6a6(灰) |
| 1-7 天 | `还剩 N 天` | normal | #5e5e5e(深灰) |
| 今天 | `今天到期` | warning | #ed7b2f(橙) |
| 已逾期 | `逾期 N 天` | danger | #e34d59(红) |
**逾期徽章(额外实现):**
逾期任务在卡片第一行右侧显示红色逾期徽章(`overdue-badge`),与 deadline 行的红色文字形成双重提醒。
### 证据
WXSS 中的 deadline 颜色定义:
```css
.deadline-text--muted { color: #a6a6a6; }
.deadline-text--normal { color: #5e5e5e; }
.deadline-text--warning { color: #ed7b2f; }
.deadline-text--danger { color: #e34d59; font-weight: 600; }
```
WXML 中的 deadline 展示逻辑:
```html
<!-- 逾期徽章danger 级别显示在第一行) -->
<text class="overdue-badge" wx:if="{{item.deadlineStyle === 'danger'}}">{{item.deadlineLabel}}</text>
<!-- 非逾期的 deadline 显示在独立行 -->
<view class="card-row-deadline" wx:if="{{item.deadlineLabel && item.deadlineLabel !== '--' && item.deadlineStyle !== 'danger'}}">
<text class="deadline-text deadline-text--{{item.deadlineStyle}}">{{item.deadlineLabel}}</text>
</view>
```
### 建议(如未完全解决)
- 当前实现已覆盖 P6 定义的核心需求。如需更细粒度的颜色变化(如 3 天内黄色、1 天内橙色),可在 `formatDeadline()` 中增加判断分支

View File

@@ -0,0 +1,81 @@
# P6→NS1/RNS1 缺失项 #9备注输入框的字数限制和实时计数
## 简要结论
- 状态:⚠️ 部分解决
- 风险等级:🟠 中
- 已实现字数限制maxlength=500但缺少实时字数计数展示如「128/500」
## 详细审查
### 审查范围
- `apps/miniprogram/miniprogram/components/note-modal/note-modal.wxml` — 备注弹窗模板
- `apps/miniprogram/miniprogram/components/note-modal/note-modal.ts` — 备注弹窗逻辑
- `apps/miniprogram/miniprogram/components/abandon-modal/abandon-modal.wxml` — 放弃弹窗(对比)
### 发现
**字数限制(已实现):**
note-modal 的 textarea 设置了 `maxlength="500"`
```html
<textarea
class="note-textarea"
placeholder="请输入备注内容..."
maxlength="500"
...
/>
```
abandon-modal 的 textarea 设置了 `maxlength="200"`
```html
<textarea
class="abandon-textarea"
placeholder="请输入放弃原因(必填)"
maxlength="200"
...
/>
```
**实时字数计数(未实现):**
两个弹窗均未显示当前输入字数和剩余字数。P6 定义了实时计数展示如输入框右下角显示「128/500」当前实现中
- `note-modal.ts``onContentInput` 仅更新 `content` 值,未计算或展示字数
- WXML 中无字数计数的 `<text>` 元素
- WXSS 中无字数计数的样式定义
### 证据
note-modal.ts 中的输入处理(无字数计数):
```typescript
onContentInput(e: WechatMiniprogram.CustomEvent<{ value: string }>) {
this.setData({ content: e.detail.value })
}
```
note-modal.wxml 中 textarea 区域(无计数展示):
```html
<view class="textarea-section">
<textarea
class="note-textarea"
placeholder="请输入备注内容..."
value="{{content}}"
bindinput="onContentInput"
maxlength="500"
auto-height
...
/>
<!-- 此处缺少字数计数展示 -->
</view>
```
### 建议(如未完全解决)
1. 在 note-modal 的 textarea 下方添加字数计数:
```html
<text class="char-count">{{content.length}}/500</text>
```
2. 在 abandon-modal 的 textarea 下方添加字数计数:
```html
<text class="char-count">{{content.length}}/200</text>
```
3. 样式建议右对齐、小字号22rpx、灰色#a6a6a6接近限制时变橙/红色
4. 可在 `onContentInput` 中添加接近限制的提示逻辑(如剩余 20 字时变色)

View File

@@ -0,0 +1,43 @@
# P6→NS1/RNS1 缺失项 #10任务详情页各模块的折叠/展开默认状态
## 简要结论
- 状态:❌ 未解决
- 风险等级:🟠 中
- 任务详情页所有模块(与我的关系、任务建议、维客线索、备注、服务记录)均为始终展开状态,无折叠/展开控制机制。
## 详细审查
### 审查范围
- `apps/miniprogram/miniprogram/pages/task-detail/task-detail.wxml`
- `apps/miniprogram/miniprogram/pages/task-detail/task-detail.ts`
- `apps/miniprogram/miniprogram/pages/task-detail/task-detail.wxss`
### 发现
1. task-detail.wxml 中所有 `.card` 区块(与我的关系、任务建议、维客线索、行动建议、备注、服务记录)均直接渲染,无 `wx:if` 条件控制折叠状态
2. task-detail.ts 的 `data` 中无任何 `collapsed`/`expanded`/`folded` 状态变量
3.`toggleSection`/`toggleCollapse` 等方法
4. 唯一的展开/收起逻辑是维客线索卡片的 `onToggleClue`(控制单条线索描述的展开),但这不是模块级折叠
### 证据
task-detail.wxml 中各模块均为直接渲染:
```xml
<!-- 与我的关系 -->
<view class="card">...</view>
<!-- 任务建议 -->
<view class="card">...</view>
<!-- 维客线索 -->
<view class="card">...</view>
<!-- 备注 -->
<view class="card">...</view>
<!-- 服务记录 -->
<view class="card">...</view>
```
无任何折叠/展开的 `wx:if``hidden` 控制。
task-detail.ts 中无折叠状态变量grep `collapsed|expanded|fold|toggleSection|toggleCollapse` 结果为空)。
### 建议
1. 为每个模块添加折叠状态变量(如 `sectionCollapsed: { relationship: false, suggestion: false, clues: false, notes: true, records: true }`
2.`.card-header` 上添加 `bindtap` 事件切换折叠状态
3. 建议默认展开前 3 个模块(关系、建议、线索),折叠后 2 个(备注、服务记录),减少首屏滚动长度
4. 添加折叠/展开的过渡动画(`max-height` + `transition`

View File

@@ -0,0 +1,51 @@
# P6→NS1/RNS1 缺失项 #11维客线索的展示样式
## 简要结论
- 状态:✅ 已解决
- 风险等级:🟠 中(原始风险已消除)
- clue-card 组件已实现完整的 tag 颜色映射6 种 + 2 种别名)和卡片布局,样式覆盖 P6 定义的所有场景。
## 详细审查
### 审查范围
- `apps/miniprogram/miniprogram/components/clue-card/clue-card.wxml`
- `apps/miniprogram/miniprogram/components/clue-card/clue-card.ts`
- `apps/miniprogram/miniprogram/components/clue-card/clue-card.wxss`
- `apps/miniprogram/miniprogram/pages/task-detail/task-detail.wxml`(调用处)
### 发现
1. clue-card 组件已实现,接受 `tag``category``emoji``title``source``content` 六个属性
2. wxss 中定义了 8 种 tag 颜色类,覆盖 VI 规范 2.1 六种客户标签配色:
- `clue-tag-primary`(客户基础 — 蓝色)
- `clue-tag-success`(消费习惯 — 绿色)
- `clue-tag-orange`(玩法偏好 — 橙色)
- `clue-tag-gold`(促销偏好 — 金色)
- `clue-tag-purple`(社交关系 — 紫色)
- `clue-tag-error`(重要反馈 — 红色)
- `clue-tag-pink`(社交关系别名)
- `clue-tag-warning`(促销偏好别名)
3. 卡片布局包含72rpx 方形 tag 图标 + 右侧内容区(标题+来源+描述)
4. task-detail.wxml 中通过 `<clue-card>` 组件渲染维客线索列表
### 证据
clue-card.wxss 中的颜色映射:
```css
/* VI 规范 2.1 六种客户标签配色 */
.clue-tag-primary { background: rgba(0, 82, 217, 0.10); color: #0052d9; }
.clue-tag-success { background: rgba(0, 168, 112, 0.10); color: #00a870; }
.clue-tag-orange { background: rgba(237, 123, 47, 0.12); color: #ed7b2f; }
.clue-tag-gold { background: rgba(251, 191, 36, 0.15); color: #d4920a; }
.clue-tag-purple { background: rgba(123, 97, 255, 0.10); color: #7b61ff; }
.clue-tag-error { background: rgba(227, 77, 89, 0.10); color: #e34d59; }
```
task-detail.wxml 调用处:
```xml
<clue-card wx:for="{{retentionClues}}" wx:key="index"
tag="{{item.tag}}" category="{{item.tagColor}}"
emoji="{{item.emoji}}" title="{{item.text}}"
source="{{item.source}}" content="{{item.desc || ''}}" />
```
### 建议
无需额外补充。

View File

@@ -0,0 +1,43 @@
# P6→NS1/RNS1 缺失项 #12任务列表页的搜索功能
## 简要结论
- 状态:❌ 未解决
- 风险等级:🟠 中
- 前端无搜索组件,后端 TASK-1 API 不支持搜索参数,前端 services 层无搜索相关调用。
## 详细审查
### 审查范围
- `apps/miniprogram/miniprogram/pages/task-list/task-list.wxml`
- `apps/miniprogram/miniprogram/pages/task-list/task-list.ts`
- `apps/backend/app/routers/xcx_tasks.py`
- `apps/backend/app/schemas/xcx_tasks.py`
- `apps/miniprogram/miniprogram/services/`grep 搜索)
### 发现
1. task-list.wxml 中无 `<t-search>` 或任何搜索输入组件
2. task-list.ts 中无搜索相关的 data 字段(如 `searchKeyword`)或方法(如 `onSearch`
3. 后端 `GET /api/xcx/tasks` 仅支持 `status``page``page_size` 三个查询参数,无 `keyword`/`search`/`query` 参数
4. services 目录下 grep `search|keyword|query` 结果为空
5. TaskListResponse schema 中无搜索相关字段
### 证据
后端路由签名:
```python
@router.get("", response_model=TaskListResponse)
async def get_tasks(
status: str = Query("pending", pattern="^(pending|completed|abandoned)$"),
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=100),
user: CurrentUser = Depends(require_approved()),
):
```
`keyword``search` 参数。
task-list.wxml 中 banner 区域和任务列表区域之间无搜索栏。
### 建议
1. 前端:在 banner 下方、任务列表上方添加 `<t-search>` 组件,支持按客户名/手机号搜索
2. 后端:`GET /api/xcx/tasks` 添加可选参数 `keyword: str = Query(None)`,在 SQL 中对 `member_name``member_phone``ILIKE` 模糊匹配
3. 前端搜索应做防抖300ms避免频繁请求
4. 搜索结果为空时显示专用空状态("未找到匹配的客户"

View File

@@ -0,0 +1,51 @@
# P6→NS1/RNS1 缺失项 #13任务完成后的成功反馈动画
## 简要结论
- 状态:⚠️ 部分解决
- 风险等级:🟠 中
- 已有基础的 `wx.showToast` 反馈(备注保存、删除、放弃/取消放弃),但缺少 P6 定义的"任务完成"专属成功动画(如 Lottie 动画、全屏庆祝效果)。
## 详细审查
### 审查范围
- `apps/miniprogram/miniprogram/pages/task-detail/task-detail.ts`
- `apps/miniprogram/miniprogram/pages/task-detail/task-detail.wxml`
- `apps/miniprogram/miniprogram/pages/task-detail/task-detail.wxss`
### 发现
1. task-detail.ts 中存在多处 `wx.showToast` 调用,覆盖以下操作:
- 备注保存:`wx.showToast({ title: '备注已保存', icon: 'success' })`
- 备注删除:`wx.showToast({ title: '已删除', icon: 'success' })`
- 取消放弃:`wx.showToast({ title: '已取消放弃', icon: 'success' })`
- 放弃任务:`wx.showToast({ title: '已放弃该客户的维护', icon: 'none' })`
- 手机号复制:`wx.showToast({ title: '手机号码已复制', icon: 'none' })`
2. 但无"任务完成"的专属操作入口和反馈动画
3. 无 Lottie 动画组件、无自定义成功动画 CSS、无全屏庆祝效果
4. 当前任务状态只有 `pending``abandoned`,缺少 `completed` 状态的处理流程
### 证据
task-detail.ts 中的 toast 调用(仅为操作反馈,非任务完成动画):
```typescript
// 备注保存
wx.showToast({ title: '备注已保存', icon: 'success' })
// 取消放弃
wx.showToast({ title: '已取消放弃', icon: 'success' })
// 放弃
wx.showToast({ title: '已放弃该客户的维护', icon: 'none' })
```
task-detail.wxml 底部操作栏只有"问问助手"和"备注"两个按钮,无"标记完成"按钮:
```xml
<view class="bottom-bar safe-area-bottom">
<view class="btn-ask" bindtap="onAskAssistant">...</view>
<view class="btn-note" bindtap="onAddNote">...</view>
</view>
```
### 建议
1. 在底部操作栏添加"标记完成"按钮(或在长按菜单中添加)
2. 任务完成后显示自定义成功动画(推荐方案):
- 方案 A全屏半透明遮罩 + CSS 动画(✓ 图标放大 + 文字淡入)
- 方案 B引入 Lottie 动画组件(`lottie-miniprogram`)播放庆祝动画
3. 动画播放完毕后自动返回任务列表页,并刷新列表数据
4. 后端需添加 `POST /api/xcx/tasks/{id}/complete` 接口

View File

@@ -0,0 +1,64 @@
# P6→NS1/RNS1 缺失项 #14网络异常时的离线提示和重试机制
## 简要结论
- 状态:⚠️ 部分解决
- 风险等级:🟠 中
- request.ts 有基础的错误抛出和 401 自动刷新机制,页面级有错误状态和重试按钮,但缺少统一的网络异常拦截、离线检测提示和全局重试机制。
## 详细审查
### 审查范围
- `apps/miniprogram/miniprogram/utils/request.ts`
- `apps/miniprogram/miniprogram/pages/task-list/task-list.wxml`
- `apps/miniprogram/miniprogram/pages/task-list/task-list.ts`
- `apps/miniprogram/miniprogram/pages/task-detail/task-detail.wxml`
- `apps/miniprogram/miniprogram/pages/task-detail/task-detail.ts`
### 发现
#### 已实现的部分
1. `request.ts``wxRequest``fail` 回调会 reject 错误(`statusCode: 0`),但未做网络类型判断
2. `request.ts` 实现了 401 自动刷新 token + 排队重试机制
3. task-list.wxml 有错误状态 UI + 重试按钮:
```xml
<view class="state-error" wx:if="{{pageState === 'error'}}">
<text class="error-text">加载失败,请重试</text>
<view class="retry-btn" bindtap="onRetry">重试</view>
</view>
```
4. task-detail.wxml 同样有错误状态 + 重试按钮
#### 缺失的部分
1. `request.ts` 中无 `wx.getNetworkType()` 离线检测
2. 无全局网络状态监听(`wx.onNetworkStatusChange`
3. 无统一的网络异常 toast 提示(如"网络连接失败,请检查网络"
4. 无请求超时配置wx.request 默认 60s
5. 无自动重试机制(非 401 场景的网络错误不会自动重试)
6. 无离线缓存策略(断网时无法展示上次加载的数据)
### 证据
request.ts 中 fail 回调仅简单 reject
```typescript
fail(err) {
reject({ statusCode: 0, data: err })
},
```
无网络类型判断、无离线提示、无重试逻辑。
task-list.ts 中 loadData 的 catch 仅设置错误状态:
```typescript
} catch {
this.setData({ pageState: 'error' })
}
```
无区分网络错误和服务端错误。
### 建议
1. `request.ts` 增强:
- 请求前调用 `wx.getNetworkType()` 检测网络状态,无网络时直接提示
- 添加请求超时配置(建议 15s
- 非 401 网络错误自动重试 1 次(指数退避)
- 统一的网络错误 toast`wx.showToast({ title: '网络连接失败', icon: 'none' })`
2. `app.ts` 中注册 `wx.onNetworkStatusChange` 全局监听,断网时显示顶部提示条
3. 页面级错误状态区分"网络错误"和"服务端错误",显示不同的提示文案
4. 可选:添加离线缓存(`wx.setStorageSync` 缓存上次成功的列表数据)

View File

@@ -0,0 +1,57 @@
# P6→NS1/RNS1 缺失项 #15任务卡片的长按/滑动操作
## 简要结论
- 状态:⚠️ 部分解决
- 风险等级:🟡 低
- 长按操作已完整实现(上下文菜单含置顶/备注/AI助手/放弃/取消放弃),但滑动操作(如滑动删除、滑动置顶)未实现。
## 详细审查
### 审查范围
- `apps/miniprogram/miniprogram/pages/task-list/task-list.wxml`
- `apps/miniprogram/miniprogram/pages/task-list/task-list.ts`
- `apps/miniprogram/miniprogram/pages/task-list/task-list.wxss`
### 发现
#### 已实现:长按操作
1. 所有任务卡片(置顶/一般/已放弃)均绑定了 `bindlongpress="onTaskLongPress"`
2. 长按后弹出上下文菜单(`.ctx-menu`),菜单项包括:
- 📌 置顶/取消置顶(`onCtxPin`
- 📝 备注(`onCtxNote`
- 🤖 问问AI助手`onCtxAI`
- 🗑️ 放弃任务(`onCtxAbandon`
- ↩️ 取消放弃(已放弃任务专属,`onCtxCancelAbandon`
3. 菜单定位跟随手指触摸位置,有边界检测防止溢出屏幕
#### 未实现:滑动操作
1.`bindtouchmove`/`bindtouchstart`/`bindtouchend` 事件
2. 无滑动删除swipe-to-deleteUI
3. 无滑动置顶交互
4.`<t-swipe-cell>` 组件使用
### 证据
task-list.wxml 中卡片事件绑定(有 longpress无 touchmove
```xml
<view class="task-card ..."
bindtap="onTaskTap" bindlongpress="onTaskLongPress">
```
task-list.ts 中长按处理完整实现:
```typescript
onTaskLongPress(e: WechatMiniprogram.TouchEvent) {
this._longPressed = true
// ... 获取目标任务、计算菜单位置、显示上下文菜单
this.setData({
contextMenuVisible: true,
contextMenuX: x, contextMenuY: y,
contextMenuTarget: target,
})
}
```
### 建议
1. 考虑是否真正需要滑动操作 — 长按菜单已覆盖所有操作场景,滑动操作可能增加交互复杂度
2. 如需实现,推荐使用 TDesign 的 `<t-swipe-cell>` 组件包裹任务卡片
3. 滑动操作建议仅暴露最常用的 1-2 个操作(如置顶、放弃),避免操作过载
4. 优先级较低,可作为后续体验优化迭代

View File

@@ -0,0 +1,58 @@
# P6→NS1/RNS1 缺失项 #16页面切换时的转场动画规范
## 简要结论
- 状态:❌ 未解决
- 风险等级:🟡 低
- 无自定义转场动画配置完全依赖微信小程序默认的页面切换动画。router.ts 仅封装了 wx.navigateTo/switchTab/navigateBack无动画参数。
## 详细审查
### 审查范围
- `apps/miniprogram/miniprogram/utils/router.ts`
- `apps/miniprogram/miniprogram/app.json`window 配置)
- `apps/miniprogram/miniprogram/pages/task-list/task-list.ts`(页面跳转调用)
- `apps/miniprogram/miniprogram/pages/task-detail/task-detail.ts`(页面跳转调用)
### 发现
1. `router.ts` 仅封装了三个基础路由方法(`navigateTo``switchTab``navigateBack`),无 `routeType`/`animationType`/`animationDuration` 参数
2. `app.json``window` 配置仅设置了导航栏样式,无 `pageOrientation``animationType` 等动画配置
3. 页面跳转直接调用 `wx.navigateTo({ url: ... })`,未使用 `routeType` 参数
4. 无页面进入/退出的自定义 CSS 动画
5.`wx.navigateTo``routeType` 参数(微信基础库 2.29.2+ 支持)
### 证据
router.ts 完整内容(无动画配置):
```typescript
export function navigateTo(url: string): void {
wx.navigateTo({ url })
}
export function switchTab(url: string): void {
wx.switchTab({ url })
}
export function navigateBack(delta: number = 1): void {
wx.navigateBack({ delta })
}
```
app.json window 配置(无动画相关字段):
```json
"window": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "球房运营助手",
"navigationBarBackgroundColor": "#ffffff"
}
```
task-list.ts 中直接调用 wx.navigateTo
```typescript
wx.navigateTo({
url: `${DETAIL_ROUTE}?id=${id}`,
fail: () => wx.showToast({ title: '页面跳转失败', icon: 'none' }),
})
```
### 建议
1. 微信小程序默认转场动画(从右滑入/滑出)已满足基本体验,此项优先级较低
2. 如需自定义,可在 `router.ts``navigateTo` 中添加 `routeType` 参数(需基础库 2.29.2+
3. 可选方案:页面 `onLoad` 时添加入场 CSS 动画opacity + translateY 渐入),提升视觉流畅感
4. 建议作为 P13前端打磨的后续迭代项

View File

@@ -0,0 +1,58 @@
# P6→NS1/RNS1 缺失项 #17任务列表的批量操作
## 简要结论
- 状态:❌ 未解决
- 风险等级:🟡 低
- 前端无多选模式、无批量操作 UI后端无批量操作接口。所有操作均为单任务粒度。
## 详细审查
### 审查范围
- `apps/miniprogram/miniprogram/pages/task-list/task-list.wxml`
- `apps/miniprogram/miniprogram/pages/task-list/task-list.ts`
- `apps/backend/app/routers/xcx_tasks.py`
### 发现
#### 前端
1. task-list.wxml 中无 checkbox/多选组件
2. task-list.ts 中无 `selectedTasks`/`isMultiSelect`/`batchMode` 等状态变量
3. 无"全选"/"批量标记完成"/"批量放弃"等操作按钮
4. 无编辑模式切换入口(如顶部"编辑"按钮)
#### 后端
1. `xcx_tasks.py` 中所有操作接口均为单任务粒度:
- `POST /{task_id}/pin`
- `POST /{task_id}/unpin`
- `POST /{task_id}/abandon`
- `POST /{task_id}/restore`
2. 无批量操作接口(如 `POST /batch/pin``POST /batch/abandon`
### 证据
后端路由清单(全部为单任务操作):
```python
@router.post("/{task_id}/pin") # 单个置顶
@router.post("/{task_id}/unpin") # 单个取消置顶
@router.post("/{task_id}/abandon") # 单个放弃
@router.post("/{task_id}/restore") # 单个恢复
```
task-list.ts data 中无批量相关字段:
```typescript
data: {
pageState: 'loading',
pinnedTasks: [],
normalTasks: [],
abandonedTasks: [],
taskCount: 0,
// ... 无 selectedTasks、batchMode 等
}
```
### 建议
1. 此功能优先级较低 — 当前任务列表规模(每日 10-30 条)下,单任务操作已满足需求
2. 如需实现,建议分步:
- 第一步:前端添加编辑模式(长按进入多选 → 底部浮出批量操作栏)
- 第二步:后端添加批量接口 `POST /api/xcx/tasks/batch` 接受 `task_ids` 数组 + `action` 枚举
3. 批量操作建议限制:单次最多选择 20 条,防止误操作
4. 建议在用户反馈确认需求后再实现,避免过度设计

View File

@@ -0,0 +1,62 @@
# P6→NS1/RNS1 缺失项 #18无障碍适配
## 简要结论
- 状态:❌ 未解决
- 风险等级:🟡 低
- 项目自有页面task-list、task-detail 及自定义组件)中无任何 `aria-label``aria-role` 等无障碍属性。仅 TDesign 组件库内部自带了部分无障碍支持。
## 详细审查
### 审查范围
- `apps/miniprogram/miniprogram/pages/task-list/task-list.wxml`
- `apps/miniprogram/miniprogram/pages/task-detail/task-detail.wxml`
- `apps/miniprogram/miniprogram/components/clue-card/clue-card.wxml`
- `apps/miniprogram/miniprogram/components/` 下所有自定义组件
- `apps/miniprogram/miniprogram/miniprogram_npm/tdesign-miniprogram/`(对比参考)
### 发现
1. 全局 grep `aria-label|aria-role|aria-hidden|role=` 在项目自有 wxml 文件中结果为零
2. 仅 TDesign 组件库(`miniprogram_npm/tdesign-miniprogram/`)内部使用了无障碍属性:
- `swiper-nav.wxml``aria-role="button" aria-label="上一张/下一张"`
- `tabs.wxml``aria-role="tablist"`
- `upload.wxml``aria-role="presentation"` + 动态 `aria-label`
3. task-list.wxml 中的交互元素缺少无障碍标注:
- 任务卡片无 `aria-role="button"``aria-label`
- 重试按钮无 `aria-role="button"`
- 上下文菜单项无 `aria-label`
4. task-detail.wxml 中同样缺失:
- 底部操作栏按钮无 `aria-label`
- 手机号查看/复制按钮无 `aria-label`
- 话术复制按钮无 `aria-label`
### 证据
task-list.wxml 中任务卡片(无无障碍属性):
```xml
<view class="task-card ..."
hover-class="task-card--hover" hover-stay-time="100"
data-id="{{item.id}}" data-tasktype="{{item.taskType}}"
bindtap="onTaskTap" bindlongpress="onTaskLongPress">
```
task-detail.wxml 中底部操作栏(无无障碍属性):
```xml
<view class="btn-ask" bindtap="onAskAssistant" hover-class="btn-ask--hover">
<t-icon name="chat" size="36rpx" color="#ffffff" />
<text class="btn-text">问问助手</text>
</view>
```
对比 TDesign 组件(有无障碍属性):
```xml
<view ... aria-role="button" aria-label="上一张"/>
```
### 建议
1. 为所有可交互元素添加 `aria-role``aria-label`
- 任务卡片:`aria-role="button" aria-label="{{item.customerName}} {{item.taskTypeLabel}}"`
- 操作按钮:`aria-role="button" aria-label="问问助手"` / `aria-label="添加备注"`
- 上下文菜单项:`aria-role="menuitem" aria-label="置顶"`
2. 为非交互的装饰性元素添加 `aria-hidden="true"`(如 banner 背景图、调试面板)
3. 确保焦点顺序合理banner → 任务列表 → 底部操作栏
4. 建议创建一个无障碍适配 checklist在后续页面开发中统一执行
5. 优先级较低,可作为 P13 前端打磨的后续迭代

View File

@@ -0,0 +1,57 @@
# P7→NS1/RNS1 缺失项 #1营业日 08:00 分割点的完整处理规范
## 简要结论
- 状态:✅ 已解决
- 风险等级:🟡 低
- ETL 层已通过 `biz_date_sql_expr()` 统一实现 08:00 营业日分割DWS 表中的 biz_date/stat_date 字段已按此规则生成;后端查询使用 `create_time` 按自然月过滤(非 biz_date但因 DWS 层已预聚合,实际数据口径一致。
## 详细审查
### 审查范围
- `packages/shared/src/neozqyy_shared/datetime_utils.py``biz_date_sql_expr()` 函数
- `apps/etl/connectors/feiqiu/tasks/dws/assistant_customer_task.py` — DWS 客户统计 ETL
- `apps/etl/connectors/feiqiu/tasks/dws/member_visit_task.py` — 会员到店 ETL
- `apps/etl/connectors/feiqiu/tasks/dws/finance_discount_task.py` — 财务折扣 ETL
- `apps/backend/app/services/fdw_queries.py` — 后端 FDW 查询
- `apps/backend/app/services/performance_service.py` — 绩效服务
### 发现
1. **ETL 层已完整实现 08:00 分割**
- `biz_date_sql_expr(col, day_start_hour=8)` 生成 `DATE(col - INTERVAL '8 hours')` SQL 表达式
- 所有 DWS 任务assistant_customer_task、member_visit_task、finance_discount_task、goods_stock_daily_task、assistant_project_tag_task均调用此函数
- `cutoff` 值从配置 `app.business_day_start_hour` 读取,默认 8
2. **Python 层也有对应函数**
- `business_date(dt, day_start_hour=8)` — 将时间戳归属到营业日
- `business_month(dt, day_start_hour=8)` — 将时间戳归属到营业月
- `business_day_range(biz_date)` — 返回营业日精确时间戳范围 `[当天08:00, 次日08:00)`
3. **后端查询层**
- `get_service_records()` 使用 `create_time >= start_date AND create_time < end_date` 按自然月过滤
- `get_salary_calc()` 使用 `salary_month` 字段DWS 预聚合,已按营业日口径)
- 服务记录明细查询按自然月时间戳过滤,与 P7 定义的"当月1日 08:00 ~ 次月1日 08:00"存在微小差异(差 8 小时),但实际影响极小
### 证据
```python
# packages/shared/src/neozqyy_shared/datetime_utils.py
def biz_date_sql_expr(col: str, day_start_hour: int = 8) -> str:
return f"DATE({col} - INTERVAL '{day_start_hour} hours')"
# assistant_customer_task.py — DWS 层使用
cutoff = self.config.get("app.business_day_start_hour", 8)
biz_expr = biz_date_sql_expr("start_use_time", cutoff)
# → DATE(start_use_time - INTERVAL '8 hours') AS service_date
```
```python
# fdw_queries.py — 后端查询(按自然月,非 biz_date
start_date = f"{year}-{month:02d}-01"
end_date = f"{year}-{month + 1:02d}-01"
# WHERE sl.create_time >= start_date AND sl.create_time < end_date
```
### 建议(微调项)
- 后端 `get_service_records()` 的月份过滤可考虑使用 `business_month_range()` 生成 `[当月1日 08:00, 次月1日 08:00)` 范围,与 ETL 层 biz_date 口径完全对齐
- 当前差异仅影响每月 1 日 00:00-08:00 之间的少量记录归属,风险极低

View File

@@ -0,0 +1,63 @@
# P7→NS1/RNS1 缺失项 #2"预估"标记的判断逻辑
## 简要结论
- 状态:⚠️ 部分解决
- 风险等级:🟠 中
- 前端已实现"当月 = 预估"的判断逻辑并展示预估标签,但后端 `is_estimate` 字段硬编码为 `False`未实现真正的预估判断。当前方案是纯前端判断year/month == 当前年月),未考虑 ETL 数据更新延迟等场景。
## 详细审查
### 审查范围
- `apps/backend/app/services/fdw_queries.py``get_service_records()``is_estimate` 字段
- `apps/backend/app/schemas/xcx_performance.py` — PERF-1/PERF-2 响应 schema
- `apps/miniprogram/miniprogram/pages/performance/performance.wxml` — 预估标签展示
- `apps/miniprogram/miniprogram/pages/performance/performance.ts``isCurrentMonth` 判断
- `apps/miniprogram/miniprogram/pages/performance-records/performance-records.wxml` — 预估标签展示
### 发现
1. **后端 `is_estimate` 硬编码为 `False`**
- `fdw_queries.get_service_records()` 第 405 行注释明确写道:`# is_estimate 不存在于视图中,默认 False`
- `PerformanceOverviewResponse` schema 中没有 `is_estimate` 字段
- `RecordsSummary` schema 中也没有 `is_estimate` 字段
2. **前端使用纯客户端判断**
- `performance.ts` 中:`const isCurrentMonth = year === nowYear && month === nowMonth`
- `performance-records.ts` 中:同样的 `isCurrentMonth` 判断
- WXML 中根据 `isCurrentMonth` 展示"预估"标签和"我的预估收入"文案
3. **前端展示已到位**
- `performance.wxml``<text wx:if="{{isCurrentMonth}}" class="estimate-tag">预估</text>`
- 收入标签:`{{isCurrentMonth ? '我的预估收入' : '我的收入'}}`
- 合计标签:`本月合计<text wx:if="{{isCurrentMonth}}"> 预估</text>`
- `performance-records.wxml`:统计概览中 `<text class="stat-hint" wx:if="{{isCurrentMonth}}">预估</text>`
### 证据
```python
# fdw_queries.py — is_estimate 硬编码
records.append({
...
"income": float(row[10]) if row[10] is not None else 0.0,
# is_estimate 不存在于视图中,默认 False
"is_estimate": False,
})
```
```typescript
// performance.ts — 纯前端判断
const isCurrentMonth = year === nowYear && month === nowMonth
```
```xml
<!-- performance.wxml — 预估标签展示 -->
<text wx:if="{{isCurrentMonth}}" class="estimate-tag">预估</text>
<text class="income-label">{{isCurrentMonth ? '我的预估收入' : '我的收入'}}</text>
```
### 建议
1. **明确"预估"的业务定义**P7 AC7 要求"当月数据显示预估标记",当前前端的"当月 = 预估"实现基本满足此需求,但需确认:
- 是否所有当月数据都算预估?还是仅未结算的记录?
- 月末 ETL 完成最终计算后,当月数据是否仍标记为预估?
2. **后端应提供 `is_estimate` 字段**:即使当前逻辑是"当月 = 预估",也应由后端返回此标记,避免前后端判断逻辑不一致
3. **单条记录级别的预估标记**`is_estimate` 字段已在 `xcx_tasks.py``xcx_customers.py` 的 schema 中定义,但在绩效 schema 中缺失

View File

@@ -0,0 +1,73 @@
# P7→NS1/RNS1 缺失项 #3定档折算惩罚的展示格式
## 简要结论
- 状态:⚠️ 部分解决
- 风险等级:🟠 中
- 后端已返回 `service_hours`(折算后)和 `service_hours_raw`(折算前)两个字段,前端 performance-records 页面已实现折前/折后对比展示DISPLAY-STANDARDS.md 已定义课时展示规范。但 P7 AC6 要求的"120分钟定档折算30分钟"格式未被采用,实际使用的是"2.0h(折后 2.5h"格式,且 performance 概览页未展示折算信息。
## 详细审查
### 审查范围
- `apps/backend/app/services/performance_service.py``compute_summary()``group_records_by_date()`
- `apps/backend/app/schemas/xcx_performance.py``RecordsSummary` 中的 `total_hours_raw`
- `apps/miniprogram/miniprogram/pages/performance-records/performance-records.wxml` — 折算展示
- `docs/miniprogram-dev/design-system/DISPLAY-STANDARDS.md` — §2 课时展示规范
### 发现
1. **后端数据已完整**
- `fdw_queries.get_service_records()` 返回 `service_hours``income_seconds / 3600.0`)和 `service_hours_raw``real_use_seconds / 3600.0`
- `compute_summary()` 计算 `total_hours``total_hours_raw`
- `RecordsSummary` schema 包含 `total_hours: float``total_hours_raw: float`
2. **performance-records 页面已实现折算展示**
- WXML 中:`<text class="record-hours-deduct" wx:if="{{rec.hoursRaw && rec.hoursRaw !== rec.hours}}">折前 {{fmt.hours(rec.hoursRaw)}}</text>`
- 统计概览中:`<text class="stat-hours-raw" wx:if="{{totalHoursRawLabel}}">折前 {{totalHoursRawLabel}}</text>`
- 格式为"折前 Nh"而非 P7 要求的"120分钟定档折算30分钟"
3. **DISPLAY-STANDARDS.md 已定义规范**
- §2.1 规则总表:`带折算备注 | 实际h折后 原始h | 2.0h(折后 2.5h`
- §2.3 折算标注字段约定:`hours`(折算后)、`hoursRaw`(折算前),仅当两者不同时展示括号备注
4. **performance 概览页未展示折算**
- `group_records_by_date()` 中的 `record_item` 只有 `hours` 字段,没有 `hoursRaw`
- 概览页 WXML 中服务记录只展示 `rec.hours`,无折算信息
5. **格式差异**
- P7 AC6 要求:"120分钟定档折算30分钟"(分钟单位,括号内说明折算量)
- 实际实现:"2.0h(折后 2.5h"(小时单位,括号内展示折前原始值)
- performance-records 实际使用:"折前 2.5h"(无括号,前缀"折前"
### 证据
```python
# performance_service.py — compute_summary 包含 total_hours_raw
def compute_summary(records: list[dict]) -> dict:
total_hours = sum(r.get("service_hours", 0.0) for r in records)
total_hours_raw = sum(r.get("service_hours_raw", 0.0) for r in records)
return {
"total_count": len(records),
"total_hours": round(total_hours, 2),
"total_hours_raw": round(total_hours_raw, 2),
"total_income": round(total_income, 2),
}
```
```xml
<!-- performance-records.wxml — 折算展示 -->
<text class="record-hours">{{fmt.hours(rec.hours)}}</text>
<text class="record-hours-deduct"
wx:if="{{rec.hoursRaw && rec.hoursRaw !== rec.hours}}">
折前 {{fmt.hours(rec.hoursRaw)}}
</text>
```
```markdown
<!-- DISPLAY-STANDARDS.md §2.1 -->
| 带折算备注 | `实际h折后 原始h` | `2.0h(折后 2.5h` |
```
### 建议
1. **统一展示格式**:当前 performance-records 页面使用"折前 Xh"格式,与 DISPLAY-STANDARDS.md 定义的"实际h折后 原始h"格式不一致,需统一
2. **确认是否采用 P7 的分钟格式**P7 要求"120分钟定档折算30分钟",但设计规范和实际实现均使用小时单位,需与产品确认最终格式
3. **performance 概览页补充折算信息**`group_records_by_date()` 应在 `record_item` 中加入 `hours_raw` 字段

View File

@@ -0,0 +1,81 @@
# P7→NS1/RNS1 缺失项 #4"我的新客"筛选逻辑的完整定义
## 简要结论
- 状态:⚠️ 部分解决
- 风险等级:🟠 中
- 后端已实现新客筛选逻辑,但采用的是"本月有服务 + 历史无记录"的简化定义,与 P7 AC3 定义的"首次服务 + 2月内 + 服务次数≤2"条件不完全一致。未使用 `dws_assistant_customer_stats` 表(该表已在 ETL 层建好),而是直接查询 DWD 层视图。
## 详细审查
### 审查范围
- `apps/backend/app/services/performance_service.py``_build_customer_lists()` 函数
- `apps/backend/app/services/fdw_queries.py` — FDW 查询
- `apps/etl/connectors/feiqiu/tasks/dws/assistant_customer_task.py` — DWS 客户统计 ETL
- `docs/database/ddl/etl_feiqiu__dws.sql``dws_assistant_customer_stats` 表结构
- `apps/miniprogram/miniprogram/pages/performance/performance.wxml` — 新客列表展示
### 发现
1. **后端实现的新客定义**
- `_build_customer_lists()` 中新客判断:`if mid not in historical_members`
- 历史查询:`WHERE create_time < 本月1日 AND tenant_member_id = ANY(本月服务过的会员)`
- 即:**本月有服务记录 + 本月之前从未有过服务记录** = 新客
- 未检查"2月内"和"服务次数≤2"条件
2. **P7 AC3 的完整定义**
- 首次服务first_service_date 在本月)
- 2月内首次服务距今不超过 2 个月)
- 服务次数 ≤ 2
3. **DWS 层已有更完整的数据**
- `dws_assistant_customer_stats` 表包含 `first_service_date``last_service_date``total_service_count` 等字段
- ETL 任务 `AssistantCustomerTask` 已按 `biz_date_sql_expr` 计算营业日归属
-`app.v_dws_assistant_customer_stats` RLS 视图可供后端查询
- 但后端 `fdw_queries.py` 中未使用此视图
4. **前端展示已到位**
- 新客列表展示:姓名、头像、最近服务日期、服务次数
- `<text class="customer-detail">最近服务: {{item.lastService}} · {{item.count}}次</text>`
### 证据
```python
# performance_service.py — 新客判断逻辑
# 查询历史记录(本月之前是否有服务记录)
try:
start_date = f"{year}-{month:02d}-01"
with fdw_queries._fdw_context(conn, site_id) as cur:
cur.execute("""
SELECT DISTINCT tenant_member_id
FROM app.v_dwd_assistant_service_log
WHERE site_assistant_id = %s
AND is_delete = 0
AND create_time < %s::timestamptz
AND tenant_member_id = ANY(%s)
""", (assistant_id, start_date, member_ids))
for row in cur.fetchall():
historical_members.add(row[0])
# 新客:历史无记录
if mid not in historical_members:
new_customers.append({...})
```
```sql
-- dws_assistant_customer_stats 表结构(已存在但未被后端使用)
CREATE TABLE dws.dws_assistant_customer_stats (
id bigint NOT NULL,
site_id bigint NOT NULL,
tenant_id bigint NOT NULL,
-- ... 包含 first_service_date, last_service_date, total_service_count 等
-- 唯一约束: (site_id, assistant_id, member_id, stat_date)
);
```
### 建议
1. **对齐 P7 AC3 的完整新客定义**:当前"历史无记录"的判断过于宽松,应补充:
- `first_service_date` 在本月范围内
- `total_service_count <= 2`(或根据业务确认阈值)
- "2月内"条件在当前"当月查询"场景下自然满足,但跨月查看时需考虑
2. **使用 `dws_assistant_customer_stats` 表**:该表已有 `first_service_date``total_service_count` 等预聚合字段,比直接查 DWD 层更高效且口径更准确
3. **确认新客定义的业务边界**:与产品确认"首次服务"是指该助教的首次服务还是全店首次服务

View File

@@ -0,0 +1,74 @@
# P7→NS1/RNS1 缺失项 #5"我的常客"的展示字段
## 简要结论
- 状态:✅ 已解决
- 风险等级:🟡 低
- 后端已在 PERF-1 响应中返回常客列表,包含 P7 AC4 要求的次数、小时数、收入合计三个核心字段。前端已完整展示。Schema 中 `RegularCustomer` 模型字段齐全。
## 详细审查
### 审查范围
- `apps/backend/app/services/performance_service.py``_build_customer_lists()` 常客构建逻辑
- `apps/backend/app/schemas/xcx_performance.py``RegularCustomer` 模型
- `apps/miniprogram/miniprogram/pages/performance/performance.wxml` — 常客列表展示
### 发现
1. **后端常客字段完整**
- `_build_customer_lists()` 中常客判断:`if stats["count"] >= 2`(本月服务次数 ≥ 2
- 返回字段:`name``avatar_char``avatar_color``hours`(总小时数)、`income`(收入合计,格式 `¥N,NNN.NN`)、`count`(服务次数)
- 按收入倒序排列
2. **Schema 定义完整**
- `RegularCustomer(CustomerSummary)` 包含:`hours: float``income: str``count: int`
- `PerformanceOverviewResponse` 包含 `regular_customers: list[RegularCustomer]`
3. **前端展示完整**
- WXML`<text class="customer-detail">{{item.count}}次 · {{fmt.hours(item.hours)}} · {{fmt.safe(item.income)}}</text>`
- 展示格式:`3次 · 4.5h · ¥1,200`
- 支持展开/收起(默认显示 5 条,可展开至 20 条)
4. **P7 AC4 要求对照**
- ✅ 次数 → `count` 字段
- ✅ 小时数 → `hours` 字段
- ✅ 工资合计 → `income` 字段(注:实际为收入合计,非"工资",语义更准确)
### 证据
```python
# performance_service.py — 常客构建
if stats["count"] >= 2:
regular_customers.append({
"name": name,
"avatar_char": char,
"avatar_color": color,
"hours": round(stats["total_hours"], 2),
"income": f"¥{stats['total_income']:,.2f}",
"count": stats["count"],
})
# 按收入倒序
regular_customers.sort(
key=lambda x: float(x.get("income", "¥0").replace("¥", "").replace(",", "")),
reverse=True,
)
```
```python
# xcx_performance.py — Schema
class RegularCustomer(CustomerSummary):
"""常客。"""
hours: float
income: str
count: int
```
```xml
<!-- performance.wxml — 常客展示 -->
<text class="customer-detail">
{{item.count}}次 · {{fmt.hours(item.hours)}} · {{fmt.safe(item.income)}}
</text>
```
### 建议(微调项)
- 常客阈值 `count >= 2` 当前硬编码,可考虑从配置表读取(遵循 feiqiu-data-rules 规则 6
- `income` 字段在后端格式化为字符串(`¥N,NNN.NN`),与 DISPLAY-STANDARDS.md 的金额规范(`¥N,NNN` 无小数)略有差异,建议统一

View File

@@ -0,0 +1,106 @@
# P7→NS1/RNS1 缺失项 #6收入与业绩档位卡片的视觉设计
## 简要结论
- 状态:✅ 已解决
- 风险等级:🟡 低
- 前端已完整实现收入卡片、档位卡片(当前/下一阶段)、升级提示、进度条组件的视觉设计。`perf-progress-bar``metric-card` 组件均已开发完成WXSS 中包含完整的渐变、动画、布局样式。
## 详细审查
### 审查范围
- `apps/miniprogram/miniprogram/pages/performance/performance.wxml` — 收入卡片和档位卡片布局
- `apps/miniprogram/miniprogram/pages/performance/performance.wxss` — 视觉样式
- `apps/miniprogram/miniprogram/components/perf-progress-bar/` — 进度条组件
- `apps/miniprogram/miniprogram/components/metric-card/` — 指标卡片组件
### 发现
1. **收入概览卡片**Banner 区域):
- 双卡片布局:`income-overview` flex 容器,两个 `income-card`
- 左卡片:"我的预估收入"/"我的收入" + 金额44rpx 粗体白色)
- 右卡片:"上月收入" + 金额(绿色高亮 `#a7f3d0`
- 毛玻璃效果:`backdrop-filter: blur(4px)`,半透明白色背景
- SVG 渐变底图:`banner-bg-blue-light-aurora.svg`
2. **档位卡片**(收入情况 Section
- 当前档位:绿色渐变背景(`#f0fdf4 → #dcfce7`),绿色边框,绿色 badge
- 下一阶段:黄色渐变背景(`#fefce8 → #fef9c3`),黄色边框,黄色 badge
- 每个档位卡片展示emoji 图标 + 档位标签 + 基础课费率 + 激励课费率
- 费率展示:`{rate}元/h` 格式,带分隔线
3. **升级提示卡片**
- 蓝色渐变背景(`#eff6ff → #eef2ff`
- 左侧:⏱️ emoji + "距离下一阶段" + "需完成 X 小时"
- 右侧:橙色渐变按钮 "到达即得 X元"
4. **perf-progress-bar 组件**
- 渐变填充条 + 高光动画 + 导火索火星效果
- 支持动态刻度(`ticks` 数组从接口传入,不硬编码)
- 档位高亮:`currentTier` 控制刻度高亮状态
- 动画:`shine`(高光扫过)+ `spark`火花粒子6 个粒子)
5. **metric-card 组件**
- 通用指标卡片:标题 + 数值 + 单位 + 趋势标签
- 趋势支持up绿色箭头/ down红色箭头/ flat横线
- 帮助图标:可选 `helpText`,点击触发 `helpTap` 事件
- 空值处理:`null/undefined → '--'`
6. **前端仍使用 mock 数据**
- `performance.ts``loadData()` 使用 `setTimeout` + 空骨架数据
- 注释标注 `// TODO: 替换为真实 API — 已清空为骨架项`
- 视觉组件和布局已完成,但未接入真实 API
### 证据
```xml
<!-- performance.wxml — 档位卡片 -->
<view class="tier-card tier-current">
<view class="tier-badge badge-current">当前档位</view>
<view class="tier-row">
<view class="tier-icon-label">
<text class="tier-emoji">📊</text>
<text class="tier-label tier-label-green">当前档位</text>
</view>
<view class="tier-rates">
<view class="rate-item">
<text class="rate-value rate-green">{{currentTier.basicRate}}</text>
<text class="rate-unit rate-green-light">元/h</text>
<text class="rate-desc rate-green-light">基础课到手</text>
</view>
<!-- ... 激励课费率 ... -->
</view>
</view>
</view>
```
```css
/* performance.wxss — 档位卡片样式 */
.tier-current {
background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%);
border: 2rpx solid #86efac;
}
.tier-next {
background: linear-gradient(135deg, #fefce8 0%, #fef9c3 100%);
border: 2rpx solid #fde047;
}
.badge-current {
background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
}
```
```typescript
// perf-progress-bar — 组件属性
properties: {
filledPct: { type: Number, value: 0 }, // 进度百分比
clampedSparkPct: { type: Number, value: 0 }, // 火星位置
currentTier: { type: Number, value: 0 }, // 当前档位
ticks: { type: Array, value: [] }, // 刻度数组(接口传入)
shineRunning: { type: Boolean, value: false },
sparkRunning: { type: Boolean, value: false },
}
```
### 建议(微调项)
- performance 页面尚未使用 `perf-progress-bar` 组件WXML 中未引用),需在联调时集成
- performance 页面尚未使用 `metric-card` 组件,当前收入展示是内联实现
- 前端 mock 数据需替换为真实 API 调用(已有 TODO 标注)

View File

@@ -0,0 +1,58 @@
# P7→NS1/RNS1 缺失项 #7服务记录按天归总的展示格式
## 简要结论
- 状态:⚠️ 部分解决
- 风险等级:🟠 中
- 后端 DateGroup 结构完整,日期标签格式为 `M月D日`(如"3月15日"但缺少星期信息P7 定义的"3月15日 周五"格式);前端直接透传后端 `date` 字段(`YYYY-MM-DD` 格式),未做二次格式化。
## 详细审查
### 审查范围
- `apps/backend/app/schemas/xcx_performance.py` — DateGroup / DateGroupRecord 模型
- `apps/backend/app/services/performance_service.py``group_records_by_date()``_format_date_label()`
- `apps/miniprogram/miniprogram/pages/performance-records/performance-records.wxml` — 日期标签渲染
- `apps/miniprogram/miniprogram/pages/performance/performance.wxml` — 日期标签渲染
- `docs/miniprogram-dev/design-system/DATETIME-DISPLAY-STANDARD.md` — 日期展示规范
### 发现
1. **后端 DateGroup 结构已定义且完整**`DateGroup` 包含 `date``total_hours``total_income``records` 四个字段,`DateGroupRecord` 包含客户名、时间范围、课时、课程类型、地点、收入等完整字段。
2. **后端 `_format_date_label()` 格式为 `M月D日`**:该函数输出如 `3月15日`,但不包含星期信息。然而实际上 `group_records_by_date()``date` 字段使用的是 `YYYY-MM-DD` 格式的 `date_key`,并未调用 `_format_date_label()`
3. **`_format_date_label()` 未被 `group_records_by_date()` 使用**`group_records_by_date()` 直接将 `date_key``YYYY-MM-DD`)赋值给 `date` 字段,`_format_date_label()` 函数虽然存在但未在 DateGroup 构建中被调用。
4. **前端直接渲染后端返回的 `date` 字段**WXML 中 `{{item.date}}` 直接展示,未做格式化。因此用户看到的是 `2026-03-15` 而非 `3月15日 周五`
5. **设计规范文档 DATETIME-DISPLAY-STANDARD.md 定义的是相对时间规范**(刚刚/N分钟前/N天前/日期),不涉及"M月D日 周X"这种绝对日期+星期的格式。
### 证据
后端 `group_records_by_date()` 中日期赋值performance_service.py:130
```python
result.append({
"date": date_key, # date_key = settle_time.strftime("%Y-%m-%d")
"total_hours": f"{total_hours:g}",
"total_income": f"{total_income:.2f}",
"records": recs,
})
```
后端 `_format_date_label()` 存在但未被 DateGroup 使用performance_service.py:188
```python
def _format_date_label(dt) -> str:
"""格式化日期为 "M月D日" 格式。"""
if hasattr(dt, "strftime"):
return f"{dt.month}{dt.day}"
```
前端直接渲染performance-records.wxml
```xml
<text decode class="dd-date">{{item.date}}&nbsp;&nbsp;</text>
```
### 建议
1. **后端**:在 `group_records_by_date()` 中将 `date` 字段改为调用 `_format_date_label()` 并追加星期信息,格式为 `M月D日 周X`(如"3月15日 周五")。或新增 `date_label` 字段保留格式化后的展示文本,`date` 保留 `YYYY-MM-DD` 用于排序。
2. **前端**:如果后端不改,前端可在 WXS 中增加日期格式化函数,将 `YYYY-MM-DD` 转为 `M月D日 周X`
3. **设计规范**:在 DATETIME-DISPLAY-STANDARD.md 中补充"按天归总场景"的日期标签格式规范(`M月D日 周X`)。

View File

@@ -0,0 +1,73 @@
# P7→NS1/RNS1 缺失项 #8本月/上月切换的交互细节
## 简要结论
- 状态:⚠️ 部分解决
- 风险等级:🟠 中
- performance-records 页面已实现月份切换(含 loading 状态和数据刷新),但 performance 主页面尚未实现月份切换功能(仅展示当月数据,使用 mock 骨架数据)。两个页面均无切换动画。
## 详细审查
### 审查范围
- `apps/miniprogram/miniprogram/pages/performance/performance.ts` — 绩效主页面逻辑
- `apps/miniprogram/miniprogram/pages/performance/performance.wxml` — 绩效主页面模板
- `apps/miniprogram/miniprogram/pages/performance-records/performance-records.ts` — 业绩明细页逻辑
- `apps/miniprogram/miniprogram/pages/performance-records/performance-records.wxml` — 业绩明细页模板
- `apps/backend/app/routers/xcx_performance.py` — 后端路由参数
### 发现
1. **performance-records 页面月份切换已实现**
-`switchMonth()` 方法,支持 prev/next 方向切换
-`canGoPrev`/`canGoNext` 状态控制按钮可用性
- 切换后调用 `loadData()` 重新请求数据
-`pageState: 'loading'` 状态展示 loading 浮层
- 不能超过当前月(`canGoNext` 逻辑正确)
2. **performance 主页面未实现月份切换**
- `loadData()` 中硬编码 `year = nowYear, month = nowMonth`,仅展示当月
- WXML 中无月份切换 UI 组件
- 数据仍使用 `setTimeout` + mock 骨架数据,未接入真实 API
-`TODO: 联调时从接口参数或页面参数获取 year/month` 注释
3. **后端 API 已支持月份参数**PERF-1 和 PERF-2 均接受 `year`/`month` 查询参数。
4. **无切换动画**:两个页面的月份切换均为即时替换,无过渡动画效果。
### 证据
performance.ts 中硬编码当月Line 87-89
```typescript
// TODO: 联调时从接口参数或页面参数获取 year/month
const year = nowYear
const month = nowMonth
```
performance-records.ts 中月份切换实现switchMonth 方法):
```typescript
switchMonth(e: WechatMiniprogram.TouchEvent) {
const direction = e.currentTarget.dataset.direction as 'prev' | 'next'
let { currentYear, currentMonth } = this.data
// ... 月份加减逻辑 ...
this.setData({ currentYear, currentMonth, monthLabel, canGoNext, canGoPrev, isCurrentMonth })
this.loadData()
}
```
performance-records.wxml 中月份切换 UI
```xml
<view class="month-switcher">
<view class="month-btn {{canGoPrev ? '' : 'month-btn-disabled'}}" data-direction="prev" bindtap="switchMonth">
<t-icon name="chevron-left" size="32rpx" />
</view>
<text class="month-label">{{monthLabel}}</text>
<view class="month-btn {{canGoNext ? '' : 'month-btn-disabled'}}" data-direction="next" bindtap="switchMonth">
<t-icon name="chevron-right" size="32rpx" />
</view>
</view>
```
### 建议
1. **performance 主页面**:完成 API 联调,移除 mock 数据,增加月份切换 UI 和逻辑(可复用 performance-records 的 `switchMonth` 模式)。
2. **切换动画**P7 AC2 提到的切换动画可作为低优先级优化,当前 loading 浮层已提供基本的状态反馈。
3. **数据刷新策略**:切换月份时已有 loading → 请求 → 渲染的完整流程,满足基本需求。可考虑增加本地缓存避免重复请求已加载过的月份。

View File

@@ -0,0 +1,61 @@
# P7→NS1/RNS1 缺失项 #9绩效页面的空状态
## 简要结论
- 状态:✅ 已解决
- 风险等级:🟡 低
- performance 主页面和 performance-records 页面均已实现空状态处理,包含空态图标、文案和错误重试。
## 详细审查
### 审查范围
- `apps/miniprogram/miniprogram/pages/performance/performance.ts` — pageState 状态管理
- `apps/miniprogram/miniprogram/pages/performance/performance.wxml` — 空态 UI
- `apps/miniprogram/miniprogram/pages/performance-records/performance-records.ts` — pageState 状态管理
- `apps/miniprogram/miniprogram/pages/performance-records/performance-records.wxml` — 空态 UI
### 发现
1. **performance 主页面空状态已实现**
- `pageState` 支持 `'loading' | 'empty' | 'error' | 'normal'` 四种状态
- WXML 中有独立的空态区块:图标 `chart-bar` + 文案"暂无业绩数据"
- 有错误态区块:图标 `close-circle` + 文案"加载失败,请点击重试" + 重试按钮
2. **performance-records 页面空状态已实现**
- 同样支持四种 pageState
- 空态区块:图标 `chart-bar` + 文案"暂无数据"
- 错误态区块:图标 `close-circle` + 文案"加载失败,请点击重试" + 重试按钮
-`dateGroups.length === 0` 时自动切换到 `'empty'` 状态
3. **loading 态使用 toast 浮层**:不销毁内容,避免白屏闪烁。
### 证据
performance.wxml 空态区块:
```xml
<!-- 空数据态 -->
<view class="page-empty" wx:elif="{{pageState === 'empty'}}">
<t-icon name="chart-bar" size="40px" color="#dcdcdc" />
<text class="empty-text">暂无业绩数据</text>
</view>
```
performance-records.wxml 空态区块:
```xml
<!-- 空态 -->
<view class="page-empty" wx:if="{{pageState === 'empty'}}">
<t-icon name="chart-bar" size="120rpx" color="#dcdcdc" />
<text class="empty-text">暂无数据</text>
</view>
```
performance-records.ts 中空态判断:
```typescript
this.setData({
pageState: dateGroups.length > 0 ? 'normal' : 'empty',
// ...
})
```
### 建议
无需额外补充。空状态处理已覆盖新助教无数据场景。

View File

@@ -0,0 +1,49 @@
# P7→NS1/RNS1 缺失项 #10业绩明细的导出功能
## 简要结论
- 状态:❌ 未解决
- 风险等级:🟡 低
- 后端无绩效数据导出接口,前端无导出按钮。现有的 Excel 相关接口仅用于租户管理后台的数据上传,与绩效导出无关。
## 详细审查
### 审查范围
- `apps/backend/app/routers/xcx_performance.py` — 绩效路由端点清单
- `apps/backend/app/routers/` — 全部路由文件搜索 export/导出/excel 关键词
- `apps/miniprogram/miniprogram/pages/performance/performance.wxml` — 导出按钮
- `apps/miniprogram/miniprogram/pages/performance-records/performance-records.wxml` — 导出按钮
### 发现
1. **后端无绩效导出接口**`xcx_performance.py` 仅定义两个端点:
- `GET /api/xcx/performance` — 绩效概览PERF-1
- `GET /api/xcx/performance/records` — 绩效明细PERF-2
-`/export` 或类似导出端点
2. **现有 Excel 接口与绩效无关**`tenant_excel.py` 提供的是租户管理后台的 Excel 上传/校验/写入功能(财务支出、团购收入、助教奖罚、充值业绩归属模板),不涉及绩效数据导出。
3. **前端无导出按钮**performance 和 performance-records 两个页面的 WXML 中均无导出相关的 UI 元素。
4. **P7 中"导出 Excel"为隐含需求**:原始 PRD 中提到但未作为核心功能明确定义NS1/RNS1 未将其纳入实现范围。
### 证据
后端路由完整端点清单xcx_performance.py 文件头注释):
```python
"""
端点清单:
- GET /api/xcx/performance — 绩效概览PERF-1
- GET /api/xcx/performance/records — 绩效明细PERF-2
"""
```
全局搜索 `export|导出|excel` 在后端路由中的结果:仅 `tenant_excel.py`(租户 Excel 上传)和 `env_config.py`(环境配置导出),无绩效相关导出。
### 建议
1. **评估优先级**:导出功能在 P7 中为隐含需求,非核心交互。建议在 MVP 阶段暂不实现,后续根据用户反馈决定是否补充。
2. **如需实现**
- 后端新增 `GET /api/xcx/performance/export?year=&month=` 端点,返回 Excel 文件openpyxl 生成)
- 前端在 performance-records 页面顶部或底部增加"导出本月明细"按钮
- 小程序端可通过 `wx.downloadFile` + `wx.openDocument` 实现文件下载和预览
3. **替代方案**可在管理后台admin-web而非小程序端提供导出功能降低小程序端复杂度。

View File

@@ -0,0 +1,75 @@
# P7→NS1/RNS1 缺失项 #11绩效数据的刷新频率说明
## 简要结论
- 状态:⚠️ 部分解决
- 风险等级:🟡 低
- 后端直接查询 ETL 库 DWS/DWD 视图(通过 FDW无应用层缓存数据新鲜度取决于 ETL 执行频率。ETL 为手动触发CLI无自动定时调度但代码中无数据新鲜度说明文档。
## 详细审查
### 审查范围
- `apps/backend/app/services/performance_service.py` — 数据来源和查询方式
- `apps/backend/app/services/fdw_queries.py` — FDW 查询函数(`get_salary_calc``get_service_records`
- `apps/etl/connectors/feiqiu/orchestration/` — 调度配置
- `apps/etl/connectors/feiqiu/cli/main.py` — CLI 入口
- `apps/etl/connectors/feiqiu/tasks/dws/assistant_salary_task.py` — DWS 薪资任务
### 发现
1. **后端无缓存,直接查 FDW 视图**
- `get_salary_calc()` 查询 `app.v_dws_assistant_salary_calc`DWS 层视图)
- `get_service_records()` 查询 `app.v_dwd_assistant_service_log`DWD 层视图)
- 两者均通过 FDWpostgres_fdw从 ETL 库只读访问
- 无 Redis/内存缓存层,每次请求直接查库
2. **ETL 为手动 CLI 触发,无自动定时调度**
- `cli/main.py` 提供 `--flow``--tasks` 等参数手动执行
- `orchestration/scheduler.py` 已标记为弃用(`ETLScheduler 已弃用`
- 无 cron/定时任务配置文件
- 无 Windows Task Scheduler 或 systemd timer 配置
3. **DWS 薪资表使用 delete-before-insert 策略**`assistant_salary_task.py` 中有 `_delete_by_month()` 方法,符合 DWS 层幂等策略。
4. **无数据新鲜度文档**NS1/RNS1 未定义数据刷新频率,前端也未展示"数据更新时间"提示。
### 证据
fdw_queries.py 中 `get_salary_calc()` 数据来源:
```python
cur.execute("""
SELECT salary_month, assistant_level_name, tier_id, ...
FROM app.v_dws_assistant_salary_calc
WHERE assistant_id = %s AND salary_month = %s::date
""", (assistant_id, calc_month))
```
fdw_queries.py 中 `get_service_records()` 数据来源:
```python
cur.execute("""
SELECT sl.assistant_service_id, dm.nickname AS customer_name, ...
FROM app.v_dwd_assistant_service_log sl
LEFT JOIN app.v_dim_member dm ON ...
WHERE sl.site_assistant_id = %s AND sl.is_delete = 0
AND sl.create_time >= %s::timestamptz
AND sl.create_time < %s::timestamptz
ORDER BY sl.create_time DESC
LIMIT %s OFFSET %s
""", ...)
```
ETL 调度器弃用标记orchestration/scheduler.py
```python
class ETLScheduler:
"""调度器薄包装层(已弃用)。"""
def __init__(self, config, logger):
warnings.warn("ETLScheduler 已弃用,请直接使用 TaskExecutor 和 FlowRunner", ...)
```
### 建议
1. **文档补充**:在 NS1 或运维文档中明确说明数据新鲜度策略:
- DWD 层服务记录ETL 执行后即时更新
- DWS 层薪资计算ETL 执行后 delete-before-insert 重算
- 当前为手动触发,建议说明推荐执行频率(如每日一次)
2. **前端提示**:可在绩效页面底部增加"数据更新于 YYYY-MM-DD HH:mm"提示(需后端返回 ETL 最后执行时间)。
3. **自动调度**:长期建议配置 Windows Task Scheduler 或 cron 定时执行 ETL flow确保数据每日刷新。

View File

@@ -0,0 +1,72 @@
# P7→NS1/RNS1 缺失项 #12业绩明细页的口径选择交互
## 简要结论
- 状态:❌ 未解决
- 风险等级:🟡 低
- 后端 PERF-2 API 仅支持 `year`/`month` 参数(月口径),不支持周口径参数。前端 performance-records 页面仅有月份切换,无周口径切换 UI。
## 详细审查
### 审查范围
- `apps/backend/app/routers/xcx_performance.py` — PERF-2 端点参数定义
- `apps/backend/app/services/performance_service.py``get_records()` 函数签名
- `apps/backend/app/services/fdw_queries.py``get_service_records()` 查询条件
- `apps/miniprogram/miniprogram/pages/performance-records/performance-records.ts` — 口径切换逻辑
- `apps/miniprogram/miniprogram/pages/performance-records/performance-records.wxml` — 口径切换 UI
### 发现
1. **后端 PERF-2 仅支持月口径**
- 路由参数:`year: int, month: int, page: int, page_size: int`
-`period_type`(月/周)、`week_start`/`week_end` 等周口径参数
- `get_records()` 函数签名:`(user_id, site_id, year, month, page, page_size)`
2. **FDW 查询按月过滤**`get_service_records()` 使用 `create_time >= '{year}-{month:02d}-01'``create_time < '{year}-{month+1:02d}-01'` 作为时间范围,硬编码为自然月。
3. **前端无周口径切换**
- performance-records.ts 中仅有 `switchMonth()` 方法
- WXML 中仅有月份切换器(`month-switcher`),无"本周/上周"切换 UI
- 全局搜索 `week|周|weekly` 在后端绩效文件中无匹配
4. **P7 提到"本周/上周"口径**:原始 PRD 中定义了周维度的业绩查看,但 NS1/RNS1 仅实现了月维度。
### 证据
后端 PERF-2 路由定义xcx_performance.py
```python
@router.get("/records", response_model=PerformanceRecordsResponse)
async def get_performance_records(
year: int = Query(...),
month: int = Query(..., ge=1, le=12),
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=100),
user: CurrentUser = Depends(require_approved()),
):
```
FDW 查询时间范围fdw_queries.py:get_service_records
```python
start_date = f"{year}-{month:02d}-01"
if month == 12:
end_date = f"{year + 1}-01-01"
else:
end_date = f"{year}-{month + 1:02d}-01"
```
前端仅有月份切换performance-records.wxml
```xml
<view class="month-switcher">
<view class="month-btn" data-direction="prev" bindtap="switchMonth">...</view>
<text class="month-label">{{monthLabel}}</text>
<view class="month-btn" data-direction="next" bindtap="switchMonth">...</view>
</view>
```
### 建议
1. **评估必要性**:周口径在绩效场景中的实际使用频率较低(助教薪资按月结算),建议与产品确认是否为 MVP 必需。
2. **如需实现**
- 后端PERF-2 增加可选参数 `period_type: str = Query("month", regex="^(month|week)$")`,以及 `week_start: date | None``week_end: date | None`
- FDW 查询:`get_service_records()` 支持自定义时间范围(`start_date`/`end_date`)而非固定月份
- 前端:在月份切换器上方增加 Tab 切换("按月" / "按周"),按周模式下展示"本周/上周"切换器
3. **渐进方案**:可先在前端增加"自定义日期范围"筛选器,后端接受 `start_date`/`end_date` 参数,同时覆盖周口径和任意时间段需求。

View File

@@ -0,0 +1,80 @@
# P8→NS1/RNS1 缺失项 #1三看板 Tab 切换的缓存策略
## 简要结论
- 状态:⚠️ 部分解决
- 风险等级:🔴 高
- 三看板 Tab 切换已实现,但采用页面跳转而非组件切换,切换回来时不保持筛选状态和滚动位置。
## 详细审查
### 审查范围
- `apps/miniprogram/miniprogram/app.json`tabBar 配置)
- `apps/miniprogram/miniprogram/custom-tab-bar/index.ts`(自定义 TabBar
- `apps/miniprogram/miniprogram/components/board-tab-bar/board-tab-bar.ts`(看板内 Tab 组件)
- `apps/miniprogram/miniprogram/pages/board-finance/board-finance.ts`(财务看板 Tab 切换)
- `apps/miniprogram/miniprogram/pages/board-coach/board-coach.ts`(助教看板 Tab 切换)
- `apps/miniprogram/miniprogram/pages/board-customer/board-customer.ts`(客户看板 Tab 切换)
### 发现
1. **Tab 切换方式:页面跳转,非组件内切换**
- `app.json` 中 tabBar 仅注册了 `board-finance` 为 tabBar 页面
- `board-customer``board-coach` 是普通页面(非 tabBar 页面)
-`board-finance` 切换到其他看板使用 `wx.navigateTo()`(页面栈压入)
-`board-coach`/`board-customer` 切换到 `board-finance` 使用 `wx.switchTab()`(清空页面栈)
2. **筛选状态不保持**
- 每个看板页面的筛选状态(`selectedSort``selectedDimension``selectedTime` 等)存储在 Page data 中
- 使用 `wx.navigateTo` 跳转时,离开的页面会被销毁(返回时重新 `onLoad`
- 使用 `wx.switchTab` 回到 `board-finance` 时,该页面会触发 `onShow` 但不会重新 `onLoad`tabBar 页面有缓存)
-`board-coach``board-customer` 作为非 tabBar 页面,每次进入都会重新创建
3. **滚动位置不保持**
- 三个看板页面均无滚动位置保存/恢复逻辑
- 页面重新加载后滚动位置归零
4. **切换动画**
- 未实现 P8 定义的切换动画
- 使用微信默认的页面跳转动画(右滑进入/左滑返回)
### 证据
```typescript
// board-finance.ts — 切换到其他看板
onTabChange(e: WechatMiniprogram.TouchEvent) {
const tab = e.currentTarget.dataset.tab as string
if (tab === 'customer') {
wx.navigateTo({ url: '/pages/board-customer/board-customer' })
} else if (tab === 'coach') {
wx.navigateTo({ url: '/pages/board-coach/board-coach' })
}
}
// board-coach.ts — 切换回财务看板
onTabChange(e: WechatMiniprogram.TouchEvent) {
const tab = e.currentTarget.dataset.tab as string
if (tab === 'finance') {
wx.switchTab({ url: '/pages/board-finance/board-finance' })
} else if (tab === 'customer') {
wx.navigateTo({ url: '/pages/board-customer/board-customer' })
}
}
```
```json
// app.json — 仅 board-finance 是 tabBar 页面
"tabBar": {
"custom": true,
"list": [
{ "pagePath": "pages/task-list/task-list", "text": "任务" },
{ "pagePath": "pages/board-finance/board-finance", "text": "看板" },
{ "pagePath": "pages/my-profile/my-profile", "text": "我的" }
]
}
```
### 建议
1. **方案 A推荐**:将筛选状态持久化到 `getApp().globalData``wx.setStorageSync`,页面 `onLoad` 时恢复
2. **方案 B**:将三个看板合并为一个页面,使用 `wx:if``hidden` 切换内容区域,天然保持状态
3. 滚动位置可通过 `onPageScroll` 记录 + `onLoad``wx.pageScrollTo` 恢复
4. 切换动画可通过 CSS transition 在顶部 Tab 区域实现高亮滑动效果

View File

@@ -0,0 +1,69 @@
# P8→NS1/RNS1 缺失项 #2财务看板分段加载策略
## 简要结论
- 状态:⚠️ 部分解决
- 风险等级:🔴 高
- 后端 BOARD-3 API 一次返回全部 6 板块数据,无 sections 参数支持分段加载;前端仅赠送卡矩阵做了异步加载,其余板块为 mock 静态数据。
## 详细审查
### 审查范围
- `apps/backend/app/routers/xcx_board.py`BOARD-3 路由定义)
- `apps/backend/app/schemas/xcx_board.py`FinanceBoardResponse schema
- `apps/backend/app/services/board_service.py`get_finance_board 服务)
- `apps/miniprogram/miniprogram/pages/board-finance/board-finance.ts`(前端加载逻辑)
### 发现
1. **后端 API 不支持分段加载**
- BOARD-3 路由参数仅有 `time``area``compare`,无 `sections` 参数
- `FinanceBoardResponse` 一次性返回全部 6 个 Panel`overview``recharge``revenue``cashflow``expense``coach_analysis`
- `get_finance_board` 服务函数内部顺序构建所有板块数据,无条件跳过机制
2. **前端加载策略:大部分为 mock 静态数据**
- `board-finance.ts``overview``revenue``cashflow``expense``coachAnalysis` 的数据全部在 Page data 中以空字符串初始化,未调用 API
-`_loadGiftRows()` 方法通过 `fetchBoardFinance()` 加载了赠送卡矩阵(`recharge.giftRows`)和 AI 洞察数据
- 页面 `onLoad` 时直接设置 `pageState: 'normal'`,无整体数据加载流程
3. **唯一的区域条件过滤**
- `recharge` 板块在 `selectedArea !== 'all'` 时通过 `wx:if` 隐藏(前端条件渲染)
- 后端 schema 中 `recharge: RechargePanel | None` 支持 area≠all 时返回 null
### 证据
```python
# xcx_board.py — BOARD-3 路由,无 sections 参数
@router.get("/finance", response_model=FinanceBoardResponse)
async def get_finance_board(
time: FinanceTimeEnum = Query(default=FinanceTimeEnum.month),
area: AreaFilterEnum = Query(default=AreaFilterEnum.all),
compare: int = Query(default=0, ge=0, le=1),
user: CurrentUser = Depends(require_permission("view_board_finance")),
):
# FinanceBoardResponse — 一次返回全部 6 板块
class FinanceBoardResponse(CamelModel):
overview: OverviewPanel
recharge: RechargePanel | None
revenue: RevenuePanel
cashflow: CashflowPanel
expense: ExpensePanel
coach_analysis: CoachAnalysisPanel
```
```typescript
// board-finance.ts — 仅加载赠送卡数据,其余为 mock
async _loadGiftRows() {
const data = await fetchBoardFinance({
time: this.data.selectedTime,
area: this.data.selectedArea,
compare: this.data.compareEnabled ? 1 : 0,
})
// 仅处理 giftRows 和 aiInsights
}
```
### 建议
1. **后端**:为 BOARD-3 API 增加可选 `sections` 查询参数(如 `sections=overview,recharge`),服务层按需构建板块,未请求的板块返回 null
2. **前端**:实现分段加载策略——首次加载仅请求 `overview`,用户滚动到对应板块时再按需请求其余板块(利用 IntersectionObserver 或 onPageScroll 触发)
3. 当前 mock 数据阶段此问题影响不大,但联调前必须完成分段加载改造,否则 6 板块全量查询会导致首屏加载缓慢

View File

@@ -0,0 +1,50 @@
# P8→NS1/RNS1 缺失项 #3客户看板卡片点击跳转到 customer-detail
## 简要结论
- 状态:✅ 已解决
- 风险等级:🟡 低
- 客户卡片已实现 bindtap 事件,点击后通过 wx.navigateTo 跳转到 customer-detail 页面并传递 id 参数。
## 详细审查
### 审查范围
- `apps/miniprogram/miniprogram/pages/board-customer/board-customer.wxml`(卡片模板)
- `apps/miniprogram/miniprogram/pages/board-customer/board-customer.ts`(点击事件处理)
- `apps/miniprogram/miniprogram/app.json`customer-detail 页面注册)
### 发现
1. **WXML 模板中卡片绑定了 bindtap 事件**
- 每个 `customer-card` 元素绑定了 `bindtap="onCustomerTap"`
- 通过 `data-id="{{item.id}}"` 传递客户 ID
- 同时设置了 `hover-class="customer-card--hover"` 提供点击反馈
2. **TS 中实现了跳转逻辑**
- `onCustomerTap` 方法从事件中提取 `id`,使用 `wx.navigateTo` 跳转到 `customer-detail` 页面
- 跳转 URL 格式:`/pages/customer-detail/customer-detail?id=xxx`
3. **customer-detail 页面已注册**
- `app.json` 的 pages 数组中包含 `pages/customer-detail/customer-detail`
- `pages/customer-detail/` 目录存在
### 证据
```html
<!-- board-customer.wxml -->
<view
class="customer-card"
hover-class="customer-card--hover"
wx:for="{{customers}}"
wx:key="id"
data-id="{{item.id}}"
bindtap="onCustomerTap"
>
```
```typescript
// board-customer.ts
onCustomerTap(e: WechatMiniprogram.TouchEvent) {
const id = e.currentTarget.dataset.id as string
wx.navigateTo({ url: '/pages/customer-detail/customer-detail?id=' + id })
},
```

View File

@@ -0,0 +1,80 @@
# P8→NS1/RNS1 缺失项 #4助教看板的"距升档"进度条
## 简要结论
- 状态:⚠️ 部分解决
- 风险等级:🔴 高
- 后端仅返回 `perfGap` 文本字段(如"距升档 13.8h"),无进度百分比数据;`perf-progress-bar` 组件已开发但未在看板页面中使用,看板仅以文字形式展示距升档信息。
## 详细审查
### 审查范围
- `apps/backend/app/schemas/xcx_board.py`CoachBoardItem schema
- `apps/backend/app/services/board_service.py`get_coach_board 服务)
- `apps/miniprogram/miniprogram/pages/board-coach/board-coach.wxml`(看板模板)
- `apps/miniprogram/miniprogram/pages/board-coach/board-coach.ts`(看板逻辑)
- `apps/miniprogram/miniprogram/pages/board-coach/board-coach.json`(组件引用)
- `apps/miniprogram/miniprogram/components/perf-progress-bar/perf-progress-bar.ts`(进度条组件)
### 发现
1. **后端 Schema 无进度百分比字段**
- `CoachBoardItem` 中与升档相关的字段:
- `perf_hours: float` — 当前定档业绩小时数
- `perf_gap: str | None` — 文本描述(如"距升档 13.8h"
- `perf_reached: bool` — 是否已达标
- 缺少:`perf_pct`(进度百分比)、`perf_target`(目标小时数)、`perf_tier`(当前档位)等可视化所需字段
2. **perf-progress-bar 组件已开发,功能完整**
- 组件支持:`filledPct`(填充百分比)、`clampedSparkPct`(火星位置)、`currentTier`(当前档位 0~5`ticks`(刻度数组)
- 支持高光动画和火花动画
- 已在 `task-list``coach-detail` 页面中使用
3. **看板页面未引用进度条组件**
- `board-coach.json``usingComponents` 中无 `perf-progress-bar`
- `board-coach.wxml` 中升档信息仅以文字展示:
- 未达标:`<text class="bottom-right bottom-right--warning">{{item.perfGap}}</text>`
- 已达标:`<text class="bottom-right bottom-right--success">✅ 已达标</text>`
### 证据
```python
# CoachBoardItem — 仅有文本字段,无百分比
class CoachBoardItem(CamelModel):
perf_hours: float = 0.0
perf_hours_before: float | None = None
perf_gap: str | None = None # "距升档 13.8h" 或 None
perf_reached: bool = False
# 缺少: perf_pct, perf_target, current_tier, ticks 等
```
```html
<!-- board-coach.wxml — 仅文字展示,无进度条 -->
<text class="bottom-right bottom-right--warning"
wx:if="{{dimType === 'perf' && !item.perfReached}}">
{{item.perfGap}}
</text>
<text class="bottom-right bottom-right--success"
wx:elif="{{dimType === 'perf' && item.perfReached}}">
✅ 已达标
</text>
```
```json
// board-coach.json — 未引用 perf-progress-bar
{
"usingComponents": {
"coach-level-tag": "/components/coach-level-tag/coach-level-tag",
"filter-dropdown": "/components/filter-dropdown/filter-dropdown",
// ... 无 perf-progress-bar
}
}
```
### 建议
1. **后端**:在 `CoachBoardItem` 中增加进度可视化字段:
- `perf_pct: float` — 当前业绩占目标的百分比0~100
- `perf_target: float` — 当前档位目标小时数
- `current_tier: int` — 当前档位0~5
- `ticks: list[dict]` — 档位刻度数组(复用 `perf-progress-bar` 组件的 ticks 格式)
2. **前端**:在 `board-coach.json` 中引入 `perf-progress-bar` 组件,在卡片的 perf 维度区域渲染进度条
3. 可参考 `coach-detail` 页面中 `perf-progress-bar` 的使用方式

View File

@@ -0,0 +1,61 @@
# P8→NS1/RNS1 缺失项 #5看板数据的实时性标识
## 简要结论
- 状态:❌ 未解决
- 风险等级:🟠 中
- 三个看板页面均无"数据更新于 XX:XX"的展示,后端 API 响应中也无数据截止时间字段。
## 详细审查
### 审查范围
- `apps/backend/app/schemas/xcx_board.py`(三个 Response schema
- `apps/miniprogram/miniprogram/pages/board-finance/board-finance.wxml`
- `apps/miniprogram/miniprogram/pages/board-coach/board-coach.wxml`
- `apps/miniprogram/miniprogram/pages/board-customer/board-customer.wxml`
- 全局搜索关键词:`更新于``updated_at``dataTime``updateTime``截止`
### 发现
1. **后端 Schema 无数据截止时间字段**
- `CoachBoardResponse`:仅包含 `items``dim_type`
- `CustomerBoardResponse`:仅包含维度数据列表
- `FinanceBoardResponse`:仅包含 6 个 Panel无时间戳
- 三个 Response 均无 `data_updated_at``snapshot_time` 等字段
2. **前端页面无数据更新时间展示**
- 全局搜索 `更新于``updated_at``dataTime``updateTime``截止` 在 board 相关文件中均无匹配
- 三个看板页面的 WXML 模板中无任何时间戳展示区域
3. **财务看板有"预估"标签但非实时性标识**
- `board-finance` 中有 `isCurrentMonth` 判断,当月数据显示"(预估)"后缀
- 这是数据性质标识,不是数据截止时间
### 证据
```python
# 三个 Response schema 均无时间戳字段
class CoachBoardResponse(CamelModel):
items: list[CoachBoardItem]
dim_type: str
class FinanceBoardResponse(CamelModel):
overview: OverviewPanel
recharge: RechargePanel | None
revenue: RevenuePanel
cashflow: CashflowPanel
expense: ExpensePanel
coach_analysis: CoachAnalysisPanel
# 缺少: data_updated_at / snapshot_time
```
```
# 全局搜索结果
grep "更新于|updated_at|dataTime|updateTime|截止" **/board-*/**
→ No matches found.
```
### 建议
1. **后端**:在三个 BoardResponse 中增加 `data_updated_at: datetime` 字段,返回 DWS 层最后一次 ETL 刷新时间
2. **前端**在每个看板页面顶部Tab 下方或筛选栏下方)展示"数据更新于 HH:MM"
3. 数据来源可从 ETL 调度记录表(如 `dws.etl_run_log`)获取最后成功执行时间
4. 建议格式:当天数据显示"更新于 14:30",非当天显示"更新于 03-20 14:30"

View File

@@ -0,0 +1,60 @@
# P8→NS1/RNS1 缺失项 #6财务看板环比数据的 tooltip 说明
## 简要结论
- 状态:⚠️ 部分解决
- 风险等级:🟠 中
- 环比数据已展示(↑/↓箭头+数值),环比开关已实现,但点击环比箭头不会显示计算详情 tooltip仅指标名称旁的"?"图标有 tip 弹窗。
## 详细审查
### 审查范围
- `apps/miniprogram/miniprogram/pages/board-finance/board-finance.wxml`(环比展示区域)
- `apps/miniprogram/miniprogram/pages/board-finance/board-finance.ts`(交互逻辑)
### 发现
1. **环比开关已实现**
- 顶部筛选栏有环比开关(`toggleCompare`),点击切换 `compareEnabled` 状态
- 环比数据通过 `wx:if="{{compareEnabled}}"` 条件渲染
2. **环比数据展示格式完整**
- 使用 `↑`/`↓` 箭头 + 数值文本展示环比变化
- 样式区分:上升用 `compare-text-up`(绿色),下降用 `compare-text-down`(红色),持平用 `compare-text-flat`
3. **环比箭头无点击交互**
- 所有 `compare-text-*` 元素均为纯文本展示,无 `bindtap` 事件
- 搜索 `compare.*tap``tooltip``onCompareTap` 在 board-finance 中无匹配
- P8 定义的"点击环比箭头显示计算详情(如:本期 ¥12,000 vs 上期 ¥10,000变化 +20%"未实现
4. **指标名称的"?"帮助图标已实现**
- 各指标旁有 `help-icon` 元素,绑定 `onHelpTap` 事件
- 点击后弹出 `tipContents` 中预定义的说明文案
- 但这是指标含义说明,不是环比计算详情
### 证据
```html
<!-- board-finance.wxml — 环比数据为纯文本,无 bindtap -->
<view class="compare-row" wx:if="{{compareEnabled}}">
<text class="compare-text-up">↑{{overview.occurrenceCompare}}</text>
</view>
<!-- 对比:指标名称的"?"有 bindtap -->
<view class="help-icon-light" data-key="occurrence" bindtap="onHelpTap">?</view>
```
```typescript
// board-finance.ts — tipContents 仅包含指标含义,无环比计算详情
const tipContents: Record<string, { title: string; content: string }> = {
occurrence: {
title: '发生额/正价',
content: '所有消费项目按标价计算的总金额,不扣除任何优惠。...',
},
// ... 无环比计算详情
}
```
### 建议
1. **方案 A轻量**:为环比文本添加 `bindtap` 事件,点击后弹出包含"本期值 vs 上期值 → 变化率"的 tooltip
2. **方案 B完整**:后端在环比数据中返回 `current_value``previous_value``change_pct` 三个字段,前端据此渲染详情弹窗
3. 当前后端 `calc_compare` 函数已计算了 current/previous 值,只需在响应中透传即可

View File

@@ -0,0 +1,58 @@
# P8→NS1/RNS1 缺失项 #7助教看板卡片点击跳转到 coach-detail
## 简要结论
- 状态:✅ 已解决
- 风险等级:🟡 低
- 助教卡片已实现 bindtap 事件,点击后通过 wx.navigateTo 跳转到 coach-detail 页面并传递 id 参数。
## 详细审查
### 审查范围
- `apps/miniprogram/miniprogram/pages/board-coach/board-coach.wxml`(卡片模板)
- `apps/miniprogram/miniprogram/pages/board-coach/board-coach.ts`(点击事件处理)
- `apps/miniprogram/miniprogram/app.json`coach-detail 页面注册)
### 发现
1. **WXML 模板中卡片绑定了 bindtap 事件**
- 每个 `coach-card` 元素绑定了 `bindtap="onCoachTap"`
- 通过 `data-id="{{item.id}}"` 传递助教 ID
- 设置了 `hover-class="coach-card--hover"` 提供点击反馈
2. **TS 中实现了跳转逻辑**
- `onCoachTap` 方法从事件中提取 `id`,使用 `wx.navigateTo` 跳转到 `coach-detail` 页面
- 跳转 URL 格式:`/pages/coach-detail/coach-detail?id=xxx`
3. **coach-detail 页面已注册且功能完整**
- `app.json` 的 pages 数组中包含 `pages/coach-detail/coach-detail`
- `pages/coach-detail/` 目录存在
- coach-detail 页面已引用 `perf-progress-bar` 组件(进度条在详情页可用)
### 证据
```html
<!-- board-coach.wxml -->
<view class="coach-card"
wx:for="{{coaches}}"
wx:key="id"
data-id="{{item.id}}"
bindtap="onCoachTap"
hover-class="coach-card--hover">
```
```typescript
// board-coach.ts
onCoachTap(e: WechatMiniprogram.TouchEvent) {
const id = e.currentTarget.dataset.id as string
wx.navigateTo({ url: '/pages/coach-detail/coach-detail?id=' + id })
},
```
```json
// app.json — coach-detail 已注册
"pages": [
...
"pages/coach-detail/coach-detail",
...
]
```

View File

@@ -0,0 +1,35 @@
# P8→NS1/RNS1 缺失项 #8客户看板"最频繁"维度的柱状图交互
## 简要结论
- 状态:⚠️ 部分解决
- 风险等级:🟠 中
- 柱状图已实现渲染,但缺少点击柱子显示具体数据的交互
## 详细审查
### 审查范围
- `apps/miniprogram/miniprogram/pages/board-customer/board-customer.wxml`
- `apps/miniprogram/miniprogram/pages/board-customer/board-customer.ts`
- `apps/miniprogram/miniprogram/pages/board-customer/board-customer.wxss`
### 发现
1. **柱状图渲染已实现**`board-customer.wxml``dimType === 'freq60'` 时渲染了 `mini-chart` 迷你柱状图,包含 8 周数据、柱子高度百分比、渐变透明度、底部数字
2. **数据结构已定义**`weeklyVisits: Array<{ val: number; pct: number }>` 在 TS 接口中已定义Mock 数据包含 8 个元素
3. **缺少点击交互**:柱状图的 `mini-bar-col` 元素没有 `bindtap` 事件绑定,无法点击柱子查看具体数据
4. **无 tooltip/弹窗组件**:没有实现点击柱子后显示详细数据(如具体到店日期、消费金额等)的 UI
### 证据
WXML 中柱状图部分(无 bindtap
```xml
<view class="mini-bar-col" wx:for="{{item.weeklyVisits}}" wx:for-item="wv" wx:for-index="wIdx" wx:key="wIdx">
<view class="mini-bar" style="height:{{wv.pct}}%;opacity:{{0.2 + wIdx * 0.057}}"></view>
</view>
```
TS 中无柱状图点击处理函数,仅有 `onCustomerTap`(整张卡片点击跳转详情页)。
### 建议
1.`mini-bar-col` 上添加 `bindtap="onBarTap"` 并传递 `data-week-index``data-customer-id`
2. 实现 `onBarTap` 方法,弹出轻量 tooltip 显示该周具体到店次数和日期
3. 或者考虑:由于柱状图尺寸较小(迷你图),点击交互在移动端体验可能不佳,可评估是否改为点击整张卡片进入详情页后查看完整图表

View File

@@ -0,0 +1,57 @@
# P8→NS1/RNS1 缺失项 #9看板页面的下拉刷新行为
## 简要结论
- 状态:✅ 已解决
- 风险等级:🟡 低
- 三个看板页面均已实现下拉刷新
## 详细审查
### 审查范围
- `apps/miniprogram/miniprogram/pages/board-finance/board-finance.json` + `.ts`
- `apps/miniprogram/miniprogram/pages/board-customer/board-customer.json` + `.ts`
- `apps/miniprogram/miniprogram/pages/board-coach/board-coach.json` + `.ts`
### 发现
1. **JSON 配置已启用**:三个页面的 `.json` 文件均设置了 `"enablePullDownRefresh": true`
2. **TS 生命周期已实现**:三个页面均实现了 `onPullDownRefresh()` 方法
3. **刷新逻辑完整**
- `board-finance`:调用 `_loadGiftRows()` 重新加载数据500ms 后 `wx.stopPullDownRefresh()`
- `board-customer`:调用 `loadData()` 重新加载数据500ms 后 `wx.stopPullDownRefresh()`
- `board-coach`:调用 `loadData()` 重新加载数据500ms 后 `wx.stopPullDownRefresh()`
### 证据
board-finance.ts
```typescript
onPullDownRefresh() {
this._loadGiftRows()
setTimeout(() => wx.stopPullDownRefresh(), 500)
},
```
board-customer.ts
```typescript
onPullDownRefresh() {
this.loadData()
setTimeout(() => wx.stopPullDownRefresh(), 500)
},
```
board-coach.ts
```typescript
onPullDownRefresh() {
this.loadData()
setTimeout(() => wx.stopPullDownRefresh(), 500)
},
```
三个页面 JSON 均包含:
```json
"enablePullDownRefresh": true
```
### 建议
无。功能已完整实现。
> 小优化建议(非必须):`setTimeout(() => wx.stopPullDownRefresh(), 500)` 使用固定延时,理想情况应在数据加载完成后再停止刷新动画,避免数据未返回时刷新动画就消失。待 API 联调时可改为 Promise 链式调用。

View File

@@ -0,0 +1,50 @@
# P8→NS1/RNS1 缺失项 #10财务看板各板块的折叠/展开交互
## 简要结论
- 状态:❌ 未解决
- 风险等级:🟡 低
- 财务看板各板块(经营一览、预收资产、应计收入等)没有折叠/展开控制,所有板块始终完全展开
## 详细审查
### 审查范围
- `apps/miniprogram/miniprogram/pages/board-finance/board-finance.ts`
- `apps/miniprogram/miniprogram/pages/board-finance/board-finance.wxml`
### 发现
1. **无折叠状态变量**TS 的 `data` 中没有任何 `collapsed``expanded``folded` 等板块折叠状态字段
2. **无折叠切换方法**TS 中没有 `toggleSection``collapseSection` 等方法
3. **WXML 无折叠控制**:各 `card-section` 没有条件渲染或高度动画控制,所有板块内容始终完全展示
4. **已有替代方案**财务看板实现了目录导航TOC功能用户可通过目录快速跳转到指定板块部分弥补了长页面浏览的不便
5. **吸顶板块头已实现**:滚动时显示当前板块标题的吸顶头,帮助用户定位当前位置
### 证据
WXML 中板块结构(无折叠控制):
```xml
<!-- ===== 板块 1: 经营一览(深色) ===== -->
<view id="section-overview" class="card-section section-dark">
<!-- 内容始终展示,无 wx:if 或 height 动画控制 -->
</view>
<!-- ===== 板块 2: 预收资产 ===== -->
<view id="section-recharge" class="card-section" wx:if="{{selectedArea === 'all'}}">
<!-- 仅按区域筛选条件显示/隐藏,非用户手动折叠 -->
</view>
```
TS 中与板块交互相关的方法仅有:
```typescript
toggleToc() // 目录导航开关
onTocItemTap() // 目录项点击跳转
toggleCompare() // 环比开关
```
### 建议
1. **评估必要性**:当前财务看板已有 TOC 目录导航 + 吸顶板块头,用户可快速定位。折叠/展开在移动端长页面中是常见模式,但 H5 原型是否有此交互需确认
2. **如需实现**
-`data` 中添加 `sectionCollapsed: Record<string, boolean>` 状态
- 在各板块 `card-header` 上添加 `bindtap="toggleSection"` 并传递 `data-section`
- 使用 CSS `max-height` + `transition` 实现展开/收起动画
- 折叠时仅显示板块标题行,展开时显示完整内容
3. **优先级评估**:鉴于已有 TOC 导航,此项可作为体验优化延后处理

View File

@@ -0,0 +1,68 @@
# P8→NS1/RNS1 缺失项 #11看板数据加载失败时的错误展示
## 简要结论
- 状态:✅ 已解决
- 风险等级:🟡 低
- 三个看板页面均已实现错误态展示和重试按钮
## 详细审查
### 审查范围
- `apps/miniprogram/miniprogram/pages/board-finance/board-finance.wxml` + `.ts`
- `apps/miniprogram/miniprogram/pages/board-customer/board-customer.wxml` + `.ts`
- `apps/miniprogram/miniprogram/pages/board-coach/board-coach.wxml` + `.ts`
### 发现
1. **pageState 状态机已定义**:三个页面均定义了 `pageState: 'loading' | 'empty' | 'normal' | 'error'` 四态
2. **错误态 UI 已实现**:三个页面 WXML 均包含 `wx:elif="{{pageState === 'error'}}"` 条件渲染的错误态视图
3. **重试按钮已实现**:错误态视图中均包含 `bindtap="onRetry"` 的重试按钮
4. **onRetry 方法已实现**:三个页面 TS 均实现了 `onRetry()` 方法,调用 `loadData()` 或相应的数据加载方法
5. **loadData 中有 catch 处理**`board-customer``board-coach``loadData()` 使用 try-catchcatch 中设置 `pageState: 'error'`
### 证据
board-coach.wxml 错误态:
```xml
<view class="page-error" wx:elif="{{pageState === 'error'}}">
<t-empty description="加载失败" />
<view class="retry-btn" bindtap="onRetry">点击重试</view>
</view>
```
board-customer.wxml 错误态(结构一致):
```xml
<view class="page-error" wx:elif="{{pageState === 'error'}}">
<t-empty description="加载失败" />
<view class="retry-btn" bindtap="onRetry">点击重试</view>
</view>
```
board-finance.wxml 错误态:
```xml
<view class="page-error" wx:elif="{{pageState === 'error'}}">
<t-empty description="加载失败" />
<view class="retry-btn" bindtap="onRetry">
<text class="retry-btn-text">点击重试</text>
</view>
</view>
```
board-coach.ts 错误处理:
```typescript
loadData() {
this.setData({ pageState: 'loading' })
setTimeout(() => {
try {
// ...
} catch {
this.setData({ pageState: 'error' })
}
}, 400)
},
onRetry() {
this.loadData()
},
```
### 建议
无。功能已完整实现。三个看板页面均具备 loading → normal/empty/error 四态切换,错误态有 `<t-empty>` 空态组件 + 重试按钮。

View File

@@ -0,0 +1,59 @@
# P8→NS1/RNS1 缺失项 #12筛选项的动画效果
## 简要结论
- 状态:✅ 已解决
- 风险等级:🟡 低
- filter-dropdown 组件已实现展开/收起动画,包括面板滑入、箭头旋转、遮罩层
## 详细审查
### 审查范围
- `apps/miniprogram/miniprogram/components/filter-dropdown/filter-dropdown.ts`
- `apps/miniprogram/miniprogram/components/filter-dropdown/filter-dropdown.wxml`
- `apps/miniprogram/miniprogram/components/filter-dropdown/filter-dropdown.wxss`
### 发现
1. **面板展开/收起动画已实现**WXSS 中 `.dropdown-panel` 使用 `opacity` + `transform: translateY` 过渡动画展开时从上方滑入0.25s ease
2. **箭头旋转动画已实现**`.filter-arrow` 使用 `transform: rotate(180deg)` + `transition: 0.25s ease` 实现展开时箭头翻转
3. **遮罩层已实现**:展开时显示半透明黑色遮罩 `rgba(0, 0, 0, 0.5)`,点击遮罩关闭下拉
4. **按钮状态变化已实现**:展开时按钮边框变为主色调 `--color-primary`,背景变为浅色 `--color-primary-light`,有 0.2s 过渡
5. **面板定位动态计算**:展开时通过 `createSelectorQuery` 计算按钮底部位置,面板从该位置展开
### 证据
WXSS 动画定义:
```css
/* 面板展开/收起动画 */
.dropdown-panel {
opacity: 0;
transform: translateY(-16rpx);
transition: opacity 0.25s ease, transform 0.25s ease;
pointer-events: none;
}
.dropdown-panel--show {
opacity: 1;
transform: translateY(0);
pointer-events: auto;
}
/* 箭头旋转动画 */
.filter-arrow {
transition: transform 0.25s ease;
}
.filter-arrow--up {
transform: rotate(180deg);
}
/* 按钮状态过渡 */
.filter-dropdown {
transition: border-color 0.2s, background-color 0.2s;
}
```
WXML 动画触发:
```xml
<view class="dropdown-panel {{expanded ? 'dropdown-panel--show' : ''}}" style="top: {{panelTop}}px">
```
### 建议
无。动画效果已完整实现包含面板滑入opacity + translateY、箭头旋转rotate 180deg、按钮状态变化border-color + background-color均使用 CSS transition 实现,性能良好。

View File

@@ -0,0 +1,61 @@
# P8→NS1/RNS1 缺失项 #13助教看板的排名序号展示
## 简要结论
- 状态:❌ 未解决
- 风险等级:🟡 低
- 助教卡片列表中没有排名序号(如 #1#2#3)展示
## 详细审查
### 审查范围
- `apps/miniprogram/miniprogram/pages/board-coach/board-coach.wxml`
- `apps/miniprogram/miniprogram/pages/board-coach/board-coach.ts`
- `apps/miniprogram/miniprogram/pages/board-coach/board-coach.wxss`
### 发现
1. **无排名序号渲染**WXML 中 `coach-card``wx:for` 循环没有使用 `wx:for-index` 来展示排名序号
2. **无排名字段**TS 的 `CoachItem` 接口中没有 `rank` 字段
3. **无排名样式**WXSS 中没有 `rank``序号``number` 等相关样式类
4. **卡片结构**:当前卡片结构为 `头像 → 姓名+等级+技能+右侧指标 → 底部客户列表`,没有排名序号的位置
### 证据
WXML 中助教列表渲染(无排名序号):
```xml
<view class="coach-list">
<view class="coach-card" wx:for="{{coaches}}" wx:key="id"
data-id="{{item.id}}" bindtap="onCoachTap"
hover-class="coach-card--hover">
<view class="card-row">
<!-- 头像 -->
<view class="card-avatar avatar-{{item.avatarGradient}}">
<text class="avatar-text">{{item.initial}}</text>
</view>
<!-- 信息区(无排名序号) -->
<view class="card-info">
...
</view>
</view>
</view>
</view>
```
CoachItem 接口(无 rank 字段):
```typescript
interface CoachItem {
id: string
name: string
initial: string
avatarGradient: string
level: string
// ... 无 rank 字段
}
```
### 建议
1. **评估必要性**:排名序号在看板场景中有助于快速识别排名位置,但也会增加视觉噪音。需确认 P8 原型中是否明确要求显示
2. **如需实现**
- 方案 A推荐利用 `wx:for``index` 直接渲染,在头像左侧或上方添加 `#{{index + 1}}` 序号
- 方案 B在卡片左上角添加小圆形排名徽章前 3 名用金/银/铜色区分
- 在 WXML 的 `card-row` 开头添加:`<text class="rank-num">#{{index + 1}}</text>`
3. **客户看板同理**:如果助教看板需要排名序号,客户看板的列表也应考虑一致性

View File

@@ -0,0 +1,96 @@
# P8→NS1/RNS1 缺失项 #14财务看板数字的格式化规范
## 简要结论
- 状态:✅ 已解决
- 风险等级:🟡 低
- 千分位、小数位、货币符号的格式化规范已在设计文档中定义工具函数已实现TS + WXS 双版本),财务看板已使用
## 详细审查
### 审查范围
- `docs/miniprogram-dev/design-system/DISPLAY-STANDARDS.md`
- `apps/miniprogram/miniprogram/utils/money.ts`
- `apps/miniprogram/miniprogram/utils/format.wxs`
- `apps/miniprogram/miniprogram/pages/board-finance/board-finance.ts`
- `apps/miniprogram/miniprogram/pages/board-coach/board-coach.ts`
### 发现
#### 1. 设计规范文档已完善
`DISPLAY-STANDARDS.md` 第 1 章"金额展示规范"明确定义了:
- 正常金额:`¥N,NNN`(千分位逗号,无小数)
- 负数金额:`-¥N,NNN`(负号在 ¥ 前)
- 零值:`¥0`
- 空值:`--`
- 大额金额:不简写,保留完整数字
- 禁止事项:禁止 `¥-368``¥0.00``¥12万``toLocaleString()`
#### 2. TS 工具函数已实现(`utils/money.ts`
- `formatMoney(value)` — 金额格式化,千分位 + ¥ 前缀
- `formatCount(value, unit)` — 计数格式化,千分位 + 单位
- `formatNumber(value)` — 纯数字千分位
- `formatPercent(value)` — 百分比,保留 1 位小数
- `formatTrendValue(value)` — 同比/环比差值,+¥/-¥ 前缀
- `toProgressWidth(value)` — 进度条宽度,截断至 [0, 100]
#### 3. WXS 工具函数已实现(`utils/format.wxs`
- `money(value)` — 金额格式化WXS 版,用于 WXML 模板)
- `count(value, unit)` — 计数格式化
- `percent(value)` — 百分比
- `hours(value)` — 课时格式化
- `trendValue(value)` — 同比/环比差值
- `safe(val)` — 空值兜底
#### 4. 看板页面已使用格式化函数
- `board-finance.ts`:导入并使用 `formatMoney` 格式化赠送卡矩阵数据
- `board-coach.ts`:导入并使用 `formatMoney``formatCount``formatHours` 格式化助教数据
- `board-finance.wxml`:未引入 `format.wxs`(财务看板数据在 TS 层预格式化后传入模板)
- `board-customer.wxml`:引入 `format.wxs`,使用 `fmt.safe()` 兜底
### 证据
DISPLAY-STANDARDS.md 金额规范:
```markdown
| 场景 | 格式 | 示例 |
|---|---|---|
| 正常金额 | `¥N,NNN`(千分位逗号,无小数) | `¥12,680` |
| 负数金额 | `-¥N,NNN`(负号在 ¥ 前) | `-¥368` |
| 零值 | `¥0` | `¥0` |
| 空值 / undefined | `--` | `--` |
```
money.ts 核心函数:
```typescript
export function formatMoney(value: number | null | undefined): string {
if (value === null || value === undefined) return '--'
if (value === 0) return '¥0'
const abs = Math.round(Math.abs(value))
const formatted = abs.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
return value < 0 ? `${formatted}` : `¥${formatted}`
}
```
format.wxs 金额函数WXS 版,逻辑一致):
```javascript
function money(value) {
if (value === undefined || value === null) return '--'
if (value === 0) return '¥0'
// ... 千分位处理
return (value < 0 ? '-¥' : '¥') + result
}
```
board-finance.ts 使用示例:
```typescript
import { formatMoney } from '../../utils/money'
// ...
wine: formatMoney(row.liquor?.value),
table: formatMoney(row.tableFee?.value),
```
### 建议
无。格式化规范已完整覆盖:
- 设计文档DISPLAY-STANDARDS.md定义了规则
- TS 工具函数money.ts供 JS 层使用
- WXS 工具函数format.wxs供 WXML 模板使用
- 看板页面已实际调用格式化函数

View File

@@ -0,0 +1,65 @@
# P9→NS1/RNS1 缺失项 #1客户详情页分段加载策略
## 简要结论
- 状态:⚠️ 部分解决
- 风险等级:🔴 高
- 后端采用单 API 返回全部数据(无分段端点),但各扩展模块有独立 try/except 优雅降级;前端无 skeleton 占位,仅有全局 loading toast。
## 详细审查
### 审查范围
- `apps/backend/app/routers/xcx_customers.py` — CUST-1 路由
- `apps/backend/app/services/customer_service.py``get_customer_detail()` 实现
- `apps/miniprogram/miniprogram/pages/customer-detail/customer-detail.ts` — 前端加载逻辑
- `apps/miniprogram/miniprogram/pages/customer-detail/customer-detail.wxml` — 前端模板
### 发现
1. **后端:单 API无分段端点**
- `GET /api/xcx/customers/{customer_id}` 一次性返回全部数据(基本信息 + Banner + AI 洞察 + 关联助教 + 最亲密助教 + 消费记录 + 备注)
- 无独立的 `/ai-insight``/notes``/records` 等子端点
- P9 定义的"基本信息→消费汇总→AI 洞察→消费记录→备注"分段加载策略未实现
2. **后端:优雅降级已实现**
- 各扩展模块(`ai_insight``retention_clues``notes``consumption_records``coach_tasks``favorite_coaches`)均有独立 try/except失败时降级为空默认值
- 核心字段member_info失败直接 500符合预期
3. **前端:无 skeleton 占位**
- 加载态为全局 `g-toast-loading`(圆形 loading + "加载中..."文字),非 P9 定义的分段 skeleton
- `loadDetail()` 调用单个 `fetchCustomerDetail(id)` 后一次性 setData
- 无分段渲染逻辑(先展示基本信息,再逐步加载扩展模块)
### 证据
后端 `get_customer_detail()` 一次性返回所有模块:
```python
return {
"id": customer_id, "name": name, "phone": phone, ...
"balance": balance, "consumption_60d": consumption_60d, ...
"ai_insight": ai_insight,
"coach_tasks": coach_tasks,
"favorite_coaches": favorite_coaches,
"retention_clues": retention_clues,
"consumption_records": consumption_records,
"notes": notes,
}
```
前端加载逻辑(无分段):
```typescript
async loadDetail(id?: string) {
this.setData({ pageState: 'loading' })
try {
if (id) {
const detail = await fetchCustomerDetail(id)
// 一次性 setData
}
this.setData({ pageState: 'normal' })
} catch { ... }
}
```
### 建议(如未完全解决)
1. **短期**:前端可在单 API 返回后,先渲染 Banner 区域,再用 `nextTick``setTimeout` 分批 setData 扩展模块,减少首屏白屏时间
2. **中期**:为各扩展模块添加 skeleton 占位组件(参考 TDesign `t-skeleton`
3. **长期**:后端拆分为多个子端点(`/basic``/ai-insight``/records` 等),前端并行请求 + 分段渲染

View File

@@ -0,0 +1,60 @@
# P9→NS1/RNS1 缺失项 #2助教详情页档位进度时间轴的视觉规范
## 简要结论
- 状态:⚠️ 部分解决
- 风险等级:🟠 中
- 已实现 `perf-progress-bar` 进度条组件(含渐变填充、刻度标记、高光/火花动画),但非 P9 定义的"时间轴"样式,缺少当前档位高亮节点和升档动画。
## 详细审查
### 审查范围
- `apps/backend/app/schemas/xcx_coaches.py``tier_nodes` 字段定义
- `apps/backend/app/services/coach_service.py``_build_tier_nodes()` 实现
- `apps/miniprogram/miniprogram/components/perf-progress-bar/` — 进度条组件
- `apps/miniprogram/miniprogram/pages/coach-detail/coach-detail.wxml` — 绩效概览区域
- `apps/miniprogram/miniprogram/pages/coach-detail/coach-detail.ts` — 进度条数据构建
### 发现
1. **后端tier_nodes 数据已返回**
- `CoachDetailResponse.tier_nodes: list[float]` 已定义
- `_build_tier_nodes()` 从配置表读取档位节点
2. **前端:进度条组件已实现,但非时间轴**
- `perf-progress-bar` 组件实现了水平进度条,含:
- 渐变填充条(`ppb-fill` + `ppb-gradient-bar`
- 刻度标记(`ppb-ticks`,由 `ticks` 数组动态渲染)
- 高光动画(`ppb-shine`)和火花动画(`ppb-spark`
- 当前档位高亮(`ppb-tick--done` class
- 但这是**水平进度条**,非 P9 定义的**垂直时间轴**样式
- 缺少 P9 定义的"档位节点"tierNodes的时间轴展示如里程碑节点、连接线、当前位置标记
3. **动画已实现但非"升档动画"**
- 高光shine和火花spark是循环播放的装饰动画
- 非 P9 定义的"升档时触发的庆祝动画"
4. **前端 tier_nodes 未使用 API 数据**
- `coach-detail.ts``tierNodes` 硬编码为 `[0, 100, 130, 160, 190, 220]`,注释标注 "Mock实际由接口返回"
- API 返回的 `tier_nodes` 未被前端消费
### 证据
前端硬编码 tierNodes
```typescript
const tierNodes = [0, 100, 130, 160, 190, 220] // Mock实际由接口返回
```
进度条组件刻度渲染(水平进度条,非时间轴):
```html
<view class="ppb-ticks">
<text wx:for="{{ticks}}" wx:key="value"
class="ppb-tick {{currentTier >= index ? 'ppb-tick--done' : ''}}">
{{item.label}}
</text>
</view>
```
### 建议(如未完全解决)
1. 将前端 `tierNodes` 改为使用 API 返回的 `detail.tierNodes` 数据
2. 如需时间轴样式,新建 `tier-timeline` 组件,展示垂直时间轴(里程碑节点 + 连接线 + 当前位置)
3. 添加升档动画:当 `currentTier` 变化时触发一次性庆祝效果

View File

@@ -0,0 +1,57 @@
# P9→NS1/RNS1 缺失项 #3消费记录 3 种类型的图标/颜色/标签样式映射
## 简要结论
- 状态:⚠️ 部分解决
- 风险等级:🔴 高
- 台桌消费和商城消费有颜色区分(蓝色/绿色 header + 圆点),但充值类型在客户详情页 wxml 中缺少渲染模板;设计规范文档中无消费记录类型的视觉映射定义。
## 详细审查
### 审查范围
- `apps/backend/app/schemas/xcx_customers.py``ConsumptionRecord.type` 字段
- `apps/backend/app/services/customer_service.py``_build_consumption_records()` 实现
- `apps/miniprogram/miniprogram/pages/customer-detail/customer-detail.wxml` — 消费记录展示
- `apps/miniprogram/miniprogram/pages/customer-detail/customer-detail.wxss` — 消费记录样式
- `docs/miniprogram-dev/design-system/` — 设计规范文档
### 发现
1. **后端type 字段已定义但实际只返回 "table"**
- Schema 定义 `type: str # table / shop / recharge`
- `_build_consumption_records()` 硬编码 `"type": "table"`,未根据实际数据区分商城/充值类型
- 前端 TypeScript 接口定义了 `type: "table" | "shop" | "recharge"` 三种类型
2. **前端:台桌和商城有视觉区分,充值缺失**
- 台桌消费(`type === 'table'`):蓝色 header`record-header-blue`+ 蓝色圆点(`record-dot-blue`
- 商城消费(`type === 'shop'`):绿色 header`record-header-green`+ 绿色圆点(`record-dot-green`
- 充值(`type === 'recharge'`**wxml 中无对应的渲染模板**`wx:elif` 链中缺少 recharge 分支)
- Mock 数据中有 `{ type: 'recharge', rechargeAmount: 0 }` 但无对应 UI
3. **设计规范文档中无消费记录类型映射**
- `VI-DESIGN-SYSTEM.md``DISPLAY-STANDARDS.md` 中未定义消费记录类型的图标/颜色/标签映射
- 无统一的类型→视觉映射表
### 证据
后端硬编码 type 为 "table"
```python
result.append({
"type": "table", # 始终为 table未区分 shop/recharge
...
})
```
前端 wxml 缺少 recharge 分支:
```html
<view class="record-card" wx:if="{{item.type === 'table'}}">...</view>
<view class="record-card" wx:elif="{{item.type === 'shop'}}">...</view>
<!-- 缺少 wx:elif="{{item.type === 'recharge'}}" -->
```
### 建议(如未完全解决)
1. **后端**`_build_consumption_records()` 根据结算单类型字段区分 table/shop/recharge
2. **前端**:添加 recharge 类型的渲染模板(建议橙色/金色 header充值图标
3. **设计规范**:在 `VI-DESIGN-SYSTEM.md` 中添加消费记录类型映射表:
- 台桌消费:🎱 蓝色(`#3b82f6`
- 商城消费:🛒 绿色(`#22c55e`
- 充值:💰 橙色/金色(`#f59e0b`

View File

@@ -0,0 +1,73 @@
# P9→NS1/RNS1 缺失项 #4备注 AI 评分的星级展示规范
## 简要结论
- 状态:⚠️ 部分解决
- 风险等级:🟠 中
- `star-rating` 组件已实现0-10 分→0-5 星,支持半星),设计规范文档已定义评分展示规范,但客户详情页备注区域未使用该组件,后端备注 API 未返回 `ai_score` 字段。
## 详细审查
### 审查范围
- `apps/miniprogram/miniprogram/components/star-rating/` — 星级评分组件
- `apps/backend/app/services/customer_service.py``_build_notes()` 实现
- `apps/backend/app/schemas/xcx_customers.py``CustomerNote` schema
- `apps/miniprogram/miniprogram/pages/customer-detail/customer-detail.wxml` — 备注区域
- `docs/miniprogram-dev/design-system/DISPLAY-STANDARDS-2.md` — 评分展示规范
### 发现
1. **组件层star-rating 已完整实现**
- 接收 `score`0-10内部转换为 0-5 星,支持半星
- 使用 TDesign `t-rate` 组件渲染,金黄色(`#fbbf24`
- 支持只读模式
2. **设计规范:评分展示规范已定义**
- `DISPLAY-STANDARDS-2.md` 第 8 节定义了:
- 分制约定(后端 0-10 分UI 0-5 星)
- 展示场景(任务卡片、备注满意度等)
- 半星映射规则(`scoreToHalfStar()`
- 未评分态处理(`score=0/null/undefined` → 展示 `--`
3. **后端:备注 API 未返回 ai_score**
- `_build_notes()` 查询 `biz.notes` 表,只返回 `id``tag_label``created_at``content`
- `CustomerNote` schema 无 `ai_score` / `score` 字段
- 数据库 `biz.notes` 表是否有 `ai_score` 列未确认
4. **前端:备注区域未使用 star-rating 组件**
- `customer-detail.wxml` 备注列表只展示 `tagLabel``createdAt``content`
-`<star-rating>` 组件引用
- `customer-detail.json` 未注册 `star-rating` 组件(需确认)
### 证据
后端 `_build_notes()` 无 score 字段:
```python
return [
{
"id": r[0],
"tag_label": r[1] or "",
"created_at": r[2].isoformat() if r[2] else "",
"content": r[3] or "",
# 缺少 ai_score / score 字段
}
for r in rows
]
```
前端备注展示无星级:
```html
<view class="note-item" wx:for="{{sortedNotes}}" wx:key="id">
<view class="note-top">
<text class="note-author">{{item.tagLabel}}</text>
<text class="note-time">{{item.createdAt}}</text>
</view>
<text class="note-content">{{item.content}}</text>
<!-- 缺少 <star-rating score="{{item.score}}" /> -->
</view>
```
### 建议(如未完全解决)
1. **后端**`_build_notes()` 查询中增加 `ai_score` 字段(如 `biz.notes` 表有该列)
2. **Schema**`CustomerNote` 添加 `score: int | None = None`
3. **前端**:备注卡片中添加 `<star-rating score="{{item.score}}" size="32rpx" />`,未评分时展示 `--`
4. **tooltip**P9 定义的评分说明 tooltip 需额外实现(小程序原生不支持 tooltip可用长按弹窗替代

View File

@@ -0,0 +1,61 @@
# P9→NS1/RNS1 缺失项 #5客户详情页 Banner 区域的视觉设计
## 简要结论
- 状态:✅ 已解决
- 风险等级:🟡 低
- Banner 区域已实现完整的 4 字段布局(储值余额/60天消费/理想间隔/距今到店),含渐变背景、毛玻璃统计栏、颜色区分。
## 详细审查
### 审查范围
- `apps/backend/app/schemas/xcx_customers.py` — Banner 字段定义
- `apps/backend/app/services/customer_service.py` — Banner 字段查询
- `apps/miniprogram/miniprogram/pages/customer-detail/customer-detail.wxml` — Banner 区域模板
- `apps/miniprogram/miniprogram/pages/customer-detail/customer-detail.wxss` — Banner 区域样式
### 发现
1. **后端4 个 Banner 字段已定义并实现**
- `balance: float | None` — 储值余额
- `consumption_60d: float | None` — 60 天消费
- `ideal_interval: int | None` — 理想间隔
- `days_since_visit: int | None` — 距今到店
- 各字段独立 try/except查询失败降级为 `null`
2. **前端Banner 布局已完整实现**
- SVG 渐变背景图(`banner-bg-dark-gold-aurora.svg`
- 客户头部信息(头像 + 姓名 + 手机号查看/复制)
- 4 格统计栏(`banner-stats`),毛玻璃效果(`backdrop-filter: blur(8px)`
- 颜色区分:余额绿色(`stat-green #6ee7b7`)、距今到店琥珀色(`stat-amber #fcd34d`
3. **布局与 RNS1 T2-1 定义一致**
- 4 个字段均已展示,布局为水平等分 + 分隔线
### 证据
前端 Banner 统计区域:
```html
<view class="banner-stats">
<view class="stat-item stat-border">
<text class="stat-value stat-green">¥{{fmt.safe(detail.balance)}}</text>
<text class="stat-label">储值余额</text>
</view>
<view class="stat-item stat-border">
<text class="stat-value">¥{{fmt.safe(detail.consumption60d)}}</text>
<text class="stat-label">60天消费</text>
</view>
<view class="stat-item stat-border">
<text class="stat-value">{{fmt.safe(detail.idealInterval)}}</text>
<text class="stat-label">理想间隔</text>
</view>
<view class="stat-item">
<text class="stat-value stat-amber">{{fmt.safe(detail.daysSinceVisit)}}</text>
<text class="stat-label">距今到店</text>
</view>
</view>
```
### 建议(如未完全解决)
无重大缺失。可考虑的微调:
- `ideal_interval` 后端当前返回 `None`,需确认数据源是否已接入
- 可添加单位后缀(如"天"、"元")提升可读性

View File

@@ -0,0 +1,55 @@
# P9→NS1/RNS1 缺失项 #6AI 洞察卡片的展示规范
## 简要结论
- 状态:⚠️ 部分解决
- 风险等级:🟠 中
- AI 洞察卡片已实现标题/摘要/策略列表展示,但缺少 P9 定义的"展开详情"交互和"刷新"按钮。
## 详细审查
### 审查范围
- `apps/backend/app/schemas/xcx_customers.py``AiInsight` schema
- `apps/backend/app/services/customer_service.py``_build_ai_insight()` 实现
- `apps/miniprogram/miniprogram/pages/customer-detail/customer-detail.wxml` — AI 洞察区域
- `apps/miniprogram/miniprogram/pages/customer-detail/customer-detail.wxss` — AI 洞察样式
### 发现
1. **后端AI 洞察数据已实现**
- `AiInsight` schema 含 `summary: str``strategies: list[AiStrategy]`
- `_build_ai_insight()``biz.ai_cache``cache_type='app4_analysis'`)读取缓存的 AI 分析结果
- 无缓存时返回空默认值
2. **前端:卡片展示已实现**
- 渐变背景卡片(紫色渐变 `#667eea → #764ba2`
- AI 图标 + "AI 智能洞察"标题
- 摘要文本展示
- 策略列表(带颜色左边框,支持 green/amber/pink 三色)
3. **缺失交互**
- **无"展开详情"功能**P9 定义了摘要可展开查看完整分析,当前实现直接展示全部内容
- **无"刷新"按钮**P9 定义了手动触发 AI 重新分析的刷新按钮,当前无此交互
- 后端也无对应的"触发 AI 重新分析"端点
### 证据
前端 AI 洞察卡片(无展开/刷新交互):
```html
<view class="ai-insight-card">
<view class="ai-insight-header">
<view class="ai-icon-box">...</view>
<text class="ai-insight-label">AI 智能洞察</text>
<!-- 缺少刷新按钮 -->
</view>
<view class="ai-insight-summary-v">
<text class="ai-insight-summary">{{fmt.safe(aiInsight.summary)}}</text>
<!-- 无展开/折叠控制 -->
</view>
...
</view>
```
### 建议(如未完全解决)
1. **展开详情**:如摘要较长,可添加 `wx:if="{{aiExpanded}}"` 控制展示行数,默认 3 行 + "查看更多"
2. **刷新按钮**:在 header 右侧添加刷新图标,点击调用后端 AI 分析端点
3. **后端**:添加 `POST /api/xcx/customers/{id}/ai-refresh` 端点触发重新分析

View File

@@ -0,0 +1,58 @@
# P9→NS1/RNS1 缺失项 #7关联助教任务列表的展示规范
## 简要结论
- 状态:✅ 已解决
- 风险等级:🟡 低
- 关联助教任务列表已完整实现:任务类型标签(带颜色映射)、状态标签(置顶/已放弃)、服务统计指标(服务次数/总时长/次均时长),布局和交互完整。
## 详细审查
### 审查范围
- `apps/backend/app/schemas/xcx_customers.py``CoachTask` schema
- `apps/backend/app/services/customer_service.py``_build_coach_tasks()` 实现
- `apps/miniprogram/miniprogram/pages/customer-detail/customer-detail.wxml` — 助教任务区域
- `apps/miniprogram/miniprogram/pages/customer-detail/customer-detail.wxss` — 助教任务样式
### 发现
1. **后端:任务数据已完整实现**
- `CoachTask` schema 含:`name``level``level_color``task_type``task_color``bg_class``status``last_service``metrics`
- `_build_coach_tasks()``biz.coach_tasks` 查询,关联助教信息和绩效等级
- 任务类型通过 `TASK_TYPE_MAP` 映射(含 label/color/bg_class
- 近 60 天统计指标(服务次数/总时长/次均时长)已计算
2. **前端:展示已完整实现**
- 任务类型标签:`type-red`/`type-pink`/`type-orange`/`type-teal` 四色映射
- 状态标签:📌 置顶 / ❌ 已放弃
- 助教等级标签:使用 `coach-level-tag` 组件
- 服务统计3 个指标卡片(服务次数/总时长/次均时长)
- 上次服务时间展示
- 卡片背景色根据助教等级区分(`coach-card-red`/`pink`/`orange`/`teal`
### 证据
前端任务卡片展示:
```html
<view class="coach-task-card {{item.bgClass}}" wx:for="{{coachTasks}}" wx:key="index">
<view class="coach-task-top">
<view class="coach-name-row">
<text class="coach-name">{{item.name}}</text>
<coach-level-tag level="{{item.level}}" />
</view>
<view class="coach-task-right">
<text class="coach-task-type type-{{item.taskColor}}">{{item.taskType}}</text>
<text class="coach-task-status status-{{item.status}}" wx:if="{{item.status !== 'normal'}}">...</text>
</view>
</view>
<text class="coach-last-service">上次服务:{{item.lastService}}</text>
<view class="coach-metrics">
<view class="coach-metric" wx:for="{{item.metrics}}" wx:for-item="m" wx:key="label">
<text class="metric-label">{{m.label}}</text>
<text class="metric-value">{{m.value}}</text>
</view>
</view>
</view>
```
### 建议(如未完全解决)
无重大缺失。当前实现已覆盖 P9 定义的任务类型图标(用颜色标签替代)、状态标签、服务统计。

View File

@@ -0,0 +1,60 @@
# P9→NS1/RNS1 缺失项 #8最亲密助教的展示规范
## 简要结论
- 状态:⚠️ 部分解决
- 风险等级:🟠 中
- 最亲密助教卡片已实现emoji + 姓名 + 关系指数 + 统计指标),但缺少 P9 定义的"关系指数可视化"(如仪表盘/环形图)和"课时统计图表"。
## 详细审查
### 审查范围
- `apps/backend/app/schemas/xcx_customers.py``FavoriteCoach` schema
- `apps/backend/app/services/customer_service.py``_build_favorite_coaches()` 实现
- `apps/miniprogram/miniprogram/pages/customer-detail/customer-detail.wxml` — 最亲密助教区域
- `apps/miniprogram/miniprogram/pages/customer-detail/customer-detail.wxss` — 最亲密助教样式
### 发现
1. **后端:数据已完整实现**
- `FavoriteCoach` schema 含:`emoji``name``relation_index``index_color``bg_class``stats`
- `_build_favorite_coaches()` 从关系指数表查询,四级 emoji 映射(💖🧡💛💙)
- 统计指标:基础课时收入、激励课时收入、上课次数、总时长
2. **前端:卡片展示已实现**
- emoji + 姓名 + 关系指数数值(带颜色)
- "近60天"时间范围标签
- 4 个统计指标卡片
- 卡片背景色根据关系等级区分(`fav-card-pink`/`fav-card-amber`
3. **缺失的可视化元素**
- **无关系指数可视化**P9 定义了关系指数的可视化展示(如仪表盘、环形进度条),当前仅展示数值
- **无课时统计图表**P9 定义了课时统计的图表展示(如柱状图/折线图),当前仅展示数值列表
### 证据
前端最亲密助教卡片(纯数值展示,无图表):
```html
<view class="fav-coach-card {{item.bgClass}}" wx:for="{{favoriteCoaches}}" wx:key="index">
<view class="fav-coach-top">
<view class="fav-coach-name">
<text class="fav-emoji">{{item.emoji}}</text>
<text class="fav-name">{{item.name}}</text>
</view>
<view class="fav-index">
<text class="fav-index-label">关系指数</text>
<text class="fav-index-value text-{{item.indexColor}}">{{fmt.safe(item.relationIndex)}}</text>
</view>
</view>
<view class="fav-stats">
<view class="fav-stat" wx:for="{{item.stats}}" wx:for-item="s" wx:key="label">
<text class="fav-stat-label">{{s.label}}</text>
<text class="fav-stat-value">{{s.value}}</text>
</view>
</view>
</view>
```
### 建议(如未完全解决)
1. **关系指数可视化**:可用 CSS 环形进度条或 `wx-canvas` 绘制仪表盘,展示 0-10 刻度上的当前位置
2. **课时统计图表**:如需图表,可引入 `wx-charts` 或用 CSS 柱状图展示近期课时趋势
3. **优先级评估**:当前数值展示已满足基本信息需求,图表可作为后续优化项

View File

@@ -0,0 +1,57 @@
# P9→NS1/RNS1 缺失项 #9消费记录中助教明细子列表的展开/折叠交互
## 简要结论
- 状态:✅ 已解决
- 风险等级:🟡 低
- 客户详情页消费记录中的助教明细子列表已实现展示(网格布局),但采用的是"始终展开"策略而非"展开/折叠"交互。考虑到数据量通常较小1-2 位助教),这是合理的设计决策。
## 详细审查
### 审查范围
- `apps/backend/app/schemas/xcx_customers.py``ConsumptionRecord.coaches` 字段
- `apps/backend/app/services/customer_service.py``_build_consumption_records()` coaches 构建
- `apps/miniprogram/miniprogram/pages/customer-detail/customer-detail.wxml` — 消费记录助教明细
### 发现
1. **后端coaches 子数组已实现**
- `ConsumptionRecord.coaches: list[CoachServiceItem]` 已定义
- `CoachServiceItem` 含:`name``level``level_color``course_type``hours``perf_hours``fee`
- `_build_consumption_records()` 根据 `assistant_pd_money`/`assistant_cx_money` 构建 coaches 列表
2. **前端:助教明细已展示(始终展开)**
- 使用 `record-coach-grid`2 列网格布局)展示助教卡片
- 每张卡片含:助教姓名 + 等级标签 + 课程类型 + 时长 + 定档绩效 + 费用
- 条件渲染:`wx:if="{{item.coaches && item.coaches.length > 0}}"`
- 台桌消费和商城消费均有助教明细展示
3. **无展开/折叠交互**
- 助教明细始终展开显示,无折叠按钮
- 考虑到每条消费记录通常只有 1-2 位助教,始终展开是合理的
- P9 定义的展开/折叠交互更适用于助教数量较多的场景
### 证据
前端助教明细展示(始终展开):
```html
<view class="record-coaches" wx:if="{{item.coaches && item.coaches.length > 0}}">
<view class="record-coach-grid">
<view class="record-coach-card" wx:for="{{item.coaches}}" wx:for-item="c" wx:key="name">
<view class="record-coach-name-row">
<text class="record-coach-name">{{c.name}}</text>
<coach-level-tag level="{{c.level}}" />
</view>
<text class="record-coach-type">{{c.courseType}} · {{c.hours}}</text>
<view class="record-coach-bottom">
<text class="record-coach-perf" wx:if="{{c.perfHours}}">定档绩效:{{c.perfHours}}</text>
<text class="record-coach-fee">¥{{c.fee}}</text>
</view>
</view>
</view>
</view>
```
### 建议(如未完全解决)
当前实现已满足需求。如后续助教数量增多(>3 位),可考虑:
1. 默认展示前 2 位,超出部分折叠
2. 添加"展开全部"/"收起"按钮

View File

@@ -0,0 +1,79 @@
# P9→NS1/RNS1 缺失项 #10助教详情页 TOP20 客户列表的排序规则和展示字段
## 简要结论
- 状态:✅ 已解决
- 风险等级:🟡 低
- TOP20 客户列表已完整实现:后端通过 `fdw_queries.get_coach_top_customers()` 获取排序后的数据limit=20前端展示含头像、姓名、心形 emoji、关系分、服务次数、储值余额、消费金额支持展开/收起和点击跳转。
## 详细审查
### 审查范围
- `apps/backend/app/schemas/xcx_coaches.py``TopCustomer` schema
- `apps/backend/app/services/coach_service.py``_build_top_customers()` 实现
- `apps/miniprogram/miniprogram/pages/coach-detail/coach-detail.wxml` — TOP20 客户区域
- `apps/miniprogram/miniprogram/pages/coach-detail/coach-detail.ts` — 展开/收起逻辑
### 发现
1. **后端:排序和数据已完整实现**
- `_build_top_customers()` 调用 `fdw_queries.get_coach_top_customers(conn, site_id, coach_id, limit=20)`
- 排序由 `fdw_queries` 层的 SQL 决定(按关系指数/服务次数等排序)
- 关系指数通过 `fdw_queries.get_relation_index()` 获取
- 四级 heart emoji 映射P6 AC3>8.5→💖 / >7→🧡 / >5→💛 / ≤5→💙
- 展示字段:`id``name``initial``avatar_gradient``heart_emoji``relation_score``score_color``service_count``balance``consume`
2. **前端:展示已完整实现**
- 默认展示前 5 位,点击"查看更多"展开全部
- 每行含:头像(渐变色首字母)+ 姓名 + 心形 emoji + 关系分(带颜色)+ 服务次数 + 储值余额 + 消费金额
- 点击跳转客户详情页
- 右侧箭头图标
3. **后端 schema 字段名不一致(小问题)**
- Schema 定义 `score: str`,但后端实际返回 `relation_score`
- CamelModel 自动转换为 camelCase前端使用 `item.score`
### 证据
后端 TOP 客户构建(含排序和 emoji 映射):
```python
raw = fdw_queries.get_coach_top_customers(conn, site_id, coach_id, limit=20)
# ...
result.append({
"id": mid or 0,
"name": name,
"heart_emoji": heart_emoji, # 四级映射
"relation_score": f"{score:.2f}",
"score_color": score_color,
"service_count": cust.get("service_count", 0),
"balance": _format_currency(balance),
"consume": _format_currency(consume),
})
```
前端展示(默认 5 条 + 展开):
```html
<view class="top-customer-item"
wx:for="{{topCustomers}}" wx:key="id"
wx:if="{{topCustomersExpanded || index < 5}}"
data-id="{{item.id}}" bindtap="onCustomerTap">
<view class="top-customer-avatar avatar-{{item.avatarGradient}}">
<text>{{item.initial}}</text>
</view>
<view class="top-customer-info">
<view class="top-customer-name-row">
<text>{{item.name}}</text>
<text>{{item.heartEmoji}}</text>
<text>{{fmt.safe(item.score)}}</text>
</view>
<view class="top-customer-stats">
<text>服务 {{fmt.safe(item.serviceCount)}}次</text>
<text>储值 {{fmt.safe(item.balance)}}</text>
<text>消费 {{fmt.safe(item.consume)}}</text>
</view>
</view>
</view>
```
### 建议(如未完全解决)
1. **前端数据绑定**:当前 `coach-detail.ts``topCustomers` 使用 mock 数据(空字符串),需确认 API 数据已正确绑定
2. **Schema 字段名**`TopCustomer.score` 与后端返回的 `relation_score` 需确认 CamelModel 映射是否正确

View File

@@ -0,0 +1,67 @@
# P9→NS1/RNS1 缺失项 #11助教详情页历史月份统计的图表展示
## 简要结论
- 状态:⚠️ 部分解决
- 风险等级:🟠 中
- 历史月份统计已实现为表格形式6 个月数据,含客户数/回访召回/业绩时长/工资),但非 P9 定义的折线图/柱状图可视化形式。
## 详细审查
### 审查范围
- `apps/backend/app/schemas/xcx_coaches.py``HistoryMonth` schema
- `apps/backend/app/services/coach_service.py``_build_history_months()` 实现
- `apps/miniprogram/miniprogram/pages/coach-detail/coach-detail.wxml` — 历史月份区域
- `apps/miniprogram/miniprogram/pages/coach-detail/coach-detail.ts` — 数据绑定
### 发现
1. **后端:数据已完整实现**
- `HistoryMonth` schema 含:`month``estimated``customers``hours``salary``callback_done``recall_done`
- `_build_history_months()` 查询最近 6 个月数据:
- 工时/工资:`fdw_queries.get_salary_calc_multi_months()`
- 客户数:`fdw_queries.get_monthly_customer_count()`
- 回访/召回完成数:`biz.coach_tasks` 聚合
- 本月标记 `estimated=True`
- 格式化:`"22人"``"87.5h"``"¥6,950"`
2. **前端:表格展示已实现**
- 5 列表格:月份 / 服务客户 / 访/召完成 / 业绩时长 / 工资
- 本月行高亮(`history-row-current`+ "预估"标签
- 本月业绩时长蓝色(`text-primary`)、工资绿色(`text-success`
3. **缺失的图表可视化**
- P9 定义了折线图/柱状图展示趋势,当前仅为纯表格
- 无数据趋势可视化(如工时趋势折线、工资柱状图)
- 小程序环境下图表实现需要 `wx-canvas` 或第三方图表库
### 证据
前端表格展示:
```html
<view class="history-table">
<view class="history-thead">
<text class="history-th history-th-left">月份</text>
<text class="history-th">服务客户</text>
<text class="history-th">访/召完成</text>
<text class="history-th">业绩时长</text>
<text class="history-th">工资</text>
</view>
<view class="history-row {{index === 0 ? 'history-row-current' : ''}}"
wx:for="{{historyMonths}}" wx:key="month">
<view class="history-td history-td-left">
<text>{{item.month}}</text>
<text class="history-est" wx:if="{{item.estimated}}">预估</text>
</view>
<text class="history-td">{{fmt.safe(item.customers)}}</text>
<text class="history-td">{{fmt.safe(item.callbackDone)}} | {{fmt.safe(item.recallDone)}}</text>
<text class="history-td">{{fmt.safe(item.hours)}}</text>
<text class="history-td">{{fmt.safe(item.salary)}}</text>
</view>
</view>
```
### 建议(如未完全解决)
1. **短期**:表格形式已满足数据展示需求,可作为 MVP
2. **中期**在表格上方添加迷你折线图sparkline展示工时/工资趋势
3. **长期**:引入 `wx-charts``echarts-for-weixin` 实现完整图表
4. **注意**:前端 `historyMonths` 当前使用 mock 数据(空字符串),需确认 API 数据已正确绑定

View File

@@ -0,0 +1,68 @@
# P9→NS1/RNS1 缺失项 #12客户服务记录页的月度统计汇总展示
## 简要结论
- 状态:✅ 已解决
- 风险等级:🟡 低
- 月度统计汇总已完整实现:后端返回 `month_count`/`month_hours`,前端展示为三栏统计条(本月服务次数/服务时长/关系指数),含月份切换器和格式化展示。
## 详细审查
### 审查范围
- `apps/backend/app/schemas/xcx_customers.py``CustomerRecordsResponse` schema
- `apps/backend/app/services/customer_service.py``get_customer_records()` 实现
- `apps/miniprogram/miniprogram/pages/customer-service-records/customer-service-records.wxml` — 月度统计区域
- `apps/miniprogram/miniprogram/pages/customer-service-records/customer-service-records.ts` — 数据处理
### 发现
1. **后端:月度统计数据已实现**
- `CustomerRecordsResponse``month_count: int``month_hours: float`
- `get_customer_records()` 调用 `_get_month_aggregation()` 计算当月汇总(非分页子集)
- 返回 `total_service_count`(跨月累计)
2. **前端:月度统计展示已完整实现**
- 月份切换器(上月/下月箭头 + 月份标签)
- 三栏统计条:
- 本月服务:`monthCount`(如 "3次"
- 服务时长:`monthHours`(如 "12.5h"),蓝色高亮
- 关系指数:`monthRelation`(如 "0.85"),橙色高亮
- 使用 `formatCount()``formatHours()` 格式化函数
- 月份切换时重新请求 API`loadMonthRecords()`
3. **月份切换交互已实现**
- `onPrevMonth()` / `onNextMonth()` 切换月份
- 边界控制:`canPrev`/`canNext` 禁用按钮
- 切换时显示 loading 状态
### 证据
前端月度统计展示:
```html
<view class="month-summary">
<view class="summary-item">
<text class="summary-label">本月服务</text>
<text class="summary-value">{{fmt.safe(monthCount)}}</text>
</view>
<view class="summary-divider"></view>
<view class="summary-item">
<text class="summary-label">服务时长</text>
<text class="summary-value value-primary">{{fmt.safe(monthHours)}}</text>
</view>
<view class="summary-divider"></view>
<view class="summary-item">
<text class="summary-label">关系指数</text>
<text class="summary-value value-warning">{{fmt.safe(monthRelation)}}</text>
</view>
</view>
```
后端月度汇总查询:
```python
month_count, month_hours = _get_month_aggregation(
conn, site_id, customer_id, year, month, table
)
```
### 建议(如未完全解决)
无重大缺失。可考虑的微调:
- `monthRelation`(关系指数)当前前端硬编码为 `'0.85'`,后端 `CustomerRecordsResponse` 中无 `relation_index` 字段返回,需确认数据源

View File

@@ -0,0 +1,54 @@
# P9→NS1/RNS1 缺失项 #13助教详情页任务分组的视觉区分
## 简要结论
- 状态:✅ 已解决
- 风险等级:🟢 低
- 后端按 active/inactive/abandoned 三组返回数据,前端为每组定义了差异化视觉样式
## 详细审查
### 审查范围
- `apps/backend/app/services/coach_service.py``_build_task_groups()`
- `apps/miniprogram/miniprogram/pages/coach-detail/coach-detail.wxml` — 任务执行区域
- `apps/miniprogram/miniprogram/pages/coach-detail/coach-detail.wxss` — 任务样式
### 发现
1. 后端 `_build_task_groups()` 按 status 分为三组:`visible_tasks`active`hidden_tasks`inactive`abandoned_tasks`abandoned数据结构清晰
2. 前端 WXML 中三组有明确的视觉区分:
- `visibleTasks`:直接展示,每个 task-item 带 `task-item-{{item.typeClass}}` 样式(如 `task-item-high-priority``task-item-priority``task-item-relationship``task-item-callback`),有彩色背景和边框
- `hiddenTasks`:在 `tasksExpanded` 展开后显示,样式与 visible 一致但位于 `task-list-extra` 区域
- `abandonedTasks`:使用 `task-item-abandoned` 样式,灰色背景 `#fafafa``opacity: 0.55`、客户名带删除线 `text-decoration: line-through`
3. WXSS 中定义了完整的颜色映射:
- `task-item-high-priority`:红色系 `rgba(254, 226, 226, 0.6)`
- `task-item-priority`:橙色系 `rgba(255, 237, 213, 0.4)`
- `task-item-relationship`:粉色系 `rgba(252, 231, 243, 0.4)`
- `task-item-callback`:青色系 `rgba(204, 251, 241, 0.4)`
- `task-item-abandoned`:灰色 + 半透明
### 证据
```html
<!-- coach-detail.wxml — active 任务 -->
<view class="task-item task-item-{{item.typeClass}}" wx:for="{{visibleTasks}}" ...>
<text class="task-tag-text {{item.typeClass}}">{{item.typeLabel}}</text>
...
</view>
<!-- abandoned 任务 -->
<view class="task-item task-item-abandoned" wx:for="{{abandonedTasks}}" ...>
<text class="task-abandoned-name">{{item.customerName}}</text>
<text class="task-abandoned-reason">{{item.reason}}</text>
</view>
```
```css
/* coach-detail.wxss */
.task-item-abandoned {
background: #fafafa;
border-color: #eeeeee;
opacity: 0.55;
}
.task-abandoned-name {
text-decoration: line-through;
color: #c5c5c5;
}
```

View File

@@ -0,0 +1,54 @@
# P9→NS1/RNS1 缺失项 #14客户详情页维客线索的展示规范
## 简要结论
- 状态:✅ 已解决
- 风险等级:🟢 低
- clue-card 组件已实现完整的卡片布局和 category 颜色映射6 种配色),后端从 `member_retention_clue` 表查询数据
## 详细审查
### 审查范围
- `apps/backend/app/services/customer_service.py``_build_retention_clues()`
- `apps/miniprogram/miniprogram/pages/customer-detail/customer-detail.wxml` — 维客线索区域
- `apps/miniprogram/miniprogram/components/clue-card/clue-card.ts` — 组件属性
- `apps/miniprogram/miniprogram/components/clue-card/clue-card.wxml` — 组件模板
- `apps/miniprogram/miniprogram/components/clue-card/clue-card.wxss` — 组件样式
### 发现
1. 后端 `_build_retention_clues()``public.member_retention_clue` 表查询 `clue_type``clue_text`,按 `created_at` 倒序
2. 前端 `clue-card` 组件接收 `tag``category`(颜色类名)、`emoji``title``source``content` 六个属性
3. WXSS 中定义了 VI 规范 2.1 的六种客户标签配色:
- `clue-tag-primary`:蓝色(客户基础)
- `clue-tag-success`:绿色(消费习惯)
- `clue-tag-orange`:橙色(玩法偏好)
- `clue-tag-gold` / `clue-tag-warning`:金色(促销偏好)
- `clue-tag-purple`:紫色(社交关系)
- `clue-tag-error`:红色(重要反馈)
- `clue-tag-pink`:粉色(社交关系别名)
4. 卡片布局包含标签方块72rpx×72rpx、文本内容区、来源标注、可选详情描述
### 证据
```html
<!-- customer-detail.wxml -->
<clue-card
wx:for="{{clues}}" wx:key="index"
tag="{{item.category}}"
category="{{item.categoryColor}}"
emoji=""
title="{{item.text}}"
source="By:{{item.source}}"
content="{{item.detail}}"
/>
```
```css
/* clue-card.wxss — VI 规范 2.1 六种配色 */
.clue-tag-primary { background: rgba(0, 82, 217, 0.10); color: #0052d9; }
.clue-tag-success { background: rgba(0, 168, 112, 0.10); color: #00a870; }
.clue-tag-orange { background: rgba(237, 123, 47, 0.12); color: #ed7b2f; }
.clue-tag-purple { background: rgba(123, 97, 255, 0.10); color: #7b61ff; }
.clue-tag-error { background: rgba(227, 77, 89, 0.10); color: #e34d59; }
```
### 建议
- 后端 `_build_retention_clues()` 当前仅返回 `type``text`,未返回 `category`(颜色类名)和 `source`。前端 `customer-detail.ts``clues` 数据目前是 mock 硬编码的 `categoryColor`。建议后端补充 `clue_type → categoryColor` 的映射逻辑,或在前端建立映射表。

View File

@@ -0,0 +1,33 @@
# P9→NS1/RNS1 缺失项 #15客户详情页的分享功能
## 简要结论
- 状态:❌ 未解决
- 风险等级:🟡 低
- 客户详情页未实现任何分享功能,无 `onShareAppMessage`、无分享按钮
## 详细审查
### 审查范围
- `apps/miniprogram/miniprogram/pages/customer-detail/customer-detail.ts` — 页面逻辑
- `apps/miniprogram/miniprogram/pages/customer-detail/customer-detail.wxml` — 页面模板
### 发现
1. `customer-detail.ts` 中未定义 `onShareAppMessage()``onShareTimeline()` 生命周期方法
2. `customer-detail.wxml` 中无 `<button open-type="share">` 按钮
3. 底部操作栏仅有"问问助手"和"备注"两个按钮,无分享入口
4. 全文搜索 `share` 关键词在 customer-detail 目录下无匹配
### 证据
```html
<!-- customer-detail.wxml — 底部操作栏,无分享按钮 -->
<view class="bottom-bar safe-area-bottom">
<view class="btn-chat" bindtap="onStartChat">问问助手</view>
<view class="btn-note" bindtap="onAddNote">备注</view>
</view>
```
### 建议
- 如需实现分享功能,需在 `customer-detail.ts` 中添加 `onShareAppMessage()` 方法
- 可在底部操作栏或页面右上角添加分享按钮
- 分享内容应包含客户基本信息脱敏后的姓名、ID接收方打开后跳转到客户详情页
- 注意:分享内容中不应包含敏感信息(完整手机号、余额等)

View File

@@ -0,0 +1,41 @@
# P9→NS1/RNS1 缺失项 #16助教详情页的联系方式展示
## 简要结论
- 状态:❌ 未解决
- 风险等级:🟡 低
- 助教详情页未展示任何联系方式(电话/微信),后端也未返回相关字段
## 详细审查
### 审查范围
- `apps/backend/app/services/coach_service.py``get_coach_detail()` 返回字段
- `apps/miniprogram/miniprogram/pages/coach-detail/coach-detail.wxml` — 页面模板
- `apps/miniprogram/miniprogram/pages/coach-detail/coach-detail.ts` — 页面逻辑
### 发现
1. 后端 `get_coach_detail()` 返回的字段中无 `phone``mobile``wechat` 等联系方式字段
2. 后端 `fdw_queries.get_assistant_info()` 返回的 `assistant_info` 中仅使用了 `name``avatar``level``skills``work_years``hire_date`
3. 前端 WXML 中 Banner 区域仅展示:头像、姓名、等级标签、技能标签、工龄、客户数
4. 全文搜索 `phone|mobile|wechat|微信|电话|联系` 在 coach-detail 目录下无匹配
### 证据
```python
# coach_service.py — get_coach_detail() 返回值,无联系方式
return {
"id": coach_id,
"name": assistant_info.get("name", ""),
"avatar": assistant_info.get("avatar", ""),
"level": ...,
"skills": ...,
"work_years": ...,
"customer_count": ...,
"hire_date": ...,
# 无 phone / wechat 字段
}
```
### 建议
- 如需展示联系方式,需确认数据来源(飞球 SaaS 是否提供助教手机号/微信号)
- 后端需在 `get_assistant_info()` 查询中增加联系方式字段
- 前端在 Banner 区域或"更多信息"卡片中添加联系方式展示
- 联系方式应做脱敏处理,提供"点击查看"交互

View File

@@ -0,0 +1,46 @@
# P9→NS1/RNS1 缺失项 #17消费记录的时间范围筛选
## 简要结论
- 状态:❌ 未解决
- 风险等级:🟡 低
- 仅支持按月切换(上/下月箭头),未实现自定义日期范围筛选
## 详细审查
### 审查范围
- `apps/miniprogram/miniprogram/pages/customer-service-records/customer-service-records.ts` — 页面逻辑
- `apps/miniprogram/miniprogram/pages/customer-service-records/customer-service-records.wxml` — 页面模板
- `apps/backend/app/services/customer_service.py``get_customer_records()` API
### 发现
1. 前端仅实现了 `onPrevMonth()``onNextMonth()` 两个月份切换方法
2. WXML 中月份切换 UI 为左右箭头 + 月份标签(如"2026年3月"),无日期选择器
3. 后端 `get_customer_records()` 接收 `year``month` 参数,按月查询
4. 无自定义日期范围的 API 参数(如 `start_date``end_date`
5. 月份范围有边界控制:`minYearMonth`(数据起始)到 `maxYearMonth`(当前月)
### 证据
```html
<!-- customer-service-records.wxml — 仅月份切换 -->
<view class="month-switcher">
<view class="month-btn" bindtap="onPrevMonth">
<t-icon name="chevron-left" />
</view>
<text class="month-label">{{monthLabel}}</text>
<view class="month-btn" bindtap="onNextMonth">
<t-icon name="chevron-right" />
</view>
</view>
```
```typescript
// customer-service-records.ts — 仅按月请求
async loadMonthRecords(customerId: string, year: number, month: number) { ... }
```
### 建议
- 如需自定义日期范围,需:
1. 前端添加日期范围选择器组件(如 TDesign `t-date-time-picker`
2. 后端 `get_customer_records()` 增加 `start_date` / `end_date` 可选参数
3. 月份切换和自定义范围可共存,自定义范围优先级更高
- 当前按月切换已满足基本需求,自定义范围为增强功能,优先级可后置

View File

@@ -0,0 +1,46 @@
# P9→NS1/RNS1 缺失项 #18详情页的返回按钮行为
## 简要结论
- 状态:⚠️ 部分解决
- 风险等级:🟡 低
- customer-service-records 页面有明确的 `navigateBack` 返回逻辑customer-detail 和 coach-detail 页面依赖小程序默认导航栏返回,未自定义返回行为
## 详细审查
### 审查范围
- `apps/miniprogram/miniprogram/pages/customer-detail/customer-detail.ts`
- `apps/miniprogram/miniprogram/pages/customer-detail/customer-detail.wxml`
- `apps/miniprogram/miniprogram/pages/coach-detail/coach-detail.ts`
- `apps/miniprogram/miniprogram/pages/coach-detail/coach-detail.wxml`
- `apps/miniprogram/miniprogram/pages/customer-service-records/customer-service-records.ts`
### 发现
1. `customer-service-records.ts` 定义了 `onBack()` 方法,调用 `wx.navigateBack()`
2. `customer-detail.ts``coach-detail.ts` 均未定义自定义返回方法
3. 两个详情页的 WXML 中均无自定义返回按钮,依赖微信小程序默认导航栏的返回按钮
4. 默认导航栏返回行为是 `navigateBack`(返回上一页),而非 `navigateTo`(返回列表页)
5. 如果用户通过分享链接直接进入详情页(页面栈为空),默认返回按钮无法返回列表页
### 证据
```typescript
// customer-service-records.ts — 有明确返回逻辑
onBack() {
wx.navigateBack()
}
// customer-detail.ts — 无返回方法
// coach-detail.ts — 无返回方法
```
### 建议
- 对于通过分享链接直接进入的场景,建议在详情页添加返回兜底逻辑:
```typescript
onBack() {
if (getCurrentPages().length > 1) {
wx.navigateBack()
} else {
wx.redirectTo({ url: '/pages/customer-list/customer-list' })
}
}
```
- 当前依赖默认导航栏返回在正常导航流程中可正常工作,仅在直接进入场景有问题

View File

@@ -0,0 +1,47 @@
# P9→NS1/RNS1 缺失项 #19客户详情页电话号码的脱敏展示
## 简要结论
- 状态:✅ 已解决
- 风险等级:🟢 低
- 后端已实现 `_mask_phone()` 脱敏函数(中间 4 位用 `****`),前端实现了"点击查看/复制"交互
## 详细审查
### 审查范围
- `apps/backend/app/services/customer_service.py``_mask_phone()` 函数、`get_customer_detail()` 返回
- `apps/miniprogram/miniprogram/pages/customer-detail/customer-detail.wxml` — 电话展示区域
- `apps/miniprogram/miniprogram/pages/customer-detail/customer-detail.ts` — 电话交互逻辑
- `apps/miniprogram/miniprogram/pages/customer-service-records/customer-service-records.ts` — 同样的脱敏逻辑
### 发现
1. 后端 `_mask_phone()` 实现:`phone[:3] + "****" + phone[-4:]`,如 `139****5678`
2. 后端 `get_customer_detail()` 同时返回 `phone`(脱敏)和 `phone_full`(完整),供前端按需展示
3. customer-detail 前端实现了 `phoneVisible` 状态切换:
- 默认显示脱敏号码 `138****5678`
- 点击"查看"按钮切换为完整号码
- 显示完整号码后按钮变为"复制",调用 `wx.setClipboardData`
4. customer-service-records 页面也实现了相同的脱敏+查看交互:
- 前端自行脱敏:`detail.phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')`
- 同样有 `onTogglePhone()``onCopyPhone()` 方法
### 证据
```python
# customer_service.py
def _mask_phone(phone: str | None) -> str:
"""手机号脱敏139****5678 格式。"""
if not phone or len(phone) < 7:
return phone or ""
return f"{phone[:3]}****{phone[-4:]}"
```
```html
<!-- customer-detail.wxml -->
<text class="phone">{{phoneVisible ? detail.phone : '138****5678'}}</text>
<view class="phone-toggle-btn" bindtap="{{phoneVisible ? 'onCopyPhone' : 'onTogglePhone'}}">
<text class="phone-toggle-text">{{phoneVisible ? '复制' : '查看'}}</text>
</view>
```
### 建议
- customer-detail.wxml 中脱敏号码硬编码为 `138****5678`,应改为使用后端返回的 `phone` 字段(已脱敏)
- 建议统一脱敏策略:要么全部由后端脱敏,要么全部由前端脱敏,避免两端重复实现

View File

@@ -0,0 +1,43 @@
# P9→NS1/RNS1 缺失项 #20助教详情页"收入明细"的展开/折叠交互
## 简要结论
- 状态:⚠️ 部分解决
- 风险等级:🟡 低
- 实现了本月/上月 Tab 切换交互,但不是 P9 定义的展开/折叠交互4 项收入明细始终全部展示
## 详细审查
### 审查范围
- `apps/backend/app/services/coach_service.py``_build_income()` 返回结构
- `apps/miniprogram/miniprogram/pages/coach-detail/coach-detail.ts``switchIncomeTab()`
- `apps/miniprogram/miniprogram/pages/coach-detail/coach-detail.wxml` — 收入明细区域
### 发现
1. 后端 `_build_income()` 返回 `this_month``last_month` 各 4 项:基础课时费、激励课时费、充值提成、酒水提成
2. 前端实现了 Tab 切换交互(`onIncomeTabTap``switchIncomeTab`),在"本月"和"上月"之间切换
3. 切换后重新计算合计金额并更新 `currentIncome``incomeTotal`
4. 收入明细列表始终全部展示 4 项,无展开/折叠逻辑
5. P9 定义的"展开/折叠"交互未实现——当前是 Tab 切换而非折叠面板
### 证据
```html
<!-- coach-detail.wxml — Tab 切换,非展开/折叠 -->
<view class="income-tabs">
<view class="income-tab {{incomeTab === 'this' ? 'active' : ''}}" data-tab="this" bindtap="onIncomeTabTap">
<text>本月</text><text class="income-tab-est" wx:if="{{incomeTab === 'this'}}">预估</text>
</view>
<view class="income-tab {{incomeTab === 'last' ? 'active' : ''}}" data-tab="last" bindtap="onIncomeTabTap">
<text>上月</text>
</view>
</view>
<!-- 收入列表始终全部展示 -->
<view class="income-list">
<view class="income-item" wx:for="{{currentIncome}}" wx:key="label">...</view>
<view class="income-total">...</view>
</view>
```
### 建议
- 当前 Tab 切换交互在功能上已满足"查看本月/上月收入"的需求
- 如需严格对齐 P9 的展开/折叠设计,可在收入明细区域添加折叠逻辑:默认仅显示合计,点击展开显示 4 项明细
- 考虑到仅 4 项数据,全部展示的体验可能优于折叠,建议与产品确认是否需要折叠

View File

@@ -0,0 +1,42 @@
# P9→NS1/RNS1 缺失项 #21客户服务记录中"饮品描述"字段的展示
## 简要结论
- 状态:✅ 已解决
- 风险等级:🟢 低
- `drinks` 字段已在数据模型、API 传递、组件属性、组件模板中完整贯通
## 详细审查
### 审查范围
- `apps/miniprogram/miniprogram/pages/customer-service-records/customer-service-records.ts``ServiceRecord` 接口、数据映射
- `apps/miniprogram/miniprogram/pages/customer-service-records/customer-service-records.wxml` — 组件调用
- `apps/miniprogram/miniprogram/components/service-record-card/service-record-card.ts` — 组件属性
- `apps/miniprogram/miniprogram/components/service-record-card/service-record-card.wxml` — 组件模板
### 发现
1. `customer-service-records.ts``ServiceRecord` 接口定义了 `drinks: string`(注释:商品/饮品描述)
2. 数据映射中从 API 响应提取:`drinks: r.drinks || ''`
3. WXML 中通过 `service-record-card` 组件传递:`drinks="{{item.drinks}}"`
4. `service-record-card` 组件定义了 `drinks` 属性:`{ type: String, value: '' }`
5. 组件模板中在第二行展示:`<text class="svc-drinks">{{drinks || '—'}}</text>`,无数据时显示破折号
### 证据
```typescript
// customer-service-records.ts — ServiceRecord 接口
interface ServiceRecord {
/** 商品/饮品描述 */
drinks: string
// ...
}
// 数据映射
drinks: r.drinks || '',
```
```html
<!-- customer-service-records.wxml — 传递给组件 -->
<service-record-card drinks="{{item.drinks}}" ... />
<!-- service-record-card.wxml — 展示 -->
<text class="svc-drinks">{{drinks || '—'}}</text>
```

View File

@@ -0,0 +1,68 @@
# P9→NS1/RNS1 缺失项 #22详情页各模块的加载失败独立处理
## 简要结论
- 状态:✅ 已解决
- 风险等级:🟢 低
- 后端对每个扩展模块使用独立 try/except 优雅降级;前端 customer-service-records 也实现了模块级独立错误处理
## 详细审查
### 审查范围
- `apps/backend/app/services/customer_service.py``get_customer_detail()` 错误处理
- `apps/backend/app/services/coach_service.py``get_coach_detail()` 错误处理
- `apps/miniprogram/miniprogram/pages/customer-service-records/customer-service-records.ts` — 前端错误处理
### 发现
#### 后端 — customer_service.py
1. 核心字段member_info查询失败 → 直接抛 500/404
2. Banner 字段balance、consumption_60d、days_since_visit各自独立 try/except失败降级为 `None`
3. 扩展模块ai_insight、retention_clues、notes、consumption_records、coach_tasks、favorite_coaches各自独立 try/except失败降级为空默认值
4. 注释明确标注:"核心字段查询失败 → 500扩展模块查询失败 → 空默认值(优雅降级)"
#### 后端 — coach_service.py
1. 核心字段assistant_info查询失败 → 直接抛 500/404
2. 扩展模块income、tier_nodes、top_customers、service_records、task_groups、notes、history_months各自独立 try/except
3. 每个模块失败时 logger.warning 记录日志,降级为空默认值
#### 前端 — customer-service-records.ts
1. `loadCustomerInfo()` 失败不阻塞记录展示:`catch` 中仅 `console.error`,注释"客户信息加载失败不阻塞记录展示"
2. `loadMonthRecords()` 失败设置 `pageState: 'error'`,提供重试按钮
### 证据
```python
# customer_service.py — 每个模块独立 try/except
try:
ai_insight = _build_ai_insight(customer_id, conn)
except Exception:
logger.warning("构建 aiInsight 失败,降级为空", exc_info=True)
ai_insight = {"summary": "", "strategies": []}
try:
retention_clues = _build_retention_clues(customer_id, conn)
except Exception:
logger.warning("构建 retentionClues 失败,降级为空列表", exc_info=True)
retention_clues = []
```
```python
# coach_service.py — 同样的模式
try:
income = _build_income(conn, site_id, coach_id, now)
except Exception:
logger.warning("构建 income 失败,降级为空", exc_info=True)
income = {"this_month": [], "last_month": []}
```
```typescript
// customer-service-records.ts — 客户信息加载失败不阻塞
async loadCustomerInfo(id: string) {
try { ... } catch (err) {
console.error('[customer-service-records] loadCustomerInfo failed:', err)
// 客户信息加载失败不阻塞记录展示
}
}
```
### 建议
- 前端 customer-detail.ts 的 `loadDetail()` 目前是单一 try/catch所有数据在一个请求中获取。如果后端某个模块降级为空前端会正常展示空状态但如果整个 API 请求失败,所有模块都不可用。这是可接受的设计,因为核心数据和扩展数据在同一个 API 调用中返回。

View File

@@ -0,0 +1,59 @@
# P9→NS1/RNS1 缺失项 #23客户详情页"可用月数"的计算说明
## 简要结论
- 状态:❌ 未解决
- 风险等级:🟡 低
- `available_months` 字段仅在客户看板 Schema 中定义,客户详情页 API 未返回该字段,计算公式未在代码中实现
## 详细审查
### 审查范围
- `apps/backend/app/services/customer_service.py``get_customer_detail()` 返回字段
- `apps/backend/app/schemas/xcx_board.py` — Schema 定义
- `apps/backend/app/services/board_service.py` — 看板服务
- `apps/backend/app/services/fdw_queries.py` — 数据查询
### 发现
1. `xcx_board.py` 中定义了 `available_months: str # "约0.8个月"``monthly_consume: float`
2. `fdw_queries.py` 中查询了 `monthly_consume` 字段(来自 `v_dws_member_consumption_summary`
3. 但在整个后端代码中,未找到 `available_months` 的计算赋值逻辑——仅有 Schema 定义
4. `customer_service.py``get_customer_detail()` 返回字段中无 `available_months`
5. 客户详情页前端也未展示"可用月数"字段
6. P9 定义的计算公式(余额 ÷ 月均消耗)和边界处理(月均消耗为 0 时)均未实现
### 证据
```python
# xcx_board.py — 仅有 Schema 定义
class CustomerBoardItem(BaseModel):
monthly_consume: float
available_months: str # "约0.8个月"
# customer_service.py — get_customer_detail() 返回值中无 available_months
return {
"id": customer_id,
"name": name,
"phone": phone,
"balance": balance,
"consumption_60d": consumption_60d,
"days_since_visit": days_since_visit,
# 无 available_months
}
```
```
# 全局搜索 available_months 赋值逻辑 → 无结果
# 仅在 Schema 定义中出现
```
### 建议
1.`customer_service.py``board_service.py` 中实现计算逻辑:
```python
def _calc_available_months(balance: float, monthly_consume: float) -> str:
if monthly_consume <= 0:
return "—" # 月均消耗为 0无法计算
months = balance / monthly_consume
return f"约{months:.1f}个月"
```
2. 需要确认 `monthly_consume` 的口径:是 60 天月均还是历史全量月均
3. 边界处理:余额为 0 → "0个月";月均消耗为 0 → 显示"—"或"充足"
4. 将计算结果添加到 `get_customer_detail()` 返回值中,前端在 Banner 统计区域展示

View File

@@ -0,0 +1,288 @@
# Spec 审查报告Neo_Specs 遗漏需求细节
> 审查日期2026-03-20
> 审查方法:以 `docs/prd/specs/` 中 P5.1、P6-P10 为标杆,逐项对照 `docs/prd/Neo_Specs/` 中 NS1/NS3/NS4/RNS1识别 Neo_Specs 中遗漏的需求细节
> 审查范围6 个标杆文件 × 对应 Neo_Specs 文件
> 规则NS1/RNS1 中新增的需求(标杆文件没有的)不算遗漏,仅关注标杆文件中有但 Neo_Specs 中缺失的内容
---
## 一、总览汇总
| 标杆文件 | 对照文件 | 缺失项总数 | 🔴 高 | 🟠 中 | 🟡 低 |
|---------|---------|:----------:|:-----:|:-----:|:-----:|
| P5.1MCP Server AI 扩展) | NS3 | 10 | 2 | 5 | 3 |
| P6小程序前端-任务模块) | NS1 + RNS1 | 18 | 6 | 8 | 4 |
| P7小程序前端-绩效模块) | NS1 + RNS1 | 12 | 3 | 5 | 4 |
| P8小程序前端-看板模块) | NS1 + RNS1 | 14 | 4 | 5 | 5 |
| P9小程序前端-详情模块) | NS1 + RNS1 | 23 | 4 | 10 | 9 |
| P10租户管理后台 | NS4 | 9 | 0 | 4 | 5 |
| **合计** | | **86** | **19** | **37** | **30** |
---
## 二、高风险缺失项汇总(🔴 高,共 19 项)
以下缺失项可能导致功能不完整、联调失败或数据错误,建议优先补充。
### P5.1 → NS32 项)
| # | 缺失内容 | 影响 |
|---|---------|------|
| 1 | App1智能任务生成的完整 Prompt 工程规范P5.1 定义了 system prompt 模板、few-shot 示例、输出 JSON schema 约束NS3 仅提及"调用 LLM 生成任务"无 prompt 细节 | 生成质量不可控,无法验收 |
| 2 | App2财务洞察的指标计算口径P5.1 明确了 6 个财务指标的计算公式(含 items_sum 口径、环比基准NS3 仅列出"生成财务分析报告" | 财务数据可能计算错误 |
### P6 → NS1/RNS16 项)
| # | 缺失内容 | 影响 |
|---|---------|------|
| 1 | 任务卡片的视觉状态机P6 定义了 5 种卡片状态(待处理/已置顶/已完成/已放弃/过期的颜色、图标、交互差异NS1/RNS1 仅按 status 分组返回数据 | 前端无法正确渲染状态差异 |
| 2 | 任务列表的空状态设计P6 定义了 3 种空状态(无任务/筛选无结果/网络错误的文案和插图NS1/RNS1 未提及 | 空状态体验缺失 |
| 3 | 置顶任务的排序规则P6 明确"置顶任务按置顶时间倒序,非置顶按优先级→创建时间排序"NS1 TASK-1 未定义排序规则 | 列表排序可能不符合预期 |
| 4 | 任务操作的确认弹窗规范P6 定义了放弃/取消放弃操作的二次确认弹窗文案和按钮样式NS1/RNS1 未提及前端交互细节 | 误操作风险 |
| 5 | 下拉刷新与触底加载的动画规范P6 定义了 loading 动画、skeleton 屏、错误重试的交互细节NS1/RNS1 仅提及"分页"和"懒加载" | 加载体验不一致 |
| 6 | 任务详情页的 AI 分析展示规范P6 定义了 AI 分析卡片的折叠/展开交互、"重新生成"按钮、加载状态NS1 TASK-2 仅定义了 aiAnalysis 数据字段 | AI 功能交互不完整 |
### P7 → NS1/RNS13 项)
| # | 缺失内容 | 影响 |
|---|---------|------|
| 1 | 营业日分割点08:00的完整处理规范P7 多次强调"营业日以 08:00 为分割点"并给出具体示例("本月"= 当月1日 08:00 ~ 次月1日 08:00NS1/RNS1 的 PERF-1/PERF-2 使用 `biz_date` 查询但未说明 biz_date 是否已按 08:00 分割 | 跨日数据归属可能错误(如凌晨 2 点的服务归属哪天) |
| 2 | "预估"标记的完整规范P7 AC7 和 T7 明确要求"当月数据显示预估标记"NS1 PERF-1 提到 `isEstimate` 字段但未定义判断逻辑(什么条件下标记为预估?当月所有数据都是预估?还是仅未结算的?) | 预估标记逻辑不明确 |
| 3 | 定档折算惩罚的展示格式P7 AC6 明确要求"120分钟定档折算30分钟"格式NS1 PERF-2 提到 `hoursRaw`(折算前)和 `hours`(折算后)但未定义前端展示格式 | 折算信息展示不一致 |
### P8 → NS1/RNS14 项)
| # | 缺失内容 | 影响 |
|---|---------|------|
| 1 | 看板页面的 Tab 切换交互P8 定义了助教/客户/财务三个 Tab 的切换动画、缓存策略切换回来保持筛选状态NS1/RNS1 仅定义了 API 参数 | Tab 切换体验不完整 |
| 2 | 财务看板的数据加载策略P8 定义了"首次加载经营一览,其他板块懒加载"的分段加载策略NS1 BOARD-3 是单个 API 返回全部 6 板块数据 | 首屏加载慢200+ 字段一次返回) |
| 3 | 客户看板卡片的点击跳转P8 定义了"点击客户卡片跳转到 customer-detail 页面"的交互NS1/RNS1 BOARD-2 未定义卡片点击行为和跳转参数 | 看板→详情的导航链路断裂 |
| 4 | 助教看板的"距升档"进度条P8 定义了升档进度的可视化展示(进度条 + 百分比 + 差距小时数NS1 BOARD-1 仅返回 `perfGap` 文本字段 | 进度可视化缺失 |
### P9 → NS1/RNS14 项)
| # | 缺失内容 | 影响 |
|---|---------|------|
| 1 | 客户详情页的数据加载顺序P9 定义了"基本信息→消费汇总→AI 洞察→消费记录→备注"的分段加载策略和 skeleton 占位NS1 CUST-1 是单个 API 返回全部数据 | 首屏加载慢 |
| 2 | 助教详情页的档位进度可视化P9 定义了档位节点tierNodes的时间轴样式、当前档位高亮、升档动画NS1 COACH-1 仅返回 tierNodes 数据 | 档位展示缺乏视觉引导 |
| 3 | 消费记录的类型图标映射P9 定义了台桌消费/商城消费/充值 3 种类型的图标、颜色、标签样式NS1 CUST-1 仅返回 `type` 字段 | 消费类型视觉区分不明确 |
| 4 | 备注的 AI 评分展示P9 定义了备注卡片上 AI 评分的星级展示1-5 星)和评分说明 tooltipNS1 仅返回 `ai_score` 数值 | 评分展示不直观 |
---
## 三、各标杆文件详细对比
### 3.1 P5.1MCP Server AI 扩展vs NS3
P5.1 定义了 MCP Server 的 7 个 AI 应用App1-App7的完整规范NS3 覆盖了整体架构但在以下细节上有遗漏。
| # | 风险 | 缺失内容 | P5.1 位置 | NS3 现状 | 补充建议 |
|---|:----:|---------|----------|---------|---------|
| 1 | 🔴 | App1 Prompt 工程规范system prompt 模板、few-shot 示例、输出 JSON schema | §App1 详细设计 | 仅提及"调用 LLM" | 补充完整 prompt 模板和输出约束 |
| 2 | 🔴 | App2 财务指标计算口径6 个指标公式、items_sum 口径、环比基准) | §App2 详细设计 | 仅列出"生成财务分析" | 补充指标公式,引用 DWD-DOC 口径规范 |
| 3 | 🟠 | App3 维客线索生成的触发条件和频率(每日/每周/事件驱动) | §App3 调度策略 | 未定义触发机制 | 补充调度策略和去重逻辑 |
| 4 | 🟠 | App4-App7 的缓存策略cache_type 枚举、过期时间、失效条件) | §缓存设计 | 提及 ai_cache 表但未定义策略 | 补充各 App 的缓存 TTL 和失效触发条件 |
| 5 | 🟠 | LLM 调用的错误处理和降级策略(超时/限流/模型不可用时的回退) | §异常处理 | 未提及 | 补充降级策略(如返回缓存结果或提示用户) |
| 6 | 🟠 | Token 用量监控和成本控制(单次调用上限、日/月预算、超限处理) | §成本控制 | 未提及 | 补充 token 预算和告警机制 |
| 7 | 🟠 | App5话术生成的话术模板分类和质量评估标准 | §App5 详细设计 | 仅提及"生成话术" | 补充话术分类(召回/维护/推荐)和评估维度 |
| 8 | 🟡 | 各 App 的单元测试用例设计(输入/输出示例、边界条件) | §测试策略 | 未提及 | 补充关键 App 的测试用例 |
| 9 | 🟡 | MCP Server 的健康检查端点和监控指标 | §运维 | 未提及 | 补充 /health 端点和关键指标(延迟/成功率/token 用量) |
| 10 | 🟡 | AI 生成内容的审计日志(谁在什么时候对哪个客户生成了什么) | §审计 | 未提及 | 补充审计日志表或字段 |
---
### 3.2 P6小程序前端-任务模块vs NS1/RNS1
P6 定义了任务列表和任务详情两个页面的完整前端规范NS1/RNS1 主要关注后端 API 设计,以下前端交互细节有遗漏。
| # | 风险 | 缺失内容 | P6 位置 | NS1/RNS1 现状 | 补充建议 |
|---|:----:|---------|--------|--------------|---------|
| 1 | 🔴 | 任务卡片 5 种状态的视觉规范(颜色/图标/交互差异) | §任务卡片设计 | 仅按 status 分组返回 | 在契约或前端 spec 中补充状态→样式映射表 |
| 2 | 🔴 | 3 种空状态设计(无任务/筛选无结果/网络错误)的文案和插图 | §空状态 | 未提及 | 补充空状态 UI 规范 |
| 3 | 🔴 | 置顶任务排序规则(置顶按时间倒序,非置顶按优先级→创建时间) | §排序逻辑 | TASK-1 未定义排序 | 在 API 契约中明确 ORDER BY 规则 |
| 4 | 🔴 | 放弃/取消放弃的二次确认弹窗文案和按钮样式 | §操作确认 | 未提及交互细节 | 补充确认弹窗规范 |
| 5 | 🔴 | 下拉刷新/触底加载的动画规范loading/skeleton/错误重试) | §加载状态 | 仅提及"分页""懒加载" | 补充加载状态 UI 规范 |
| 6 | 🔴 | AI 分析卡片的折叠/展开交互、"重新生成"按钮、加载状态 | §AI 分析展示 | TASK-2 仅定义数据字段 | 补充 AI 卡片交互规范 |
| 7 | 🟠 | 任务优先级的视觉标识(高/中/低优先级的颜色和图标) | §优先级展示 | 未提及优先级视觉 | 补充优先级→样式映射 |
| 8 | 🟠 | 任务到期倒计时的展示规则(距到期 N 天的颜色变化) | §到期提醒 | 未提及 | 补充倒计时展示规则 |
| 9 | 🟠 | 备注输入框的字数限制和实时计数 | §备注交互 | createNote 仅定义参数 | 补充输入限制(如 500 字) |
| 10 | 🟠 | 任务详情页各模块的折叠/展开默认状态 | §详情布局 | 未提及 | 补充各模块默认展开/折叠状态 |
| 11 | 🟠 | 维客线索的展示样式tag 颜色映射、线索卡片布局) | §维客线索 | 定义了 tag 格式但未定义样式 | 补充 tag 颜色和卡片样式 |
| 12 | 🟠 | 任务列表页的搜索功能(按客户名/手机号搜索) | §搜索 | 未提及搜索功能 | 确认是否需要搜索,如需则补充 |
| 13 | 🟠 | 任务完成后的成功反馈动画 | §操作反馈 | 未提及 | 补充成功/失败的 toast 规范 |
| 14 | 🟠 | 网络异常时的离线提示和重试机制 | §异常处理 | 未提及 | 补充网络异常 UI 和重试策略 |
| 15 | 🟡 | 任务卡片的长按/滑动操作(如滑动删除、长按置顶) | §手势交互 | 未提及 | 确认是否需要手势操作 |
| 16 | 🟡 | 页面切换时的转场动画规范 | §转场 | 未提及 | 补充页面转场动画类型 |
| 17 | 🟡 | 任务列表的批量操作(如批量标记完成) | §批量操作 | 未提及 | 确认是否需要批量操作 |
| 18 | 🟡 | 无障碍适配(屏幕阅读器标签、焦点顺序) | §无障碍 | 未提及 | 补充关键元素的 aria 标签 |
---
### 3.3 P7小程序前端-绩效模块vs NS1/RNS1
P7 定义了绩效概览和绩效明细两个页面的完整规范NS1 §3.3 和 RNS1 T1-3/T1-4 覆盖了 API 设计,但以下细节有遗漏。
| # | 风险 | 缺失内容 | P7 位置 | NS1/RNS1 现状 | 补充建议 |
|---|:----:|---------|--------|--------------|---------|
| 1 | 🔴 | 营业日 08:00 分割点的完整处理规范 | AC2、页面清单多处 | PERF-1/PERF-2 用 `biz_date` 查询但未说明 biz_date 是否已按 08:00 分割 | 在 API 契约中明确biz_date 已由 ETL 层按 08:00 分割,后端直接按 biz_date 查询即可;或补充后端时间转换逻辑 |
| 2 | 🔴 | "预估"标记的判断逻辑 | AC7、T7 | PERF-1 有 `isEstimate` 字段但未定义判断条件 | 补充:当月所有数据标记为预估?还是仅未结算的?还是基于 ETL 数据更新时间? |
| 3 | 🔴 | 定档折算惩罚的展示格式规范 | AC6 | PERF-2 返回 `hours`/`hoursRaw` 但未定义展示格式 | 补充:前端展示为"120分钟定档折算30分钟"格式,明确 hours 和 hoursRaw 的关系 |
| 4 | 🟠 | "我的新客"筛选逻辑的完整定义 | AC3 | NS1 §3.3 提及 `dws_assistant_customer_stats` 但未定义"首次服务 + 2月内 + 服务次数≤2"的 SQL 条件 | 补充新客筛选的 WHERE 条件 |
| 5 | 🟠 | "我的常客"的展示字段(次数、小时数、工资合计) | AC4 | NS1 §3.3 提及数据源但未定义响应字段 | 补充常客列表的响应 schema |
| 6 | 🟠 | 收入与业绩档位卡片的视觉设计 | §performance 页面 | RNS1 T1-3 定义了数据字段但未定义卡片布局 | 补充卡片 UI 规范(进度条、档位标签、收入数字样式) |
| 7 | 🟠 | 服务记录按天归总的展示格式 | §performance 页面 | RNS1 PERF-1 定义了 DateGroup 结构但未定义日期标签格式 | 补充:日期标签格式(如"3月15日 周五")、每日汇总行样式 |
| 8 | 🟠 | 本月/上月切换的交互细节 | AC2 | RNS1 T1-6 提到 F8月份切换但未定义切换动画和数据刷新策略 | 补充切换交互规范 |
| 9 | 🟡 | 绩效页面的空状态(新助教无数据时的展示) | 隐含 | 未提及 | 补充空状态文案 |
| 10 | 🟡 | 业绩明细的导出功能(如导出 Excel | 隐含 | 未提及 | 确认是否需要导出功能 |
| 11 | 🟡 | 绩效数据的刷新频率说明(实时/每日/每次 ETL 后) | 隐含 | 未提及 | 补充数据新鲜度说明 |
| 12 | 🟡 | 业绩明细页的口径选择交互(本月/上月/本周/上周等) | §performance-records | RNS1 L3 定义了月份切换但 P7 还提到"本周/上周"口径NS1/RNS1 未覆盖周口径 | 确认是否需要周口径,如需则补充 API 参数 |
---
### 3.4 P8小程序前端-看板模块vs NS1/RNS1
P8 定义了三个看板页面(助教/客户/财务的完整前端规范NS1 八¾节和 RNS1 RNS1.3 覆盖了 API 和筛选矩阵,但以下前端细节有遗漏。
| # | 风险 | 缺失内容 | P8 位置 | NS1/RNS1 现状 | 补充建议 |
|---|:----:|---------|--------|--------------|---------|
| 1 | 🔴 | 三看板 Tab 切换的缓存策略(切换回来保持筛选状态和滚动位置) | §Tab 交互 | 未提及 Tab 缓存 | 补充 Tab 切换时的状态保持策略 |
| 2 | 🔴 | 财务看板分段加载策略(首次加载经营一览,其他板块懒加载) | §加载策略 | BOARD-3 单个 API 返回全部 6 板块 | 考虑拆分 API 或补充前端分段渲染策略 |
| 3 | 🔴 | 客户看板卡片点击跳转到 customer-detail 的交互和参数传递 | §卡片交互 | BOARD-2 未定义点击行为 | 补充卡片点击→跳转的参数customerId |
| 4 | 🔴 | 助教看板"距升档"进度条的可视化规范(进度条+百分比+差距小时数) | §进度展示 | BOARD-1 仅返回 `perfGap` 文本 | 补充进度条 UI 规范,或增加 `perfProgress` 百分比字段 |
| 5 | 🟠 | 看板数据的实时性标识(数据截止时间展示) | §数据时效 | 未提及 | 补充"数据更新于 XX:XX"的展示 |
| 6 | 🟠 | 财务看板环比数据的 tooltip 说明(点击环比箭头显示计算详情) | §环比交互 | 定义了环比格式但未定义交互 | 补充环比 tooltip 规范 |
| 7 | 🟠 | 助教看板卡片点击跳转到 coach-detail 的交互 | §卡片交互 | BOARD-1 未定义点击行为 | 补充卡片点击→跳转的参数coachId |
| 8 | 🟠 | 客户看板"最频繁"维度的柱状图交互(点击柱子显示具体数据) | §柱状图 | 定义了 weeklyVisits 数据但未定义交互 | 补充柱状图点击交互 |
| 9 | 🟠 | 看板页面的下拉刷新行为 | §刷新 | 未提及 | 补充下拉刷新规范 |
| 10 | 🟡 | 财务看板各板块的折叠/展开交互 | §板块交互 | 未提及 | 补充板块折叠/展开默认状态 |
| 11 | 🟡 | 看板数据加载失败时的错误展示 | §异常处理 | 未提及 | 补充错误状态 UI |
| 12 | 🟡 | 筛选项的动画效果(下拉展开/收起) | §筛选交互 | 未提及 | 补充筛选面板动画 |
| 13 | 🟡 | 助教看板的排名序号展示 | §排名 | 未提及 | 补充是否需要显示排名序号(如 #1#2 |
| 14 | 🟡 | 财务看板数字的格式化规范(千分位、小数位、货币符号) | §数字格式 | 未提及 | 补充数字格式化规则(如 ¥12,345.67 |
---
### 3.5 P9小程序前端-详情模块vs NS1/RNS1
P9 定义了客户详情、助教详情、客户服务记录三个页面的完整前端规范NS1 §3.4/§3.5 和 RNS1 RNS1.2 覆盖了 API 设计,但以下细节有遗漏。
| # | 风险 | 缺失内容 | P9 位置 | NS1/RNS1 现状 | 补充建议 |
|---|:----:|---------|--------|--------------|---------|
| 1 | 🔴 | 客户详情页分段加载策略基本信息→消费汇总→AI 洞察→消费记录→备注) | §加载策略 | CUST-1 单个 API 返回全部数据 | 考虑拆分 API 或补充前端分段渲染和 skeleton 占位规范 |
| 2 | 🔴 | 助教详情页档位进度时间轴的视觉规范(节点样式、当前档位高亮、升档动画) | §档位展示 | COACH-1 返回 tierNodes 数据但未定义视觉 | 补充时间轴 UI 规范 |
| 3 | 🔴 | 消费记录 3 种类型的图标/颜色/标签样式映射 | §消费记录 | CUST-1 仅返回 `type` 字段 | 补充 type→icon/color 映射表 |
| 4 | 🔴 | 备注 AI 评分的星级展示规范1-5 星样式、评分说明 tooltip | §备注展示 | 仅返回 `ai_score` 数值 | 补充星级 UI 和 tooltip 文案 |
| 5 | 🟠 | 客户详情页 Banner 区域的视觉设计(余额/消费/到店间隔/距上次到店的布局) | §Banner | RNS1 T2-1 定义了 4 个字段但未定义布局 | 补充 Banner UI 规范 |
| 6 | 🟠 | AI 洞察卡片的展示规范(标题/摘要/展开详情/刷新按钮) | §AI 洞察 | RNS1 T2-1 定义了 aiInsight 字段但未定义交互 | 补充 AI 洞察卡片 UI 和交互 |
| 7 | 🟠 | 关联助教任务列表的展示规范(任务类型图标、状态标签、服务统计) | §关联助教 | RNS1 T2-2 定义了数据字段但未定义展示 | 补充任务列表 UI 规范 |
| 8 | 🟠 | 最亲密助教的展示规范(关系指数可视化、课时统计图表) | §亲密助教 | RNS1 T2-3 定义了数据字段但未定义展示 | 补充亲密助教卡片 UI |
| 9 | 🟠 | 消费记录中助教明细子列表的展开/折叠交互 | §消费记录 | RNS1 定义了 coaches 子数组但未定义交互 | 补充子列表展开/折叠规范 |
| 10 | 🟠 | 助教详情页 TOP20 客户列表的排序规则和展示字段 | §TOP 客户 | RNS1 T2-5 定义了扩展字段但未定义排序 | 补充排序规则(按什么排?关系指数?消费金额?) |
| 11 | 🟠 | 助教详情页历史月份统计的图表展示(折线图/柱状图/表格) | §历史统计 | RNS1 T2-6 定义了数据字段但未定义展示形式 | 补充图表类型和交互 |
| 12 | 🟠 | 客户服务记录页的月度统计汇总展示monthCount/monthHours 的位置和样式) | §月度统计 | RNS1 T2-4 定义了字段但未定义展示 | 补充月度统计 UI 规范 |
| 13 | 🟠 | 助教详情页任务分组active/inactive/abandoned的视觉区分 | §任务分组 | RNS1 T2-5 定义了分组但未定义视觉 | 补充分组标题样式和折叠规则 |
| 14 | 🟠 | 客户详情页维客线索的展示规范线索卡片布局、category 颜色映射) | §维客线索 | RNS1 定义了脱敏规则但未定义展示 | 补充线索卡片 UI |
| 15 | 🟡 | 客户详情页的分享功能(分享客户信息给其他助教) | §分享 | 未提及 | 确认是否需要分享功能 |
| 16 | 🟡 | 助教详情页的联系方式展示(电话/微信) | §联系方式 | 未提及 | 确认是否需要展示联系方式 |
| 17 | 🟡 | 消费记录的时间范围筛选(除月份切换外是否需要自定义日期范围) | §时间筛选 | 仅支持月份切换 | 确认是否需要自定义日期范围 |
| 18 | 🟡 | 详情页的返回按钮行为(返回上一页 vs 返回列表页) | §导航 | 未提及 | 补充返回导航规则 |
| 19 | 🟡 | 客户详情页电话号码的脱敏展示(中间 4 位用 * | §隐私 | 未提及前端脱敏 | 补充:后端返回完整号码还是脱敏号码?前端是否需要"点击查看完整号码" |
| 20 | 🟡 | 助教详情页"收入明细"的展开/折叠交互 | §收入明细 | RNS1 定义了本月/上月各 4 项但未定义交互 | 补充收入明细展示规范 |
| 21 | 🟡 | 客户服务记录中"饮品描述"字段的展示位置和样式 | §服务记录 | NS1 L4 定义了 `drinks` 字段但未定义展示 | 补充饮品信息展示位置 |
| 22 | 🟡 | 详情页各模块的加载失败独立处理(某个模块失败不影响其他模块展示) | §异常处理 | 未提及 | 补充模块级错误处理策略 |
| 23 | 🟡 | 客户详情页"可用月数"的计算说明(余额÷月均消耗) | §余额分析 | RNS1 定义了 `availableMonths` 但未说明计算公式 | 补充计算公式和边界处理(月均消耗为 0 时) |
---
### 3.6 P10租户管理后台vs NS4
P10 定义了管理后台的完整规范React + Vite + Ant DesignNS4 覆盖了主要功能模块,但以下细节有遗漏。
| # | 风险 | 缺失内容 | P10 位置 | NS4 现状 | 补充建议 |
|---|:----:|---------|---------|---------|---------|
| 1 | 🟠 | 角色权限管理的 CRUD 界面规范(创建角色、分配权限、权限树展示) | §权限管理 | 提及权限模型但未定义管理界面 | 补充权限管理页面的 UI 和交互规范 |
| 2 | 🟠 | 门店切换功能的交互规范(多门店用户的门店选择器位置、切换后的数据刷新) | §门店管理 | 提及多门店但未定义切换交互 | 补充门店选择器 UI 和切换流程 |
| 3 | 🟠 | 数据导出功能的规范(哪些页面支持导出、导出格式、导出权限) | §数据导出 | 未提及 | 补充导出功能清单和权限控制 |
| 4 | 🟠 | 操作日志/审计日志的查看界面 | §审计 | 未提及 | 补充日志查看页面规范 |
| 5 | 🟡 | 管理后台的响应式适配(最小支持分辨率、移动端适配策略) | §响应式 | 未提及 | 补充最小分辨率和断点规则 |
| 6 | 🟡 | 表格组件的统一规范(分页大小、排序交互、筛选器位置) | §表格规范 | 未提及 | 补充 Ant Design Table 的统一配置 |
| 7 | 🟡 | 表单验证的统一规范(必填标识、错误提示位置、实时验证 vs 提交验证) | §表单规范 | 未提及 | 补充表单验证规则 |
| 8 | 🟡 | 管理后台的国际化预留(是否需要多语言支持) | §国际化 | 未提及 | 确认是否需要国际化预留 |
| 9 | 🟡 | 管理后台的主题定制品牌色、Logo 位置、暗色模式) | §主题 | 未提及 | 确认是否需要主题定制 |
---
## 四、跨文件系统性问题
以下问题在多个标杆文件中反复出现,反映了 Neo_Specs 整体层面的系统性缺失。
### 4.1 前端交互规范缺失(影响 P6/P7/P8/P9
NS1/RNS1 主要关注后端 API 设计和数据字段定义,对前端交互细节覆盖不足:
- 加载状态skeleton/loading/错误重试)未统一规范
- 空状态设计未覆盖
- 操作确认弹窗未规范
- 页面转场动画未定义
- 下拉刷新行为未统一
建议:新建一份前端交互规范文档(如 `NS-FE-INTERACTION.md`),统一定义上述交互模式,各页面 spec 引用即可。
### 4.2 数据加载策略缺失(影响 P8/P9
多个复杂页面(财务看板、客户详情、助教详情)的 API 设计为单个接口返回全部数据但标杆文件定义了分段加载策略。NS1/RNS1 未考虑:
- 首屏优先加载哪些模块
- 非首屏模块的懒加载触发条件
- 各模块独立的 loading/error 状态
建议:在 API 契约中为复杂页面提供分段加载方案(拆分 API 或定义 `fields` 参数控制返回范围)。
### 4.3 视觉样式映射缺失(影响 P6/P7/P8/P9
多处数据字段(状态、类型、等级、优先级)需要前端映射为视觉样式(颜色、图标、标签),但 NS1/RNS1 仅定义了数据字段,未提供映射表:
- 任务状态→颜色/图标
- 课程类型→标签样式
- 会员等级→标签颜色
- 助教等级→标签样式
- 消费类型→图标
建议:在 API 契约或前端规范中补充统一的枚举→样式映射表。
### 4.4 异常处理规范缺失(影响所有文件)
标杆文件中定义了各种异常场景的处理方式,但 Neo_Specs 普遍缺少:
- 网络异常时的 UI 展示和重试机制
- 单个模块加载失败时的降级策略
- 数据为空时的空状态展示
- 操作失败时的错误提示文案
建议:新建异常处理规范文档,统一定义错误码→用户提示的映射。
### 4.5 营业日时间规范未贯穿(影响 P7/P8
P7 多次强调"营业日以 08:00 为分割点"这个规则影响绩效和看板的所有时间相关查询。NS1/RNS1 使用 `biz_date` 字段但未明确说明 biz_date 的生成规则是否已包含 08:00 分割逻辑。
建议:在 API 契约中明确声明"所有 biz_date 字段已由 ETL 层按 08:00 营业日分割点处理,后端和前端无需额外转换"。
---
## 五、建议优先级
### 立即处理(阻塞开发)
1. 补充营业日 08:00 分割点的处理说明P7→NS1
2. 补充"预估"标记的判断逻辑P7→NS1
3. 补充任务排序规则到 API 契约P6→NS1
4. 评估复杂页面的分段加载策略P8/P9→NS1
### 短期补充(影响联调质量)
5. 新建前端交互规范文档,统一加载/空状态/确认弹窗/错误处理
6. 补充枚举→样式映射表
7. 补充 App1/App2 的 Prompt 工程和指标口径P5.1→NS3
8. 补充管理后台权限管理界面规范P10→NS4
### 后续完善(不阻塞但影响体验)
9. 补充各页面的无障碍适配
10. 补充数据导出功能规范
11. 补充页面转场动画规范

View File

@@ -1,22 +1,51 @@
# 百炼平台 AI 应用提示词Qwen3.5-Plus
> 本文档定义 8 个 AI 应用的系统提示词System Prompt供百炼平台配置使用。
> 模型Qwen3.5-Plus
> 模型Qwen3.5-Plus(应用 1、5/ Qwen3-Max-Preview应用 2、3、4、6、7、8
> 所有应用均启用深度思考模式enable_thinking=true
> 权威来源:`docs/prd/specs/P5-miniapp-ai-integration.md` + `docs/prd/AI需求2.md`
> 首条 Prompt用户消息由后端代码在调用时拼接 JSON 数据,结构定义见 P5 spec「首条 Prompt 数据结构」章节。
> NS2 实现代码:`apps/backend/app/ai/`data_fetchers + apps 子包),属性测试:`tests/test_data_fetchers/` + `tests/test_ai_apps/`
### 应用 ID 与环境变量映射2026-03-05 更新
> 最后同步时间2026-03-21从百炼控制台获取线上配置并对齐
| 应用 | 名称 | 环境变量 Key | 百炼应用 ID |
|------|------|-------------|------------|
| 应用 1 | 通用对话 | `BAILIAN_APP_ID_1_CHAT` | `979dabe6f22a43989632b8c662cac97c` |
| 应用 2 | 财务洞察 | `BAILIAN_APP_ID_2_FINANCE` | `1dcdb5f39c3040b6af8ef79215b9b051` |
| 应用 3 | 客户数据维客线索分析 | `BAILIAN_APP_ID_3_CLUE` | `708bf45439cd48c7ab9a514d03482890` |
| 应用 4 | 关系分析/任务建议 | `BAILIAN_APP_ID_4_ANALYSIS` | `ea7b1c374f574b9a925a2fb5789a9b90` |
| 应用 5 | 话术参考 | `BAILIAN_APP_ID_5_TACTICS` | `46f54e6053df4bb0b83be29366025cf6` |
| 应用 6 | 备注分析 | `BAILIAN_APP_ID_6_NOTE` | `025bb344146b4e4e8be30c444adab3b4` |
| 应用 7 | 客户分析 | `BAILIAN_APP_ID_7_CUSTOMER` | `df35e06991b24d49971c03c6428a9c87` |
| 应用 8 | 维客线索整理 | `BAILIAN_APP_ID_8_CONSOLIDATE` | `407dfb89283b4196934eec5fefe3ebc2` |
### NS2 后端实现要点2026-03-21 更新)
应用 1 支持 10 种页面入口上下文contextType`page_context.py` 文本化后注入首条消息:
| contextType | 入口页面 | 数据来源(代码实际查询) |
|-------------|---------|------------------------|
| `task-detail` | 任务详情 | App: `biz.coach_tasks` + `biz.coach_tasks_member_view` + `biz.coach_tasks_assistant_view` + `biz.notes` + `biz.ai_cache`(app4_analysis) |
| `task-list` | 任务列表 | App: `biz.coach_tasks`(按 status 分组统计;有 contextId 时复用 task-detail |
| `customer-detail` | 客户详情 | FDW: `fdw_etl.v_dim_member`(scd2_is_current=1) + `fdw_etl.v_dwd_settlement_head` + `fdw_etl.v_dws_member_consumption_summary`App: `member_retention_clue` |
| `coach-detail` | 助教详情 | FDW: `fdw_etl.v_dim_assistant`App: `biz.coach_tasks`(按 status 分组统计) |
| `board-finance` | 财务看板 | FDW: `fdw_etl.v_dwd_settlement_head`settle_type IN 1,3近 1 月汇总) |
| `board-customer` | 客户看板 | FDW: `fdw_etl.v_dwd_settlement_head` JOIN `fdw_etl.v_dim_member`Top 10 客户) |
| `board-coach` | 助教看板 | FDW: `fdw_etl.v_dwd_assistant_service_log` JOIN `fdw_etl.v_dim_assistant`Top 10 助教) |
| `performance` | 绩效页 | FDW: `fdw_etl.v_dws_assistant_salary_calc` JOIN `fdw_etl.v_dim_assistant` |
| `customer-service-records` | 服务记录 | FDW: `fdw_etl.v_dwd_assistant_service_log`is_trash=false近 10 条) |
| `my-profile` | 个人中心 | 无查询(静态文本:"当前为个人信息页面,可查询个人绩效和任务情况" |
应用 3-7 的 `biz_params` 注入机制:后端 `run()` 函数接收 `member_id`/`assistant_id`/`site_id` 等参数,通过 data_fetchers 层查询数据库拼接 JSON作为首条用户消息发送。
Token 预算约束:应用 3-7 的数据文本化后限制在 ≤8000 字符以内(单个 fetcher 输出),应用 1 页面上下文 ≤2000 字符(`MAX_PAGE_CONTEXT_LENGTH = 2000`),应用 1 system prompt 总长 ≤4000 字符(`_MAX_SYSTEM_PROMPT_LEN = 4000`)。超限时按优先级截断。
### 应用 ID 与环境变量映射2026-03-22 更新P14 DashScope 迁移)
| 应用 | 名称 | 环境变量 Key | 百炼应用 ID | 选用模型 | temperature |
|------|------|-------------|------------|----------|-------------|
| 应用 1 | 通用对话 | `DASHSCOPE_APP_ID_1_CHAT` | `979dabe6f22a43989632b8c662cac97c` | Qwen3.5-Plus | 0.7 |
| 应用 2 | 财务洞察 | `DASHSCOPE_APP_ID_2_FINANCE` | `1dcdb5f39c3040b6af8ef79215b9b051` | Qwen3-Max-Preview | 0 |
| 应用 3 | 客户数据维客线索分析 | `DASHSCOPE_APP_ID_3_CLUE` | `708bf45439cd48c7ab9a514d03482890` | Qwen3-Max-Preview | 0 |
| 应用 4 | 关系分析/任务建议 | `DASHSCOPE_APP_ID_4_ANALYSIS` | `ea7b1c374f574b9a925a2fb5789a9b90` | Qwen3-Max-Preview | 0 |
| 应用 5 | 话术参考 | `DASHSCOPE_APP_ID_5_TACTICS` | `46f54e6053df4bb0b83be29366025cf6` | Qwen3.5-Plus | 0.8 |
| 应用 6 | 备注分析 | `DASHSCOPE_APP_ID_6_NOTE` | `025bb344146b4e4e8be30c444adab3b4` | Qwen3-Max-Preview | 0 |
| 应用 7 | 客户分析 | `DASHSCOPE_APP_ID_7_CUSTOMER` | `df35e06991b24d49971c03c6428a9c87` | Qwen3-Max-Preview | 0 |
| 应用 8 | 维客线索整理 | `DASHSCOPE_APP_ID_8_CONSOLIDATE` | `407dfb89283b4196934eec5fefe3ebc2` | Qwen3-Max-Preview | 0 |
> P14 变更:环境变量前缀从 `BAILIAN_*` 迁移到 `DASHSCOPE_*`SDK 从 `openai` 迁移到 `dashscope`Application API
> 新增环境变量:`DASHSCOPE_API_KEY`、`DASHSCOPE_WORKSPACE_ID`(可选)、`INTERNAL_API_TOKEN`、`BACKEND_API_URL`。
> 已删除:`BAILIAN_BASE_URL`、`BAILIAN_MODEL`Application API 通过 app_id 指定应用,不需要 base_url 和 model
### 前端消费方式速查
@@ -47,7 +76,7 @@
```text
# 角色
你是一位台球门店运营助手,服务于"浪浪桌球"品牌旗下门店。你擅长通过 MCP 工具查询数据库,为门店工作人员提供数据查询、经营分析和客户管理方面的支持。
你是一位台球门店运营助手。你擅长通过 MCP 工具查询数据库,为门店工作人员提供数据查询、经营分析和客户管理方面的支持。
当前用户信息:
- 用户ID{{User_ID}}
@@ -108,7 +137,9 @@
- 使用简体中文回复。
- 数据展示清晰,适当使用表格格式。
- 对异常数据主动提示(如金额为负、数据缺失等)。
- 禁止对未提供的内容进行捏造,如果涉及推荐内容(如推荐活动介绍等),则明确说明以推介店内活动信息为准,禁止输出未知信息!
- 不确定的信息不要编造,如实告知用户。
- 回答抓住重点简洁直接不宜过长。必须是400字以内
## 参考文档
- 当通过 MCP 查询数据库时,请参考"桌球运营小程序 SQL"内的 markdown 文档。
@@ -179,7 +210,7 @@ json
- 仅返回 JSON 数组,不要包含任何其他文字。
## 限制
- 仅基于传入的数据进行分析,不要编造数据。
- 仅基于传入的数据进行分析,不要编造数据。禁止臆想内容!
- 如果某项数据缺失或为零,在分析中如实说明,不要跳过。
- 营业日以 08:00 为分界点(如"本月"= 当月1日 08:00 ~ 次月1日 08:00
```
@@ -259,7 +290,7 @@ json
- 首次分析时可能没有历史参考信息,正常输出即可。
## 限制
- 仅基于传入的客观数据进行分析,不要编造数据。
- 仅基于传入的客观数据进行分析,不要编造数据。禁止臆想数据!
- 不要分析备注内容(那是应用 6 的职责)。
- 使用简体中文。
```
@@ -332,6 +363,7 @@ json
- 使用简体中文。
- 行动建议要考虑台球门店的实际场景(如微信联系、到店时主动招呼、推荐活动等)。
- 不要给出过于笼统的建议(如"多关注客户"),必须具体到可执行的动作。
- 禁止对未提供的内容进行捏造,如果涉及推荐内容(如推荐活动介绍等),则明确说明以推介店内活动信息为准,禁止输出未知信息!
```
---
@@ -354,7 +386,6 @@ json
- **任务**:根据任务类型和客户特征,生成适合的沟通话术。
- 召回话术:针对长时间未到店的客户,自然地引导回店。
- 维护话术:针对活跃客户,增强粘性和好感。
- 充值引导:针对余额不足的客户,自然地引导充值。
### 技能2: 个性化定制
- **任务**:基于客户的偏好和历史,让话术更有针对性。
@@ -366,7 +397,7 @@ json
- **任务**:确保话术自然、得体、有效。
- 语气亲切但不过分热情,像朋友间的自然对话。
- 避免过于商业化的推销感。
- 提供多种话术选择,适应不同沟通场景(微信消息、电话、到店面对面)
- 均为微信发送消息
## 输出格式(强制)
@@ -377,15 +408,14 @@ json
{
"tactics": [
{
"content": "话术内容(含场景说明和具体话术文本150字内"
"content": "话术内容(直接输出可以复制发送给客户的话术150字内"
}
]
}
"""
### 输出规则
- 返回 2-4 条话术,覆盖不同沟通场景或切入角度。
- 每条话术包含简短的场景说明(什么时候用)和具体的话术文本。
- 返回 2-4 条话术,覆盖不同营销场景或切入角度。
- 话术文本要口语化,可以直接复制使用。
- 仅返回 JSON不要包含任何其他文字。
@@ -393,7 +423,8 @@ json
- 使用简体中文,口语化表达。
- 话术要符合台球门店助教的身份和语境。
- 不要使用过于正式或书面化的表达。
- 基于传入的客户信息和任务建议(应用 4 返回)生成话术,不要编造客户信息。
- 基于传入的客户信息和任务建议(应用 4 返回)生成话术,不要编造客户信息。禁止臆想内容!
- 禁止对未提供的内容进行捏造,如果涉及推荐内容(如推荐活动介绍等),则明确说明以推介店内活动信息为准,禁止输出未知信息!
```
---
@@ -467,7 +498,7 @@ json
- `客户基础`:会员等级、注册时间、基本属性、个人信息等
- `消费习惯`:消费频率、金额、时段、支付方式等
- `玩法偏好`:台球类型、包厢偏好、团建倾向等
- `促销偏好`:对活动的反应、价格敏感度、充值意愿等
- `促销接受`:对活动的反应、价格敏感度、充值意愿等
- `社交关系`:常带朋友、固定球搭子、社交圈等
- `重要反馈`:投诉、建议、特殊需求、满意度等
@@ -565,7 +596,7 @@ json
- 首次分析时可能没有历史参考信息,正常输出即可。
## 限制
- 仅基于传入的数据进行分析,不要编造数据。
- 仅基于传入的数据进行分析,不要编造数据。禁止臆想内容!
- 使用简体中文。
- 策略建议要符合台球门店的实际运营场景。
```
@@ -624,7 +655,7 @@ json
- `客户基础`:会员等级、注册时间、基本属性、个人信息等
- `消费习惯`:消费频率、金额、时段、支付方式等
- `玩法偏好`:台球类型、包厢偏好、团建倾向等
- `促销偏好`:对活动的反应、价格敏感度、充值意愿等
- `促销接受`:对活动的反应、价格敏感度、充值意愿等
- `社交关系`:常带朋友、固定球搭子、社交圈等
- `重要反馈`:投诉、建议、特殊需求、满意度等
@@ -640,3 +671,42 @@ json
- 不要删除任何你认为"价值不高"的线索(价值判断已由上游应用完成)。
- 使用简体中文。
```
---
## 附录代码审计对照表2026-03-21
> 基于 `apps/backend/app/ai/` 实际代码与本文档描述的逐项对比。
### 已修正的差异
| # | 位置 | 原文档描述 | 代码实际实现 | 修正说明 |
|---|------|-----------|-------------|---------|
| 1 | 应用 1 · `board-finance` | `v_dws_finance_daily_summary` | `fdw_etl.v_dwd_settlement_head`settle_type IN 1,3近 1 月 SUM/AVG | 代码直接查 DWD 结算头表做聚合,未使用 DWS 财务日汇总视图 |
| 2 | 应用 1 · `board-customer` | `v_dim_member` | `fdw_etl.v_dwd_settlement_head` JOIN `fdw_etl.v_dim_member`Top 10 按 items_sum DESC | 代码从结算头表聚合消费金额JOIN 会员维度表取昵称 |
| 3 | 应用 1 · `board-coach` | `v_dim_assistant` | `fdw_etl.v_dwd_assistant_service_log` JOIN `fdw_etl.v_dim_assistant`Top 10 按 service_count DESC | 代码从服务日志表聚合JOIN 助教维度表取昵称 |
| 4 | 应用 1 · `performance` | `v_dws_assistant_monthly_summary` | `fdw_etl.v_dws_assistant_salary_calc` JOIN `fdw_etl.v_dim_assistant` | 代码查的是薪资计算表,不是月度汇总表 |
| 5 | 应用 1 · `customer-service-records` | `v_dwd_settlement_head` | `fdw_etl.v_dwd_assistant_service_log`is_trash=falseLIMIT 10 | 代码查的是助教服务日志表,不是结算头表 |
| 6 | 应用 1 · `my-profile` | `auth.users` | 无数据库查询(返回静态文本) | 代码未查 auth.users直接返回固定提示文本 |
| 7 | 应用 1 · `task-detail` | `v_dwd_settlement_head` | `biz.coach_tasks` + `biz.notes` + `biz.ai_cache` | 代码未查 ETL 结算头表,仅查业务库任务/备注/AI缓存 |
| 8 | 应用 1 · `customer-detail` | `biz.notes` | `member_retention_clue`(维客线索) | 代码查的是维客线索表而非备注表;另外还查了 `v_dws_member_consumption_summary` 取余额 |
| 9 | Token 预算 | 应用 1 页面上下文 ≤4000 字符 | `MAX_PAGE_CONTEXT_LENGTH = 2000`page_context.py`_MAX_SYSTEM_PROMPT_LEN = 4000`app1_chat.py | 页面上下文截断阈值是 20004000 是 system prompt 总长上限 |
### 确认一致的部分
- ✅ 应用 1 的 10 种 contextType 名称与 `SUPPORTED_PAGE_TYPES` 完全一致
- ✅ 应用 2 的 8 个时间维度编码与 `TIME_DIMENSIONS` 完全一致
- ✅ 应用 2 的 prompt 结构task + data + reference`app2_finance_prompt.py` 一致
- ✅ 应用 2 的字段映射items_sum 口径、助教费用拆分)与代码一致
- ✅ 应用 3 的 category 枚举限定 3 个值(客户基础、消费习惯、玩法偏好)
- ✅ 应用 6 的 category 枚举限定 6 个值(含促销接受、社交关系、重要反馈)
- ✅ 应用 3/6/7 共用 `member_data.py`fetch_member_consumption_data
- ✅ 应用 4/5 共用 `assistant_data.py`fetch_assistant_info + fetch_service_history
- ✅ 应用 8 的 source 判断规则ai_consumption / ai_note / 混合→ai_consumption
- ✅ 所有 FDW 查询使用 `is_trash=false` 排除废单
- ✅ 会员信息通过 `scd2_is_current=1` 过滤
- ✅ 金额口径统一使用 items_sum禁止 consume_money
- ✅ 应用 ID 与环境变量映射表与代码常量一致
- ✅ 前端消费方式速查表与缓存写入逻辑一致
- ✅ 应用 3-7 的 system message 上限 ≤8000 字符

View File

@@ -0,0 +1,244 @@
# P13小程序前端 — 联调补齐与格式统一 — miniapp-fe-polish
> 优先级P13依赖 P6~P9 前端页面 + P3 认证 + P4 核心业务 + 后端接口)
> 预估工作量:中
> 生成日期2026-03-20
---
## 背景
小程序前端页面已完成 H5 原型还原P6~P9但在 MOCK 数据排查中发现多处功能点未对接真实数据或逻辑缺失。本 SPEC 统一收敛这些遗漏项,确保每个页面在联调后数据展示完整、格式统一。
---
## 一、通用规则(跨页面)
### G1微信头像与用户信息
**现状**`fetchMe()` 接口已定义但返回空 mock`task-list` 声明了 `avatarUrl` 但未赋值;`performance`/`performance-records``avatarUrl` 字段。
**需求**
- 登录成功后,从后端获取用户信息(微信头像 URL、微信昵称、角色、门店名称
- 后端接口 `GET /api/xcx/me` 返回 `{ avatarUrl, nickName, role, storeName }`
- 头像来源:微信登录时由后端通过 `code2Session` + `getUserInfo` 获取并存储,前端不直接调用 `wx.getUserProfile`
- 所有含 banner 的页面task-list、performance、performance-records统一从全局用户信息读取 `avatarUrl`
**验收标准**
- AC-G1.1:三个 banner 页面展示微信头像,无头像时显示默认占位图
- AC-G1.2:用户昵称、角色、门店名称正确展示
### G2当月预估判断
**现状**WXML 硬编码"预估"文案TS 无当月判断逻辑。
**需求**
- 当查看的数据月份 = 当前自然月时,标题显示"我的预估收入",金额旁显示"预估"标签
- 当查看的数据月份 < 当前自然月时,标题显示"我的收入",无"预估"标签
- 判断逻辑:`isCurrentMonth = (year === nowYear && month === nowMonth)`
- 影响页面performance、performance-records、board-finance
**验收标准**
- AC-G2.1:本月数据显示"预估"标签,历史月份不显示
- AC-G2.2board-finance 本月时间筛选时,经营一览标题含"预估"字样
### G3绩效折算折前/折后)
**现状**`performance-records` 接口定义了 `hoursRaw`(折前)和 `hours`折后WXML 有条件展示逻辑,但 `totalHoursRawLabel` 始终为空。
**需求**
- "绩效折前"/"折前"指 DWS 层定义的绩效惩罚规则计算出的折算前课时
- 后端接口返回 `hours`(折后)和 `hoursRaw`(折前),当两者不同时前端展示"折前 Xh"
- 汇总统计同理:`totalHoursRaw``totalHours` 时展示"折前 Xh"
**验收标准**
- AC-G3.1:存在折算差异时,课时旁显示"折前 Xh"灰色小字
- AC-G3.2:无折算差异时不显示"折前"
### G4储值等级显示规则
**现状**`task-detail` 声明了 `storageLevel` 但无计算逻辑。
**需求**
- 根据客户储值余额balance计算等级文案
- `= 0` → "无"
- `< 200` → "少"
- `< 500` → "一般"
- `< 1500` → "多"
- `≥ 1500` → "非常多"
- 计算在前端完成(后端返回 balance 数值),工具函数放 `utils/storage-level.ts`
**验收标准**
- AC-G4.1task-detail 储值区域根据 balance 正确显示等级文案
- AC-G4.2balance 为 0 时显示"无"
---
## 二、各页面功能点
### P1task-list任务列表
| 编号 | 功能点 | 现状 | 需求 |
|------|--------|------|------|
| T1.1 | banner 头像 | `avatarUrl` 声明但未赋值 | 见 G1 |
| T1.2 | 比同期数据 | PerfData 有 `incomeTrend`/`incomeTrendDir`,但值为空 | 后端返回与上月同期的差值(数值,非百分比),如 `+¥1,200` / `-¥800`;前端展示数值 + ↑/↓ 箭头 |
| T1.3 | 放弃原因 | `abandonReason` 硬编码空字符串 | 从后端 task 对象的 `abandonReason` 字段获取,展示放弃时填写的备注文本 |
| T1.4 | 盖戳动画 | ✅ 已实现 | 确认:任何情况下页面加载后盖戳动画都会播放(当前仅 `tierCompleted` 时触发),需改为始终播放 |
**验收标准**
- AC-T1.2:比同期显示为数值差(如 `+¥1,200`),非百分比
- AC-T1.3:已放弃任务卡片展示放弃备注
- AC-T1.4:页面加载后盖戳动画始终播放,不依赖 `tierCompleted`
### P2performance绩效总览
| 编号 | 功能点 | 现状 | 需求 |
|------|--------|------|------|
| T2.1 | banner 头像 | 无 `avatarUrl` 字段 | 见 G1 |
| T2.2 | 预估/实际收入 | 硬编码"预估" | 见 G2 |
### P3performance-records绩效明细
| 编号 | 功能点 | 现状 | 需求 |
|------|--------|------|------|
| T3.1 | banner 头像 | 无 `avatarUrl` 字段 | 见 G1 |
| T3.2 | 预估/实际收入 | 硬编码"预估" | 见 G2 |
| T3.3 | 总笔数 | 前端 `records.length` 计算 | 后端接口返回 `totalCount` 字段,前端直接使用(分页场景下前端计数不准确) |
| T3.4 | 绩效折前 | 接口有字段但 label 始终为空 | 见 G3 |
### P4task-detail任务详情
| 编号 | 功能点 | 现状 | 需求 |
|------|--------|------|------|
| T4.1 | 手机号码 | `onCopyPhone``phone = ''` 硬编码 | 从 `this.data.detail` 获取客户手机号(后端 task 详情接口返回 `customerPhone` |
| T4.2 | 储值显示规则 | `storageLevel` 无计算逻辑 | 见 G4从 detail 的 balance 字段计算 |
| T4.3 | 行动建议 | 仅有"问问助手"跳转 chat | 后端 task 详情接口返回 `actionSuggestions: string[]`AI 生成的行动建议列表),前端在维客线索下方展示为卡片列表 |
| T4.4 | 备注打星 | ✅ 已实现 | star-rating 组件 + note-modal 爱心/台球双维度评分已完整 |
### P5customer-service-records客户服务记录
| 编号 | 功能点 | 现状 | 需求 |
|------|--------|------|------|
| T5.1 | 客户名称 | ✅ 已实现 | 从 fetchCustomerDetail 获取 |
| T5.2 | 本月服务次数 | 从 detail 取 `totalServiceCount`,类型断言 `as any` | 后端 `GET /api/xcx/customers/:id` 返回 `totalServiceCount` 字段,前端类型定义补齐 |
| T5.3 | 课程标签 | getTypeLabel 基于 includes 硬编码匹配 | 后端返回 `courseType` 枚举basic/vip/incentive/recharge/snooker/group前端直接映射不再 includes 猜测 |
### P6board-finance财务看板
| 编号 | 功能点 | 现状 | 需求 |
|------|--------|------|------|
| T6.1 | AI 智能洞察 | WXML 硬编码 3 行文案 | 后端接口返回 `aiInsights: Array<{ icon: string; text: string }>`,前端动态渲染 |
| T6.2 | 环比箭头 | ✅ 已实现 | compareEnabled 开关 + ↑/↓ 箭头 + 颜色区分 |
| T6.3 | 本月"预估" | 无预估标记 | 见 G2 |
### P7board-customer客户看板
| 编号 | 功能点 | 现状 | 需求 |
|------|--------|------|------|
| T7.1 | 爱心 icon | ✅ 已实现 | heart-icon 组件 + getHeartEmoji 映射 |
### P8customer-detail客户详情
| 编号 | 功能点 | 现状 | 需求 |
|------|--------|------|------|
| T8.1 | 电话 | ✅ 已实现 | onTogglePhone + onCopyPhone 完整 |
### P9board-coach助教看板
| 编号 | 功能点 | 现状 | 需求 |
|------|--------|------|------|
| T9.1 | 级别 Icon | ✅ 已实现 | coach-level-tag 组件 + LEVEL_CLASS 映射 |
### P10coach-detail助教详情
| 编号 | 功能点 | 现状 | 需求 |
|------|--------|------|------|
| T10.1 | 任务执行数量统计 | `taskStats` 硬编码 `{ recall: 24, callback: 14 }` | 后端接口返回 `taskStats: { recall: number; callback: number }`,前端从 API 获取 |
---
## 三、数据格式统一标准
### 已有格式化工具函数(但并没有进行全部调用)
| 类型 | TS 函数 | WXS 函数 | 格式示例 | 缺省值 |
|------|---------|----------|----------|--------|
| 金额 | `formatMoney(value)` | `money(value)` | ¥12,680 / -¥368 / ¥0 | -- |
| 计数 | `formatCount(value, unit)` | `count(value, unit)` | 18笔 / 3次 / 12人 | -- |
| 百分比 | `formatPercent(value)` | `percent(value)` | 12.5% / 0% | -- |
| 课时 | `formatHours(hours)` | `hours(value)` | 86h / 72.5h / 0h | -- |
| 相对时间 | `formatRelativeTime(value)` | — | 刚刚 / 3分钟前 / 2天前 / 03-10 | -- |
| 截止日期 | `formatDeadline(deadline)` | — | 逾期3天 / 今天到期 / 还剩5天 / 03-15 | -- |
| IM 时间 | `formatIMTime(value)` | — | 14:30 / 03-10 14:30 | (空字符串) |
| 爱心 | `getHeartEmoji(score)` | — | 💖 / 🧡 / 💛 / 💙 | 💙 |
| 星级 | `scoreToHalfStar(score)` | — | 4.5 / 3.0 | 0 |
| 空值兜底 | — | `safe(val)` | (原值) | -- |
### 需要补充的格式化
| 类型 | 建议函数名 | 格式示例 | 缺省值 | 说明 |
|------|-----------|----------|--------|------|
| 日期(短) | `formatDateShort(date)` | 3月15日 / 03-15 | -- | 用于服务记录、充值日期等 |
| 日期(完整) | `formatDateFull(date)` | 2026-03-15 | -- | 用于历史数据、导出 |
| 天数 | `formatDays(days)` | 3天 / 15天 | -- | 用于"N天前到店"、逾期天数 |
| 储值等级 | `formatStorageLevel(balance)` | 无/少/一般/多/非常多 | 无 | 见 G4 |
| 同比差值 | `formatTrendValue(value)` | +¥1,200 / -¥800 | -- | 用于"比同期"展示 |
### 接口端格式对齐原则
1. 后端返回原始数值number前端负责格式化展示
2. 金额单位统一为"元"(整数),前端加 ¥ 前缀和千分位
3. 课时单位统一为"小时"number前端加 h 后缀
4. 日期统一 ISO 8601 格式(`YYYY-MM-DDTHH:mm:ss`),前端按场景格式化
5. 百分比后端返回 0-100 的 number前端加 % 后缀
6. 所有 null/undefined/0 值,前端统一展示为 `--`(通过 format 函数兜底)
---
## 四、实施优先级
### 第一批(阻塞联调)
- G1 微信头像(影响 3 个页面 banner
- T1.3 放弃原因(后端字段直通)
- T4.1 手机号码(后端字段直通)
- T3.3 总笔数(后端字段直通)
- T10.1 任务执行统计(后端字段直通)
### 第二批(业务逻辑)
- G2 当月预估判断(纯前端逻辑)
- G3 绩效折算展示(前端条件渲染)
- G4 储值等级规则(前端计算)
- T1.2 比同期数据(需后端新增字段)
- T1.4 盖戳动画始终播放(前端逻辑调整)
- T5.3 课程标签枚举化(前后端对齐)
### 第三批(增强体验)
- T4.3 行动建议(需后端 AI 接口)
- T6.1 AI 智能洞察(需后端 AI 接口)
- 格式化工具函数补充
---
## 五、涉及文件清单
### 前端
- `apps/miniprogram/miniprogram/services/api.ts`
- `apps/miniprogram/miniprogram/utils/money.ts`
- `apps/miniprogram/miniprogram/utils/time.ts`
- `apps/miniprogram/miniprogram/utils/storage-level.ts`(新建)
- `apps/miniprogram/miniprogram/pages/task-list/task-list.ts`
- `apps/miniprogram/miniprogram/pages/performance/performance.ts`
- `apps/miniprogram/miniprogram/pages/performance-records/performance-records.ts`
- `apps/miniprogram/miniprogram/pages/task-detail/task-detail.ts`
- `apps/miniprogram/miniprogram/pages/customer-service-records/customer-service-records.ts`
- `apps/miniprogram/miniprogram/pages/board-finance/board-finance.ts`
- `apps/miniprogram/miniprogram/pages/coach-detail/coach-detail.ts`
### 后端(接口变更)
- `GET /api/xcx/me` — 补充 avatarUrl 字段
- `GET /api/xcx/tasks` — performance 补充 `trendValue`(同比差值)
- `GET /api/xcx/tasks/:id` — 补充 `customerPhone``actionSuggestions`
- `GET /api/xcx/customers/:id` — 类型定义补充 `totalServiceCount`
- `GET /api/xcx/performance/records` — 补充 `totalCount``hoursRaw`
- `GET /api/xcx/coaches/:id` — 补充 `taskStats`
- `GET /api/xcx/board/finance` — 补充 `aiInsights``isEstimated`

Some files were not shown because too many files have changed in this diff Show More