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:
@@ -2,7 +2,7 @@
|
||||
|
||||
> 本文档记录项目中所有文档资产的位置、类型和内容概要,方便快速定位。
|
||||
> 归档规则见末尾「文档归档规则」章节;程序输出路径规范见 `docs/deployment/EXPORT-PATHS.md`。
|
||||
> 最后更新:2026-03-20(RNS1.4 CHAT 模块重建 + FDW→直连统一 + R3 筛选修复 审计收口)
|
||||
> 最后更新:2026-03-28(board-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 SDK,P14 迁移后由新测试替代 |
|
||||
|
||||
### 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 5:app_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 19:Prompt 截断) |
|
||||
| `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 8:Dashboard 聚合正确性 |
|
||||
| `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 管理 API(8 端点:日期/请求列表/详情/清理/设置/覆盖率) |
|
||||
|
||||
Monorepo 级属性测试(DevTrace):
|
||||
|
||||
| 路径 | 内容 |
|
||||
|------|------|
|
||||
| `tests/test_trace_context_props.py` | Property 1:Request ID 唯一性、Property 2:Span 顺序保持 |
|
||||
| `tests/test_trace_writer_props.py` | Property 3:TraceSpan 结构完整性、Property 5:JSON 序列化往返一致性、Property 6:日志文件路径生成 |
|
||||
| `tests/test_trace_cleanup_props.py` | Property 8:清理保留期正确性 |
|
||||
| `tests/test_trace_auth_props.py` | Property 4:Token 前缀截断、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 17:SSE 流式 Trace 完整性 |
|
||||
| `tests/test_trace_ws_props.py` | Property 18:WebSocket 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-health,4 库连接池/大小/慢查询) |
|
||||
| `app/routers/admin_triggers.py` | 触发器统一视图路由(GET /api/admin/triggers/unified,biz/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 Schema(PATCH 配置编辑) |
|
||||
| `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 1:DB 健康 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 6:cron 表达式校验拒绝无效输入 |
|
||||
|
||||
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`(P14:ai_run_logs、ai_trigger_jobs 新表 + ai_conversations.session_id)、`2026-03-22__ns41_registry_tables.sql`(NS4.1:biz.connectors/tenants/sites/site_code_history 四张新表 + 种子数据迁移)、`2026-03-22__p16_min_run_interval.sql`(P16:scheduled_tasks 三字段)、`2026-03-23__p15_ai_monitoring.sql`(P15:ai_run_logs.alert_status + BRIN 索引) |
|
||||
| `etl_feiqiu/README.md` | ETL 库文档:六层 Schema 说明、表清单 |
|
||||
| `etl_feiqiu/migrations/` | ETL 库迁移脚本(已合并入 DDL 基线,目录保留 .gitkeep) |
|
||||
| `fdw/` | FDW(Foreign 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 属性测试) |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 后端架构文档 — FastAPI 服务层
|
||||
|
||||
> 更新日期:2026-03-19(RNS1.3 三看板接口 + CONFIG-1 技能类型)
|
||||
> 更新日期:2026-03-23(DevTrace 全链路日志模块)
|
||||
> 位置:`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 span(HTTPException + 未捕获异常 + 堆栈摘要) |
|
||||
| `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_DISCONNECT),trace_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
|
||||
→ 创建 TraceContext(contextvars)
|
||||
→ AUTH span(鉴权层)
|
||||
→ @trace_service span(Service 层)
|
||||
→ 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,不影响核心响应
|
||||
|
||||
@@ -289,7 +289,7 @@ RS 默认参数:
|
||||
|
||||
| 参数 | 默认值 | 说明 |
|
||||
|------|--------|------|
|
||||
| `lookback_days` | 60 | 回溯天数 |
|
||||
| `lookback_days` | 90 | 回溯天数 |
|
||||
| `session_merge_hours` | 4 | 会话合并间隔(小时) |
|
||||
| `incentive_weight` | 1.5 | 激励权重 |
|
||||
| `halflife_session` | 14.0 | 会话频次半衰期(天) |
|
||||
|
||||
@@ -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 对齐 Demo(T1-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 对齐 Demo(T1-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 对齐 Demo(T1-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
@@ -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`):
|
||||
|
||||
247
docs/guides/FRONTEND-BACKEND-INTEGRATION.md
Normal file
247
docs/guides/FRONTEND-BACKEND-INTEGRATION.md
Normal 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-27):WXML 用 `{{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 不是 date(2026-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_conn(2026-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 天聚合
|
||||
@@ -1,7 +1,13 @@
|
||||
# 小程序接口契约文档
|
||||
|
||||
<!-- AI_CHANGELOG
|
||||
- 2026-03-20 | Prompt: 小程序文档过期检查与落盘 | 状态标记从"联调前草案"更新为
|
||||
"后端已实现(26 个端点全部就绪),前端联调中";新增最后更新日期。
|
||||
-->
|
||||
|
||||
> 创建时间:2026-03-18
|
||||
> 状态:联调前草案,后端开发时以此为契约基准
|
||||
> 最后更新:2026-03-20
|
||||
> 状态:后端已实现(26 个端点全部就绪),前端联调中
|
||||
> 基础路径:`/api/xcx`
|
||||
|
||||
---
|
||||
|
||||
@@ -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_params(User_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. **文本化格式**:页面上下文是输出为结构化中文文本还是 JSON?AI 对哪种格式理解更好?
|
||||
9. **数据量控制**:每个页面上下文的字符上限?是否需要根据页面类型动态调整?
|
||||
10. **实时性要求**:应用 1 的页面上下文是否需要实时获取最新数据?还是可以使用缓存(如 task_detail_cache)?
|
||||
8. **文本化格式**:输出为结构化中文描述文本(分段标题 + 缩进),非 JSON。中文描述更便于 AI 理解上下文语义。
|
||||
9. **数据量控制**:每个页面上下文统一 ≤ 2000 字符,不按页面类型动态调整。超出截断并标注。
|
||||
10. **实时性要求**:实时获取最新数据(不使用缓存),设置 5 秒 FDW 查询超时。数据获取失败时返回降级文本,不阻断对话。
|
||||
|
||||
### 7.4 性能与安全
|
||||
|
||||
11. **FDW 查询并发**:多个数据获取函数是否可以并发执行(asyncio.gather)?FDW 连接池是否支持?
|
||||
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 B:Prompt 拼接实现
|
||||
- [ ] T4:完善 `app3_clue.py` 的 `build_prompt()`(客户消费数据 → 维客线索分析)
|
||||
- [ ] T5:完善 `app4_analysis.py` 的 `build_prompt()`(助教+客户数据 → 关系分析)
|
||||
- [ ] T6:完善 `app5_tactics.py` 的 `build_prompt()`(复用应用 4 数据 + task_suggestion)
|
||||
- [ ] T7:完善 `app6_note.py` 的 `build_prompt()`(备注+客户数据 → 备注分析)
|
||||
- [ ] T8:完善 `app7_customer.py` 的 `build_prompt()`(客户全量数据 → 运营策略)
|
||||
### Batch 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 B:Prompt 拼接实现(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/`
|
||||
|
||||
512
docs/prd/Neo_Specs/NS4.1-tenant-admin-redesign.md
Normal file
512
docs/prd/Neo_Specs/NS4.1-tenant-admin-redesign.md
Normal 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`(无下拉选项,无名称参考)
|
||||
- 无法删除管理员记录
|
||||
- 无法管理简写ID(site_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` 为当前生效的简写ID(6 位,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 '当前生效的简写ID,6位字符(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
|
||||
|
||||
```
|
||||
用户在管理后台修改某店铺的简写ID(old_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_history(is_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 不存在)→ INSERT(site_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 表结构 |
|
||||
247
docs/prd/Neo_Specs/review-audit-report.md
Normal file
247
docs/prd/Neo_Specs/review-audit-report.md
Normal 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→NS3(MCP/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→NS3:MCP 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 | 租户管理后台 |
|
||||
107
docs/prd/Neo_Specs/review-audit/P10-NS4-01.md
Normal file
107
docs/prd/Neo_Specs/review-audit/P10-NS4-01.md
Normal 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_ids(BIGINT[])控制管辖范围 |
|
||||
|
||||
种子数据已预置 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_ids,aud=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 标杆文件的隐含期望产生歧义。
|
||||
119
docs/prd/Neo_Specs/review-audit/P10-NS4-02.md
Normal file
119
docs/prd/Neo_Specs/review-audit/P10-NS4-02.md
Normal 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}` 列表,供前端门店选择器使用。
|
||||
99
docs/prd/Neo_Specs/review-audit/P10-NS4-03.md
Normal file
99
docs/prd/Neo_Specs/review-audit/P10-NS4-03.md
Normal 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 数据导出,定义支持导出的页面清单、导出格式、权限规则、导出日志记录要求
|
||||
85
docs/prd/Neo_Specs/review-audit/P10-NS4-04.md
Normal file
85
docs/prd/Neo_Specs/review-audit/P10-NS4-04.md
Normal 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`(分页 + 筛选)
|
||||
64
docs/prd/Neo_Specs/review-audit/P10-NS4-05.md
Normal file
64
docs/prd/Neo_Specs/review-audit/P10-NS4-05.md
Normal 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 默认行为已提供基本可用性,建议在后续迭代中逐步完善,不阻塞当前交付
|
||||
137
docs/prd/Neo_Specs/review-audit/P10-NS4-06.md
Normal file
137
docs/prd/Neo_Specs/review-audit/P10-NS4-06.md
Normal 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×2(Card 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 条时补充后端分页。
|
||||
150
docs/prd/Neo_Specs/review-audit/P10-NS4-07.md
Normal file
150
docs/prd/Neo_Specs/review-audit/P10-NS4-07.md
Normal 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`)中没有独立的"表单规范"章节,但在验收标准和设计要点中隐含了以下要求:
|
||||
- AC4:Excel 上传校验(必填、金额精度、表头格式、类型合法)— ✅ 已实现
|
||||
- 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 个表单)下,现有的隐式一致性已足够,文档化为锦上添花。
|
||||
73
docs/prd/Neo_Specs/review-audit/P10-NS4-08.md
Normal file
73
docs/prd/Neo_Specs/review-audit/P10-NS4-08.md
Normal 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,改动范围可控
|
||||
92
docs/prd/Neo_Specs/review-audit/P10-NS4-09.md
Normal file
92
docs/prd/Neo_Specs/review-audit/P10-NS4-09.md
Normal 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 应尽早落地。
|
||||
63
docs/prd/Neo_Specs/review-audit/P5.1-NS3-01.md
Normal file
63
docs/prd/Neo_Specs/review-audit/P5.1-NS3-01.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# P5.1→NS3 缺失项 #1:App1 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 工程规范。
|
||||
75
docs/prd/Neo_Specs/review-audit/P5.1-NS3-02.md
Normal file
75
docs/prd/Neo_Specs/review-audit/P5.1-NS3-02.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# P5.1→NS3 缺失项 #2:App2 财务指标计算口径
|
||||
|
||||
## 简要结论
|
||||
- 状态:✅ 已解决
|
||||
- 风险等级:🟡 低
|
||||
- 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_amount(settle_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_amount(settle_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_amount(settle_type=5)
|
||||
80
docs/prd/Neo_Specs/review-audit/P5.1-NS3-03.md
Normal file
80
docs/prd/Neo_Specs/review-audit/P5.1-NS3-03.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# P5.1→NS3 缺失项 #3:App3 维客线索生成的触发条件和频率
|
||||
|
||||
## 简要结论
|
||||
- 状态:✅ 已解决
|
||||
- 风险等级:🟡 低
|
||||
- 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 如有助教)。"""
|
||||
# 步骤 1:App3 线索分析
|
||||
app3_result = await self._run_step("app3_clue", app3_run, context)
|
||||
# 步骤 2:App8 线索整理
|
||||
# 步骤 3:App7 客户分析
|
||||
# 步骤 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(客户分析)
|
||||
106
docs/prd/Neo_Specs/review-audit/P5.1-NS3-04.md
Normal file
106
docs/prd/Neo_Specs/review-audit/P5.1-NS3-04.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# P5.1→NS3 缺失项 #4:App4-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 缓存为失效,确保前端读到最新分析。
|
||||
128
docs/prd/Neo_Specs/review-audit/P5.1-NS3-05.md
Normal file
128
docs/prd/Neo_Specs/review-audit/P5.1-NS3-05.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# P5.1→NS3 缺失项 #5:LLM 调用的错误处理和降级策略
|
||||
|
||||
## 简要结论
|
||||
- 状态:⚠️ 部分解决
|
||||
- 风险等级:🟠 中
|
||||
- 重试机制和异常分类已完整实现,调用链级容错(步骤失败后继续)也已到位。但缺少限流(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 / 超时 / 连接错误:重试
|
||||
- 4xx(400/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` 字段,前端据此展示不同的用户提示。
|
||||
70
docs/prd/Neo_Specs/review-audit/P5.1-NS3-06.md
Normal file
70
docs/prd/Neo_Specs/review-audit/P5.1-NS3-06.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# P5.1→NS3 缺失项 #6:Token 用量监控和成本控制
|
||||
|
||||
## 简要结论
|
||||
- 状态:⚠️ 部分解决
|
||||
- 风险等级:🟠 中
|
||||
- 已实现消息级 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` 提取。所有 App(3-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 并熔断
|
||||
79
docs/prd/Neo_Specs/review-audit/P5.1-NS3-07.md
Normal file
79
docs/prd/Neo_Specs/review-audit/P5.1-NS3-07.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# P5.1→NS3 缺失项 #7:App5 话术模板分类和质量评估标准
|
||||
|
||||
## 简要结论
|
||||
- 状态:⚠️ 部分解决
|
||||
- 风险等级:🟠 中
|
||||
- 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 中注入预定义的优秀话术范例,提升生成质量
|
||||
78
docs/prd/Neo_Specs/review-audit/P5.1-NS3-08.md
Normal file
78
docs/prd/Neo_Specs/review-audit/P5.1-NS3-08.md
Normal 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 用例)
|
||||
76
docs/prd/Neo_Specs/review-audit/P5.1-NS3-09.md
Normal file
76
docs/prd/Neo_Specs/review-audit/P5.1-NS3-09.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# P5.1→NS3 缺失项 #9:MCP 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 lifespan(server.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 格式暴露)
|
||||
127
docs/prd/Neo_Specs/review-audit/P5.1-NS3-10.md
Normal file
127
docs/prd/Neo_Specs/review-audit/P5.1-NS3-10.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# P5.1→NS3 缺失项 #10:AI 生成内容的审计日志
|
||||
|
||||
## 简要结论
|
||||
- 状态:✅ 已解决
|
||||
- 风险等级:🟡 低
|
||||
- `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) | 关联 ID(taskId/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` 可能占用大量存储,建议制定保留策略
|
||||
53
docs/prd/Neo_Specs/review-audit/P6-NS1-01.md
Normal file
53
docs/prd/Neo_Specs/review-audit/P6-NS1-01.md
Normal 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 列表页尚未实现
|
||||
52
docs/prd/Neo_Specs/review-audit/P6-NS1-02.md
Normal file
52
docs/prd/Neo_Specs/review-audit/P6-NS1-02.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# P6→NS1/RNS1 缺失项 #2:3 种空状态设计
|
||||
|
||||
## 简要结论
|
||||
- 状态:⚠️ 部分解决
|
||||
- 风险等级:🟠 中
|
||||
- 已实现 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` 组件替代自定义实现,自带图标和文案插槽
|
||||
52
docs/prd/Neo_Specs/review-audit/P6-NS1-03.md
Normal file
52
docs/prd/Neo_Specs/review-audit/P6-NS1-03.md
Normal 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 中按「📌 置顶 → 正常任务 → 已放弃」三组依次渲染,组内保持后端返回顺序。
|
||||
|
||||
### 证据
|
||||
|
||||
后端 SQL(task_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 的精确定义有微小差异。
|
||||
54
docs/prd/Neo_Specs/review-audit/P6-NS1-04.md
Normal file
54
docs/prd/Neo_Specs/review-audit/P6-NS1-04.md
Normal 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` 实现简单确认弹窗
|
||||
81
docs/prd/Neo_Specs/review-audit/P6-NS1-05.md
Normal file
81
docs/prd/Neo_Specs/review-audit/P6-NS1-05.md
Normal 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 动画(当前使用微信原生样式)
|
||||
64
docs/prd/Neo_Specs/review-audit/P6-NS1-06.md
Normal file
64
docs/prd/Neo_Specs/review-audit/P6-NS1-06.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# P6→NS1/RNS1 缺失项 #6:AI 分析卡片的折叠/展开交互
|
||||
|
||||
## 简要结论
|
||||
- 状态:❌ 未解决
|
||||
- 风险等级:🔴 高
|
||||
- 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` 仅支持读取缓存)
|
||||
65
docs/prd/Neo_Specs/review-audit/P6-NS1-07.md
Normal file
65
docs/prd/Neo_Specs/review-audit/P6-NS1-07.md
Normal 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. 注意:当前任务类型名称已隐含优先级信息(「高优先召回」),是否需要额外的优先级标识需与产品确认
|
||||
68
docs/prd/Neo_Specs/review-audit/P6-NS1-08.md
Normal file
68
docs/prd/Neo_Specs/review-audit/P6-NS1-08.md
Normal 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()` 中增加判断分支
|
||||
81
docs/prd/Neo_Specs/review-audit/P6-NS1-09.md
Normal file
81
docs/prd/Neo_Specs/review-audit/P6-NS1-09.md
Normal 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 字时变色)
|
||||
43
docs/prd/Neo_Specs/review-audit/P6-NS1-10.md
Normal file
43
docs/prd/Neo_Specs/review-audit/P6-NS1-10.md
Normal 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`)
|
||||
51
docs/prd/Neo_Specs/review-audit/P6-NS1-11.md
Normal file
51
docs/prd/Neo_Specs/review-audit/P6-NS1-11.md
Normal 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 || ''}}" />
|
||||
```
|
||||
|
||||
### 建议
|
||||
无需额外补充。
|
||||
43
docs/prd/Neo_Specs/review-audit/P6-NS1-12.md
Normal file
43
docs/prd/Neo_Specs/review-audit/P6-NS1-12.md
Normal 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. 搜索结果为空时显示专用空状态("未找到匹配的客户")
|
||||
51
docs/prd/Neo_Specs/review-audit/P6-NS1-13.md
Normal file
51
docs/prd/Neo_Specs/review-audit/P6-NS1-13.md
Normal 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` 接口
|
||||
64
docs/prd/Neo_Specs/review-audit/P6-NS1-14.md
Normal file
64
docs/prd/Neo_Specs/review-audit/P6-NS1-14.md
Normal 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` 缓存上次成功的列表数据)
|
||||
57
docs/prd/Neo_Specs/review-audit/P6-NS1-15.md
Normal file
57
docs/prd/Neo_Specs/review-audit/P6-NS1-15.md
Normal 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-delete)UI
|
||||
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. 优先级较低,可作为后续体验优化迭代
|
||||
58
docs/prd/Neo_Specs/review-audit/P6-NS1-16.md
Normal file
58
docs/prd/Neo_Specs/review-audit/P6-NS1-16.md
Normal 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(前端打磨)的后续迭代项
|
||||
58
docs/prd/Neo_Specs/review-audit/P6-NS1-17.md
Normal file
58
docs/prd/Neo_Specs/review-audit/P6-NS1-17.md
Normal 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. 建议在用户反馈确认需求后再实现,避免过度设计
|
||||
62
docs/prd/Neo_Specs/review-audit/P6-NS1-18.md
Normal file
62
docs/prd/Neo_Specs/review-audit/P6-NS1-18.md
Normal 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 前端打磨的后续迭代
|
||||
57
docs/prd/Neo_Specs/review-audit/P7-NS1-01.md
Normal file
57
docs/prd/Neo_Specs/review-audit/P7-NS1-01.md
Normal 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 之间的少量记录归属,风险极低
|
||||
63
docs/prd/Neo_Specs/review-audit/P7-NS1-02.md
Normal file
63
docs/prd/Neo_Specs/review-audit/P7-NS1-02.md
Normal 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 中缺失
|
||||
73
docs/prd/Neo_Specs/review-audit/P7-NS1-03.md
Normal file
73
docs/prd/Neo_Specs/review-audit/P7-NS1-03.md
Normal 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` 字段
|
||||
81
docs/prd/Neo_Specs/review-audit/P7-NS1-04.md
Normal file
81
docs/prd/Neo_Specs/review-audit/P7-NS1-04.md
Normal 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. **确认新客定义的业务边界**:与产品确认"首次服务"是指该助教的首次服务还是全店首次服务
|
||||
74
docs/prd/Neo_Specs/review-audit/P7-NS1-05.md
Normal file
74
docs/prd/Neo_Specs/review-audit/P7-NS1-05.md
Normal 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` 无小数)略有差异,建议统一
|
||||
106
docs/prd/Neo_Specs/review-audit/P7-NS1-06.md
Normal file
106
docs/prd/Neo_Specs/review-audit/P7-NS1-06.md
Normal 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 标注)
|
||||
58
docs/prd/Neo_Specs/review-audit/P7-NS1-07.md
Normal file
58
docs/prd/Neo_Specs/review-audit/P7-NS1-07.md
Normal 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}} —</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`)。
|
||||
73
docs/prd/Neo_Specs/review-audit/P7-NS1-08.md
Normal file
73
docs/prd/Neo_Specs/review-audit/P7-NS1-08.md
Normal 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 → 请求 → 渲染的完整流程,满足基本需求。可考虑增加本地缓存避免重复请求已加载过的月份。
|
||||
61
docs/prd/Neo_Specs/review-audit/P7-NS1-09.md
Normal file
61
docs/prd/Neo_Specs/review-audit/P7-NS1-09.md
Normal 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',
|
||||
// ...
|
||||
})
|
||||
```
|
||||
|
||||
### 建议
|
||||
|
||||
无需额外补充。空状态处理已覆盖新助教无数据场景。
|
||||
49
docs/prd/Neo_Specs/review-audit/P7-NS1-10.md
Normal file
49
docs/prd/Neo_Specs/review-audit/P7-NS1-10.md
Normal 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)而非小程序端提供导出功能,降低小程序端复杂度。
|
||||
75
docs/prd/Neo_Specs/review-audit/P7-NS1-11.md
Normal file
75
docs/prd/Neo_Specs/review-audit/P7-NS1-11.md
Normal 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 层视图)
|
||||
- 两者均通过 FDW(postgres_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,确保数据每日刷新。
|
||||
72
docs/prd/Neo_Specs/review-audit/P7-NS1-12.md
Normal file
72
docs/prd/Neo_Specs/review-audit/P7-NS1-12.md
Normal 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` 参数,同时覆盖周口径和任意时间段需求。
|
||||
80
docs/prd/Neo_Specs/review-audit/P8-NS1-01.md
Normal file
80
docs/prd/Neo_Specs/review-audit/P8-NS1-01.md
Normal 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 区域实现高亮滑动效果
|
||||
69
docs/prd/Neo_Specs/review-audit/P8-NS1-02.md
Normal file
69
docs/prd/Neo_Specs/review-audit/P8-NS1-02.md
Normal 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 板块全量查询会导致首屏加载缓慢
|
||||
50
docs/prd/Neo_Specs/review-audit/P8-NS1-03.md
Normal file
50
docs/prd/Neo_Specs/review-audit/P8-NS1-03.md
Normal 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 })
|
||||
},
|
||||
```
|
||||
80
docs/prd/Neo_Specs/review-audit/P8-NS1-04.md
Normal file
80
docs/prd/Neo_Specs/review-audit/P8-NS1-04.md
Normal 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` 的使用方式
|
||||
61
docs/prd/Neo_Specs/review-audit/P8-NS1-05.md
Normal file
61
docs/prd/Neo_Specs/review-audit/P8-NS1-05.md
Normal 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"
|
||||
60
docs/prd/Neo_Specs/review-audit/P8-NS1-06.md
Normal file
60
docs/prd/Neo_Specs/review-audit/P8-NS1-06.md
Normal 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 值,只需在响应中透传即可
|
||||
58
docs/prd/Neo_Specs/review-audit/P8-NS1-07.md
Normal file
58
docs/prd/Neo_Specs/review-audit/P8-NS1-07.md
Normal 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",
|
||||
...
|
||||
]
|
||||
```
|
||||
35
docs/prd/Neo_Specs/review-audit/P8-NS1-08.md
Normal file
35
docs/prd/Neo_Specs/review-audit/P8-NS1-08.md
Normal 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. 或者考虑:由于柱状图尺寸较小(迷你图),点击交互在移动端体验可能不佳,可评估是否改为点击整张卡片进入详情页后查看完整图表
|
||||
57
docs/prd/Neo_Specs/review-audit/P8-NS1-09.md
Normal file
57
docs/prd/Neo_Specs/review-audit/P8-NS1-09.md
Normal 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 链式调用。
|
||||
50
docs/prd/Neo_Specs/review-audit/P8-NS1-10.md
Normal file
50
docs/prd/Neo_Specs/review-audit/P8-NS1-10.md
Normal 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 导航,此项可作为体验优化延后处理
|
||||
68
docs/prd/Neo_Specs/review-audit/P8-NS1-11.md
Normal file
68
docs/prd/Neo_Specs/review-audit/P8-NS1-11.md
Normal 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-catch,catch 中设置 `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>` 空态组件 + 重试按钮。
|
||||
59
docs/prd/Neo_Specs/review-audit/P8-NS1-12.md
Normal file
59
docs/prd/Neo_Specs/review-audit/P8-NS1-12.md
Normal 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 实现,性能良好。
|
||||
61
docs/prd/Neo_Specs/review-audit/P8-NS1-13.md
Normal file
61
docs/prd/Neo_Specs/review-audit/P8-NS1-13.md
Normal 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. **客户看板同理**:如果助教看板需要排名序号,客户看板的列表也应考虑一致性
|
||||
96
docs/prd/Neo_Specs/review-audit/P8-NS1-14.md
Normal file
96
docs/prd/Neo_Specs/review-audit/P8-NS1-14.md
Normal 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 模板使用
|
||||
- 看板页面已实际调用格式化函数
|
||||
65
docs/prd/Neo_Specs/review-audit/P9-NS1-01.md
Normal file
65
docs/prd/Neo_Specs/review-audit/P9-NS1-01.md
Normal 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` 等),前端并行请求 + 分段渲染
|
||||
60
docs/prd/Neo_Specs/review-audit/P9-NS1-02.md
Normal file
60
docs/prd/Neo_Specs/review-audit/P9-NS1-02.md
Normal 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` 变化时触发一次性庆祝效果
|
||||
57
docs/prd/Neo_Specs/review-audit/P9-NS1-03.md
Normal file
57
docs/prd/Neo_Specs/review-audit/P9-NS1-03.md
Normal 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`)
|
||||
73
docs/prd/Neo_Specs/review-audit/P9-NS1-04.md
Normal file
73
docs/prd/Neo_Specs/review-audit/P9-NS1-04.md
Normal 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,可用长按弹窗替代)
|
||||
61
docs/prd/Neo_Specs/review-audit/P9-NS1-05.md
Normal file
61
docs/prd/Neo_Specs/review-audit/P9-NS1-05.md
Normal 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`,需确认数据源是否已接入
|
||||
- 可添加单位后缀(如"天"、"元")提升可读性
|
||||
55
docs/prd/Neo_Specs/review-audit/P9-NS1-06.md
Normal file
55
docs/prd/Neo_Specs/review-audit/P9-NS1-06.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# P9→NS1/RNS1 缺失项 #6:AI 洞察卡片的展示规范
|
||||
|
||||
## 简要结论
|
||||
- 状态:⚠️ 部分解决
|
||||
- 风险等级:🟠 中
|
||||
- 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` 端点触发重新分析
|
||||
58
docs/prd/Neo_Specs/review-audit/P9-NS1-07.md
Normal file
58
docs/prd/Neo_Specs/review-audit/P9-NS1-07.md
Normal 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 定义的任务类型图标(用颜色标签替代)、状态标签、服务统计。
|
||||
60
docs/prd/Neo_Specs/review-audit/P9-NS1-08.md
Normal file
60
docs/prd/Neo_Specs/review-audit/P9-NS1-08.md
Normal 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. **优先级评估**:当前数值展示已满足基本信息需求,图表可作为后续优化项
|
||||
57
docs/prd/Neo_Specs/review-audit/P9-NS1-09.md
Normal file
57
docs/prd/Neo_Specs/review-audit/P9-NS1-09.md
Normal 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. 添加"展开全部"/"收起"按钮
|
||||
79
docs/prd/Neo_Specs/review-audit/P9-NS1-10.md
Normal file
79
docs/prd/Neo_Specs/review-audit/P9-NS1-10.md
Normal 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 映射是否正确
|
||||
67
docs/prd/Neo_Specs/review-audit/P9-NS1-11.md
Normal file
67
docs/prd/Neo_Specs/review-audit/P9-NS1-11.md
Normal 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 数据已正确绑定
|
||||
68
docs/prd/Neo_Specs/review-audit/P9-NS1-12.md
Normal file
68
docs/prd/Neo_Specs/review-audit/P9-NS1-12.md
Normal 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` 字段返回,需确认数据源
|
||||
54
docs/prd/Neo_Specs/review-audit/P9-NS1-13.md
Normal file
54
docs/prd/Neo_Specs/review-audit/P9-NS1-13.md
Normal 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;
|
||||
}
|
||||
```
|
||||
54
docs/prd/Neo_Specs/review-audit/P9-NS1-14.md
Normal file
54
docs/prd/Neo_Specs/review-audit/P9-NS1-14.md
Normal 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` 的映射逻辑,或在前端建立映射表。
|
||||
33
docs/prd/Neo_Specs/review-audit/P9-NS1-15.md
Normal file
33
docs/prd/Neo_Specs/review-audit/P9-NS1-15.md
Normal 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),接收方打开后跳转到客户详情页
|
||||
- 注意:分享内容中不应包含敏感信息(完整手机号、余额等)
|
||||
41
docs/prd/Neo_Specs/review-audit/P9-NS1-16.md
Normal file
41
docs/prd/Neo_Specs/review-audit/P9-NS1-16.md
Normal 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 区域或"更多信息"卡片中添加联系方式展示
|
||||
- 联系方式应做脱敏处理,提供"点击查看"交互
|
||||
46
docs/prd/Neo_Specs/review-audit/P9-NS1-17.md
Normal file
46
docs/prd/Neo_Specs/review-audit/P9-NS1-17.md
Normal 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. 月份切换和自定义范围可共存,自定义范围优先级更高
|
||||
- 当前按月切换已满足基本需求,自定义范围为增强功能,优先级可后置
|
||||
46
docs/prd/Neo_Specs/review-audit/P9-NS1-18.md
Normal file
46
docs/prd/Neo_Specs/review-audit/P9-NS1-18.md
Normal 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' })
|
||||
}
|
||||
}
|
||||
```
|
||||
- 当前依赖默认导航栏返回在正常导航流程中可正常工作,仅在直接进入场景有问题
|
||||
47
docs/prd/Neo_Specs/review-audit/P9-NS1-19.md
Normal file
47
docs/prd/Neo_Specs/review-audit/P9-NS1-19.md
Normal 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` 字段(已脱敏)
|
||||
- 建议统一脱敏策略:要么全部由后端脱敏,要么全部由前端脱敏,避免两端重复实现
|
||||
43
docs/prd/Neo_Specs/review-audit/P9-NS1-20.md
Normal file
43
docs/prd/Neo_Specs/review-audit/P9-NS1-20.md
Normal 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 项数据,全部展示的体验可能优于折叠,建议与产品确认是否需要折叠
|
||||
42
docs/prd/Neo_Specs/review-audit/P9-NS1-21.md
Normal file
42
docs/prd/Neo_Specs/review-audit/P9-NS1-21.md
Normal 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>
|
||||
```
|
||||
68
docs/prd/Neo_Specs/review-audit/P9-NS1-22.md
Normal file
68
docs/prd/Neo_Specs/review-audit/P9-NS1-22.md
Normal 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 调用中返回。
|
||||
59
docs/prd/Neo_Specs/review-audit/P9-NS1-23.md
Normal file
59
docs/prd/Neo_Specs/review-audit/P9-NS1-23.md
Normal 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 统计区域展示
|
||||
288
docs/prd/Neo_Specs/review-report.md
Normal file
288
docs/prd/Neo_Specs/review-report.md
Normal 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.1(MCP 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 → NS3(2 项)
|
||||
|
||||
| # | 缺失内容 | 影响 |
|
||||
|---|---------|------|
|
||||
| 1 | App1(智能任务生成)的完整 Prompt 工程规范:P5.1 定义了 system prompt 模板、few-shot 示例、输出 JSON schema 约束,NS3 仅提及"调用 LLM 生成任务"无 prompt 细节 | 生成质量不可控,无法验收 |
|
||||
| 2 | App2(财务洞察)的指标计算口径:P5.1 明确了 6 个财务指标的计算公式(含 items_sum 口径、环比基准),NS3 仅列出"生成财务分析报告" | 财务数据可能计算错误 |
|
||||
|
||||
### P6 → NS1/RNS1(6 项)
|
||||
|
||||
| # | 缺失内容 | 影响 |
|
||||
|---|---------|------|
|
||||
| 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/RNS1(3 项)
|
||||
|
||||
| # | 缺失内容 | 影响 |
|
||||
|---|---------|------|
|
||||
| 1 | 营业日分割点(08:00)的完整处理规范:P7 多次强调"营业日以 08:00 为分割点"并给出具体示例("本月"= 当月1日 08:00 ~ 次月1日 08:00),NS1/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/RNS1(4 项)
|
||||
|
||||
| # | 缺失内容 | 影响 |
|
||||
|---|---------|------|
|
||||
| 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/RNS1(4 项)
|
||||
|
||||
| # | 缺失内容 | 影响 |
|
||||
|---|---------|------|
|
||||
| 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 星)和评分说明 tooltip,NS1 仅返回 `ai_score` 数值 | 评分展示不直观 |
|
||||
|
||||
---
|
||||
|
||||
## 三、各标杆文件详细对比
|
||||
|
||||
### 3.1 P5.1(MCP 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 Design),NS4 覆盖了主要功能模块,但以下细节有遗漏。
|
||||
|
||||
| # | 风险 | 缺失内容 | 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. 补充页面转场动画规范
|
||||
@@ -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=false,LIMIT 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) | 页面上下文截断阈值是 2000,4000 是 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 字符
|
||||
|
||||
244
docs/prd/specs/P13-miniapp-fe-polish.md
Normal file
244
docs/prd/specs/P13-miniapp-fe-polish.md
Normal 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.2:board-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.1:task-detail 储值区域根据 balance 正确显示等级文案
|
||||
- AC-G4.2:balance 为 0 时显示"无"
|
||||
|
||||
---
|
||||
|
||||
## 二、各页面功能点
|
||||
|
||||
### P1:task-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`
|
||||
|
||||
### P2:performance(绩效总览)
|
||||
|
||||
| 编号 | 功能点 | 现状 | 需求 |
|
||||
|------|--------|------|------|
|
||||
| T2.1 | banner 头像 | 无 `avatarUrl` 字段 | 见 G1 |
|
||||
| T2.2 | 预估/实际收入 | 硬编码"预估" | 见 G2 |
|
||||
|
||||
### P3:performance-records(绩效明细)
|
||||
|
||||
| 编号 | 功能点 | 现状 | 需求 |
|
||||
|------|--------|------|------|
|
||||
| T3.1 | banner 头像 | 无 `avatarUrl` 字段 | 见 G1 |
|
||||
| T3.2 | 预估/实际收入 | 硬编码"预估" | 见 G2 |
|
||||
| T3.3 | 总笔数 | 前端 `records.length` 计算 | 后端接口返回 `totalCount` 字段,前端直接使用(分页场景下前端计数不准确) |
|
||||
| T3.4 | 绩效折前 | 接口有字段但 label 始终为空 | 见 G3 |
|
||||
|
||||
### P4:task-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 爱心/台球双维度评分已完整 |
|
||||
|
||||
### P5:customer-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 猜测 |
|
||||
|
||||
### P6:board-finance(财务看板)
|
||||
|
||||
| 编号 | 功能点 | 现状 | 需求 |
|
||||
|------|--------|------|------|
|
||||
| T6.1 | AI 智能洞察 | WXML 硬编码 3 行文案 | 后端接口返回 `aiInsights: Array<{ icon: string; text: string }>`,前端动态渲染 |
|
||||
| T6.2 | 环比箭头 | ✅ 已实现 | compareEnabled 开关 + ↑/↓ 箭头 + 颜色区分 |
|
||||
| T6.3 | 本月"预估" | 无预估标记 | 见 G2 |
|
||||
|
||||
### P7:board-customer(客户看板)
|
||||
|
||||
| 编号 | 功能点 | 现状 | 需求 |
|
||||
|------|--------|------|------|
|
||||
| T7.1 | 爱心 icon | ✅ 已实现 | heart-icon 组件 + getHeartEmoji 映射 |
|
||||
|
||||
### P8:customer-detail(客户详情)
|
||||
|
||||
| 编号 | 功能点 | 现状 | 需求 |
|
||||
|------|--------|------|------|
|
||||
| T8.1 | 电话 | ✅ 已实现 | onTogglePhone + onCopyPhone 完整 |
|
||||
|
||||
### P9:board-coach(助教看板)
|
||||
|
||||
| 编号 | 功能点 | 现状 | 需求 |
|
||||
|------|--------|------|------|
|
||||
| T9.1 | 级别 Icon | ✅ 已实现 | coach-level-tag 组件 + LEVEL_CLASS 映射 |
|
||||
|
||||
### P10:coach-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
Reference in New Issue
Block a user