feat: P1-P3 全栈集成 — 数据库基础 + DWS 扩展 + 小程序鉴权 + 工程化体系
## P1 数据库基础 - zqyy_app: 创建 auth/biz schema、FDW 连接 etl_feiqiu - etl_feiqiu: 创建 app schema RLS 视图、商品库存预警表 - 清理 assistant_abolish 残留数据 ## P2 ETL/DWS 扩展 - 新增 DWS 助教订单贡献度表 (dws.assistant_order_contribution) - 新增 assistant_order_contribution_task 任务及 RLS 视图 - member_consumption 增加充值字段、assistant_daily 增加处罚字段 - 更新 ODS/DWD/DWS 任务文档及业务规则文档 - 更新 consistency_checker、flow_runner、task_registry 等核心模块 ## P3 小程序鉴权系统 - 新增 xcx_auth 路由/schema(微信登录 + JWT) - 新增 wechat/role/matching/application 服务层 - zqyy_app 鉴权表迁移 + 角色权限种子数据 - auth/dependencies.py 支持小程序 JWT 鉴权 ## 文档与审计 - 新增 DOCUMENTATION-MAP 文档导航 - 新增 7 份 BD_Manual 数据库变更文档 - 更新 DDL 基线快照(etl_feiqiu 6 schema + zqyy_app auth) - 新增全栈集成审计记录、部署检查清单更新 - 新增 BACKLOG 路线图、FDW→Core 迁移计划 ## Kiro 工程化 - 新增 5 个 Spec(P1/P2/P3/全栈集成/核心业务) - 新增审计自动化脚本(agent_on_stop/build_audit_context/compliance_prescan) - 新增 6 个 Hook(合规检查/会话日志/提交审计等) - 新增 doc-map steering 文件 ## 运维与测试 - 新增 ops 脚本:迁移验证/API 健康检查/ETL 监控/集成报告 - 新增属性测试:test_dws_contribution / test_auth_system - 清理过期 export 报告文件 - 更新 .gitignore 排除规则
This commit is contained in:
230
docs/DOCUMENTATION-MAP.md
Normal file
230
docs/DOCUMENTATION-MAP.md
Normal file
@@ -0,0 +1,230 @@
|
||||
# NeoZQYY 文档地图
|
||||
|
||||
> 本文档记录项目中所有文档资产的位置、类型和内容概要,方便快速定位。
|
||||
|
||||
---
|
||||
|
||||
## 一、根目录
|
||||
|
||||
| 文件 | 内容 |
|
||||
|------|------|
|
||||
| `README.md` | 项目总览:模块表(含文档链接)、技术栈、快速开始命令 |
|
||||
| `.env.template` | 环境变量模板,列出所有可配置项及说明 |
|
||||
|
||||
---
|
||||
|
||||
## 二、项目级文档 `docs/`
|
||||
|
||||
### 2.1 文档中心首页
|
||||
|
||||
| 文件 | 内容 |
|
||||
|------|------|
|
||||
| `docs/README.md` | 项目架构图、模块文档索引、技术栈速览、认证体系概览、四库架构、常用命令 |
|
||||
| `docs/etl-feiqiu-architecture.md` | ETL Connector 整体架构说明 |
|
||||
|
||||
### 2.2 数据库变更手册 `docs/database/`
|
||||
|
||||
每个 `BD_Manual_*.md` 是一次数据库结构变更的完整审计文档,包含:变更说明、字段定义、影响分析、回滚策略、验证 SQL。
|
||||
|
||||
| 文件 | 记录内容 |
|
||||
|------|----------|
|
||||
| `BD_Manual_auth_tables.md` | auth Schema 8 张认证表(users、roles、permissions、user_applications 等) |
|
||||
| `BD_Manual_auth_biz_schemas.md` | auth + biz Schema 创建 |
|
||||
| `BD_Manual_fdw_etl_setup.md` | FDW 跨库访问配置(zqyy_app → etl_feiqiu) |
|
||||
| `BD_Manual_app_schema_rls_views.md` | app Schema RLS 视图 |
|
||||
| `BD_Manual_dws_assistant_order_contribution.md` | DWS 助教订单贡献表 |
|
||||
| `BD_Manual_dws_goods_stock_summary.md` | DWS 商品库存汇总表 |
|
||||
| `BD_Manual_dws_member_spending_power_index.md` | DWS 会员消费力指数表 |
|
||||
| `BD_Manual_member_balance_changes.md` | 会员余额变动表 |
|
||||
| `BD_Manual_recharge_settlements.md` | 充值结算表 |
|
||||
| `BD_Manual_goods_stock_movements.md` | 商品库存流水表 |
|
||||
| `BD_Manual_goods_stock_summary.md` | 商品库存汇总表 |
|
||||
| `BD_Manual_goods_stock_warning_info.md` | 商品库存预警表 |
|
||||
| `BD_Manual_store_goods_master.md` | 门店商品主表 |
|
||||
| `BD_Manual_store_goods_sales_records.md` | 门店商品销售记录 |
|
||||
| `BD_Manual_tenant_goods_master.md` | 租户商品主表 |
|
||||
| `BD_Manual_assistant_accounts_master.md` | 助教账户主表 |
|
||||
| `BD_Manual_assistant_service_records.md` | 助教服务记录表 |
|
||||
| `BD_Manual_site_tables_master.md` | 门店台桌主表 |
|
||||
| `README.md` | 数据库文档目录说明 |
|
||||
|
||||
子目录:
|
||||
|
||||
| 目录 | 内容 |
|
||||
|------|------|
|
||||
| `ddl/` | 9 个 DDL 基线文件,覆盖 etl_feiqiu 六层 Schema(meta/ods/dwd/core/dws/app)+ zqyy_app 两个 Schema(auth/public)+ FDW |
|
||||
| `_archived/` | 10 个已归档的历史变更文档(已废弃的表、已回滚的变更等) |
|
||||
|
||||
### 2.3 审计记录 `docs/audit/`
|
||||
|
||||
项目变更的完整审计追踪体系。
|
||||
|
||||
| 路径 | 内容 |
|
||||
|------|------|
|
||||
| `audit_dashboard.md` | 审计仪表盘,汇总所有变更审计记录 |
|
||||
| `README.md` | 审计目录说明 |
|
||||
| `changes/` | 31 份变更审计文档(`YYYY-MM-DD__<slug>.md` 格式),每份包含:变更原因、影响范围、回滚策略、验证 SQL |
|
||||
| `prompt_logs/` | ~500 份 Prompt 日志(`prompt_log_YYYYMMDD_HHMMSS.md`),记录每次 AI 交互的输入输出 |
|
||||
|
||||
### 2.4 数据契约 `docs/contracts/`
|
||||
|
||||
| 路径 | 内容 |
|
||||
|------|------|
|
||||
| `openapi/backend-api.json` | 后端 API 的 OpenAPI 规范文件 |
|
||||
| `data_dictionary/` | 数据字典(预留,待填充) |
|
||||
| `schemas/` | 数据 Schema 定义(预留,待填充) |
|
||||
|
||||
### 2.5 部署文档 `docs/deployment/`
|
||||
|
||||
| 文件 | 内容 |
|
||||
|------|------|
|
||||
| `LAUNCH-CHECKLIST.md` | 上线检查清单:环境配置、数据库迁移、服务启动、验证步骤 |
|
||||
| `EXPORT-PATHS.md` | 输出路径规范:环境变量映射表、目录结构、新增场景检查清单 |
|
||||
|
||||
### 2.6 产品需求 `docs/prd/`
|
||||
|
||||
| 路径 | 内容 |
|
||||
|------|------|
|
||||
| `小程序前后端.txt` | 小程序前后端原始需求描述 |
|
||||
| `PRD审阅-Q&A.md` | PRD 审阅问答记录(第一轮) |
|
||||
| `PRD审阅-Q&A-R2.md` | PRD 审阅问答记录(第二轮) |
|
||||
| `SPI 消费力指数.md` | 消费力指数(SPI)算法需求说明 |
|
||||
| `specs/00-数据依赖矩阵.md` | 各 SPEC 间的数据依赖关系矩阵 |
|
||||
| `specs/01-SPEC任务拆分总览.md` | 11 个 SPEC 的任务拆分总览 |
|
||||
| `specs/P1~P11` | 11 份 SPEC 拆分文档,覆盖:数据库基础(P1)、ETL DWS 扩展(P2)、认证系统(P3)、核心业务(P4)、AI 集成(P5)、前端任务/绩效/看板/详情(P6-P9)、租户管理后台(P10)、部署上线(P11) |
|
||||
|
||||
### 2.7 小程序 UI 原型 `docs/h5_ui/`
|
||||
|
||||
H5 静态原型页面,用于小程序 UI 设计参考。
|
||||
|
||||
| 路径 | 内容 |
|
||||
|------|------|
|
||||
| `index.html` | 原型首页入口 |
|
||||
| `pages/` | 23 个页面原型,包括:登录(`login`)、申请(`apply`)、审核中(`reviewing`)、无权限(`no-permission`)、任务列表/详情(`task-list`/`task-detail`)、绩效(`performance`/`performance-records`)、助教详情(`coach-detail`)、客户详情(`customer-detail`)、客户服务记录(`customer-service-records`)、看板(`board-coach`/`board-customer`/`board-finance`)、聊天(`chat`/`chat-history`)、个人中心(`my-profile`)、首页设置(`home-settings`)、笔记(`notes`)、AI 图标演示(`ai-icon-demo`) |
|
||||
| `css/` | 6 个样式文件 |
|
||||
| `js/` | 8 个交互脚本 |
|
||||
| `img/` | 图片资源 |
|
||||
|
||||
### 2.8 其他项目级文档目录
|
||||
|
||||
| 路径 | 内容 |
|
||||
|------|------|
|
||||
| `docs/architecture/` | 架构文档(预留,待填充) |
|
||||
| `docs/roadmap/BACKLOG.md` | 项目待办事项 |
|
||||
| `docs/roadmap/2026-02-24__fdw-dwd-to-core-migration-plan.md` | FDW + DWD→Core 迁移计划 |
|
||||
| `docs/migrate/monorepo-migration-summary.md` | Monorepo 迁移总结 |
|
||||
| `docs/migrate/oldworkspace-kiro-agent-config-summary.md` | 旧工作区 Kiro 配置迁移记录 |
|
||||
| `docs/ops/` | 运维文档(预留,待填充) |
|
||||
| `docs/permission_matrix/` | 权限矩阵(预留,待填充) |
|
||||
| `docs/spec-input/2026-02-22__etl-aggregation-fix-spec-input.md` | ETL 聚合修复的 Spec 输入文档 |
|
||||
| `docs/etl-feiqiu-architecture.md` | ETL Connector 整体架构说明 |
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 三、模块内部文档
|
||||
|
||||
### 3.1 FastAPI 后端 `apps/backend/`
|
||||
|
||||
| 文件 | 内容 |
|
||||
|------|------|
|
||||
| `README.md` | 架构概览、双库连接、认证系统、13 个路由模块摘要、服务层、配置加载 |
|
||||
| `docs/API-REFERENCE.md` | 完整 API 参考:13 个路由模块的所有端点、请求/响应示例、认证要求、错误码 |
|
||||
|
||||
### 3.2 ETL Connector `apps/etl/connectors/feiqiu/`
|
||||
|
||||
| 路径 | 内容 |
|
||||
|------|------|
|
||||
| `README.md` | Connector 总览、快速开始、CLI 用法 |
|
||||
| `docs/README.md` | 文档目录索引 |
|
||||
| `docs/CHANGELOG.md` | 变更日志 |
|
||||
| `docs/api-reference/` | 上游飞球 API 接口文档(字段映射、请求参数、响应结构) |
|
||||
| `docs/architecture/` | 架构设计文档(数据流、分层设计、SCD 策略) |
|
||||
| `docs/business-rules/` | 业务规则文档(金额精度、时区处理、去重逻辑) |
|
||||
| `docs/database/` | ETL 数据库文档(Schema 设计、表结构、索引策略) |
|
||||
| `docs/etl_tasks/` | ETL 任务文档(每个任务的输入输出、依赖、调度配置) |
|
||||
| `docs/operations/` | 运维文档(监控、告警、故障排查) |
|
||||
| `docs/requirements/` | 需求文档(功能需求、非功能需求) |
|
||||
|
||||
### 3.3 微信小程序 `apps/miniprogram/`
|
||||
|
||||
| 文件 | 内容 |
|
||||
|------|------|
|
||||
| `README.md` | 后端 API 集成、认证流程、权限模型、关键端点说明 |
|
||||
|
||||
### 3.4 管理后台 `apps/admin-web/`
|
||||
|
||||
| 文件 | 内容 |
|
||||
|------|------|
|
||||
| `README.md` | 8 个页面、组件体系、API 层、状态管理、开发指南 |
|
||||
|
||||
### 3.5 MCP Server `apps/mcp-server/`
|
||||
|
||||
| 文件 | 内容 |
|
||||
|------|------|
|
||||
| `README.md` | MCP Server 功能说明、工具列表、配置方式 |
|
||||
|
||||
### 3.6 共享包 `packages/shared/`
|
||||
|
||||
| 文件 | 内容 |
|
||||
|------|------|
|
||||
| `README.md` | 3 个模块(enums / money / datetime_utils)的 API 文档及用法示例 |
|
||||
|
||||
---
|
||||
|
||||
## 四、数据库目录 `db/`
|
||||
|
||||
| 路径 | 内容 |
|
||||
|------|------|
|
||||
| `README.md` | 数据库目录总览、四库架构说明 |
|
||||
| `zqyy_app/README.md` | 业务库文档:auth Schema 8 张表字段说明、迁移顺序、FDW 跨库访问 |
|
||||
| `zqyy_app/migrations/` | 业务库迁移脚本(日期前缀命名) |
|
||||
| `etl_feiqiu/README.md` | ETL 库文档:六层 Schema 说明、表清单 |
|
||||
| `etl_feiqiu/migrations/` | ETL 库迁移脚本(日期前缀命名) |
|
||||
| `fdw/` | FDW(Foreign Data Wrapper)跨库访问配置脚本 |
|
||||
| `scripts/` | 数据库运维脚本 |
|
||||
| `_archived/` | 已归档的历史数据库文件 |
|
||||
|
||||
---
|
||||
|
||||
## 五、Kiro 配置 `.kiro/`
|
||||
|
||||
### 5.1 Steering 文件(`.kiro/steering/`)
|
||||
|
||||
13 个 Steering 文件,控制 AI 助手的行为规范:
|
||||
|
||||
| 文件 | 作用 |
|
||||
|------|------|
|
||||
| `language-zh.md` | 语言规范:输出简体中文,代码标识符保留英文 |
|
||||
| `governance.md` | 治理规范:审计触发条件、执行方式、产物要求 |
|
||||
| `product.md` / `product-full.md` | 产品概述(精简版 / 完整版) |
|
||||
| `tech.md` / `tech-full.md` | 技术栈与构建(精简版 / 完整版) |
|
||||
| `structure-lite.md` / `structure.md` | 项目结构(精简版 / 完整版) |
|
||||
| `export-paths.md` / `export-paths-full.md` | 输出路径规范(精简版 / 完整版) |
|
||||
| `testing-env.md` | 测试环境规范:环境变量加载、cwd 要求、测试库使用 |
|
||||
| `db-docs.md` | 数据库文档规范 |
|
||||
| `steering-readme-maintainer.md` | README 维护者技能:变更影响审查与文档同步 |
|
||||
|
||||
### 5.2 Spec 文件(`.kiro/specs/`)
|
||||
|
||||
17 个 Spec 目录,每个包含 `requirements.md`、`design.md`、`tasks.md` 三件套:
|
||||
|
||||
| Spec | 内容 |
|
||||
|------|------|
|
||||
| `01-miniapp-db-foundation` | P1:小程序数据库基础建设 |
|
||||
| `02-etl-dws-miniapp-extensions` | P2:ETL DWS 小程序扩展 |
|
||||
| `03-miniapp-auth-system` | P3:小程序认证系统 |
|
||||
| `[ETL]-fullstack-integration` | ETL 全栈集成 |
|
||||
| `miniapp-core-business` | 小程序核心业务 |
|
||||
| `miniapp-db-foundation` | 小程序数据库基础(早期版本) |
|
||||
| `admin-web-console` | 管理后台控制台 |
|
||||
| `etl-aggregation-fix` | ETL 聚合修复 |
|
||||
| `etl-dws-flow-refactor` | ETL DWS 流程重构 |
|
||||
| `etl-pipeline-debug` | ETL 管道调试 |
|
||||
| `etl-staff-dimension` | ETL 员工维度 |
|
||||
| `dwd-phase1-refactor` | DWD 第一阶段重构 |
|
||||
| `ods-dedup-standardize` | ODS 去重标准化 |
|
||||
| `spi-spending-power-index` | SPI 消费力指数 |
|
||||
| `dataflow-field-completion` | 数据流字段补全 |
|
||||
| `dataflow-structure-audit` | 数据流结构审计 |
|
||||
| `assistant-abolish-cleanup` | 助教废除清理 |
|
||||
166
docs/README.md
166
docs/README.md
@@ -1,25 +1,153 @@
|
||||
# docs/
|
||||
# docs/ — 项目文档中心
|
||||
|
||||
## 作用说明
|
||||
NeoZQYY Monorepo 的文档中心,存放产品需求、技术架构、数据契约、运维手册、审计记录等所有文档资产。
|
||||
|
||||
项目文档中心,存放产品需求、技术架构、数据契约、运维手册、审计记录等所有文档资产。
|
||||
## 项目全貌
|
||||
|
||||
## 内部结构
|
||||
NeoZQYY 是面向台球门店业务的全栈数据平台,包含 6 个子系统:
|
||||
|
||||
- `prd/` — 产品需求文档
|
||||
- `contracts/` — 数据契约
|
||||
- `openapi/` — OpenAPI 规范
|
||||
- `schemas/` — JSON Schema
|
||||
- `data_dictionary/` — 数据字典
|
||||
- `permission_matrix/` — 权限矩阵
|
||||
- `architecture/` — 架构设计文档
|
||||
- `database/` — 数据库设计与变更文档
|
||||
- `h5_ui/` — 小程序原型与 UI 设计稿
|
||||
- `ops/` — 运维手册
|
||||
- `audit/` — 项目级统一审计目录(变更记录 + Prompt 日志 + 审计一览表)
|
||||
- `roadmap/` — 路线图
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 微信小程序(C 端) │
|
||||
│ apps/miniprogram/ │
|
||||
│ TypeScript + TDesign + Donut 多端 │
|
||||
└──────────────────────┬──────────────────────────────────────┘
|
||||
│ REST API
|
||||
┌──────────────────────▼──────────────────────────────────────┐
|
||||
│ FastAPI 后端 │
|
||||
│ apps/backend/ │
|
||||
│ 13 个路由模块 · JWT 双认证 · WebSocket 日志 │
|
||||
├──────────────┬───────────────────────┬──────────────────────┤
|
||||
│ zqyy_app │ │ etl_feiqiu │
|
||||
│ (业务库) │◄──── FDW 只读 ────────│ (ETL 数据仓库) │
|
||||
│ auth/biz │ │ 6 层 Schema │
|
||||
└──────────────┘ └──────────┬───────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────▼───────────┐
|
||||
│ ETL Connector │
|
||||
│ apps/etl/connectors/feiqiu/ │
|
||||
│ 飞球 SaaS API → ODS → DWD → DWS 三层处理 │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
## Roadmap
|
||||
┌──────────────────────┐ ┌──────────────────────┐
|
||||
│ 管理后台 │ │ MCP Server │
|
||||
│ apps/admin-web/ │ │ apps/mcp-server/ │
|
||||
│ React+Vite+AntDesign │ │ AI 工具集成 │
|
||||
└──────────────────────┘ └──────────────────────┘
|
||||
|
||||
- 补充 ADR(架构决策记录)模板
|
||||
- 完善数据字典,覆盖所有 schema 表字段
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 共享包 packages/shared/ │
|
||||
│ 枚举 · 金额精度 · 时间工具 │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 模块文档索引
|
||||
|
||||
### 应用模块(各模块 README)
|
||||
|
||||
| 模块 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| ETL Connector | [`apps/etl/connectors/feiqiu/docs/`](../apps/etl/connectors/feiqiu/docs/) | 架构、API 参考、业务规则、运维文档 |
|
||||
| FastAPI 后端 | [`apps/backend/README.md`](../apps/backend/README.md) | 架构、路由总览、认证体系、服务层 |
|
||||
| 后端 API 参考 | [`apps/backend/docs/API-REFERENCE.md`](../apps/backend/docs/API-REFERENCE.md) | 全部 API 端点详细说明 |
|
||||
| 微信小程序 | [`apps/miniprogram/README.md`](../apps/miniprogram/README.md) | 开发指南、认证流程、API 集成 |
|
||||
| 管理后台 | [`apps/admin-web/README.md`](../apps/admin-web/README.md) | 页面功能、组件、状态管理 |
|
||||
| MCP Server | [`apps/mcp-server/README.md`](../apps/mcp-server/README.md) | 工具说明、安全策略、配置 |
|
||||
| 共享包 | [`packages/shared/README.md`](../packages/shared/README.md) | 枚举、金额精度、时间工具 API |
|
||||
|
||||
### 数据库文档
|
||||
|
||||
| 文档 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| zqyy_app 架构 | [`db/zqyy_app/README.md`](../db/zqyy_app/README.md) | 业务库 Schema、表结构、迁移顺序 |
|
||||
| etl_feiqiu 架构 | [`db/etl_feiqiu/README.md`](../db/etl_feiqiu/README.md) | ETL 六层 Schema 说明 |
|
||||
| BD 手册 | [`docs/database/`](database/) | 各表的详细变更文档(BD_Manual_*.md) |
|
||||
|
||||
### 项目级文档
|
||||
|
||||
| 目录 | 说明 |
|
||||
|------|------|
|
||||
| `prd/` | 产品需求文档 |
|
||||
| `contracts/` | 数据契约(OpenAPI、JSON Schema、数据字典) |
|
||||
| `permission_matrix/` | 权限矩阵 |
|
||||
| `architecture/` | 架构设计文档 |
|
||||
| `database/` | 数据库设计与变更文档(BD 手册) |
|
||||
| `deployment/` | 部署文档(启动清单、输出路径规范) |
|
||||
| `h5_ui/` | 小程序原型与 UI 设计稿 |
|
||||
| `ops/` | 运维手册 |
|
||||
| `audit/` | 统一审计目录(变更记录 + Prompt 日志) |
|
||||
| `roadmap/` | 路线图 |
|
||||
| `migrate/` | 迁移指南 |
|
||||
| `spec-input/` | Spec 输入文档 |
|
||||
|
||||
## 技术栈速览
|
||||
|
||||
| 层级 | 技术 |
|
||||
|------|------|
|
||||
| 后端 | Python 3.10+ / FastAPI / Uvicorn / psycopg2 |
|
||||
| 前端(管理后台) | React 19 / Vite 6 / Ant Design 5 / Zustand / TypeScript |
|
||||
| 前端(小程序) | 微信原生 + Donut + TDesign / TypeScript |
|
||||
| 数据库 | PostgreSQL(4 库:etl_feiqiu / test_etl_feiqiu / zqyy_app / test_zqyy_app) |
|
||||
| 包管理 | Python: uv workspace / 前端: pnpm |
|
||||
| AI 集成 | MCP Server(PostgreSQL 只读查询) |
|
||||
|
||||
## 数据库四库架构
|
||||
|
||||
| 库名 | 用途 | 连接变量 |
|
||||
|------|------|----------|
|
||||
| `etl_feiqiu` | ETL 数据仓库(6 层 Schema) | `PG_DSN` |
|
||||
| `test_etl_feiqiu` | ETL 测试库 | `TEST_DB_DSN` |
|
||||
| `zqyy_app` | 业务数据(认证、队列、调度) | `APP_DB_DSN` |
|
||||
| `test_zqyy_app` | 业务测试库 | 默认连接 |
|
||||
|
||||
ETL 六层 Schema:`meta` → `ods` → `dwd` → `core` → `dws` → `app`
|
||||
|
||||
## 认证体系概览
|
||||
|
||||
系统支持两套独立认证:
|
||||
|
||||
| 认证方式 | 入口 | 用户来源 | 令牌 |
|
||||
|----------|------|----------|------|
|
||||
| 管理后台 | `/api/auth/login` | `admin_users` 表 | JWT(用户名+密码) |
|
||||
| 小程序 | `/api/xcx-auth/login` | `auth.users` 表 | JWT(微信 code) |
|
||||
|
||||
小程序认证流程:微信登录 → 提交申请 → 管理员审批 → 正式使用
|
||||
|
||||
## 多门店隔离
|
||||
|
||||
- 业务数据通过 `site_id` 隔离
|
||||
- ETL 数据库使用 RLS(Row Level Security)
|
||||
- 后端 JWT 令牌携带 `site_id`,所有查询自动过滤
|
||||
- 用户可关联多个门店,通过 API 切换
|
||||
|
||||
## 配置体系
|
||||
|
||||
优先级(低 → 高):根 `.env` < 应用 `.env.local` < 环境变量 < CLI 参数
|
||||
|
||||
关键环境变量见 `.env.template`。
|
||||
|
||||
## 常用命令
|
||||
|
||||
```bash
|
||||
# 安装依赖
|
||||
uv sync --all-packages
|
||||
|
||||
# 启动后端
|
||||
cd apps/backend && uv run uvicorn app.main:app --reload
|
||||
|
||||
# 启动管理后台
|
||||
cd apps/admin-web && pnpm dev
|
||||
|
||||
# ETL 执行
|
||||
cd apps/etl/connectors/feiqiu && python -m cli.main --dry-run --tasks DWD_LOAD_FROM_ODS
|
||||
|
||||
# 测试
|
||||
cd apps/etl/connectors/feiqiu && pytest tests/unit
|
||||
cd C:\NeoZQYY && pytest tests/ -v
|
||||
```
|
||||
|
||||
## 文件归属规则
|
||||
|
||||
- 模块专属的 docs/tests/scripts → 放模块内部
|
||||
- 项目级/跨模块的 docs/tests/scripts → 放根目录
|
||||
- 审计产物统一写 `docs/audit/`,禁止写入子模块内部
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
# 审计一览表
|
||||
|
||||
> 自动生成于 2026-02-16 03:44:50,请勿手动编辑。
|
||||
> 自动生成于 2026-02-26 06:28:25,请勿手动编辑。
|
||||
|
||||
## 时间线视图
|
||||
|
||||
| 日期 | 项目 | 需求摘要 | 变更类型 | 影响模块 | 风险 | 详情 |
|
||||
|------|------|----------|----------|----------|------|------|
|
||||
| 2026-02-26 | 项目级 | 变更审计:P1/P2/P3 全栈集成(DB 基础 + ETL DWS 扩展 + 小程序鉴权) | bugfix | 其他 | 低 | [链接](changes/2026-02-26__p1-p2-p3-fullstack-integration.md) |
|
||||
| 2026-02-15 | 项目级 | 审计记录:管理后台全量实现 + DB Schema 迁移 + 审计产物重组 | 清理 | 其他 | 高 | [链接](changes/2026-02-15__admin-web-console-db-migration-audit-reorg.md) |
|
||||
| 2026-02-15 | ETL-feiqiu, 项目级 | 变更审计记录(Change Audit Record) | 文档 | 其他, 文档, 质量校验 | 极低 | [链接](changes/2026-02-15__audit-consolidation-doc-reorg.md) |
|
||||
| 2026-02-15 | 后端 | 审计记录:后端依赖补全使 FastAPI 可启动 | bugfix | 其他 | 未知 | [链接](changes/2026-02-15__backend-deps-bootstrap.md) |
|
||||
@@ -85,6 +86,7 @@
|
||||
|
||||
| 日期 | 需求摘要 | 变更类型 | 影响模块 | 风险 | 详情 |
|
||||
|------|----------|----------|----------|------|------|
|
||||
| 2026-02-26 | 变更审计:P1/P2/P3 全栈集成(DB 基础 + ETL DWS 扩展 + 小程序鉴权) | bugfix | 其他 | 低 | [链接](changes/2026-02-26__p1-p2-p3-fullstack-integration.md) |
|
||||
| 2026-02-15 | 审计记录:管理后台全量实现 + DB Schema 迁移 + 审计产物重组 | 清理 | 其他 | 高 | [链接](changes/2026-02-15__admin-web-console-db-migration-audit-reorg.md) |
|
||||
| 2026-02-15 | 变更审计记录(Change Audit Record) | 文档 | 其他, 文档, 质量校验 | 极低 | [链接](changes/2026-02-15__audit-consolidation-doc-reorg.md) |
|
||||
| 2026-02-15 | 审计记录:docs/bd_manual + docs/dictionary → docs/database 合并 | 清理 | 其他, 文档, 脚本工具 | 极低 | [链接](changes/2026-02-15__docs-database-merge.md) |
|
||||
@@ -132,6 +134,7 @@
|
||||
|
||||
| 日期 | 需求摘要 | 变更类型 | 风险 | 详情 |
|
||||
|------|----------|----------|------|------|
|
||||
| 2026-02-26 | 变更审计:P1/P2/P3 全栈集成(DB 基础 + ETL DWS 扩展 + 小程序鉴权) | bugfix | 低 | [链接](changes/2026-02-26__p1-p2-p3-fullstack-integration.md) |
|
||||
| 2026-02-15 | 审计记录:管理后台全量实现 + DB Schema 迁移 + 审计产物重组 | 清理 | 高 | [链接](changes/2026-02-15__admin-web-console-db-migration-audit-reorg.md) |
|
||||
| 2026-02-15 | 变更审计记录(Change Audit Record) | 文档 | 极低 | [链接](changes/2026-02-15__audit-consolidation-doc-reorg.md) |
|
||||
| 2026-02-15 | 审计记录:后端依赖补全使 FastAPI 可启动 | bugfix | 未知 | [链接](changes/2026-02-15__backend-deps-bootstrap.md) |
|
||||
|
||||
135
docs/database/BD_Manual_app_schema_rls_views.md
Normal file
135
docs/database/BD_Manual_app_schema_rls_views.md
Normal file
@@ -0,0 +1,135 @@
|
||||
# BD_Manual:app Schema 与 RLS 视图层
|
||||
|
||||
> 目标库:`test_etl_feiqiu`(通过 `PG_DSN` 连接)
|
||||
> 迁移脚本:`db/etl_feiqiu/migrations/2026-02-24__p1_create_app_schema_rls_views.sql`
|
||||
> DDL 位置:`docs/database/ddl/etl_feiqiu__app.sql`(执行后需重新生成)
|
||||
> 关联 SPEC:`miniapp-db-foundation`(P1 基础设施层)
|
||||
|
||||
---
|
||||
|
||||
## 1. 变更说明
|
||||
|
||||
### 新增 Schema
|
||||
- `app`:RLS 视图层,供业务库通过 `postgres_fdw` 只读访问 ETL 数据
|
||||
|
||||
### 新增角色
|
||||
- `app_reader`:只读角色(`LOGIN`),拥有 `app` Schema 的 `USAGE` + `SELECT` 权限
|
||||
|
||||
### 新增视图(35 张)
|
||||
|
||||
**DWD 层(11 张,全部含 `site_id` 过滤):**
|
||||
|
||||
| 视图 | 源表 | 过滤条件 |
|
||||
|------|------|---------|
|
||||
| `app.v_dim_member` | `dwd.dim_member` | `site_id = current_setting('app.current_site_id')::bigint` |
|
||||
| `app.v_dim_assistant` | `dwd.dim_assistant` | 同上 |
|
||||
| `app.v_dim_member_card_account` | `dwd.dim_member_card_account` | 同上 |
|
||||
| `app.v_dim_table` | `dwd.dim_table` | 同上 |
|
||||
| `app.v_dwd_settlement_head` | `dwd.dwd_settlement_head` | 同上 |
|
||||
| `app.v_dwd_table_fee_log` | `dwd.dwd_table_fee_log` | 同上 |
|
||||
| `app.v_dwd_assistant_service_log` | `dwd.dwd_assistant_service_log` | 同上 |
|
||||
| `app.v_dwd_recharge_order` | `dwd.dwd_recharge_order` | 同上 |
|
||||
| `app.v_dwd_store_goods_sale` | `dwd.dwd_store_goods_sale` | 同上 |
|
||||
| `app.v_dim_staff` | `dwd.dim_staff` | 同上 |
|
||||
| `app.v_dim_staff_ex` | `dwd.dim_staff_ex` | 同上 |
|
||||
|
||||
**DWS 层 — 含 `site_id` 过滤(20 张):**
|
||||
|
||||
| 视图 | 源表 |
|
||||
|------|------|
|
||||
| `app.v_dws_member_consumption_summary` | `dws.dws_member_consumption_summary` |
|
||||
| `app.v_dws_member_visit_detail` | `dws.dws_member_visit_detail` |
|
||||
| `app.v_dws_member_winback_index` | `dws.dws_member_winback_index` |
|
||||
| `app.v_dws_member_newconv_index` | `dws.dws_member_newconv_index` |
|
||||
| `app.v_dws_member_recall_index` | `dws.dws_member_recall_index` |
|
||||
| `app.v_dws_member_assistant_relation_index` | `dws.dws_member_assistant_relation_index` |
|
||||
| `app.v_dws_member_assistant_intimacy` | `dws.dws_member_assistant_intimacy` |
|
||||
| `app.v_dws_assistant_daily_detail` | `dws.dws_assistant_daily_detail` |
|
||||
| `app.v_dws_assistant_monthly_summary` | `dws.dws_assistant_monthly_summary` |
|
||||
| `app.v_dws_assistant_salary_calc` | `dws.dws_assistant_salary_calc` |
|
||||
| `app.v_dws_assistant_customer_stats` | `dws.dws_assistant_customer_stats` |
|
||||
| `app.v_dws_assistant_finance_analysis` | `dws.dws_assistant_finance_analysis` |
|
||||
| `app.v_dws_finance_daily_summary` | `dws.dws_finance_daily_summary` |
|
||||
| `app.v_dws_finance_income_structure` | `dws.dws_finance_income_structure` |
|
||||
| `app.v_dws_finance_recharge_summary` | `dws.dws_finance_recharge_summary` |
|
||||
| `app.v_dws_finance_discount_detail` | `dws.dws_finance_discount_detail` |
|
||||
| `app.v_dws_finance_expense_summary` | `dws.dws_finance_expense_summary` |
|
||||
| `app.v_dws_platform_settlement` | `dws.dws_platform_settlement` |
|
||||
| `app.v_dws_assistant_recharge_commission` | `dws.dws_assistant_recharge_commission` |
|
||||
| `app.v_dws_order_summary` | `dws.dws_order_summary` |
|
||||
|
||||
**DWS 层 — cfg_* 配置表(4 张,无 `site_id`,直接 `SELECT *`):**
|
||||
|
||||
| 视图 | 源表 | 说明 |
|
||||
|------|------|------|
|
||||
| `app.v_cfg_performance_tier` | `dws.cfg_performance_tier` | 无 `site_id` 列,不加过滤 |
|
||||
| `app.v_cfg_assistant_level_price` | `dws.cfg_assistant_level_price` | 同上 |
|
||||
| `app.v_cfg_bonus_rules` | `dws.cfg_bonus_rules` | 同上 |
|
||||
| `app.v_cfg_index_parameters` | `dws.cfg_index_parameters` | 同上 |
|
||||
|
||||
### 权限配置
|
||||
|
||||
| 角色 | Schema | 权限 |
|
||||
|------|--------|------|
|
||||
| `app_reader` | `app` | `USAGE` + `SELECT ON ALL TABLES` + `ALTER DEFAULT PRIVILEGES` |
|
||||
|
||||
### P2 预留(注释形式,暂不创建)
|
||||
- `dws.dws_member_spending_power_index` → `app.v_dws_member_spending_power_index`
|
||||
- `dws.dws_assistant_order_contribution` → `app.v_dws_assistant_order_contribution`
|
||||
|
||||
---
|
||||
|
||||
## 2. 兼容性影响
|
||||
|
||||
| 组件 | 影响 |
|
||||
|------|------|
|
||||
| ETL 任务 | 无影响。视图仅读取 DWD/DWS 表,不影响 ETL 写入流程 |
|
||||
| 后端 API | 前置依赖。后端通过 FDW 读取 `app` Schema 视图,本脚本是 FDW 配置的前提 |
|
||||
| 小程序 | 无直接影响。小程序通过后端 API 间接访问 |
|
||||
| 管理后台 | 无直接影响 |
|
||||
| 现有 `app` Schema | 已有 7 个视图将被 `CREATE OR REPLACE` 覆盖更新,新增 28 个视图 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 回滚策略
|
||||
|
||||
迁移脚本末尾已包含注释形式的回滚语句,按逆序执行:
|
||||
|
||||
```sql
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA app REVOKE SELECT ON TABLES FROM app_reader;
|
||||
REVOKE SELECT ON ALL TABLES IN SCHEMA app FROM app_reader;
|
||||
REVOKE USAGE ON SCHEMA app FROM app_reader;
|
||||
DROP SCHEMA IF EXISTS app CASCADE; -- 会删除所有视图
|
||||
DROP ROLE IF EXISTS app_reader;
|
||||
```
|
||||
|
||||
注意:`DROP SCHEMA app CASCADE` 会级联删除所有视图和依赖的 FDW 外部表,需先回滚 FDW 配置。
|
||||
|
||||
---
|
||||
|
||||
## 4. 验证 SQL
|
||||
|
||||
```sql
|
||||
-- 1. 验证 app Schema 存在
|
||||
SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'app';
|
||||
|
||||
-- 2. 验证视图数量(应为 35 张)
|
||||
SELECT count(*) FROM information_schema.views WHERE table_schema = 'app';
|
||||
|
||||
-- 3. 验证 app_reader 角色存在且有 app Schema 权限
|
||||
SELECT has_schema_privilege('app_reader', 'app', 'USAGE') AS has_usage;
|
||||
|
||||
-- 4. 验证含 site_id 的视图定义包含 current_setting 过滤
|
||||
SELECT table_name, view_definition
|
||||
FROM information_schema.views
|
||||
WHERE table_schema = 'app'
|
||||
AND view_definition LIKE '%current_setting%'
|
||||
ORDER BY table_name;
|
||||
|
||||
-- 5. 验证 cfg_* 视图不含 current_setting 过滤
|
||||
SELECT table_name, view_definition
|
||||
FROM information_schema.views
|
||||
WHERE table_schema = 'app'
|
||||
AND table_name LIKE 'v_cfg_%'
|
||||
AND view_definition NOT LIKE '%current_setting%';
|
||||
```
|
||||
87
docs/database/BD_Manual_auth_biz_schemas.md
Normal file
87
docs/database/BD_Manual_auth_biz_schemas.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# BD_Manual:auth/biz Schema 与权限配置
|
||||
|
||||
> 目标库:`test_zqyy_app`(通过 `APP_DB_DSN` 连接)
|
||||
> 迁移脚本:`db/zqyy_app/migrations/2026-02-24__p1_create_auth_biz_schemas.sql`
|
||||
> 关联 SPEC:`miniapp-db-foundation`(P1 基础设施层)
|
||||
|
||||
---
|
||||
|
||||
## 1. 变更说明
|
||||
|
||||
### 新增 Schema
|
||||
|
||||
| Schema | 用途 |
|
||||
|--------|------|
|
||||
| `auth` | 用户认证、权限、微信 OpenID 映射等 |
|
||||
| `biz` | 业务数据(任务、备注、AI 分析、Excel 导出等) |
|
||||
|
||||
### 权限配置
|
||||
|
||||
| 角色 | Schema | 权限 |
|
||||
|------|--------|------|
|
||||
| `app_user` | `auth` | `USAGE` + `SELECT, INSERT, UPDATE, DELETE ON ALL TABLES` + `ALTER DEFAULT PRIVILEGES` |
|
||||
| `app_user` | `biz` | `USAGE` + `SELECT, INSERT, UPDATE, DELETE ON ALL TABLES` + `ALTER DEFAULT PRIVILEGES` |
|
||||
|
||||
### 未操作的 Schema
|
||||
- `public`:保留现有系统管理表(`admin_users`、`roles`、`permissions` 等)不受影响,脚本不包含任何对 `public` Schema 的操作
|
||||
|
||||
---
|
||||
|
||||
## 2. 兼容性影响
|
||||
|
||||
| 组件 | 影响 |
|
||||
|------|------|
|
||||
| ETL 任务 | 无影响。本脚本仅操作业务库,不涉及 ETL 库 |
|
||||
| 后端 API | 前置依赖。后续业务表将创建在 `auth`/`biz` Schema 中,后端需使用 `auth.` / `biz.` 前缀或设置 `search_path` |
|
||||
| 小程序 | 无直接影响。小程序通过后端 API 间接访问 |
|
||||
| 管理后台 | 无直接影响 |
|
||||
| FDW 配置 | 无影响。`fdw_etl` Schema 独立于 `auth`/`biz` |
|
||||
| `public` Schema | 无影响。脚本不包含任何对 `public` 的操作 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 回滚策略
|
||||
|
||||
迁移脚本末尾已包含注释形式的回滚语句,按逆序执行:
|
||||
|
||||
```sql
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA biz REVOKE SELECT, INSERT, UPDATE, DELETE ON TABLES FROM app_user;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA auth REVOKE SELECT, INSERT, UPDATE, DELETE ON TABLES FROM app_user;
|
||||
REVOKE SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA biz FROM app_user;
|
||||
REVOKE USAGE ON SCHEMA biz FROM app_user;
|
||||
REVOKE SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA auth FROM app_user;
|
||||
REVOKE USAGE ON SCHEMA auth FROM app_user;
|
||||
DROP SCHEMA IF EXISTS biz CASCADE;
|
||||
DROP SCHEMA IF EXISTS auth CASCADE;
|
||||
```
|
||||
|
||||
注意:`DROP SCHEMA CASCADE` 会级联删除 Schema 内所有表和依赖对象。如果 `auth`/`biz` 中已有业务表,需先备份数据再执行回滚。
|
||||
|
||||
---
|
||||
|
||||
## 4. 验证 SQL
|
||||
|
||||
```sql
|
||||
-- 1. 验证 auth 和 biz Schema 存在
|
||||
SELECT schema_name
|
||||
FROM information_schema.schemata
|
||||
WHERE schema_name IN ('auth', 'biz')
|
||||
ORDER BY schema_name;
|
||||
|
||||
-- 2. 验证 app_user 对 auth Schema 有 USAGE 权限
|
||||
SELECT has_schema_privilege('app_user', 'auth', 'USAGE') AS auth_usage,
|
||||
has_schema_privilege('app_user', 'biz', 'USAGE') AS biz_usage;
|
||||
|
||||
-- 3. 验证 ALTER DEFAULT PRIVILEGES 已设置(查询 pg_default_acl)
|
||||
SELECT n.nspname AS schema_name,
|
||||
d.defaclacl AS default_acl
|
||||
FROM pg_default_acl d
|
||||
JOIN pg_namespace n ON n.oid = d.defaclnamespace
|
||||
WHERE n.nspname IN ('auth', 'biz');
|
||||
|
||||
-- 4. 验证 public Schema 中现有表未受影响
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
ORDER BY table_name;
|
||||
```
|
||||
169
docs/database/BD_Manual_auth_tables.md
Normal file
169
docs/database/BD_Manual_auth_tables.md
Normal file
@@ -0,0 +1,169 @@
|
||||
# BD_Manual:auth Schema 认证业务表
|
||||
|
||||
> 目标库:`test_zqyy_app`(通过 `APP_DB_DSN` 连接)
|
||||
> 迁移脚本:
|
||||
> - `db/zqyy_app/migrations/2026-02-25__p3_create_auth_tables.sql`(建表)
|
||||
> - `db/zqyy_app/migrations/2026-02-25__p3_seed_roles_permissions.sql`(种子数据)
|
||||
> 关联 SPEC:`miniapp-auth-system`(P3 小程序用户认证系统)
|
||||
|
||||
---
|
||||
|
||||
## 1. 变更说明
|
||||
|
||||
### 新增表(8 张)
|
||||
|
||||
| # | 表名 | 用途 | 主要字段 |
|
||||
|---|------|------|---------|
|
||||
| 1 | `auth.users` | 微信用户主表 | `id`(PK), `wx_openid`(UK), `wx_union_id`, `wx_avatar_url`, `nickname`, `phone`, `status`(默认 `pending`), `created_at`, `updated_at` |
|
||||
| 2 | `auth.user_applications` | 用户入驻申请表 | `id`(PK), `user_id`(FK→users), `site_code`, `site_id`, `applied_role_text`, `employee_number`, `phone`, `status`(默认 `pending`), `reviewer_id`, `review_note`, `created_at`, `reviewed_at` |
|
||||
| 3 | `auth.site_code_mapping` | 球房ID与门店映射表 | `id`(PK), `site_code`(UK), `site_id`(UK), `site_name`, `tenant_id`, `created_at` |
|
||||
| 4 | `auth.roles` | 角色定义表 | `id`(PK), `code`(UK), `name`, `description`, `created_at` |
|
||||
| 5 | `auth.permissions` | 权限定义表 | `id`(PK), `code`(UK), `name`, `description`, `created_at` |
|
||||
| 6 | `auth.role_permissions` | 角色-权限关联表 | `role_id`(FK→roles), `permission_id`(FK→permissions),联合主键 |
|
||||
| 7 | `auth.user_site_roles` | 用户-门店-角色关联表 | `id`(PK), `user_id`(FK→users), `site_id`, `role_id`(FK→roles), `created_at`,`(user_id, site_id, role_id)` 唯一约束 |
|
||||
| 8 | `auth.user_assistant_binding` | 用户-人员绑定表 | `id`(PK), `user_id`(FK→users), `site_id`, `assistant_id`(可空), `staff_id`(可空), `binding_type`, `created_at` |
|
||||
|
||||
### 约束与索引
|
||||
|
||||
| 表 | 约束/索引名 | 类型 | 说明 |
|
||||
|----|-----------|------|------|
|
||||
| `users` | `uq_users_wx_openid` | UNIQUE | 微信 openid 唯一 |
|
||||
| `users` | `ix_users_wx_openid` | INDEX | openid 查询加速 |
|
||||
| `site_code_mapping` | `uq_site_code_mapping_site_code` | UNIQUE | 球房ID 唯一 |
|
||||
| `site_code_mapping` | `uq_site_code_mapping_site_id` | UNIQUE | site_id 唯一映射 |
|
||||
| `site_code_mapping` | `ix_site_code_mapping_site_code` | INDEX | site_code 查询加速 |
|
||||
| `roles` | `uq_roles_code` | UNIQUE | 角色 code 唯一 |
|
||||
| `permissions` | `uq_permissions_code` | UNIQUE | 权限 code 唯一 |
|
||||
| `role_permissions` | PK `(role_id, permission_id)` | PRIMARY KEY | 联合主键 |
|
||||
| `role_permissions` | `fk_role_permissions_role_id` | FK | → `auth.roles(id)` CASCADE |
|
||||
| `role_permissions` | `fk_role_permissions_permission_id` | FK | → `auth.permissions(id)` CASCADE |
|
||||
| `user_applications` | `fk_user_applications_user_id` | FK | → `auth.users(id)` CASCADE |
|
||||
| `user_applications` | `ix_user_applications_user_id` | INDEX | user_id 查询加速 |
|
||||
| `user_applications` | `ix_user_applications_status` | INDEX | status 过滤加速 |
|
||||
| `user_site_roles` | `uq_user_site_roles_user_site_role` | UNIQUE | 防止重复分配 |
|
||||
| `user_site_roles` | `fk_user_site_roles_user_id` | FK | → `auth.users(id)` CASCADE |
|
||||
| `user_site_roles` | `fk_user_site_roles_role_id` | FK | → `auth.roles(id)` CASCADE |
|
||||
| `user_site_roles` | `ix_user_site_roles_user_site` | INDEX | (user_id, site_id) 查询加速 |
|
||||
| `user_assistant_binding` | `fk_user_assistant_binding_user_id` | FK | → `auth.users(id)` CASCADE |
|
||||
|
||||
|
||||
### 种子数据
|
||||
|
||||
#### 权限(5 条)
|
||||
|
||||
| code | name | description |
|
||||
|------|------|-------------|
|
||||
| `view_tasks` | 查看任务 | 允许查看任务列表和任务详情 |
|
||||
| `view_board` | 查看看板 | 允许查看数据看板概览 |
|
||||
| `view_board_finance` | 查看财务看板 | 允许查看财务相关的数据看板 |
|
||||
| `view_board_customer` | 查看客户看板 | 允许查看客户相关的数据看板 |
|
||||
| `view_board_coach` | 查看助教看板 | 允许查看助教相关的数据看板 |
|
||||
|
||||
#### 角色(4 条)
|
||||
|
||||
| code | name | description |
|
||||
|------|------|-------------|
|
||||
| `coach` | 助教 | 球房助教,可查看任务和助教看板 |
|
||||
| `staff` | 员工 | 球房员工,可查看任务和数据看板 |
|
||||
| `site_admin` | 店铺管理员 | 单店管理员,可查看所有看板 |
|
||||
| `tenant_admin` | 租户管理员 | 租户级管理员,拥有全部权限 |
|
||||
|
||||
#### 角色-权限映射(14 条)
|
||||
|
||||
| 角色 | 权限列表 | 权限数 |
|
||||
|------|---------|--------|
|
||||
| `coach` | `view_tasks`, `view_board_coach` | 2 |
|
||||
| `staff` | `view_tasks`, `view_board` | 2 |
|
||||
| `site_admin` | `view_tasks`, `view_board`, `view_board_finance`, `view_board_customer`, `view_board_coach` | 5 |
|
||||
| `tenant_admin` | `view_tasks`, `view_board`, `view_board_finance`, `view_board_customer`, `view_board_coach` | 5 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 兼容性影响
|
||||
|
||||
| 组件 | 影响 |
|
||||
|------|------|
|
||||
| ETL 任务 | 无影响。本次变更仅操作业务库 `auth` Schema,不涉及 ETL 库 |
|
||||
| 后端 API | 直接依赖。FastAPI 后端将基于这些表实现微信登录、用户申请、审核、权限中间件等认证功能 |
|
||||
| 小程序 | 间接依赖。小程序通过后端 API 间接使用这些表(登录、申请、状态查询) |
|
||||
| 管理后台 | 间接依赖。管理端通过后端 API 进行申请审核操作 |
|
||||
| FDW 配置 | 无影响。`fdw_etl` Schema 独立于 `auth`,但人员匹配服务会通过 FDW 查询 ETL 库的助教/员工表 |
|
||||
| `public` Schema | 无影响。脚本不包含任何对 `public` 的操作 |
|
||||
| 现有 `auth` Schema | 兼容。`auth` Schema 已由 P1 迁移脚本创建,本次仅在其中新增表,不修改已有对象 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 回滚策略
|
||||
|
||||
按逆序 `DROP TABLE IF EXISTS CASCADE`(迁移脚本末尾已包含注释形式的回滚语句):
|
||||
|
||||
```sql
|
||||
-- 先删除种子数据(如需保留表结构)
|
||||
DELETE FROM auth.role_permissions
|
||||
WHERE role_id IN (SELECT id FROM auth.roles WHERE code IN ('coach', 'staff', 'site_admin', 'tenant_admin'))
|
||||
AND permission_id IN (SELECT id FROM auth.permissions WHERE code IN ('view_tasks', 'view_board', 'view_board_finance', 'view_board_customer', 'view_board_coach'));
|
||||
|
||||
DELETE FROM auth.roles WHERE code IN ('coach', 'staff', 'site_admin', 'tenant_admin');
|
||||
DELETE FROM auth.permissions WHERE code IN ('view_tasks', 'view_board', 'view_board_finance', 'view_board_customer', 'view_board_coach');
|
||||
|
||||
-- 删除表(按逆序,CASCADE 处理外键依赖)
|
||||
DROP TABLE IF EXISTS auth.user_assistant_binding CASCADE;
|
||||
DROP TABLE IF EXISTS auth.user_site_roles CASCADE;
|
||||
DROP TABLE IF EXISTS auth.user_applications CASCADE;
|
||||
DROP TABLE IF EXISTS auth.role_permissions CASCADE;
|
||||
DROP TABLE IF EXISTS auth.permissions CASCADE;
|
||||
DROP TABLE IF EXISTS auth.roles CASCADE;
|
||||
DROP TABLE IF EXISTS auth.site_code_mapping CASCADE;
|
||||
DROP TABLE IF EXISTS auth.users CASCADE;
|
||||
```
|
||||
|
||||
注意:`CASCADE` 会级联删除依赖对象。如果表中已有业务数据,需先备份再执行回滚。
|
||||
|
||||
---
|
||||
|
||||
## 4. 验证 SQL
|
||||
|
||||
```sql
|
||||
-- 1. 验证 auth Schema 下 8 张认证表全部存在
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'auth'
|
||||
AND table_name IN (
|
||||
'users', 'user_applications', 'site_code_mapping',
|
||||
'roles', 'permissions', 'role_permissions',
|
||||
'user_site_roles', 'user_assistant_binding'
|
||||
)
|
||||
ORDER BY table_name;
|
||||
-- 预期:返回 8 行
|
||||
|
||||
-- 2. 验证种子数据:5 条权限
|
||||
SELECT COUNT(*) AS perm_count
|
||||
FROM auth.permissions
|
||||
WHERE code IN ('view_tasks', 'view_board', 'view_board_finance', 'view_board_customer', 'view_board_coach');
|
||||
-- 预期:5
|
||||
|
||||
-- 3. 验证种子数据:4 条角色
|
||||
SELECT COUNT(*) AS role_count
|
||||
FROM auth.roles
|
||||
WHERE code IN ('coach', 'staff', 'site_admin', 'tenant_admin');
|
||||
-- 预期:4
|
||||
|
||||
-- 4. 验证角色-权限映射数量
|
||||
SELECT r.code AS role_code, COUNT(rp.permission_id) AS perm_count
|
||||
FROM auth.roles r
|
||||
JOIN auth.role_permissions rp ON r.id = rp.role_id
|
||||
GROUP BY r.code
|
||||
ORDER BY r.code;
|
||||
-- 预期:coach=2, site_admin=5, staff=2, tenant_admin=5(共 14 条映射)
|
||||
|
||||
-- 5. 验证关键约束存在
|
||||
SELECT conname, contype
|
||||
FROM pg_constraint
|
||||
WHERE conrelid IN (
|
||||
'auth.users'::regclass,
|
||||
'auth.site_code_mapping'::regclass,
|
||||
'auth.user_site_roles'::regclass
|
||||
)
|
||||
ORDER BY conrelid::regclass::text, conname;
|
||||
-- 预期:包含 uq_users_wx_openid、uq_site_code_mapping_site_code、uq_site_code_mapping_site_id、uq_user_site_roles_user_site_role 等
|
||||
```
|
||||
266
docs/database/BD_Manual_dws_assistant_order_contribution.md
Normal file
266
docs/database/BD_Manual_dws_assistant_order_contribution.md
Normal file
@@ -0,0 +1,266 @@
|
||||
# BD_Manual:dws_assistant_order_contribution(助教订单流水四项统计)
|
||||
|
||||
> DWS 表:`dws.dws_assistant_order_contribution`
|
||||
> DWD 数据源:`dwd.dwd_settlement_head`(结算主表)、`dwd.dwd_table_fee_log`(台费明细)、`dwd.dwd_assistant_service_log`(助教服务记录)
|
||||
> 任务代码:`DWS_ASSISTANT_ORDER_CONTRIBUTION`
|
||||
> 代码位置:`apps/etl/connectors/feiqiu/tasks/dws/assistant_order_contribution_task.py`
|
||||
> DDL 位置:`docs/database/ddl/etl_feiqiu__dws.sql`
|
||||
> 迁移脚本:`db/etl_feiqiu/migrations/2025-02-24__create_dws_assistant_order_contribution.sql`
|
||||
> RLS 视图:`db/etl_feiqiu/migrations/2025-02-24__create_rls_view_assistant_order_contribution.sql`
|
||||
> FDW 映射:`db/zqyy_app/migrations/2025-02-24__add_fdw_dws_extensions.sql`
|
||||
|
||||
---
|
||||
|
||||
## 1. 表结构
|
||||
|
||||
| 列名 | 类型 | 默认值 | 业务含义 | 取值范围/示例 |
|
||||
|------|------|--------|---------|-------------|
|
||||
| `contribution_id` | BIGINT (SERIAL) | nextval 序列 | 自增主键(PK) | 自增 |
|
||||
| `site_id` | INTEGER NOT NULL | — | 门店 ID | 飞球门店 ID |
|
||||
| `tenant_id` | INTEGER NOT NULL | — | 租户 ID | 飞球租户 ID |
|
||||
| `assistant_id` | BIGINT NOT NULL | — | 助教 ID | 飞球助教 ID |
|
||||
| `assistant_nickname` | VARCHAR(100) | NULL | 助教昵称 | 中文昵称 |
|
||||
| `stat_date` | DATE NOT NULL | — | 统计日期 | `2025-01-15` |
|
||||
| `order_gross_revenue` | NUMERIC(14,2) | 0 | 订单总流水 = 台费 + 酒水食品 + 所有助教服务费 | `0.00` ~ 金额值 |
|
||||
| `order_net_revenue` | NUMERIC(14,2) | 0 | 订单净流水 = 订单总流水 - 所有助教服务分成 | `0.00` ~ 金额值 |
|
||||
| `time_weighted_revenue` | NUMERIC(14,2) | 0 | 时效贡献流水 = 台费按时长分摊 + 个人服务费 + 酒水食品按时长比例 | `0.00` ~ 金额值 |
|
||||
| `time_weighted_net_revenue` | NUMERIC(14,2) | 0 | 时效净贡献 = 时效贡献流水 - 个人服务分成 | `0.00` ~ 金额值 |
|
||||
| `order_count` | INTEGER | 0 | 当日参与订单数 | `0` ~ 正整数 |
|
||||
| `total_service_seconds` | INTEGER | 0 | 当日总服务时长(秒) | `0` ~ 正整数 |
|
||||
| `created_at` | TIMESTAMPTZ | NOW() | 记录创建时间 | ISO 时间戳 |
|
||||
| `updated_at` | TIMESTAMPTZ | NOW() | 记录最后更新时间 | ISO 时间戳 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 主键与索引
|
||||
|
||||
| 名称 | 类型 | 列 | 说明 |
|
||||
|------|------|----|------|
|
||||
| `dws_assistant_order_contribution_pkey` | PRIMARY KEY | `contribution_id` | 物理主键(自增序列) |
|
||||
| `idx_aoc_site_assistant_date` | UNIQUE INDEX | `(site_id, assistant_id, stat_date)` | 业务主键:每个门店每个助教每天唯一一条记录 |
|
||||
| `idx_aoc_stat_date` | INDEX | `(site_id, stat_date)` | 按门店+日期查询,支持日度报表 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 数据写入策略
|
||||
|
||||
- **delete-before-insert**:每次执行按 `site_id` + 日期窗口全量刷新
|
||||
1. `DELETE FROM dws.dws_assistant_order_contribution WHERE site_id = %s AND stat_date BETWEEN %s AND %s`
|
||||
2. 批量 `INSERT` 新计算结果
|
||||
- 继承 `BaseDwsTask` 默认 load 实现,幂等可重跑
|
||||
|
||||
---
|
||||
|
||||
## 4. 算法概要
|
||||
|
||||
### 4.1 数据来源
|
||||
|
||||
| 来源表 | 筛选条件 | 提取内容 |
|
||||
|--------|---------|---------|
|
||||
| `dwd.dwd_settlement_head` | 日期窗口内,`settle_type IN (1, 3)` | 结算单信息、酒水食品金额 |
|
||||
| `dwd.dwd_table_fee_log` | 关联结算单 | 台桌使用时长、台费金额、区域 |
|
||||
| `dwd.dwd_assistant_service_log` | 关联结算单 | 助教服务时长、服务流水、分成、课程类型 |
|
||||
|
||||
### 4.2 四项统计公式
|
||||
|
||||
**订单总流水(order_gross_revenue)**
|
||||
```
|
||||
order_gross_revenue = total_table_fee + total_goods_amount + SUM(所有助教 ledger_amount)
|
||||
```
|
||||
每个参与助教获得相同值。
|
||||
|
||||
**订单净流水(order_net_revenue)**
|
||||
```
|
||||
order_net_revenue = order_gross_revenue - SUM(所有助教 commission)
|
||||
```
|
||||
每个参与助教获得相同值。
|
||||
|
||||
**时效贡献流水(time_weighted_revenue)**
|
||||
```
|
||||
对于台桌 t:
|
||||
billable_seconds = MAX(SUM(助教服务时长), 台桌使用时长)
|
||||
台费分摊_a = table_fee_t × (service_seconds_a / billable_seconds)
|
||||
|
||||
酒水食品分摊_a = total_goods_amount × (助教 a 总服务时长 / 所有助教总服务时长)
|
||||
|
||||
time_weighted_revenue_a = SUM(各台桌台费分摊_a) + ledger_amount_a + 酒水食品分摊_a
|
||||
```
|
||||
|
||||
**时效净贡献(time_weighted_net_revenue)**
|
||||
```
|
||||
time_weighted_net_revenue_a = time_weighted_revenue_a - commission_a
|
||||
```
|
||||
|
||||
### 4.3 超休/打赏课特殊处理
|
||||
|
||||
当 `course_type = BONUS` 时,四项统计均等于个人服务流水和分成,不参与订单级分摊。
|
||||
|
||||
---
|
||||
|
||||
## 5. 前置依赖
|
||||
|
||||
- 任务依赖:`DWD_LOAD_FROM_ODS`(需先完成 DWD 层数据加载)
|
||||
- 数据源表:`dwd.dwd_settlement_head`、`dwd.dwd_table_fee_log`、`dwd.dwd_assistant_service_log` 必须已有数据
|
||||
|
||||
---
|
||||
|
||||
## 6. 验证 SQL
|
||||
|
||||
### 6.1 检查表是否存在且有数据
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
COUNT(*) AS total_rows,
|
||||
COUNT(DISTINCT site_id) AS site_count,
|
||||
COUNT(DISTINCT assistant_id) AS assistant_count,
|
||||
MIN(stat_date) AS earliest_date,
|
||||
MAX(stat_date) AS latest_date
|
||||
FROM dws.dws_assistant_order_contribution;
|
||||
```
|
||||
|
||||
### 6.2 检查业务主键唯一性(不应有重复)
|
||||
|
||||
```sql
|
||||
SELECT site_id, assistant_id, stat_date, COUNT(*) AS cnt
|
||||
FROM dws.dws_assistant_order_contribution
|
||||
GROUP BY site_id, assistant_id, stat_date
|
||||
HAVING COUNT(*) > 1;
|
||||
-- 预期:无结果返回
|
||||
```
|
||||
|
||||
### 6.3 检查四项统计数值合理性(非负)
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
COUNT(*) FILTER (WHERE order_gross_revenue < 0) AS neg_gross,
|
||||
COUNT(*) FILTER (WHERE order_net_revenue < 0) AS neg_net,
|
||||
COUNT(*) FILTER (WHERE time_weighted_revenue < 0) AS neg_twr,
|
||||
COUNT(*) FILTER (WHERE time_weighted_net_revenue < 0) AS neg_twnr
|
||||
FROM dws.dws_assistant_order_contribution;
|
||||
-- 预期:所有列均为 0
|
||||
```
|
||||
|
||||
### 6.4 按门店查看统计概况
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
site_id,
|
||||
COUNT(*) AS record_count,
|
||||
SUM(order_count) AS total_orders,
|
||||
ROUND(AVG(order_gross_revenue), 2) AS avg_gross,
|
||||
ROUND(AVG(order_net_revenue), 2) AS avg_net,
|
||||
ROUND(AVG(time_weighted_revenue), 2) AS avg_twr,
|
||||
ROUND(AVG(time_weighted_net_revenue), 2) AS avg_twnr
|
||||
FROM dws.dws_assistant_order_contribution
|
||||
GROUP BY site_id
|
||||
ORDER BY site_id;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. RLS 视图与 FDW 映射
|
||||
|
||||
### 7.1 RLS 视图(ETL 库 app schema)
|
||||
|
||||
```sql
|
||||
-- 视图名:app.v_dws_assistant_order_contribution
|
||||
CREATE OR REPLACE VIEW app.v_dws_assistant_order_contribution AS
|
||||
SELECT * FROM dws.dws_assistant_order_contribution
|
||||
WHERE site_id = current_setting('app.current_site_id')::bigint;
|
||||
```
|
||||
|
||||
### 7.2 FDW 外部表(业务库 fdw_etl schema)
|
||||
|
||||
```sql
|
||||
-- 外部表名:fdw_etl.v_dws_assistant_order_contribution
|
||||
-- 通过 app schema RLS 视图访问,非直接访问 dws schema
|
||||
CREATE FOREIGN TABLE fdw_etl.v_dws_assistant_order_contribution (...)
|
||||
SERVER etl_server
|
||||
OPTIONS (schema_name 'app', table_name 'v_dws_assistant_order_contribution');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 兼容性说明
|
||||
|
||||
| 影响范围 | 说明 |
|
||||
|---------|------|
|
||||
| ETL 任务 | 新增任务 `DWS_ASSISTANT_ORDER_CONTRIBUTION`,依赖 `DWD_LOAD_FROM_ODS`。不影响现有 DWS 任务 |
|
||||
| 后端 API | 当前无 API 直接读取此表。后续小程序助教看板需新增接口 |
|
||||
| 管理后台 | 当前无前端页面展示。后续可在助教详情页新增流水统计展示 |
|
||||
| 小程序 | 小程序助教端将通过后端 API 读取此表数据展示四项统计 |
|
||||
| 其他 DWS 表 | 独立于现有 `dws_assistant_daily_detail`,不修改任何已有表或任务逻辑 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 回滚策略
|
||||
|
||||
### 9.1 删除数据(保留表结构)
|
||||
|
||||
```sql
|
||||
DELETE FROM dws.dws_assistant_order_contribution;
|
||||
```
|
||||
|
||||
### 9.2 完整回滚(删除表 + 视图 + FDW)
|
||||
|
||||
```sql
|
||||
-- 1. 删除 FDW 外部表(业务库)
|
||||
DROP FOREIGN TABLE IF EXISTS fdw_etl.v_dws_assistant_order_contribution;
|
||||
|
||||
-- 2. 删除 RLS 视图(ETL 库)
|
||||
DROP VIEW IF EXISTS app.v_dws_assistant_order_contribution;
|
||||
|
||||
-- 3. 删除表和索引(ETL 库)
|
||||
DROP INDEX IF EXISTS dws.idx_aoc_stat_date;
|
||||
DROP INDEX IF EXISTS dws.idx_aoc_site_assistant_date;
|
||||
DROP TABLE IF EXISTS dws.dws_assistant_order_contribution;
|
||||
```
|
||||
|
||||
### 9.3 回滚任务注册
|
||||
|
||||
从 `orchestration/task_registry.py` 中移除 `DWS_ASSISTANT_ORDER_CONTRIBUTION` 注册行,并从 `tasks/dws/__init__.py` 中移除 `AssistantOrderContributionTask` 导出。
|
||||
|
||||
---
|
||||
|
||||
## 10. 代码引用
|
||||
|
||||
- 任务类:`tasks/dws/assistant_order_contribution_task.py` → `AssistantOrderContributionTask`
|
||||
- 数据结构:`TableUsage`、`AssistantService`、`OrderData`(同文件)
|
||||
- 继承:`BaseDwsTask`
|
||||
- 任务注册:`orchestration/task_registry.py` → `DWS_ASSISTANT_ORDER_CONTRIBUTION`
|
||||
- 属性测试:`tests/test_dws_contribution_properties.py`
|
||||
- 迁移脚本:`db/etl_feiqiu/migrations/2025-02-24__create_dws_assistant_order_contribution.sql`
|
||||
- RLS 视图:`db/etl_feiqiu/migrations/2025-02-24__create_rls_view_assistant_order_contribution.sql`
|
||||
- FDW 映射:`db/zqyy_app/migrations/2025-02-24__add_fdw_dws_extensions.sql`
|
||||
- 验证脚本:`apps/etl/connectors/feiqiu/scripts/verify_dws_extensions.py`
|
||||
|
||||
---
|
||||
|
||||
## 11. 关联扩展字段说明
|
||||
|
||||
本次 Spec(02-etl-dws-miniapp-extensions)同时扩展了两张已有表的字段,简要说明如下:
|
||||
|
||||
### 11.1 dws_member_consumption_summary 新增字段
|
||||
|
||||
| 列名 | 类型 | 默认值 | 业务含义 |
|
||||
|------|------|--------|---------|
|
||||
| `recharge_count_30d` | INTEGER | 0 | 近 30 天充值次数 |
|
||||
| `recharge_count_60d` | INTEGER | 0 | 近 60 天充值次数 |
|
||||
| `recharge_count_90d` | INTEGER | 0 | 近 90 天充值次数 |
|
||||
| `recharge_amount_30d` | NUMERIC(14,2) | 0 | 近 30 天充值金额 |
|
||||
| `recharge_amount_60d` | NUMERIC(14,2) | 0 | 近 60 天充值金额 |
|
||||
| `recharge_amount_90d` | NUMERIC(14,2) | 0 | 近 90 天充值金额 |
|
||||
| `avg_ticket_amount` | NUMERIC(14,2) | 0 | 次均消费 = total_consume_amount / MAX(total_visit_count, 1) |
|
||||
|
||||
迁移脚本:`db/etl_feiqiu/migrations/2025-02-24__alter_member_consumption_add_recharge_fields.sql`
|
||||
|
||||
### 11.2 dws_assistant_daily_detail 新增字段
|
||||
|
||||
| 列名 | 类型 | 默认值 | 业务含义 |
|
||||
|------|------|--------|---------|
|
||||
| `penalty_minutes` | NUMERIC(10,2) | 0 | 定档折算惩罚分钟数,无惩罚时为 0 |
|
||||
| `penalty_reason` | TEXT | NULL | 惩罚原因描述,无惩罚时为 NULL |
|
||||
| `is_exempt` | BOOLEAN | FALSE | 是否豁免惩罚 |
|
||||
| `per_hour_contribution` | NUMERIC(14,2) | NULL | 单人每小时贡献流水 = 台费每小时单价 / 助教人数 |
|
||||
|
||||
迁移脚本:`db/etl_feiqiu/migrations/2025-02-24__alter_assistant_daily_add_penalty_fields.sql`
|
||||
251
docs/database/BD_Manual_dws_member_spending_power_index.md
Normal file
251
docs/database/BD_Manual_dws_member_spending_power_index.md
Normal file
@@ -0,0 +1,251 @@
|
||||
# BD_Manual:dws_member_spending_power_index(SPI 消费力指数)
|
||||
|
||||
> DWS 表:`dws.dws_member_spending_power_index`
|
||||
> DWD 数据源:`dwd.dwd_settlement_head`(消费订单)、`dwd.dwd_recharge_order`(充值订单)
|
||||
> 配置表:`dws.cfg_index_parameters`(`index_type='SPI'`)
|
||||
> 任务代码:`DWS_SPENDING_POWER_INDEX`
|
||||
> 代码位置:`apps/etl/connectors/feiqiu/tasks/dws/index/spending_power_index_task.py`
|
||||
> DDL 位置:`docs/database/ddl/etl_feiqiu__dws.sql`
|
||||
> 迁移脚本:`db/etl_feiqiu/migrations/2026-02-23_create_dws_member_spending_power_index.sql`
|
||||
> 种子数据:`db/etl_feiqiu/seeds/seed_index_parameters.sql`(`index_type='SPI'` 部分)
|
||||
|
||||
---
|
||||
|
||||
## 1. 表结构
|
||||
|
||||
| 列名 | 类型 | 默认值 | 业务含义 | 取值范围/示例 |
|
||||
|------|------|--------|---------|-------------|
|
||||
| `spi_id` | BIGINT (SERIAL) | nextval 序列 | 自增主键(PK) | 自增 |
|
||||
| `site_id` | INTEGER NOT NULL | — | 门店 ID | 飞球门店 ID |
|
||||
| `member_id` | BIGINT NOT NULL | — | 会员 ID | 飞球会员 ID |
|
||||
| `spend_30` | NUMERIC(14,2) | 0 | 近 30 天消费总额(元) | `0.00` ~ 金额值 |
|
||||
| `spend_90` | NUMERIC(14,2) | 0 | 近 90 天消费总额(元) | `0.00` ~ 金额值 |
|
||||
| `recharge_90` | NUMERIC(14,2) | 0 | 近 90 天充值总额(元) | `0.00` ~ 金额值 |
|
||||
| `orders_30` | INTEGER | 0 | 近 30 天消费笔数 | `0` ~ 正整数 |
|
||||
| `orders_90` | INTEGER | 0 | 近 90 天消费笔数 | `0` ~ 正整数 |
|
||||
| `visit_days_30` | INTEGER | 0 | 近 30 天消费日数(按天去重) | `0` ~ `30` |
|
||||
| `visit_days_90` | INTEGER | 0 | 近 90 天消费日数(按天去重) | `0` ~ `90` |
|
||||
| `avg_ticket_90` | NUMERIC(14,2) | 0 | 90 天客单价(= spend_90 / max(orders_90, 1)) | `0.00` ~ 金额值 |
|
||||
| `active_weeks_90` | INTEGER | 0 | 近 90 天有消费的自然周数 | `0` ~ `13` |
|
||||
| `daily_spend_ewma_90` | NUMERIC(14,2) | 0 | 日消费 EWMA(指数加权移动平均) | `0.00` ~ 金额值 |
|
||||
| `score_level_raw` | NUMERIC(10,4) | 0 | Level 子分原始分(消费水平) | ≥ 0 |
|
||||
| `score_speed_raw` | NUMERIC(10,4) | 0 | Speed 子分原始分(消费速度) | ≥ 0 |
|
||||
| `score_stability_raw` | NUMERIC(10,4) | 0 | Stability 子分原始分(消费稳定性) | `0.0000` ~ `1.0000` |
|
||||
| `score_level_display` | NUMERIC(5,2) | 0 | Level 子分展示分 | `0.00` ~ `10.00` |
|
||||
| `score_speed_display` | NUMERIC(5,2) | 0 | Speed 子分展示分 | `0.00` ~ `10.00` |
|
||||
| `score_stability_display` | NUMERIC(5,2) | 0 | Stability 子分展示分 | `0.00` ~ `10.00` |
|
||||
| `raw_score` | NUMERIC(10,4) | 0 | SPI 总分原始分(加权合成) | ≥ 0 |
|
||||
| `display_score` | NUMERIC(5,2) | 0 | SPI 总分展示分 | `0.00` ~ `10.00` |
|
||||
| `calc_time` | TIMESTAMPTZ | NOW() | 本次计算时间 | ISO 时间戳 |
|
||||
| `created_at` | TIMESTAMPTZ | NOW() | 记录创建时间 | ISO 时间戳 |
|
||||
| `updated_at` | TIMESTAMPTZ | NOW() | 记录最后更新时间 | ISO 时间戳 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 主键与索引
|
||||
|
||||
| 名称 | 类型 | 列 | 说明 |
|
||||
|------|------|----|------|
|
||||
| `dws_member_spending_power_index_pkey` | PRIMARY KEY | `spi_id` | 物理主键(自增序列) |
|
||||
| `idx_spi_site_member` | UNIQUE INDEX | `(site_id, member_id)` | 业务主键:每个门店每个会员唯一一条记录 |
|
||||
| `idx_spi_display_score` | INDEX | `(site_id, display_score DESC)` | 按门店查询展示分排名,支持 TOP-N 查询 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 数据写入策略
|
||||
|
||||
- **delete-before-insert**:每次执行按 `site_id` 全量刷新
|
||||
1. `DELETE FROM dws.dws_member_spending_power_index WHERE site_id = %s`
|
||||
2. 批量 `INSERT` 新计算结果
|
||||
- 无数据时跳过(不删除、不插入),返回 `{'status': 'skipped', 'reason': 'no_data'}`
|
||||
|
||||
---
|
||||
|
||||
## 4. 算法概要
|
||||
|
||||
### 4.1 数据来源
|
||||
|
||||
| 来源表 | 筛选条件 | 提取内容 |
|
||||
|--------|---------|---------|
|
||||
| `dwd.dwd_settlement_head` | 近 90 天,`settle_type IN (1, 3)` | 消费金额、笔数、消费日数、周覆盖、日消费序列 |
|
||||
| `dwd.dwd_recharge_order` | 近 90 天,`settle_type = 5` | 充值总额 |
|
||||
|
||||
### 4.2 子分公式
|
||||
|
||||
- **Level**(消费水平,权重 0.60):
|
||||
`L = w_s30 × ln(1 + spend_30/M30) + w_s90 × ln(1 + spend_90/M90) + w_ticket × ln(1 + avg_ticket_90/T0) + w_r90 × ln(1 + recharge_90/R90)`
|
||||
|
||||
- **Speed**(消费速度,权重 0.30):
|
||||
`S = w_abs × V_abs + w_rel × max(0, V_rel) + w_ewma × V_ewma`
|
||||
- `V_abs = ln(1 + spend_30 / (max(visit_days_30, 1) × V0))`
|
||||
- `V_rel = ln((v_30 + ε) / (v_90 + ε))`,仅加速加分
|
||||
- `V_ewma = ln(1 + daily_spend_ewma_90 / E0)`
|
||||
|
||||
- **Stability**(消费稳定性,权重 0.10):
|
||||
`P = active_weeks_90 / 13`,取值 [0, 1]
|
||||
|
||||
### 4.3 总分合成
|
||||
|
||||
`SPI_raw = w_L × L + w_S × S + w_P × P`(默认 0.60 / 0.30 / 0.10)
|
||||
|
||||
### 4.4 展示分映射
|
||||
|
||||
Raw → Winsorize(P5, P95) → 可选压缩(log1p/asinh) → MinMax [0, 10] → 可选 EWMA 平滑
|
||||
|
||||
子分(Level/Speed/Stability)各自独立映射,使用 `SPI_LEVEL` / `SPI_SPEED` / `SPI_STABILITY` 隔离分位历史。
|
||||
|
||||
---
|
||||
|
||||
## 5. 配置参数
|
||||
|
||||
所有参数存储在 `dws.cfg_index_parameters`(`index_type='SPI'`),缺失时回退到代码中 `DEFAULT_PARAMS`。
|
||||
|
||||
| 参数名 | 默认值 | 说明 |
|
||||
|--------|--------|------|
|
||||
| `spend_window_short_days` | 30 | 短窗口天数 |
|
||||
| `spend_window_long_days` | 90 | 长窗口天数 |
|
||||
| `ewma_alpha_daily_spend` | 0.3 | 日消费 EWMA 平滑系数 |
|
||||
| `amount_base_spend_30` | 500.0 | 30 天消费金额压缩基数 |
|
||||
| `amount_base_spend_90` | 1500.0 | 90 天消费金额压缩基数 |
|
||||
| `amount_base_ticket_90` | 200.0 | 客单价压缩基数 |
|
||||
| `amount_base_recharge_90` | 1000.0 | 充值金额压缩基数 |
|
||||
| `amount_base_speed_abs` | 100.0 | 绝对速度压缩基数 |
|
||||
| `amount_base_ewma_90` | 50.0 | EWMA 速度压缩基数 |
|
||||
| `w_level_spend_30` | 0.30 | Level 子分中 spend_30 权重 |
|
||||
| `w_level_spend_90` | 0.35 | Level 子分中 spend_90 权重 |
|
||||
| `w_level_ticket_90` | 0.20 | Level 子分中 avg_ticket_90 权重 |
|
||||
| `w_level_recharge_90` | 0.15 | Level 子分中 recharge_90 权重 |
|
||||
| `w_speed_abs` | 0.50 | Speed 子分中绝对速度权重 |
|
||||
| `w_speed_rel` | 0.30 | Speed 子分中相对速度权重 |
|
||||
| `w_speed_ewma` | 0.20 | Speed 子分中 EWMA 速度权重 |
|
||||
| `weight_level` | 0.60 | 总分中 Level 权重 |
|
||||
| `weight_speed` | 0.30 | 总分中 Speed 权重 |
|
||||
| `weight_stability` | 0.10 | 总分中 Stability 权重 |
|
||||
| `stability_window_days` | 90 | 稳定性计算窗口 |
|
||||
| `use_stability` | 1 | 是否启用稳定性子分(0=禁用) |
|
||||
| `percentile_lower` | 5 | Winsorize 下分位 |
|
||||
| `percentile_upper` | 95 | Winsorize 上分位 |
|
||||
| `compression_mode` | 1 | 压缩模式:0=无,1=log1p,2=asinh |
|
||||
| `use_smoothing` | 1 | 是否启用 EWMA 分位平滑 |
|
||||
| `ewma_alpha` | 0.2 | 分位平滑 EWMA 系数 |
|
||||
| `speed_epsilon` | 1e-6 | 速度计算防除零小量 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 前置依赖
|
||||
|
||||
- 任务依赖:`DWS_MEMBER_CONSUMPTION`(需先完成会员消费汇总)
|
||||
- 数据源表:`dwd.dwd_settlement_head`、`dwd.dwd_recharge_order` 必须已有数据
|
||||
- 配置表:`dws.cfg_index_parameters` 中 `index_type='SPI'` 种子数据已插入(缺失时使用默认值)
|
||||
- 分位历史表:`dws.dws_index_percentile_history`(首次执行时无历史,不平滑)
|
||||
|
||||
---
|
||||
|
||||
## 7. 验证 SQL
|
||||
|
||||
### 7.1 检查表是否存在且有数据
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
COUNT(*) AS total_rows,
|
||||
COUNT(DISTINCT site_id) AS site_count,
|
||||
MIN(calc_time) AS earliest_calc,
|
||||
MAX(calc_time) AS latest_calc
|
||||
FROM dws.dws_member_spending_power_index;
|
||||
```
|
||||
|
||||
### 7.2 检查展示分范围是否合规(应全部在 [0, 10])
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
COUNT(*) FILTER (WHERE display_score < 0 OR display_score > 10) AS spi_out_of_range,
|
||||
COUNT(*) FILTER (WHERE score_level_display < 0 OR score_level_display > 10) AS level_out_of_range,
|
||||
COUNT(*) FILTER (WHERE score_speed_display < 0 OR score_speed_display > 10) AS speed_out_of_range,
|
||||
COUNT(*) FILTER (WHERE score_stability_display < 0 OR score_stability_display > 10) AS stability_out_of_range
|
||||
FROM dws.dws_member_spending_power_index;
|
||||
-- 预期:所有列均为 0
|
||||
```
|
||||
|
||||
### 7.3 检查业务主键唯一性(不应有重复)
|
||||
|
||||
```sql
|
||||
SELECT site_id, member_id, COUNT(*) AS cnt
|
||||
FROM dws.dws_member_spending_power_index
|
||||
GROUP BY site_id, member_id
|
||||
HAVING COUNT(*) > 1;
|
||||
-- 预期:无结果返回
|
||||
```
|
||||
|
||||
### 7.4 按门店查看 SPI 分布概况
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
site_id,
|
||||
COUNT(*) AS member_count,
|
||||
ROUND(AVG(display_score), 2) AS avg_spi,
|
||||
ROUND(MIN(display_score), 2) AS min_spi,
|
||||
ROUND(MAX(display_score), 2) AS max_spi,
|
||||
ROUND(PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY display_score), 2) AS median_spi
|
||||
FROM dws.dws_member_spending_power_index
|
||||
GROUP BY site_id
|
||||
ORDER BY site_id;
|
||||
```
|
||||
|
||||
### 7.5 检查 Stability 子分原始分范围(应在 [0, 1])
|
||||
|
||||
```sql
|
||||
SELECT COUNT(*) AS out_of_range
|
||||
FROM dws.dws_member_spending_power_index
|
||||
WHERE score_stability_raw < 0 OR score_stability_raw > 1;
|
||||
-- 预期:0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 兼容性说明
|
||||
|
||||
| 影响范围 | 说明 |
|
||||
|---------|------|
|
||||
| ETL 任务 | 新增任务 `DWS_SPENDING_POWER_INDEX`,依赖 `DWS_MEMBER_CONSUMPTION`。不影响现有 WBI/NCI/RS/OS/MS/ML 指数任务 |
|
||||
| 后端 API | 当前无 API 直接读取此表。后续如需暴露 SPI 数据,需新增接口 |
|
||||
| 管理后台 | 当前无前端页面展示 SPI。后续可在会员详情页新增 SPI 展示 |
|
||||
| 小程序 | 无影响 |
|
||||
| 其他指数 | SPI 独立于现有指数体系,不修改任何已有表或任务逻辑 |
|
||||
| 分位历史 | SPI 会向 `dws.dws_index_percentile_history` 写入 `index_type='SPI'`/`SPI_LEVEL`/`SPI_SPEED`/`SPI_STABILITY` 的分位记录 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 回滚策略
|
||||
|
||||
### 9.1 删除数据(保留表结构)
|
||||
|
||||
```sql
|
||||
DELETE FROM dws.dws_member_spending_power_index;
|
||||
DELETE FROM dws.dws_index_percentile_history WHERE index_type LIKE 'SPI%';
|
||||
DELETE FROM dws.cfg_index_parameters WHERE index_type = 'SPI';
|
||||
```
|
||||
|
||||
### 9.2 完整回滚(删除表)
|
||||
|
||||
```sql
|
||||
DROP INDEX IF EXISTS dws.idx_spi_display_score;
|
||||
DROP INDEX IF EXISTS dws.idx_spi_site_member;
|
||||
DROP TABLE IF EXISTS dws.dws_member_spending_power_index;
|
||||
DROP SEQUENCE IF EXISTS dws.dws_member_spending_power_index_spi_id_seq;
|
||||
```
|
||||
|
||||
### 9.3 回滚任务注册
|
||||
|
||||
从 `orchestration/task_registry.py` 中移除 `DWS_SPENDING_POWER_INDEX` 注册行,并从 `tasks/dws/index/__init__.py` 和 `tasks/dws/__init__.py` 中移除 `SpendingPowerIndexTask` 导出。
|
||||
|
||||
---
|
||||
|
||||
## 10. 代码引用
|
||||
|
||||
- 任务类:`tasks/dws/index/spending_power_index_task.py` → `SpendingPowerIndexTask`
|
||||
- 继承:`BaseIndexTask`(`tasks/dws/index/base_index_task.py`)
|
||||
- 任务注册:`orchestration/task_registry.py` → `DWS_SPENDING_POWER_INDEX`
|
||||
- 属性测试:`tests/test_spi_properties.py`
|
||||
- 单元测试:`apps/etl/connectors/feiqiu/tests/unit/test_spi_task.py`
|
||||
- 迁移脚本:`db/etl_feiqiu/migrations/2026-02-23_create_dws_member_spending_power_index.sql`
|
||||
- 种子数据:`db/etl_feiqiu/seeds/seed_index_parameters.sql`
|
||||
123
docs/database/BD_Manual_fdw_etl_setup.md
Normal file
123
docs/database/BD_Manual_fdw_etl_setup.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# BD_Manual:FDW 跨库映射配置(fdw_etl)
|
||||
|
||||
> 目标库:`test_zqyy_app`(通过 `APP_DB_DSN` 连接)
|
||||
> 迁移脚本:`db/zqyy_app/migrations/2026-02-24__p1_setup_fdw_etl.sql`
|
||||
> 关联 SPEC:`miniapp-db-foundation`(P1 基础设施层)
|
||||
|
||||
---
|
||||
|
||||
## 1. 变更说明
|
||||
|
||||
### 新增扩展
|
||||
|
||||
| 扩展 | 用途 |
|
||||
|------|------|
|
||||
| `postgres_fdw` | PostgreSQL 外部数据包装器,支持跨库查询 |
|
||||
|
||||
### 新增外部服务器
|
||||
|
||||
| 服务器名 | 目标库 | 说明 |
|
||||
|----------|--------|------|
|
||||
| `etl_feiqiu_server` | ETL 库(通过 `PG_DSN` 连接) | 通用名称,通过 host/dbname/port 参数区分环境 |
|
||||
|
||||
### 新增用户映射
|
||||
|
||||
| 本地角色 | 远程角色 | 服务器 |
|
||||
|----------|----------|--------|
|
||||
| `app_user` | `app_reader` | `etl_feiqiu_server` |
|
||||
|
||||
### 新增 Schema
|
||||
|
||||
| Schema | 用途 |
|
||||
|--------|------|
|
||||
| `fdw_etl` | 存放从 ETL 库 `app` Schema 导入的外部表(只读) |
|
||||
|
||||
### 导入的外部表
|
||||
|
||||
通过 `IMPORT FOREIGN SCHEMA app` 批量导入,外部表与 ETL 库 `app` Schema 中的 RLS 视图一一对应(共 35 张):
|
||||
- 11 张 DWD 视图:`v_dim_member`、`v_dim_assistant`、`v_dim_member_card_account`、`v_dim_table`、`v_dwd_settlement_head`、`v_dwd_table_fee_log`、`v_dwd_assistant_service_log`、`v_dwd_recharge_order`、`v_dwd_store_goods_sale`、`v_dim_staff`、`v_dim_staff_ex`
|
||||
- 24 张 DWS 视图:`v_dws_member_consumption_summary`、`v_dws_member_visit_detail` 等
|
||||
|
||||
### 权限配置
|
||||
|
||||
| 角色 | Schema | 权限 |
|
||||
|------|--------|------|
|
||||
| `app_user` | `fdw_etl` | `USAGE` + `SELECT ON ALL TABLES` + `ALTER DEFAULT PRIVILEGES` |
|
||||
|
||||
---
|
||||
|
||||
## 2. 兼容性影响
|
||||
|
||||
| 组件 | 影响 |
|
||||
|------|------|
|
||||
| ETL 任务 | 无影响。本脚本仅在业务库创建外部表映射,不修改 ETL 库 |
|
||||
| 后端 API | 前置依赖。后端可通过 `fdw_etl.v_dim_member` 等外部表读取 ETL 数据,无需直连 ETL 库 |
|
||||
| 小程序 | 无直接影响。小程序通过后端 API 间接访问 |
|
||||
| 管理后台 | 无直接影响 |
|
||||
| `auth`/`biz` Schema | 无影响。FDW 配置独立于业务 Schema |
|
||||
| `public` Schema | 无影响 |
|
||||
| 现有 `db/fdw/setup_fdw_test.sql` | 功能重叠。本迁移脚本使用通用服务器名 `etl_feiqiu_server`(不含环境前缀),与旧脚本的 `test_etl_feiqiu_server` 共存但独立 |
|
||||
|
||||
### 幂等性说明
|
||||
|
||||
`IMPORT FOREIGN SCHEMA` 不支持 `IF NOT EXISTS`,重复执行会因外部表已存在而报错。本脚本采用 `DROP SCHEMA IF EXISTS fdw_etl CASCADE` + 重建的方式确保幂等性。副作用是每次执行会重建所有外部表,但由于外部表不存储数据,无数据丢失风险。
|
||||
|
||||
---
|
||||
|
||||
## 3. 回滚策略
|
||||
|
||||
迁移脚本末尾已包含注释形式的回滚语句,按逆序执行:
|
||||
|
||||
```sql
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA fdw_etl REVOKE SELECT ON TABLES FROM app_user;
|
||||
REVOKE SELECT ON ALL TABLES IN SCHEMA fdw_etl FROM app_user;
|
||||
REVOKE USAGE ON SCHEMA fdw_etl FROM app_user;
|
||||
DROP SCHEMA IF EXISTS fdw_etl CASCADE;
|
||||
DROP USER MAPPING IF EXISTS FOR app_user SERVER etl_feiqiu_server;
|
||||
DROP SERVER IF EXISTS etl_feiqiu_server CASCADE;
|
||||
DROP EXTENSION IF EXISTS postgres_fdw;
|
||||
```
|
||||
|
||||
注意:
|
||||
- `DROP SERVER CASCADE` 会级联删除依赖的用户映射和外部表
|
||||
- 如果其他 Schema 也使用 `postgres_fdw` 扩展,不要执行最后一行 `DROP EXTENSION`
|
||||
- 回滚不影响 ETL 库侧的 `app` Schema 和 RLS 视图
|
||||
|
||||
---
|
||||
|
||||
## 4. 验证 SQL
|
||||
|
||||
```sql
|
||||
-- 1. 验证 postgres_fdw 扩展已安装
|
||||
SELECT extname, extversion
|
||||
FROM pg_extension
|
||||
WHERE extname = 'postgres_fdw';
|
||||
|
||||
-- 2. 验证外部服务器已创建
|
||||
SELECT srvname, srvowner::regrole, srvoptions
|
||||
FROM pg_foreign_server
|
||||
WHERE srvname = 'etl_feiqiu_server';
|
||||
|
||||
-- 3. 验证用户映射已创建
|
||||
SELECT um.umid, r.rolname AS local_role, s.srvname, um.umoptions
|
||||
FROM pg_user_mappings um
|
||||
JOIN pg_foreign_server s ON s.srvname = um.srvname
|
||||
JOIN pg_roles r ON r.rolname = um.usename
|
||||
WHERE s.srvname = 'etl_feiqiu_server';
|
||||
|
||||
-- 4. 验证 fdw_etl Schema 存在且包含外部表
|
||||
SELECT foreign_table_schema, foreign_table_name, foreign_server_name
|
||||
FROM information_schema.foreign_tables
|
||||
WHERE foreign_table_schema = 'fdw_etl'
|
||||
ORDER BY foreign_table_name;
|
||||
|
||||
-- 5. 验证 app_user 对 fdw_etl 有 USAGE 权限
|
||||
SELECT has_schema_privilege('app_user', 'fdw_etl', 'USAGE') AS fdw_etl_usage;
|
||||
|
||||
-- 6. 验证 ALTER DEFAULT PRIVILEGES 已设置
|
||||
SELECT n.nspname AS schema_name,
|
||||
d.defaclacl AS default_acl
|
||||
FROM pg_default_acl d
|
||||
JOIN pg_namespace n ON n.oid = d.defaclnamespace
|
||||
WHERE n.nspname = 'fdw_etl';
|
||||
```
|
||||
102
docs/database/BD_Manual_goods_stock_warning_info.md
Normal file
102
docs/database/BD_Manual_goods_stock_warning_info.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# BD Manual: goodsStockWarningInfo(库存预警信息)
|
||||
|
||||
## 变更概述
|
||||
|
||||
- 日期:2026-02-24
|
||||
- 触发:一致性检查报告发现 API 独有嵌套字段 `goodsStockWarningInfo` 未映射到 ODS/DWD
|
||||
- 迁移脚本:`db/etl_feiqiu/migrations/2026-02-24__add_goods_stock_warning_info.sql`
|
||||
|
||||
## 字段来源
|
||||
|
||||
API 端点 `/TenantGoods/GetGoodsInventoryList` 返回的 `store_goods_master` 记录中包含嵌套对象:
|
||||
|
||||
```json
|
||||
{
|
||||
"goodsStockWarningInfo": {
|
||||
"tenant_goods_id": 0, // 冗余,已有同名顶层字段
|
||||
"site_goods_id": 0, // 冗余,对应顶层 id
|
||||
"sales_day": 0.0, // → warning_sales_day
|
||||
"warning_day_max": 0, // → warning_day_max
|
||||
"warning_day_min": 0 // → warning_day_min
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
仅提取 3 个有效字段,冗余 ID 不重复收录。
|
||||
|
||||
## 新增字段
|
||||
|
||||
| 层 | 表 | 列名 | 类型 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| ODS | `ods.store_goods_master` | `warning_sales_day` | NUMERIC(18,2) | 库存预警参考的日均销量 |
|
||||
| ODS | `ods.store_goods_master` | `warning_day_max` | INTEGER | 预警天数上限 |
|
||||
| ODS | `ods.store_goods_master` | `warning_day_min` | INTEGER | 预警天数下限 |
|
||||
| DWD | `dwd.dim_store_goods_ex` | `warning_sales_day` | NUMERIC(18,2) | 同 ODS,直接映射 |
|
||||
| DWD | `dwd.dim_store_goods_ex` | `warning_day_max` | INTEGER | 同 ODS,直接映射 |
|
||||
| DWD | `dwd.dim_store_goods_ex` | `warning_day_min` | INTEGER | 同 ODS,直接映射 |
|
||||
|
||||
## 数据流
|
||||
|
||||
```
|
||||
API goodsStockWarningInfo (嵌套 JSON)
|
||||
↓ _merge_record_layers 扁平化(_STOCK_WARNING_FIELD_MAP)
|
||||
ODS ods.store_goods_master (warning_sales_day / warning_day_max / warning_day_min)
|
||||
↓ DWD FACT_MAPPINGS 直接映射
|
||||
DWD dwd.dim_store_goods_ex (同名列)
|
||||
```
|
||||
|
||||
## 代码变更
|
||||
|
||||
| 文件 | 变更 |
|
||||
|---|---|
|
||||
| `apps/etl/connectors/feiqiu/tasks/ods/ods_tasks.py` | `_merge_record_layers` 增加 `goodsStockWarningInfo` 扁平化逻辑 |
|
||||
| `apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.py` | `FACT_MAPPINGS["dwd.dim_store_goods_ex"]` 增加 3 个映射 |
|
||||
| `db/_archived/ddl_baseline_2026-02-22/db/etl_feiqiu/schemas/ods.sql` | baseline 同步 |
|
||||
| `db/_archived/ddl_baseline_2026-02-22/db/etl_feiqiu/schemas/dwd.sql` | baseline 同步 |
|
||||
|
||||
## 兼容性
|
||||
|
||||
- 后端 API:无影响(后端不直接读取 ODS/DWD 层这些字段)
|
||||
- 小程序:无影响
|
||||
- ETL:ODS schema-aware 插入自动识别新列;DWD 通过 FACT_MAPPINGS 映射
|
||||
- 管理后台:如需展示库存预警信息,可从 `dwd.dim_store_goods_ex` 读取
|
||||
|
||||
## 回滚策略
|
||||
|
||||
```sql
|
||||
ALTER TABLE ods.store_goods_master
|
||||
DROP COLUMN IF EXISTS warning_sales_day,
|
||||
DROP COLUMN IF EXISTS warning_day_max,
|
||||
DROP COLUMN IF EXISTS warning_day_min;
|
||||
ALTER TABLE dwd.dim_store_goods_ex
|
||||
DROP COLUMN IF EXISTS warning_sales_day,
|
||||
DROP COLUMN IF EXISTS warning_day_max,
|
||||
DROP COLUMN IF EXISTS warning_day_min;
|
||||
```
|
||||
|
||||
## 验证 SQL
|
||||
|
||||
```sql
|
||||
-- 1. 确认 ODS 新列存在
|
||||
SELECT column_name, data_type FROM information_schema.columns
|
||||
WHERE table_schema = 'ods' AND table_name = 'store_goods_master'
|
||||
AND column_name IN ('warning_sales_day', 'warning_day_max', 'warning_day_min')
|
||||
ORDER BY column_name;
|
||||
-- 预期:3 行
|
||||
|
||||
-- 2. 确认 DWD 新列存在
|
||||
SELECT column_name, data_type FROM information_schema.columns
|
||||
WHERE table_schema = 'dwd' AND table_name = 'dim_store_goods_ex'
|
||||
AND column_name IN ('warning_sales_day', 'warning_day_max', 'warning_day_min')
|
||||
ORDER BY column_name;
|
||||
-- 预期:3 行
|
||||
|
||||
-- 3. 确认注释已设置
|
||||
SELECT c.column_name, pgd.description
|
||||
FROM information_schema.columns c
|
||||
JOIN pg_catalog.pg_statio_all_tables st ON st.schemaname = c.table_schema AND st.relname = c.table_name
|
||||
JOIN pg_catalog.pg_description pgd ON pgd.objoid = st.relid AND pgd.objsubid = c.ordinal_position
|
||||
WHERE c.table_schema = 'ods' AND c.table_name = 'store_goods_master'
|
||||
AND c.column_name LIKE 'warning_%';
|
||||
-- 预期:3 行,description 非空
|
||||
```
|
||||
@@ -10,10 +10,11 @@
|
||||
| `etl_feiqiu__ods.sql` | etl_feiqiu | ods | 原始数据层(23 表) |
|
||||
| `etl_feiqiu__dwd.sql` | etl_feiqiu | dwd | 明细数据层(44 表) |
|
||||
| `etl_feiqiu__core.sql` | etl_feiqiu | core | 跨门店标准化(7 表) |
|
||||
| `etl_feiqiu__dws.sql` | etl_feiqiu | dws | 汇总数据层(32 表 + 1 视图 + 8 物化视图) |
|
||||
| `etl_feiqiu__app.sql` | etl_feiqiu | app | RLS 视图层(7 视图,无表) |
|
||||
| `etl_feiqiu__dws.sql` | etl_feiqiu | dws | 汇总数据层(34 表 + 1 视图 + 8 物化视图) |
|
||||
| `etl_feiqiu__app.sql` | etl_feiqiu | app | RLS 视图层(43 视图,无表) |
|
||||
| `zqyy_app__public.sql` | zqyy_app | public | 小程序业务表(12 表) |
|
||||
| `fdw.sql` | — | — | FDW 跨库映射配置 |
|
||||
| `zqyy_app__auth.sql` | zqyy_app | auth | 用户认证与权限(8 表) |
|
||||
| `fdw.sql` | — | — | FDW 正向跨库映射配置(etl→app) |
|
||||
|
||||
## 数据字典(BD_Manual — ODS→DWD 字段映射)
|
||||
|
||||
@@ -41,9 +42,15 @@
|
||||
- `etl_feiqiu_schema_migration.md`(旧迁移汇总)
|
||||
- `zqyy_app_admin_web_tables.md`(建表记录)
|
||||
|
||||
## 注意事项
|
||||
|
||||
- `fdw.sql` 仅包含正向映射(etl_feiqiu → zqyy_app),反向映射(zqyy_app → etl_feiqiu)的可执行脚本在 `db/fdw/setup_fdw_reverse*.sql`
|
||||
- DDL 基线中的统计数字以文件实际内容为准,本 README 的表格数字可能滞后于最新导出
|
||||
|
||||
## 相关资源
|
||||
|
||||
- 种子数据:`db/etl_feiqiu/seeds/`、`db/zqyy_app/seeds/`
|
||||
- FDW 配置:`db/fdw/`
|
||||
- FDW 配置(可执行):`db/fdw/`(含正向 + 反向 + 测试环境版本)
|
||||
- DDL 生成脚本:`scripts/ops/gen_consolidated_ddl.py`
|
||||
- 迁移脚本(活跃):`db/etl_feiqiu/migrations/`、`db/zqyy_app/migrations/`
|
||||
- 迁移脚本归档:`db/_archived/ddl_baseline_2026-02-22/`
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
-- =============================================================================
|
||||
-- etl_feiqiu / core(跨门店标准化维度/事实)
|
||||
-- 生成日期:2026-02-23
|
||||
-- 生成日期:2026-02-25
|
||||
-- 来源:测试库(通过脚本自动导出)
|
||||
-- =============================================================================
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- =============================================================================
|
||||
-- etl_feiqiu / dwd(明细数据层)
|
||||
-- 生成日期:2026-02-23
|
||||
-- 生成日期:2026-02-25
|
||||
-- 来源:测试库(通过脚本自动导出)
|
||||
-- =============================================================================
|
||||
|
||||
@@ -449,7 +449,10 @@ CREATE TABLE dwd.dim_store_goods_ex (
|
||||
scd2_is_current integer,
|
||||
scd2_version integer,
|
||||
batch_stock_quantity numeric,
|
||||
time_slot_sale integer
|
||||
time_slot_sale integer,
|
||||
warning_sales_day numeric(18,2),
|
||||
warning_day_max integer,
|
||||
warning_day_min integer
|
||||
);
|
||||
|
||||
CREATE TABLE dwd.dim_table (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- =============================================================================
|
||||
-- etl_feiqiu / dws(汇总数据层)
|
||||
-- 生成日期:2026-02-23
|
||||
-- 生成日期:2026-02-25
|
||||
-- 来源:测试库(通过脚本自动导出)
|
||||
-- =============================================================================
|
||||
|
||||
@@ -17,6 +17,7 @@ CREATE SEQUENCE IF NOT EXISTS dws.dws_assistant_customer_stats_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS dws.dws_assistant_daily_detail_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS dws.dws_assistant_finance_analysis_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS dws.dws_assistant_monthly_summary_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS dws.dws_assistant_order_contribution_contribution_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS dws.dws_assistant_recharge_commission_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS dws.dws_assistant_salary_calc_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS dws.dws_finance_daily_summary_id_seq AS bigint;
|
||||
@@ -194,7 +195,11 @@ CREATE TABLE dws.dws_assistant_daily_detail (
|
||||
trashed_seconds integer DEFAULT 0 NOT NULL,
|
||||
trashed_count integer DEFAULT 0 NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
penalty_minutes numeric(10,2) DEFAULT 0,
|
||||
penalty_reason text,
|
||||
is_exempt boolean DEFAULT false,
|
||||
per_hour_contribution numeric(14,2)
|
||||
);
|
||||
|
||||
CREATE TABLE dws.dws_assistant_finance_analysis (
|
||||
@@ -258,6 +263,23 @@ CREATE TABLE dws.dws_assistant_monthly_summary (
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE dws.dws_assistant_order_contribution (
|
||||
contribution_id bigint DEFAULT nextval('dws.dws_assistant_order_contribution_contribution_id_seq'::regclass) NOT NULL,
|
||||
site_id integer NOT NULL,
|
||||
tenant_id integer NOT NULL,
|
||||
assistant_id bigint NOT NULL,
|
||||
assistant_nickname character varying(100),
|
||||
stat_date date NOT NULL,
|
||||
order_gross_revenue numeric(14,2) DEFAULT 0,
|
||||
order_net_revenue numeric(14,2) DEFAULT 0,
|
||||
time_weighted_revenue numeric(14,2) DEFAULT 0,
|
||||
time_weighted_net_revenue numeric(14,2) DEFAULT 0,
|
||||
order_count integer DEFAULT 0,
|
||||
total_service_seconds integer DEFAULT 0,
|
||||
created_at timestamp with time zone DEFAULT now(),
|
||||
updated_at timestamp with time zone DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE dws.dws_assistant_recharge_commission (
|
||||
id bigint DEFAULT nextval('dws.dws_assistant_recharge_commission_id_seq'::regclass) NOT NULL,
|
||||
site_id bigint NOT NULL,
|
||||
@@ -622,7 +644,14 @@ CREATE TABLE dws.dws_member_consumption_summary (
|
||||
is_active_90d boolean DEFAULT false NOT NULL,
|
||||
customer_tier character varying(20),
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
recharge_count_30d integer DEFAULT 0,
|
||||
recharge_count_60d integer DEFAULT 0,
|
||||
recharge_count_90d integer DEFAULT 0,
|
||||
recharge_amount_30d numeric(14,2) DEFAULT 0,
|
||||
recharge_amount_60d numeric(14,2) DEFAULT 0,
|
||||
recharge_amount_90d numeric(14,2) DEFAULT 0,
|
||||
avg_ticket_amount numeric(14,2) DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE TABLE dws.dws_member_newconv_index (
|
||||
@@ -918,6 +947,7 @@ ALTER TABLE dws.dws_assistant_finance_analysis ADD CONSTRAINT dws_assistant_fina
|
||||
ALTER TABLE dws.dws_assistant_finance_analysis ADD CONSTRAINT uk_dws_assistant_finance UNIQUE (site_id, stat_date, assistant_id);
|
||||
ALTER TABLE dws.dws_assistant_monthly_summary ADD CONSTRAINT dws_assistant_monthly_summary_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE dws.dws_assistant_monthly_summary ADD CONSTRAINT uk_dws_assistant_monthly UNIQUE (site_id, assistant_id, stat_month, assistant_level_code);
|
||||
ALTER TABLE dws.dws_assistant_order_contribution ADD CONSTRAINT dws_assistant_order_contribution_pkey PRIMARY KEY (contribution_id);
|
||||
ALTER TABLE dws.dws_assistant_recharge_commission ADD CONSTRAINT dws_assistant_recharge_commission_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE dws.dws_assistant_salary_calc ADD CONSTRAINT dws_assistant_salary_calc_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE dws.dws_assistant_salary_calc ADD CONSTRAINT uk_dws_assistant_salary UNIQUE (site_id, assistant_id, salary_month, assistant_level_code);
|
||||
@@ -978,6 +1008,8 @@ CREATE INDEX idx_dws_assistant_finance_date ON dws.dws_assistant_finance_analysi
|
||||
CREATE INDEX idx_dws_assistant_monthly_asst ON dws.dws_assistant_monthly_summary USING btree (assistant_id, stat_month);
|
||||
CREATE INDEX idx_dws_assistant_monthly_month ON dws.dws_assistant_monthly_summary USING btree (stat_month);
|
||||
CREATE INDEX idx_dws_assistant_monthly_tier ON dws.dws_assistant_monthly_summary USING btree (tier_code);
|
||||
CREATE UNIQUE INDEX idx_aoc_site_assistant_date ON dws.dws_assistant_order_contribution USING btree (site_id, assistant_id, stat_date);
|
||||
CREATE INDEX idx_aoc_stat_date ON dws.dws_assistant_order_contribution USING btree (site_id, stat_date);
|
||||
CREATE INDEX idx_dws_assistant_commission_asst ON dws.dws_assistant_recharge_commission USING btree (assistant_id, commission_month);
|
||||
CREATE INDEX idx_dws_assistant_commission_batch ON dws.dws_assistant_recharge_commission USING btree (import_batch_no);
|
||||
CREATE INDEX idx_dws_assistant_commission_month ON dws.dws_assistant_recharge_commission USING btree (commission_month);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- =============================================================================
|
||||
-- etl_feiqiu / meta(ETL 调度元数据)
|
||||
-- 生成日期:2026-02-23
|
||||
-- 生成日期:2026-02-25
|
||||
-- 来源:测试库(通过脚本自动导出)
|
||||
-- =============================================================================
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- =============================================================================
|
||||
-- etl_feiqiu / ods(原始数据层)
|
||||
-- 生成日期:2026-02-23
|
||||
-- 生成日期:2026-02-25
|
||||
-- 来源:测试库(通过脚本自动导出)
|
||||
-- =============================================================================
|
||||
|
||||
@@ -873,7 +873,10 @@ CREATE TABLE ods.store_goods_master (
|
||||
source_file text,
|
||||
source_endpoint text,
|
||||
fetched_at timestamp with time zone DEFAULT now(),
|
||||
time_slot_sale integer
|
||||
time_slot_sale integer,
|
||||
warning_sales_day numeric(18,2),
|
||||
warning_day_max integer,
|
||||
warning_day_min integer
|
||||
);
|
||||
|
||||
CREATE TABLE ods.store_goods_sales_records (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- =============================================================================
|
||||
-- FDW 跨库映射(在 zqyy_app 中执行)
|
||||
-- 生成日期:2026-02-23
|
||||
-- 生成日期:2026-02-25
|
||||
-- 来源:db/fdw/setup_fdw.sql
|
||||
-- =============================================================================
|
||||
|
||||
|
||||
135
docs/database/ddl/zqyy_app__auth.sql
Normal file
135
docs/database/ddl/zqyy_app__auth.sql
Normal file
@@ -0,0 +1,135 @@
|
||||
-- =============================================================================
|
||||
-- zqyy_app / auth(用户认证与权限)
|
||||
-- 生成日期:2026-02-25
|
||||
-- 来源:测试库(通过脚本自动导出)
|
||||
-- =============================================================================
|
||||
|
||||
CREATE SCHEMA IF NOT EXISTS auth;
|
||||
|
||||
-- 序列
|
||||
CREATE SEQUENCE IF NOT EXISTS auth.permissions_id_seq AS integer;
|
||||
CREATE SEQUENCE IF NOT EXISTS auth.roles_id_seq AS integer;
|
||||
CREATE SEQUENCE IF NOT EXISTS auth.site_code_mapping_id_seq AS integer;
|
||||
CREATE SEQUENCE IF NOT EXISTS auth.user_applications_id_seq AS integer;
|
||||
CREATE SEQUENCE IF NOT EXISTS auth.user_assistant_binding_id_seq AS integer;
|
||||
CREATE SEQUENCE IF NOT EXISTS auth.user_site_roles_id_seq AS integer;
|
||||
CREATE SEQUENCE IF NOT EXISTS auth.users_id_seq AS integer;
|
||||
|
||||
-- 表
|
||||
CREATE TABLE auth.permissions (
|
||||
id integer DEFAULT nextval('auth.permissions_id_seq'::regclass) NOT NULL,
|
||||
code character varying(100) NOT NULL,
|
||||
name character varying(200) NOT NULL,
|
||||
description text,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE auth.role_permissions (
|
||||
role_id integer NOT NULL,
|
||||
permission_id integer NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE auth.roles (
|
||||
id integer DEFAULT nextval('auth.roles_id_seq'::regclass) NOT NULL,
|
||||
code character varying(50) NOT NULL,
|
||||
name character varying(100) NOT NULL,
|
||||
description text,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE auth.site_code_mapping (
|
||||
id integer DEFAULT nextval('auth.site_code_mapping_id_seq'::regclass) NOT NULL,
|
||||
site_code character varying(10) NOT NULL,
|
||||
site_id bigint NOT NULL,
|
||||
site_name character varying(200),
|
||||
tenant_id integer,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE auth.user_applications (
|
||||
id integer DEFAULT nextval('auth.user_applications_id_seq'::regclass) NOT NULL,
|
||||
user_id integer NOT NULL,
|
||||
site_code character varying(10) NOT NULL,
|
||||
site_id bigint,
|
||||
applied_role_text character varying(100) NOT NULL,
|
||||
employee_number character varying(50),
|
||||
phone character varying(20) NOT NULL,
|
||||
status character varying(20) DEFAULT 'pending'::character varying NOT NULL,
|
||||
reviewer_id integer,
|
||||
review_note text,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
reviewed_at timestamp with time zone
|
||||
);
|
||||
|
||||
CREATE TABLE auth.user_assistant_binding (
|
||||
id integer DEFAULT nextval('auth.user_assistant_binding_id_seq'::regclass) NOT NULL,
|
||||
user_id integer NOT NULL,
|
||||
site_id bigint NOT NULL,
|
||||
assistant_id bigint,
|
||||
staff_id bigint,
|
||||
binding_type character varying(20) NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE auth.user_site_roles (
|
||||
id integer DEFAULT nextval('auth.user_site_roles_id_seq'::regclass) NOT NULL,
|
||||
user_id integer NOT NULL,
|
||||
site_id bigint NOT NULL,
|
||||
role_id integer NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE auth.users (
|
||||
id integer DEFAULT nextval('auth.users_id_seq'::regclass) NOT NULL,
|
||||
wx_openid character varying(100),
|
||||
wx_union_id character varying(100),
|
||||
wx_avatar_url text,
|
||||
nickname character varying(100),
|
||||
phone character varying(20),
|
||||
status character varying(20) DEFAULT 'pending'::character varying NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
-- 约束(主键 / 唯一 / 外键)
|
||||
ALTER TABLE auth.permissions ADD CONSTRAINT permissions_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE auth.permissions ADD CONSTRAINT permissions_code_key UNIQUE (code);
|
||||
ALTER TABLE auth.permissions ADD CONSTRAINT uq_permissions_code UNIQUE (code);
|
||||
ALTER TABLE auth.role_permissions ADD CONSTRAINT fk_role_permissions_permission_id FOREIGN KEY (permission_id) REFERENCES auth.permissions(id) ON DELETE CASCADE;
|
||||
ALTER TABLE auth.role_permissions ADD CONSTRAINT fk_role_permissions_role_id FOREIGN KEY (role_id) REFERENCES auth.roles(id) ON DELETE CASCADE;
|
||||
ALTER TABLE auth.role_permissions ADD CONSTRAINT role_permissions_permission_id_fkey FOREIGN KEY (permission_id) REFERENCES auth.permissions(id) ON DELETE CASCADE;
|
||||
ALTER TABLE auth.role_permissions ADD CONSTRAINT role_permissions_role_id_fkey FOREIGN KEY (role_id) REFERENCES auth.roles(id) ON DELETE CASCADE;
|
||||
ALTER TABLE auth.role_permissions ADD CONSTRAINT role_permissions_pkey PRIMARY KEY (role_id, permission_id);
|
||||
ALTER TABLE auth.roles ADD CONSTRAINT roles_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE auth.roles ADD CONSTRAINT roles_code_key UNIQUE (code);
|
||||
ALTER TABLE auth.roles ADD CONSTRAINT uq_roles_code UNIQUE (code);
|
||||
ALTER TABLE auth.site_code_mapping ADD CONSTRAINT site_code_mapping_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE auth.site_code_mapping ADD CONSTRAINT site_code_mapping_site_code_key UNIQUE (site_code);
|
||||
ALTER TABLE auth.site_code_mapping ADD CONSTRAINT site_code_mapping_site_id_key UNIQUE (site_id);
|
||||
ALTER TABLE auth.site_code_mapping ADD CONSTRAINT uq_site_code_mapping_site_code UNIQUE (site_code);
|
||||
ALTER TABLE auth.site_code_mapping ADD CONSTRAINT uq_site_code_mapping_site_id UNIQUE (site_id);
|
||||
ALTER TABLE auth.user_applications ADD CONSTRAINT fk_user_applications_user_id FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE;
|
||||
ALTER TABLE auth.user_applications ADD CONSTRAINT user_applications_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE;
|
||||
ALTER TABLE auth.user_applications ADD CONSTRAINT user_applications_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE auth.user_assistant_binding ADD CONSTRAINT fk_user_assistant_binding_user_id FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE;
|
||||
ALTER TABLE auth.user_assistant_binding ADD CONSTRAINT user_assistant_binding_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE;
|
||||
ALTER TABLE auth.user_assistant_binding ADD CONSTRAINT user_assistant_binding_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE auth.user_site_roles ADD CONSTRAINT fk_user_site_roles_role_id FOREIGN KEY (role_id) REFERENCES auth.roles(id) ON DELETE CASCADE;
|
||||
ALTER TABLE auth.user_site_roles ADD CONSTRAINT fk_user_site_roles_user_id FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE;
|
||||
ALTER TABLE auth.user_site_roles ADD CONSTRAINT user_site_roles_role_id_fkey FOREIGN KEY (role_id) REFERENCES auth.roles(id) ON DELETE CASCADE;
|
||||
ALTER TABLE auth.user_site_roles ADD CONSTRAINT user_site_roles_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE;
|
||||
ALTER TABLE auth.user_site_roles ADD CONSTRAINT user_site_roles_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE auth.user_site_roles ADD CONSTRAINT uq_user_site_roles_user_site_role UNIQUE (user_id, site_id, role_id);
|
||||
ALTER TABLE auth.user_site_roles ADD CONSTRAINT user_site_roles_user_id_site_id_role_id_key UNIQUE (user_id, site_id, role_id);
|
||||
ALTER TABLE auth.users ADD CONSTRAINT users_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE auth.users ADD CONSTRAINT uq_users_wx_openid UNIQUE (wx_openid);
|
||||
ALTER TABLE auth.users ADD CONSTRAINT users_wx_openid_key UNIQUE (wx_openid);
|
||||
|
||||
-- 索引
|
||||
CREATE INDEX ix_site_code_mapping_site_code ON auth.site_code_mapping USING btree (site_code);
|
||||
CREATE INDEX ix_user_applications_status ON auth.user_applications USING btree (status);
|
||||
CREATE INDEX ix_user_applications_user_id ON auth.user_applications USING btree (user_id);
|
||||
CREATE INDEX ix_user_site_roles_user_site ON auth.user_site_roles USING btree (user_id, site_id);
|
||||
CREATE INDEX ix_users_status ON auth.users USING btree (status);
|
||||
CREATE INDEX ix_users_wx_openid ON auth.users USING btree (wx_openid);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- =============================================================================
|
||||
-- zqyy_app / public(小程序业务表)
|
||||
-- 生成日期:2026-02-23
|
||||
-- 生成日期:2026-02-25
|
||||
-- 来源:测试库(通过脚本自动导出)
|
||||
-- =============================================================================
|
||||
|
||||
|
||||
@@ -23,12 +23,12 @@
|
||||
|
||||
| 状态 | 项目 |
|
||||
|------|------|
|
||||
| 20260220 | 在 Windows Server 上创建目录结构 |
|
||||
| 20260220 | 克隆仓库并切换分支 |
|
||||
| | 配置环境变量文件 |
|
||||
| | 安装 Python 依赖 |
|
||||
| | 运行 `setup-server-git.py` 配置 Git 排除规则 |
|
||||
| | 运行 `init-server-env.ps1` 删除排除文件 + 创建 export 目录 |
|
||||
| 完成 20260220 | 在 Windows Server 上创建目录结构 |
|
||||
| 完成 20260220 | 克隆仓库并切换分支 |
|
||||
| 完成 20260224 | 配置环境变量文件 |
|
||||
| 完成 20260224 | 安装 Python 依赖 |
|
||||
| 完成 20260224 | 运行 `setup-server-git.py` 配置 Git 排除规则 |
|
||||
| 完成 20260224 | 运行 `init-server-env.py` 删除排除文件 + 创建 export 目录 |
|
||||
|
||||
在 Windows Server 上执行:
|
||||
|
||||
@@ -125,11 +125,11 @@ python scripts/server/setup-server-git.py
|
||||
```powershell
|
||||
# 删除排除文件 + 创建 export 目录树(test + prod 一次搞定)
|
||||
cd D:\NeoZQYY
|
||||
.\test\repo\scripts\server\init-server-env.ps1
|
||||
python test\repo\scripts\server\init-server-env.py
|
||||
|
||||
# 也可以只初始化单个环境
|
||||
.\test\repo\scripts\server\init-server-env.ps1 -Envs test
|
||||
.\prod\repo\scripts\server\init-server-env.ps1 -Envs prod
|
||||
python test\repo\scripts\server\init-server-env.py --envs test
|
||||
python prod\repo\scripts\server\init-server-env.py --envs prod
|
||||
```
|
||||
|
||||
Git 排除方案说明(统一 .gitignore + skip-worktree):
|
||||
@@ -172,8 +172,8 @@ Git 排除方案说明(统一 .gitignore + skip-worktree):
|
||||
|
||||
| 状态 | 项目 |
|
||||
|------|------|
|
||||
| | 将 bat 脚本放到服务器 `D:\NeoZQYY\scripts\` |
|
||||
| | 登录服务器手动运行对应脚本启动服务 |
|
||||
| 完成 20260224 | 将 bat 脚本放到服务器 `D:\NeoZQYY\scripts\` |
|
||||
| 完成 20260224 | 登录服务器手动运行对应脚本启动服务 |
|
||||
|
||||
> 后续将由监控系统(见 7.2)统一管理所有服务的启停和状态监控。
|
||||
> 在监控系统上线之前,登录 Windows Server 手动双击 bat 脚本启动。
|
||||
|
||||
185
docs/h5_ui/css/ai-icons.css
Normal file
185
docs/h5_ui/css/ai-icons.css
Normal file
@@ -0,0 +1,185 @@
|
||||
/* ========== AI 标识通用样式 ========== */
|
||||
|
||||
/* --- 配色系(渐变背景,badge 用更深的 --ai-from-deep / --ai-to-deep) --- */
|
||||
.ai-color-red { --ai-from: #e74c3c; --ai-to: #f39c9c; --ai-from-deep: #c0392b; --ai-to-deep: #e74c3c; }
|
||||
.ai-color-orange { --ai-from: #e67e22; --ai-to: #f5c77e; --ai-from-deep: #ca6c17; --ai-to-deep: #e67e22; }
|
||||
.ai-color-yellow { --ai-from: #d4a017; --ai-to: #f7dc6f; --ai-from-deep: #b8860b; --ai-to-deep: #d4a017; }
|
||||
.ai-color-blue { --ai-from: #2980b9; --ai-to: #7ec8e3; --ai-from-deep: #1a5276; --ai-to-deep: #2980b9; }
|
||||
.ai-color-indigo { --ai-from: #667eea; --ai-to: #a78bfa; --ai-from-deep: #4a5fc7; --ai-to-deep: #667eea; }
|
||||
.ai-color-purple { --ai-from: #764ba2; --ai-to: #c084fc; --ai-from-deep: #5b3080; --ai-to-deep: #764ba2; }
|
||||
|
||||
/* --- 1. 嵌入 Icon(行首小图标,轻量化) --- */
|
||||
.ai-inline-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: linear-gradient(135deg, color-mix(in srgb, var(--ai-from) 45%, white), color-mix(in srgb, var(--ai-to) 50%, white));
|
||||
border-radius: 4px;
|
||||
font-size: 10px;
|
||||
line-height: 1;
|
||||
vertical-align: middle;
|
||||
margin-right: 3px;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
/* 嵌入 Icon 内的机器人 SVG */
|
||||
.ai-inline-icon svg {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
/* 微光动画(慢速、柔和、宽光带) */
|
||||
.ai-inline-icon::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
left: -50%;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background: linear-gradient(
|
||||
45deg,
|
||||
transparent 30%,
|
||||
rgba(255,255,255,0.18) 45%,
|
||||
rgba(255,255,255,0.22) 50%,
|
||||
rgba(255,255,255,0.18) 55%,
|
||||
transparent 70%
|
||||
);
|
||||
animation: ai-shimmer 12s ease-in-out infinite;
|
||||
}
|
||||
@keyframes ai-shimmer {
|
||||
0%, 100% { transform: translateX(-100%) rotate(45deg); }
|
||||
50% { transform: translateX(100%) rotate(45deg); }
|
||||
}
|
||||
|
||||
/* --- 2. Title AI 标识(标题行右侧,浅色背景+主题色文字+边框) --- */
|
||||
.ai-title-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
padding: 2px 8px 2px 3px;
|
||||
background: linear-gradient(135deg, color-mix(in srgb, var(--ai-from) 8%, white), color-mix(in srgb, var(--ai-to) 10%, white));
|
||||
border: 1px solid color-mix(in srgb, var(--ai-from) 35%, transparent);
|
||||
border-radius: 10px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
color: var(--ai-from-deep);
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
line-height: 1.4;
|
||||
/* 呼吸脉冲:模拟 AI 思考进度感 */
|
||||
animation: ai-pulse 3s ease-in-out infinite;
|
||||
}
|
||||
@keyframes ai-pulse {
|
||||
0%, 100% { box-shadow: 0 0 0 0 rgba(var(--ai-pulse-r, 102), var(--ai-pulse-g, 126), var(--ai-pulse-b, 234), 0); }
|
||||
50% { box-shadow: 0 0 8px 2px rgba(var(--ai-pulse-r, 102), var(--ai-pulse-g, 126), var(--ai-pulse-b, 234), 0.35); }
|
||||
}
|
||||
/* 各配色的脉冲 RGB 值 */
|
||||
.ai-color-red .ai-title-badge,
|
||||
.ai-title-badge.ai-color-red { --ai-pulse-r: 231; --ai-pulse-g: 76; --ai-pulse-b: 60; }
|
||||
.ai-color-orange .ai-title-badge,
|
||||
.ai-title-badge.ai-color-orange { --ai-pulse-r: 230; --ai-pulse-g: 126; --ai-pulse-b: 34; }
|
||||
.ai-color-yellow .ai-title-badge,
|
||||
.ai-title-badge.ai-color-yellow { --ai-pulse-r: 212; --ai-pulse-g: 160; --ai-pulse-b: 23; }
|
||||
.ai-color-blue .ai-title-badge,
|
||||
.ai-title-badge.ai-color-blue { --ai-pulse-r: 41; --ai-pulse-g: 128; --ai-pulse-b: 185; }
|
||||
.ai-color-indigo .ai-title-badge,
|
||||
.ai-title-badge.ai-color-indigo { --ai-pulse-r: 102; --ai-pulse-g: 126; --ai-pulse-b: 234; }
|
||||
.ai-color-purple .ai-title-badge,
|
||||
.ai-title-badge.ai-color-purple { --ai-pulse-r: 118; --ai-pulse-g: 75; --ai-pulse-b: 162; }
|
||||
|
||||
.ai-title-badge-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
flex-shrink: 0;
|
||||
/* 主题色发光 */
|
||||
filter: drop-shadow(0 0 2px var(--ai-from));
|
||||
}
|
||||
.ai-title-badge-icon svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
/* 高光扫过(柔和、微弱) */
|
||||
.ai-title-badge::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
left: -50%;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background: linear-gradient(
|
||||
45deg,
|
||||
transparent 30%,
|
||||
rgba(255,255,255,0.15) 43%,
|
||||
rgba(255,255,255,0.22) 50%,
|
||||
rgba(255,255,255,0.15) 57%,
|
||||
transparent 70%
|
||||
);
|
||||
animation: ai-shimmer 14s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* --- 星级评价(备注卡片右上角,14px) --- */
|
||||
.star-rating {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 1px;
|
||||
}
|
||||
.star-rating .star {
|
||||
position: relative;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
cursor: default;
|
||||
}
|
||||
.star-rating .star svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
/* 空星 */
|
||||
.star-rating .star .star-empty {
|
||||
color: #e8e8e8;
|
||||
}
|
||||
/* 满星 / 半星用 clip 实现 */
|
||||
.star-rating .star .star-fill {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
/* 颜色方案:浅黄→黄→橙→深红橙(0-10 分) */
|
||||
.star-rating[data-score="0"] { --star-color: #d4d4d4; }
|
||||
.star-rating[data-score="1"] { --star-color: #f5e6a3; }
|
||||
.star-rating[data-score="2"] { --star-color: #f0d86e; }
|
||||
.star-rating[data-score="3"] { --star-color: #f0c93a; }
|
||||
.star-rating[data-score="4"] { --star-color: #f0b429; }
|
||||
.star-rating[data-score="5"] { --star-color: #f09c1a; }
|
||||
.star-rating[data-score="6"] { --star-color: #ed8a0a; }
|
||||
.star-rating[data-score="7"] { --star-color: #e67e22; }
|
||||
.star-rating[data-score="8"] { --star-color: #e05d1a; }
|
||||
.star-rating[data-score="9"] { --star-color: #d44a12; }
|
||||
.star-rating[data-score="10"] { --star-color: #c0392b; }
|
||||
.star-rating .star .star-fill svg {
|
||||
color: var(--star-color);
|
||||
}
|
||||
/* 高分微光 */
|
||||
.star-rating[data-score="9"] .star .star-fill svg,
|
||||
.star-rating[data-score="10"] .star .star-fill svg {
|
||||
filter: drop-shadow(0 0 2px rgba(192,57,43,0.4));
|
||||
}
|
||||
|
||||
/* 备注卡片内星级评价:删除按钮左侧,垂直居中 */
|
||||
.note-card-wrap {
|
||||
position: relative;
|
||||
}
|
||||
.note-card-wrap .star-rating {
|
||||
/* 不再绝对定位,改为在 flex 布局中自然排列 */
|
||||
flex-shrink: 0;
|
||||
align-self: center;
|
||||
}
|
||||
@@ -234,6 +234,22 @@
|
||||
<iframe src="pages/chat.html" class="phone-screen"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 客户服务记录页 -->
|
||||
<div>
|
||||
<div class="page-label">客户服务记录页</div>
|
||||
<div class="phone-frame">
|
||||
<iframe src="pages/customer-service-records.html" class="phone-screen"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 业绩记录页 -->
|
||||
<div>
|
||||
<div class="page-label">业绩记录页</div>
|
||||
<div class="phone-frame">
|
||||
<iframe src="pages/performance-records.html" class="phone-screen"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
49
docs/h5_ui/js/ai-icons.js
Normal file
49
docs/h5_ui/js/ai-icons.js
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* AI Icon 通用初始化脚本
|
||||
* - 页面加载时随机分配配色
|
||||
* - 嵌入 Icon 注入机器人 SVG
|
||||
* - 渲染星级评价组件
|
||||
*/
|
||||
(function () {
|
||||
var ROBOT_SVG = '<svg viewBox="0 0 24 24" fill="none"><rect x="5" y="7" width="14" height="12" rx="4" fill="white"/><path d="M12 7V4" stroke="white" stroke-width="2" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="white"/><circle cx="9" cy="11.5" r="2" fill="#667eea"/><circle cx="15" cy="11.5" r="2" fill="#667eea"/><circle cx="9.5" cy="11" r="0.7" fill="white"/><circle cx="15.5" cy="11" r="0.7" fill="white"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="#667eea" stroke-width="1.5" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><rect x="3" y="10" width="2" height="4" rx="1" fill="white"/><rect x="19" y="10" width="2" height="4" rx="1" fill="white"/></svg>';
|
||||
|
||||
var COLOR_CLASSES = [
|
||||
'ai-color-red', 'ai-color-orange', 'ai-color-yellow',
|
||||
'ai-color-blue', 'ai-color-indigo', 'ai-color-purple'
|
||||
];
|
||||
|
||||
// 随机选一个配色(同一页面统一)
|
||||
var pick = COLOR_CLASSES[Math.floor(Math.random() * COLOR_CLASSES.length)];
|
||||
|
||||
// 给所有嵌入 Icon 和 title badge 加上配色 class
|
||||
document.querySelectorAll('.ai-inline-icon, .ai-title-badge').forEach(function (el) {
|
||||
el.classList.add(pick);
|
||||
});
|
||||
|
||||
// 嵌入 Icon:如果内部没有 SVG,自动注入机器人
|
||||
document.querySelectorAll('.ai-inline-icon').forEach(function (el) {
|
||||
if (!el.querySelector('svg')) {
|
||||
el.innerHTML = ROBOT_SVG;
|
||||
}
|
||||
});
|
||||
|
||||
// 渲染星级评价
|
||||
document.querySelectorAll('.star-rating').forEach(function (container) {
|
||||
var score = parseInt(container.getAttribute('data-score') || '0', 10);
|
||||
score = Math.max(0, Math.min(10, score));
|
||||
var fullStars = Math.floor(score / 2);
|
||||
var halfStar = score % 2 === 1;
|
||||
var html = '';
|
||||
for (var i = 0; i < 5; i++) {
|
||||
var fillWidth = '0%';
|
||||
if (i < fullStars) fillWidth = '100%';
|
||||
else if (i === fullStars && halfStar) fillWidth = '50%';
|
||||
html += '<span class="star">'
|
||||
+ '<svg class="star-empty" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>'
|
||||
+ '<span class="star-fill" style="width:' + fillWidth + '">'
|
||||
+ '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>'
|
||||
+ '</span></span>';
|
||||
}
|
||||
container.innerHTML = html;
|
||||
});
|
||||
})();
|
||||
80
docs/h5_ui/pages/ai-icon-demo.html
Normal file
80
docs/h5_ui/pages/ai-icon-demo.html
Normal file
@@ -0,0 +1,80 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AI 标识配色演示</title>
|
||||
<link href="../css/ai-icons.css" rel="stylesheet">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: 'Noto Sans SC', -apple-system, sans-serif; background: #f5f5f5; padding: 20px; }
|
||||
h1 { font-size: 18px; color: #242424; margin-bottom: 20px; text-align: center; }
|
||||
h2 { font-size: 15px; color: #5e5e5e; margin: 24px 0 12px; padding-left: 8px; border-left: 3px solid #667eea; }
|
||||
.demo-row { display: flex; align-items: center; gap: 16px; padding: 14px 16px; background: #fff; border-radius: 12px; margin-bottom: 10px; box-shadow: 0 1px 3px rgba(0,0,0,0.06); }
|
||||
.demo-row .label { font-size: 13px; color: #8b8b8b; min-width: 36px; }
|
||||
.demo-row .sample-text { font-size: 14px; color: #5e5e5e; display: flex; align-items: center; }
|
||||
.note { font-size: 12px; color: #a6a6a6; text-align: center; margin-top: 8px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>AI 标识配色方案演示</h1>
|
||||
|
||||
<!-- ===== 嵌入 Icon ===== -->
|
||||
<h2>嵌入 Icon(行首小图标 · 机器人)</h2>
|
||||
|
||||
<div class="demo-row">
|
||||
<span class="label">红</span>
|
||||
<span class="sample-text"><span class="ai-inline-icon ai-color-red"><svg viewBox="0 0 24 24" fill="none"><rect x="5" y="7" width="14" height="12" rx="4" fill="white"/><path d="M12 7V4" stroke="white" stroke-width="2" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="white"/><circle cx="9" cy="11.5" r="2" fill="#667eea"/><circle cx="15" cy="11.5" r="2" fill="#667eea"/><circle cx="9.5" cy="11" r="0.7" fill="white"/><circle cx="15.5" cy="11" r="0.7" fill="white"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="#667eea" stroke-width="1.5" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><rect x="3" y="10" width="2" height="4" rx="1" fill="white"/><rect x="19" y="10" width="2" height="4" rx="1" fill="white"/></svg></span>高流失风险,建议尽快联系</span>
|
||||
</div>
|
||||
<div class="demo-row">
|
||||
<span class="label">橙</span>
|
||||
<span class="sample-text"><span class="ai-inline-icon ai-color-orange"><svg viewBox="0 0 24 24" fill="none"><rect x="5" y="7" width="14" height="12" rx="4" fill="white"/><path d="M12 7V4" stroke="white" stroke-width="2" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="white"/><circle cx="9" cy="11.5" r="2" fill="#667eea"/><circle cx="15" cy="11.5" r="2" fill="#667eea"/><circle cx="9.5" cy="11" r="0.7" fill="white"/><circle cx="15.5" cy="11" r="0.7" fill="white"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="#667eea" stroke-width="1.5" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><rect x="3" y="10" width="2" height="4" rx="1" fill="white"/><rect x="19" y="10" width="2" height="4" rx="1" fill="white"/></svg></span>高流失风险,建议尽快联系</span>
|
||||
</div>
|
||||
<div class="demo-row">
|
||||
<span class="label">黄</span>
|
||||
<span class="sample-text"><span class="ai-inline-icon ai-color-yellow"><svg viewBox="0 0 24 24" fill="none"><rect x="5" y="7" width="14" height="12" rx="4" fill="white"/><path d="M12 7V4" stroke="white" stroke-width="2" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="white"/><circle cx="9" cy="11.5" r="2" fill="#667eea"/><circle cx="15" cy="11.5" r="2" fill="#667eea"/><circle cx="9.5" cy="11" r="0.7" fill="white"/><circle cx="15.5" cy="11" r="0.7" fill="white"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="#667eea" stroke-width="1.5" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><rect x="3" y="10" width="2" height="4" rx="1" fill="white"/><rect x="19" y="10" width="2" height="4" rx="1" fill="white"/></svg></span>高流失风险,建议尽快联系</span>
|
||||
</div>
|
||||
<div class="demo-row">
|
||||
<span class="label">蓝</span>
|
||||
<span class="sample-text"><span class="ai-inline-icon ai-color-blue"><svg viewBox="0 0 24 24" fill="none"><rect x="5" y="7" width="14" height="12" rx="4" fill="white"/><path d="M12 7V4" stroke="white" stroke-width="2" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="white"/><circle cx="9" cy="11.5" r="2" fill="#667eea"/><circle cx="15" cy="11.5" r="2" fill="#667eea"/><circle cx="9.5" cy="11" r="0.7" fill="white"/><circle cx="15.5" cy="11" r="0.7" fill="white"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="#667eea" stroke-width="1.5" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><rect x="3" y="10" width="2" height="4" rx="1" fill="white"/><rect x="19" y="10" width="2" height="4" rx="1" fill="white"/></svg></span>高流失风险,建议尽快联系</span>
|
||||
</div>
|
||||
<div class="demo-row">
|
||||
<span class="label">靛</span>
|
||||
<span class="sample-text"><span class="ai-inline-icon ai-color-indigo"><svg viewBox="0 0 24 24" fill="none"><rect x="5" y="7" width="14" height="12" rx="4" fill="white"/><path d="M12 7V4" stroke="white" stroke-width="2" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="white"/><circle cx="9" cy="11.5" r="2" fill="#667eea"/><circle cx="15" cy="11.5" r="2" fill="#667eea"/><circle cx="9.5" cy="11" r="0.7" fill="white"/><circle cx="15.5" cy="11" r="0.7" fill="white"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="#667eea" stroke-width="1.5" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><rect x="3" y="10" width="2" height="4" rx="1" fill="white"/><rect x="19" y="10" width="2" height="4" rx="1" fill="white"/></svg></span>高流失风险,建议尽快联系</span>
|
||||
</div>
|
||||
<div class="demo-row">
|
||||
<span class="label">紫</span>
|
||||
<span class="sample-text"><span class="ai-inline-icon ai-color-purple"><svg viewBox="0 0 24 24" fill="none"><rect x="5" y="7" width="14" height="12" rx="4" fill="white"/><path d="M12 7V4" stroke="white" stroke-width="2" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="white"/><circle cx="9" cy="11.5" r="2" fill="#667eea"/><circle cx="15" cy="11.5" r="2" fill="#667eea"/><circle cx="9.5" cy="11" r="0.7" fill="white"/><circle cx="15.5" cy="11" r="0.7" fill="white"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="#667eea" stroke-width="1.5" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><rect x="3" y="10" width="2" height="4" rx="1" fill="white"/><rect x="19" y="10" width="2" height="4" rx="1" fill="white"/></svg></span>高流失风险,建议尽快联系</span>
|
||||
</div>
|
||||
<p class="note">每个页面所有嵌入 Icon 统一使用一种配色,刷新后随机分配</p>
|
||||
|
||||
<!-- ===== Title AI 标识 ===== -->
|
||||
<h2>Title AI 标识(标题行右侧 · 机器人 · 浅色背景+边框)</h2>
|
||||
|
||||
<div class="demo-row">
|
||||
<span class="label">红</span>
|
||||
<span class="ai-title-badge ai-color-red"><span class="ai-title-badge-icon"><svg viewBox="0 0 24 24" fill="none"><rect x="5" y="7" width="14" height="12" rx="4" fill="white" stroke="currentColor" stroke-width="0.8"/><path d="M12 7V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="white" stroke="currentColor" stroke-width="0.7"/><circle cx="9" cy="11.5" r="2" fill="#667eea"/><circle cx="15" cy="11.5" r="2" fill="#667eea"/><circle cx="9.5" cy="11" r="0.7" fill="white"/><circle cx="15.5" cy="11" r="0.7" fill="white"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="#667eea" stroke-width="1.5" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><rect x="3" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/><rect x="19" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/></svg></span>AI智能洞察</span>
|
||||
</div>
|
||||
<div class="demo-row">
|
||||
<span class="label">橙</span>
|
||||
<span class="ai-title-badge ai-color-orange"><span class="ai-title-badge-icon"><svg viewBox="0 0 24 24" fill="none"><rect x="5" y="7" width="14" height="12" rx="4" fill="white" stroke="currentColor" stroke-width="0.8"/><path d="M12 7V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="white" stroke="currentColor" stroke-width="0.7"/><circle cx="9" cy="11.5" r="2" fill="#667eea"/><circle cx="15" cy="11.5" r="2" fill="#667eea"/><circle cx="9.5" cy="11" r="0.7" fill="white"/><circle cx="15.5" cy="11" r="0.7" fill="white"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="#667eea" stroke-width="1.5" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><rect x="3" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/><rect x="19" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/></svg></span>AI智能洞察</span>
|
||||
</div>
|
||||
<div class="demo-row">
|
||||
<span class="label">黄</span>
|
||||
<span class="ai-title-badge ai-color-yellow"><span class="ai-title-badge-icon"><svg viewBox="0 0 24 24" fill="none"><rect x="5" y="7" width="14" height="12" rx="4" fill="white" stroke="currentColor" stroke-width="0.8"/><path d="M12 7V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="white" stroke="currentColor" stroke-width="0.7"/><circle cx="9" cy="11.5" r="2" fill="#667eea"/><circle cx="15" cy="11.5" r="2" fill="#667eea"/><circle cx="9.5" cy="11" r="0.7" fill="white"/><circle cx="15.5" cy="11" r="0.7" fill="white"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="#667eea" stroke-width="1.5" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><rect x="3" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/><rect x="19" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/></svg></span>AI智能洞察</span>
|
||||
</div>
|
||||
<div class="demo-row">
|
||||
<span class="label">蓝</span>
|
||||
<span class="ai-title-badge ai-color-blue"><span class="ai-title-badge-icon"><svg viewBox="0 0 24 24" fill="none"><rect x="5" y="7" width="14" height="12" rx="4" fill="white" stroke="currentColor" stroke-width="0.8"/><path d="M12 7V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="white" stroke="currentColor" stroke-width="0.7"/><circle cx="9" cy="11.5" r="2" fill="#667eea"/><circle cx="15" cy="11.5" r="2" fill="#667eea"/><circle cx="9.5" cy="11" r="0.7" fill="white"/><circle cx="15.5" cy="11" r="0.7" fill="white"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="#667eea" stroke-width="1.5" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><rect x="3" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/><rect x="19" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/></svg></span>AI智能洞察</span>
|
||||
</div>
|
||||
<div class="demo-row">
|
||||
<span class="label">靛</span>
|
||||
<span class="ai-title-badge ai-color-indigo"><span class="ai-title-badge-icon"><svg viewBox="0 0 24 24" fill="none"><rect x="5" y="7" width="14" height="12" rx="4" fill="white" stroke="currentColor" stroke-width="0.8"/><path d="M12 7V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="white" stroke="currentColor" stroke-width="0.7"/><circle cx="9" cy="11.5" r="2" fill="#667eea"/><circle cx="15" cy="11.5" r="2" fill="#667eea"/><circle cx="9.5" cy="11" r="0.7" fill="white"/><circle cx="15.5" cy="11" r="0.7" fill="white"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="#667eea" stroke-width="1.5" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><rect x="3" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/><rect x="19" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/></svg></span>AI智能洞察</span>
|
||||
</div>
|
||||
<div class="demo-row">
|
||||
<span class="label">紫</span>
|
||||
<span class="ai-title-badge ai-color-purple"><span class="ai-title-badge-icon"><svg viewBox="0 0 24 24" fill="none"><rect x="5" y="7" width="14" height="12" rx="4" fill="white" stroke="currentColor" stroke-width="0.8"/><path d="M12 7V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="white" stroke="currentColor" stroke-width="0.7"/><circle cx="9" cy="11.5" r="2" fill="#667eea"/><circle cx="15" cy="11.5" r="2" fill="#667eea"/><circle cx="9.5" cy="11" r="0.7" fill="white"/><circle cx="15.5" cy="11" r="0.7" fill="white"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="#667eea" stroke-width="1.5" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><rect x="3" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/><rect x="19" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/></svg></span>AI智能洞察</span>
|
||||
</div>
|
||||
<p class="note">每个页面所有 Title AI 标识统一使用一种配色,刷新后随机分配</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -11,24 +11,24 @@
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: '#333333',
|
||||
'primary-light': '#f5f5f5',
|
||||
success: '#2d2d2d',
|
||||
warning: '#666666',
|
||||
error: '#444444',
|
||||
'gray-1': '#fafafa',
|
||||
'gray-2': '#f5f5f5',
|
||||
'gray-3': '#eeeeee',
|
||||
'gray-4': '#e0e0e0',
|
||||
'gray-5': '#bdbdbd',
|
||||
'gray-6': '#9e9e9e',
|
||||
'gray-7': '#757575',
|
||||
'gray-8': '#616161',
|
||||
'gray-9': '#424242',
|
||||
'gray-10': '#333333',
|
||||
'gray-11': '#212121',
|
||||
'gray-12': '#1a1a1a',
|
||||
'gray-13': '#111111',
|
||||
primary: '#0052d9',
|
||||
'primary-light': '#ecf2fe',
|
||||
success: '#00a870',
|
||||
warning: '#ed7b2f',
|
||||
error: '#e34d59',
|
||||
'gray-1': '#f3f3f3',
|
||||
'gray-2': '#eeeeee',
|
||||
'gray-3': '#e7e7e7',
|
||||
'gray-4': '#dcdcdc',
|
||||
'gray-5': '#c5c5c5',
|
||||
'gray-6': '#a6a6a6',
|
||||
'gray-7': '#8b8b8b',
|
||||
'gray-8': '#777777',
|
||||
'gray-9': '#5e5e5e',
|
||||
'gray-10': '#4b4b4b',
|
||||
'gray-11': '#393939',
|
||||
'gray-12': '#2c2c2c',
|
||||
'gray-13': '#242424',
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['Noto Sans SC', 'sans-serif'],
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link href="../css/banner.css" rel="stylesheet">
|
||||
<link href="../css/ai-icons.css" rel="stylesheet">
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
@@ -84,7 +85,10 @@
|
||||
<div class="p-4 space-y-4">
|
||||
<!-- 消费习惯 -->
|
||||
<div class="bg-white rounded-2xl p-5 shadow-sm">
|
||||
<h2 class="st green text-sm font-semibold text-gray-13 mb-4">消费习惯</h2>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="st green text-sm font-semibold text-gray-13">消费习惯</h2>
|
||||
<span class="ai-title-badge ai-color-red"><span class="ai-title-badge-icon"><svg viewBox="0 0 24 24" fill="none"><rect x="5" y="7" width="14" height="12" rx="4" fill="white" stroke="currentColor" stroke-width="0.8"/><path d="M12 7V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="white" stroke="currentColor" stroke-width="0.7"/><circle cx="9" cy="11.5" r="2" fill="#667eea"/><circle cx="15" cy="11.5" r="2" fill="#667eea"/><circle cx="9.5" cy="11" r="0.7" fill="white"/><circle cx="15.5" cy="11" r="0.7" fill="white"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="#667eea" stroke-width="1.5" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><rect x="3" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/><rect x="19" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/></svg></span>AI智能洞察</span>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2 mb-3">
|
||||
<span class="px-3 py-1.5 bg-gradient-to-r from-blue-50 to-indigo-50 text-primary text-xs rounded-full border border-blue-100">🌙 常来夜场</span>
|
||||
<span class="px-3 py-1.5 bg-gradient-to-r from-green-50 to-emerald-50 text-success text-xs rounded-full border border-green-100">🎱 偏爱中式</span>
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "../../../../dev/LLTQ/ETL/feiqiu-ETL"
|
||||
},
|
||||
{
|
||||
"path": "../.."
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"liveServer.settings.multiRootWorkspaceName": "LLZQ-1"
|
||||
}
|
||||
}
|
||||
@@ -100,21 +100,6 @@
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<a href="home-settings.html" class="flex items-center justify-between bg-white px-4 py-4 border-b border-gray-1" style="display:none">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-8 h-8 bg-warning/10 rounded-lg flex items-center justify-center">
|
||||
<svg class="w-5 h-5 text-warning" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="3"/>
|
||||
<path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-2 2 2 2 0 01-2-2v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 01-2-2 2 2 0 012-2h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 010-2.83 2 2 0 012.83 0l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0 012-2 2 2 0 012 2v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 0 2 2 0 010 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 012 2 2 2 0 01-2 2h-.09a1.65 1.65 0 00-1.51 1z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<span class="text-sm text-gray-13">首页设置</span>
|
||||
</div>
|
||||
<svg class="w-4 h-4 text-gray-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="9 18 15 12 9 6"/>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<button onclick="showLogoutModal()" class="w-full flex items-center justify-between bg-white px-4 py-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-8 h-8 bg-error/10 rounded-lg flex items-center justify-center">
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link href="../css/banner.css" rel="stylesheet">
|
||||
<link href="../css/task-detail.css" rel="stylesheet">
|
||||
<link href="../css/ai-icons.css" rel="stylesheet">
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
@@ -78,7 +79,10 @@
|
||||
<div class="p-4 space-y-4">
|
||||
<!-- 消费习惯 -->
|
||||
<div class="bg-white rounded-2xl p-5 shadow-sm">
|
||||
<h2 class="section-title green text-sm font-semibold text-gray-13 mb-4">消费习惯</h2>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="section-title green text-sm font-semibold text-gray-13">消费习惯</h2>
|
||||
<span class="ai-title-badge ai-color-blue"><span class="ai-title-badge-icon"><svg viewBox="0 0 24 24" fill="none"><rect x="5" y="7" width="14" height="12" rx="4" fill="white" stroke="currentColor" stroke-width="0.8"/><path d="M12 7V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="white" stroke="currentColor" stroke-width="0.7"/><circle cx="9" cy="11.5" r="2" fill="#667eea"/><circle cx="15" cy="11.5" r="2" fill="#667eea"/><circle cx="9.5" cy="11" r="0.7" fill="white"/><circle cx="15.5" cy="11" r="0.7" fill="white"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="#667eea" stroke-width="1.5" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><rect x="3" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/><rect x="19" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/></svg></span>AI智能洞察</span>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2 mb-3">
|
||||
<span class="px-3 py-1.5 bg-gradient-to-r from-blue-50 to-indigo-50 text-primary text-xs rounded-full border border-blue-100">🎱 斯诺克爱好者</span>
|
||||
<span class="px-3 py-1.5 bg-gradient-to-r from-green-50 to-emerald-50 text-success text-xs rounded-full border border-green-100">⭐ 高满意度</span>
|
||||
@@ -91,7 +95,10 @@
|
||||
|
||||
<!-- 与我的关系 -->
|
||||
<div class="bg-white rounded-2xl p-5 shadow-sm">
|
||||
<h2 class="section-title blue text-sm font-semibold text-gray-13 mb-4">与我的关系</h2>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="section-title blue text-sm font-semibold text-gray-13">与我的关系</h2>
|
||||
<span class="ai-title-badge ai-color-blue"><span class="ai-title-badge-icon"><svg viewBox="0 0 24 24" fill="none"><rect x="5" y="7" width="14" height="12" rx="4" fill="white" stroke="currentColor" stroke-width="0.8"/><path d="M12 7V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="white" stroke="currentColor" stroke-width="0.7"/><circle cx="9" cy="11.5" r="2" fill="#667eea"/><circle cx="15" cy="11.5" r="2" fill="#667eea"/><circle cx="9.5" cy="11" r="0.7" fill="white"/><circle cx="15.5" cy="11" r="0.7" fill="white"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="#667eea" stroke-width="1.5" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><rect x="3" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/><rect x="19" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/></svg></span>AI智能洞察</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-4 mb-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="px-4 py-2 bg-gradient-to-r from-pink-500 to-rose-500 text-white text-sm font-semibold rounded-xl shadow-sm">💖 非常好</span>
|
||||
@@ -164,7 +171,10 @@
|
||||
<h2 class="section-title purple text-sm font-semibold text-gray-13 mb-4">任务建议</h2>
|
||||
<div class="bg-gradient-to-br from-blue-50 to-indigo-50 rounded-xl p-4 border border-blue-100">
|
||||
<p class="text-sm text-primary leading-relaxed font-medium mb-3">
|
||||
📞 常规回访要点
|
||||
<span class="flex items-center justify-between">
|
||||
<span>📞 常规回访要点</span>
|
||||
<span class="ai-title-badge ai-color-blue"><span class="ai-title-badge-icon"><svg viewBox="0 0 24 24" fill="none"><rect x="5" y="7" width="14" height="12" rx="4" fill="white" stroke="currentColor" stroke-width="0.8"/><path d="M12 7V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="white" stroke="currentColor" stroke-width="0.7"/><circle cx="9" cy="11.5" r="2" fill="#667eea"/><circle cx="15" cy="11.5" r="2" fill="#667eea"/><circle cx="9.5" cy="11" r="0.7" fill="white"/><circle cx="15.5" cy="11" r="0.7" fill="white"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="#667eea" stroke-width="1.5" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><rect x="3" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/><rect x="19" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/></svg></span>AI智能洞察</span>
|
||||
</span>
|
||||
</p>
|
||||
<p class="text-sm text-gray-9 leading-relaxed mb-2">
|
||||
该客户上次到店是 3 天前,关系良好,进行常规关怀回访:
|
||||
@@ -176,10 +186,27 @@
|
||||
</ul>
|
||||
</div>
|
||||
<div class="mt-4 p-4 bg-gray-50 rounded-xl border border-gray-100">
|
||||
<p class="text-sm text-gray-9 leading-relaxed">
|
||||
<span class="font-medium text-gray-13">💬 话术参考:</span><br/>
|
||||
"赵姐您好!上次打球感觉怎么样?新到的球杆手感还习惯吗?这周末您有空的话,可以提前帮您预留老位置~"
|
||||
</p>
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<span class="font-medium text-gray-13 text-sm">💬 话术参考</span>
|
||||
<span class="ai-title-badge ai-color-blue"><span class="ai-title-badge-icon"><svg viewBox="0 0 24 24" fill="none"><rect x="5" y="7" width="14" height="12" rx="4" fill="white" stroke="currentColor" stroke-width="0.8"/><path d="M12 7V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="white" stroke="currentColor" stroke-width="0.7"/><circle cx="9" cy="11.5" r="2" fill="#667eea"/><circle cx="15" cy="11.5" r="2" fill="#667eea"/><circle cx="9.5" cy="11" r="0.7" fill="white"/><circle cx="15.5" cy="11" r="0.7" fill="white"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="#667eea" stroke-width="1.5" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><rect x="3" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/><rect x="19" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/></svg></span>AI智能洞察</span>
|
||||
</div>
|
||||
<div class="space-y-3">
|
||||
<p class="text-sm text-gray-9 leading-relaxed pl-3 border-l-2 border-primary/30">
|
||||
"赵姐您好!上次打球感觉怎么样?新到的球杆手感还习惯吗?这周末您有空的话,可以提前帮您预留老位置~"
|
||||
</p>
|
||||
<p class="text-sm text-gray-9 leading-relaxed pl-3 border-l-2 border-primary/30">
|
||||
"赵姐,最近店里新进了一批斯诺克专用巧克粉,手感特别好,下次来的时候可以试试~"
|
||||
</p>
|
||||
<p class="text-sm text-gray-9 leading-relaxed pl-3 border-l-2 border-primary/30">
|
||||
"赵姐好呀,上次您说想学几个高级杆法,我最近整理了一些教学视频,要不要发给您先看看?"
|
||||
</p>
|
||||
<p class="text-sm text-gray-9 leading-relaxed pl-3 border-l-2 border-primary/30">
|
||||
"赵姐,这周六下午VIP包厢有空位,要不要帮您提前预留?可以叫上朋友一起来打球~"
|
||||
</p>
|
||||
<p class="text-sm text-gray-9 leading-relaxed pl-3 border-l-2 border-primary/30">
|
||||
"赵姐您好,我们下个月有个会员积分兑换活动,您的积分可以换不少好东西,到时候提醒您哦~"
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -190,20 +217,22 @@
|
||||
<span class="text-xs text-gray-6">2 条备注</span>
|
||||
</div>
|
||||
<div id="noteList" class="space-y-3">
|
||||
<div class="flex items-start gap-3 p-3.5 bg-gray-50 rounded-xl border border-gray-100">
|
||||
<div class="note-card-wrap flex items-start gap-3 p-3.5 bg-gray-50 rounded-xl border border-gray-100">
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-xs text-gray-6 mb-1.5">2026-02-07</p>
|
||||
<p class="text-sm text-gray-9 leading-relaxed">赵姐反馈上次体验很满意,新球杆手感不错,希望下次能预留VIP包厢。</p>
|
||||
</div>
|
||||
<div class="star-rating" data-score="9"><span class="star"><svg class="star-empty" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg><span class="star-fill" style="width:100%"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg></span></span><span class="star"><svg class="star-empty" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg><span class="star-fill" style="width:100%"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg></span></span><span class="star"><svg class="star-empty" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg><span class="star-fill" style="width:100%"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg></span></span><span class="star"><svg class="star-empty" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg><span class="star-fill" style="width:100%"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg></span></span><span class="star"><svg class="star-empty" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg><span class="star-fill" style="width:50%"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg></span></span></div>
|
||||
<button onclick="confirmDeleteNote()" class="shrink-0 w-7 h-7 flex items-center justify-center rounded-lg bg-white border border-gray-200 text-gray-5">
|
||||
<svg class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex items-start gap-3 p-3.5 bg-gray-50 rounded-xl border border-gray-100">
|
||||
<div class="note-card-wrap flex items-start gap-3 p-3.5 bg-gray-50 rounded-xl border border-gray-100">
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-xs text-gray-6 mb-1.5">2026-01-25</p>
|
||||
<p class="text-sm text-gray-9 leading-relaxed">已预约本周六下午到店,需要提前安排靠窗位置。</p>
|
||||
</div>
|
||||
<div class="star-rating" data-score="9"><span class="star"><svg class="star-empty" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg><span class="star-fill" style="width:100%"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg></span></span><span class="star"><svg class="star-empty" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg><span class="star-fill" style="width:100%"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg></span></span><span class="star"><svg class="star-empty" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg><span class="star-fill" style="width:100%"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg></span></span><span class="star"><svg class="star-empty" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg><span class="star-fill" style="width:100%"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg></span></span><span class="star"><svg class="star-empty" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg><span class="star-fill" style="width:50%"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg></span></span></div>
|
||||
<button onclick="confirmDeleteNote()" class="shrink-0 w-7 h-7 flex items-center justify-center rounded-lg bg-white border border-gray-200 text-gray-5">
|
||||
<svg class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/></svg>
|
||||
</button>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link href="../css/banner.css" rel="stylesheet">
|
||||
<link href="../css/task-detail.css" rel="stylesheet">
|
||||
<link href="../css/ai-icons.css" rel="stylesheet">
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
@@ -78,7 +79,10 @@
|
||||
<div class="p-4 space-y-4">
|
||||
<!-- 消费习惯 -->
|
||||
<div class="bg-white rounded-2xl p-5 shadow-sm">
|
||||
<h2 class="section-title green text-sm font-semibold text-gray-13 mb-4">消费习惯</h2>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="section-title green text-sm font-semibold text-gray-13">消费习惯</h2>
|
||||
<span class="ai-title-badge ai-color-orange"><span class="ai-title-badge-icon"><svg viewBox="0 0 24 24" fill="none"><rect x="5" y="7" width="14" height="12" rx="4" fill="white" stroke="currentColor" stroke-width="0.8"/><path d="M12 7V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="white" stroke="currentColor" stroke-width="0.7"/><circle cx="9" cy="11.5" r="2" fill="#667eea"/><circle cx="15" cy="11.5" r="2" fill="#667eea"/><circle cx="9.5" cy="11" r="0.7" fill="white"/><circle cx="15.5" cy="11" r="0.7" fill="white"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="#667eea" stroke-width="1.5" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><rect x="3" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/><rect x="19" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/></svg></span>AI智能洞察</span>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2 mb-3">
|
||||
<span class="px-3 py-1.5 bg-gradient-to-r from-blue-50 to-indigo-50 text-primary text-xs rounded-full border border-blue-100">🌙 偏好夜场</span>
|
||||
<span class="px-3 py-1.5 bg-gradient-to-r from-green-50 to-emerald-50 text-success text-xs rounded-full border border-green-100">🎱 中式八球</span>
|
||||
@@ -91,7 +95,10 @@
|
||||
|
||||
<!-- 与我的关系 -->
|
||||
<div class="bg-white rounded-2xl p-5 shadow-sm">
|
||||
<h2 class="section-title blue text-sm font-semibold text-gray-13 mb-4">与我的关系</h2>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="section-title blue text-sm font-semibold text-gray-13">与我的关系</h2>
|
||||
<span class="ai-title-badge ai-color-orange"><span class="ai-title-badge-icon"><svg viewBox="0 0 24 24" fill="none"><rect x="5" y="7" width="14" height="12" rx="4" fill="white" stroke="currentColor" stroke-width="0.8"/><path d="M12 7V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="white" stroke="currentColor" stroke-width="0.7"/><circle cx="9" cy="11.5" r="2" fill="#667eea"/><circle cx="15" cy="11.5" r="2" fill="#667eea"/><circle cx="9.5" cy="11" r="0.7" fill="white"/><circle cx="15.5" cy="11" r="0.7" fill="white"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="#667eea" stroke-width="1.5" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><rect x="3" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/><rect x="19" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/></svg></span>AI智能洞察</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-4 mb-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="px-4 py-2 bg-gradient-to-r from-amber-400 to-yellow-500 text-white text-sm font-semibold rounded-xl shadow-sm">💛 一般</span>
|
||||
@@ -164,7 +171,10 @@
|
||||
<h2 class="section-title orange text-sm font-semibold text-gray-13 mb-4">任务建议</h2>
|
||||
<div class="bg-gradient-to-br from-orange-50 to-amber-50 rounded-xl p-4 border border-orange-100">
|
||||
<p class="text-sm text-warning leading-relaxed font-medium mb-3">
|
||||
💡 建议执行
|
||||
<span class="flex items-center justify-between">
|
||||
<span>💡 建议执行</span>
|
||||
<span class="ai-title-badge ai-color-orange"><span class="ai-title-badge-icon"><svg viewBox="0 0 24 24" fill="none"><rect x="5" y="7" width="14" height="12" rx="4" fill="white" stroke="currentColor" stroke-width="0.8"/><path d="M12 7V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="white" stroke="currentColor" stroke-width="0.7"/><circle cx="9" cy="11.5" r="2" fill="#667eea"/><circle cx="15" cy="11.5" r="2" fill="#667eea"/><circle cx="9.5" cy="11" r="0.7" fill="white"/><circle cx="15.5" cy="11" r="0.7" fill="white"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="#667eea" stroke-width="1.5" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><rect x="3" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/><rect x="19" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/></svg></span>AI智能洞察</span>
|
||||
</span>
|
||||
</p>
|
||||
<p class="text-sm text-gray-9 leading-relaxed mb-2">
|
||||
该客户消费频率从月均 4 次下降到近月仅 1 次,需要关注原因:
|
||||
@@ -176,10 +186,27 @@
|
||||
</ul>
|
||||
</div>
|
||||
<div class="mt-4 p-4 bg-gray-50 rounded-xl border border-gray-100">
|
||||
<p class="text-sm text-gray-9 leading-relaxed">
|
||||
<span class="font-medium text-gray-13">💬 话术参考:</span><br/>
|
||||
"张哥,好久没见您来打球了,最近忙吗?店里这周六有个球友聚会活动,想邀请您来玩,顺便认识一些新球友~"
|
||||
</p>
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<span class="font-medium text-gray-13 text-sm">💬 话术参考</span>
|
||||
<span class="ai-title-badge ai-color-orange"><span class="ai-title-badge-icon"><svg viewBox="0 0 24 24" fill="none"><rect x="5" y="7" width="14" height="12" rx="4" fill="white" stroke="currentColor" stroke-width="0.8"/><path d="M12 7V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="white" stroke="currentColor" stroke-width="0.7"/><circle cx="9" cy="11.5" r="2" fill="#667eea"/><circle cx="15" cy="11.5" r="2" fill="#667eea"/><circle cx="9.5" cy="11" r="0.7" fill="white"/><circle cx="15.5" cy="11" r="0.7" fill="white"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="#667eea" stroke-width="1.5" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><rect x="3" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/><rect x="19" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/></svg></span>AI智能洞察</span>
|
||||
</div>
|
||||
<div class="space-y-3">
|
||||
<p class="text-sm text-gray-9 leading-relaxed pl-3 border-l-2 border-primary/30">
|
||||
"张哥,好久没见您来打球了,最近忙吗?店里这周六有个球友聚会活动,想邀请您来玩,顺便认识一些新球友~"
|
||||
</p>
|
||||
<p class="text-sm text-gray-9 leading-relaxed pl-3 border-l-2 border-primary/30">
|
||||
"张哥好呀,最近工作还顺利吧?周末有空的话过来放松一下,我帮您约几个水平差不多的球友一起切磋~"
|
||||
</p>
|
||||
<p class="text-sm text-gray-9 leading-relaxed pl-3 border-l-2 border-primary/30">
|
||||
"张哥,店里最近新上了几款精酿啤酒,打完球来一杯特别爽,周末要不要来试试?"
|
||||
</p>
|
||||
<p class="text-sm text-gray-9 leading-relaxed pl-3 border-l-2 border-primary/30">
|
||||
"张哥,上次您说想练练组合球,我最近研究了几个不错的训练方法,下次来的时候教您~"
|
||||
</p>
|
||||
<p class="text-sm text-gray-9 leading-relaxed pl-3 border-l-2 border-primary/30">
|
||||
"张哥您好,这个月会员充值有额外赠送活动,力度挺大的,要不要了解一下?"
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -190,20 +217,22 @@
|
||||
<span class="text-xs text-gray-6">2 条备注</span>
|
||||
</div>
|
||||
<div id="noteList" class="space-y-3">
|
||||
<div class="flex items-start gap-3 p-3.5 bg-gray-50 rounded-xl border border-gray-100">
|
||||
<div class="note-card-wrap flex items-start gap-3 p-3.5 bg-gray-50 rounded-xl border border-gray-100">
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-xs text-gray-6 mb-1.5">2026-02-03</p>
|
||||
<p class="text-sm text-gray-9 leading-relaxed">张先生说最近换了工作,下班时间不固定,周末可能更方便。</p>
|
||||
</div>
|
||||
<div class="star-rating" data-score="5"><span class="star"><svg class="star-empty" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg><span class="star-fill" style="width:100%"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg></span></span><span class="star"><svg class="star-empty" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg><span class="star-fill" style="width:100%"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg></span></span><span class="star"><svg class="star-empty" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg><span class="star-fill" style="width:50%"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg></span></span><span class="star"><svg class="star-empty" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg><span class="star-fill" style="width:0%"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg></span></span><span class="star"><svg class="star-empty" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg><span class="star-fill" style="width:0%"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg></span></span></div>
|
||||
<button onclick="confirmDeleteNote()" class="shrink-0 w-7 h-7 flex items-center justify-center rounded-lg bg-white border border-gray-200 text-gray-5">
|
||||
<svg class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex items-start gap-3 p-3.5 bg-gray-50 rounded-xl border border-gray-100">
|
||||
<div class="note-card-wrap flex items-start gap-3 p-3.5 bg-gray-50 rounded-xl border border-gray-100">
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-xs text-gray-6 mb-1.5">2026-01-15</p>
|
||||
<p class="text-sm text-gray-9 leading-relaxed">推荐了周末球友聚会活动,客户表示有兴趣但还没确认。</p>
|
||||
</div>
|
||||
<div class="star-rating" data-score="5"><span class="star"><svg class="star-empty" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg><span class="star-fill" style="width:100%"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg></span></span><span class="star"><svg class="star-empty" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg><span class="star-fill" style="width:100%"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg></span></span><span class="star"><svg class="star-empty" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg><span class="star-fill" style="width:50%"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg></span></span><span class="star"><svg class="star-empty" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg><span class="star-fill" style="width:0%"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg></span></span><span class="star"><svg class="star-empty" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg><span class="star-fill" style="width:0%"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg></span></span></div>
|
||||
<button onclick="confirmDeleteNote()" class="shrink-0 w-7 h-7 flex items-center justify-center rounded-lg bg-white border border-gray-200 text-gray-5">
|
||||
<svg class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/></svg>
|
||||
</button>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link href="../css/banner.css" rel="stylesheet">
|
||||
<link href="../css/task-detail.css" rel="stylesheet">
|
||||
<link href="../css/ai-icons.css" rel="stylesheet">
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
@@ -78,7 +79,10 @@
|
||||
<div class="p-4 space-y-4">
|
||||
<!-- 消费习惯 -->
|
||||
<div class="bg-white rounded-2xl p-5 shadow-sm">
|
||||
<h2 class="section-title green text-sm font-semibold text-gray-13 mb-4">消费习惯</h2>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="section-title green text-sm font-semibold text-gray-13">消费习惯</h2>
|
||||
<span class="ai-title-badge ai-color-purple"><span class="ai-title-badge-icon"><svg viewBox="0 0 24 24" fill="none"><rect x="5" y="7" width="14" height="12" rx="4" fill="white" stroke="currentColor" stroke-width="0.8"/><path d="M12 7V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="white" stroke="currentColor" stroke-width="0.7"/><circle cx="9" cy="11.5" r="2" fill="#667eea"/><circle cx="15" cy="11.5" r="2" fill="#667eea"/><circle cx="9.5" cy="11" r="0.7" fill="white"/><circle cx="15.5" cy="11" r="0.7" fill="white"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="#667eea" stroke-width="1.5" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><rect x="3" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/><rect x="19" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/></svg></span>AI智能洞察</span>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2 mb-3">
|
||||
<span class="px-3 py-1.5 bg-gradient-to-r from-amber-50 to-yellow-50 text-amber-600 text-xs rounded-full border border-amber-100">☀️ 偏好下午</span>
|
||||
<span class="px-3 py-1.5 bg-gradient-to-r from-green-50 to-emerald-50 text-success text-xs rounded-full border border-green-100">🎱 初学者</span>
|
||||
@@ -91,7 +95,10 @@
|
||||
|
||||
<!-- 与我的关系 -->
|
||||
<div class="bg-white rounded-2xl p-5 shadow-sm">
|
||||
<h2 class="section-title blue text-sm font-semibold text-gray-13 mb-4">与我的关系</h2>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="section-title blue text-sm font-semibold text-gray-13">与我的关系</h2>
|
||||
<span class="ai-title-badge ai-color-purple"><span class="ai-title-badge-icon"><svg viewBox="0 0 24 24" fill="none"><rect x="5" y="7" width="14" height="12" rx="4" fill="white" stroke="currentColor" stroke-width="0.8"/><path d="M12 7V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="white" stroke="currentColor" stroke-width="0.7"/><circle cx="9" cy="11.5" r="2" fill="#667eea"/><circle cx="15" cy="11.5" r="2" fill="#667eea"/><circle cx="9.5" cy="11" r="0.7" fill="white"/><circle cx="15.5" cy="11" r="0.7" fill="white"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="#667eea" stroke-width="1.5" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><rect x="3" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/><rect x="19" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/></svg></span>AI智能洞察</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-4 mb-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="px-4 py-2 bg-gradient-to-r from-blue-500 to-cyan-500 text-white text-sm font-semibold rounded-xl shadow-sm">💙 待发展</span>
|
||||
@@ -164,7 +171,10 @@
|
||||
<h2 class="section-title pink text-sm font-semibold text-gray-13 mb-4">任务建议</h2>
|
||||
<div class="bg-gradient-to-br from-pink-50 to-rose-50 rounded-xl p-4 border border-pink-100">
|
||||
<p class="text-sm text-pink-600 leading-relaxed font-medium mb-3">
|
||||
💝 关系构建重点
|
||||
<span class="flex items-center justify-between">
|
||||
<span>💝 关系构建重点</span>
|
||||
<span class="ai-title-badge ai-color-purple"><span class="ai-title-badge-icon"><svg viewBox="0 0 24 24" fill="none"><rect x="5" y="7" width="14" height="12" rx="4" fill="white" stroke="currentColor" stroke-width="0.8"/><path d="M12 7V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="white" stroke="currentColor" stroke-width="0.7"/><circle cx="9" cy="11.5" r="2" fill="#667eea"/><circle cx="15" cy="11.5" r="2" fill="#667eea"/><circle cx="9.5" cy="11" r="0.7" fill="white"/><circle cx="15.5" cy="11" r="0.7" fill="white"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="#667eea" stroke-width="1.5" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><rect x="3" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/><rect x="19" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/></svg></span>AI智能洞察</span>
|
||||
</span>
|
||||
</p>
|
||||
<p class="text-sm text-gray-9 leading-relaxed mb-2">
|
||||
该客户消费潜力大但关系指数较低,建议重点培养:
|
||||
@@ -176,10 +186,27 @@
|
||||
</ul>
|
||||
</div>
|
||||
<div class="mt-4 p-4 bg-gray-50 rounded-xl border border-gray-100">
|
||||
<p class="text-sm text-gray-9 leading-relaxed">
|
||||
<span class="font-medium text-gray-13">💬 话术参考:</span><br/>
|
||||
"陈哥您好,上次看您打球进步很快呀!我们这周有个初学者交流会,可以认识一些同水平的球友一起练习,您有兴趣参加吗?"
|
||||
</p>
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<span class="font-medium text-gray-13 text-sm">💬 话术参考</span>
|
||||
<span class="ai-title-badge ai-color-purple"><span class="ai-title-badge-icon"><svg viewBox="0 0 24 24" fill="none"><rect x="5" y="7" width="14" height="12" rx="4" fill="white" stroke="currentColor" stroke-width="0.8"/><path d="M12 7V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="white" stroke="currentColor" stroke-width="0.7"/><circle cx="9" cy="11.5" r="2" fill="#667eea"/><circle cx="15" cy="11.5" r="2" fill="#667eea"/><circle cx="9.5" cy="11" r="0.7" fill="white"/><circle cx="15.5" cy="11" r="0.7" fill="white"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="#667eea" stroke-width="1.5" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><rect x="3" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/><rect x="19" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/></svg></span>AI智能洞察</span>
|
||||
</div>
|
||||
<div class="space-y-3">
|
||||
<p class="text-sm text-gray-9 leading-relaxed pl-3 border-l-2 border-primary/30">
|
||||
"陈哥您好,上次看您打球进步很快呀!我们这周有个初学者交流会,可以认识一些同水平的球友一起练习,您有兴趣参加吗?"
|
||||
</p>
|
||||
<p class="text-sm text-gray-9 leading-relaxed pl-3 border-l-2 border-primary/30">
|
||||
"陈哥好呀,上次教您的那个发力技巧练得怎么样了?下次来的时候我再帮您看看,争取早日突破~"
|
||||
</p>
|
||||
<p class="text-sm text-gray-9 leading-relaxed pl-3 border-l-2 border-primary/30">
|
||||
"陈哥,我们店里新推出了一个入门课程套餐,专门针对想快速提升的球友,性价比很高,要不要了解一下?"
|
||||
</p>
|
||||
<p class="text-sm text-gray-9 leading-relaxed pl-3 border-l-2 border-primary/30">
|
||||
"陈哥您好,这周六下午有几位球友约了练习赛,水平都差不多,要不要一起来切磋切磋?"
|
||||
</p>
|
||||
<p class="text-sm text-gray-9 leading-relaxed pl-3 border-l-2 border-primary/30">
|
||||
"陈哥,最近天气不错,下午来打打球放松一下吧,我帮您留个好位置~"
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link href="../css/banner.css" rel="stylesheet">
|
||||
<link href="../css/task-detail.css" rel="stylesheet">
|
||||
<link href="../css/ai-icons.css" rel="stylesheet">
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
@@ -39,6 +40,32 @@
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
/* 话术气泡 */
|
||||
.speech-bubble {
|
||||
position: relative;
|
||||
background: #eef2ff;
|
||||
border: 1px solid #b4c0f0;
|
||||
border-radius: 14px;
|
||||
padding: 12px 16px;
|
||||
font-size: 14px;
|
||||
line-height: 1.7;
|
||||
color: #5e5e5e;
|
||||
}
|
||||
/* 引出角:底部偏右,用旋转正方形模拟 */
|
||||
.speech-bubble::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -7px;
|
||||
right: 20px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: #eef2ff;
|
||||
border-right: 1px solid #b4c0f0;
|
||||
border-bottom: 1px solid #b4c0f0;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-1 min-h-screen">
|
||||
<!-- 通栏 Banner - 客户信息 -->
|
||||
@@ -79,7 +106,10 @@
|
||||
<div class="p-4 space-y-4">
|
||||
<!-- 消费习惯 -->
|
||||
<div class="bg-white rounded-2xl p-5 shadow-sm">
|
||||
<h2 class="section-title green text-sm font-semibold text-gray-13 mb-4">消费习惯</h2>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="section-title green text-sm font-semibold text-gray-13">消费习惯</h2>
|
||||
<span class="ai-title-badge ai-color-indigo"><span class="ai-title-badge-icon"><svg viewBox="0 0 24 24" fill="none"><rect x="5" y="7" width="14" height="12" rx="4" fill="white" stroke="currentColor" stroke-width="0.8"/><path d="M12 7V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="white" stroke="currentColor" stroke-width="0.7"/><circle cx="9" cy="11.5" r="2" fill="#667eea"/><circle cx="15" cy="11.5" r="2" fill="#667eea"/><circle cx="9.5" cy="11" r="0.7" fill="white"/><circle cx="15.5" cy="11" r="0.7" fill="white"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="#667eea" stroke-width="1.5" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><rect x="3" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/><rect x="19" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/></svg></span>AI智能洞察</span>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2 mb-3">
|
||||
<span class="px-3 py-1.5 bg-gradient-to-r from-blue-50 to-indigo-50 text-primary text-xs rounded-full border border-blue-100">🌙 常来夜场</span>
|
||||
<span class="px-3 py-1.5 bg-gradient-to-r from-green-50 to-emerald-50 text-success text-xs rounded-full border border-green-100">🎱 偏爱中式</span>
|
||||
@@ -92,7 +122,10 @@
|
||||
|
||||
<!-- 与我的关系 -->
|
||||
<div class="bg-white rounded-2xl p-5 shadow-sm">
|
||||
<h2 class="section-title blue text-sm font-semibold text-gray-13 mb-4">与我的关系</h2>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="section-title blue text-sm font-semibold text-gray-13">与我的关系</h2>
|
||||
<span class="ai-title-badge ai-color-indigo"><span class="ai-title-badge-icon"><svg viewBox="0 0 24 24" fill="none"><rect x="5" y="7" width="14" height="12" rx="4" fill="white" stroke="currentColor" stroke-width="0.8"/><path d="M12 7V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="white" stroke="currentColor" stroke-width="0.7"/><circle cx="9" cy="11.5" r="2" fill="#667eea"/><circle cx="15" cy="11.5" r="2" fill="#667eea"/><circle cx="9.5" cy="11" r="0.7" fill="white"/><circle cx="15.5" cy="11" r="0.7" fill="white"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="#667eea" stroke-width="1.5" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><rect x="3" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/><rect x="19" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/></svg></span>AI智能洞察</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-4 mb-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="px-4 py-2 bg-gradient-to-r from-pink-500 to-rose-500 text-white text-sm font-semibold rounded-xl shadow-sm">💖 非常好</span>
|
||||
@@ -165,7 +198,10 @@
|
||||
<h2 class="section-title orange text-sm font-semibold text-gray-13 mb-4">任务建议</h2>
|
||||
<div class="bg-gradient-to-br from-blue-50 to-indigo-50 rounded-xl p-4 border border-blue-100">
|
||||
<p class="text-sm text-primary leading-relaxed font-medium mb-3">
|
||||
💡 建议执行
|
||||
<span class="flex items-center justify-between">
|
||||
<span>💡 建议执行</span>
|
||||
<span class="ai-title-badge ai-color-indigo"><span class="ai-title-badge-icon"><svg viewBox="0 0 24 24" fill="none"><rect x="5" y="7" width="14" height="12" rx="4" fill="white" stroke="currentColor" stroke-width="0.8"/><path d="M12 7V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="white" stroke="currentColor" stroke-width="0.7"/><circle cx="9" cy="11.5" r="2" fill="#667eea"/><circle cx="15" cy="11.5" r="2" fill="#667eea"/><circle cx="9.5" cy="11" r="0.7" fill="white"/><circle cx="15.5" cy="11" r="0.7" fill="white"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="#667eea" stroke-width="1.5" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><rect x="3" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/><rect x="19" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/></svg></span>AI智能洞察</span>
|
||||
</span>
|
||||
</p>
|
||||
<p class="text-sm text-gray-9 leading-relaxed mb-2">
|
||||
该客户已有 15 天未到店,存在流失风险。建议通过微信联系:
|
||||
@@ -176,11 +212,18 @@
|
||||
<li>根据其偏好时段(晚间)推荐合适的时间</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="mt-4 p-4 bg-gray-50 rounded-xl border border-gray-100">
|
||||
<p class="text-sm text-gray-9 leading-relaxed">
|
||||
<span class="font-medium text-gray-13">💬 话术参考:</span><br/>
|
||||
"王哥您好,好久不见!最近店里新到了几张国际标准的斯诺克球桌,知道您是斯诺克爱好者,想邀请您有空来体验一下~"
|
||||
</p>
|
||||
<div class="mt-4">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<span class="font-medium text-gray-13 text-sm">💬 话术参考</span>
|
||||
<span class="ai-title-badge ai-color-indigo"><span class="ai-title-badge-icon"><svg viewBox="0 0 24 24" fill="none"><rect x="5" y="7" width="14" height="12" rx="4" fill="white" stroke="currentColor" stroke-width="0.8"/><path d="M12 7V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="white" stroke="currentColor" stroke-width="0.7"/><circle cx="9" cy="11.5" r="2" fill="#667eea"/><circle cx="15" cy="11.5" r="2" fill="#667eea"/><circle cx="9.5" cy="11" r="0.7" fill="white"/><circle cx="15.5" cy="11" r="0.7" fill="white"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="#667eea" stroke-width="1.5" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.6"/><rect x="3" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/><rect x="19" y="10" width="2" height="4" rx="1" fill="white" stroke="currentColor" stroke-width="0.7"/></svg></span>AI智能洞察</span>
|
||||
</div>
|
||||
<div class="flex flex-col gap-5">
|
||||
<div class="speech-bubble">王哥您好,好久不见!最近店里新到了几张国际标准的斯诺克球桌,知道您是斯诺克爱好者,想邀请您有空来体验一下~</div>
|
||||
<div class="speech-bubble">王哥,最近忙吗?这周末我们有个老客户专属的球友交流赛,奖品还挺丰富的,您要不要来参加?</div>
|
||||
<div class="speech-bubble">王哥好呀,上次您提到想练练斯诺克的走位,我最近研究了一些新的训练方法,下次来的时候可以一起试试~</div>
|
||||
<div class="speech-bubble">王哥,好久没见您了,您的老位置 A12 号台一直给您留着呢!最近晚上人不多,环境特别好,随时欢迎您来~</div>
|
||||
<div class="speech-bubble">王哥您好,我们这个月推出了储值会员专属的夜场优惠套餐,包含球台+酒水,性价比很高,给您留意着呢~</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -191,32 +234,41 @@
|
||||
<span class="text-xs text-gray-6">3 条备注</span>
|
||||
</div>
|
||||
<div id="noteList" class="space-y-3">
|
||||
<div class="flex items-start gap-3 p-3.5 bg-gray-50 rounded-xl border border-gray-100">
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-xs text-gray-6 mb-1.5">2026-02-05</p>
|
||||
<p class="text-sm text-gray-9 leading-relaxed">已通过微信联系王先生,表示对新到的斯诺克球桌感兴趣,周末可能来体验。</p>
|
||||
<div class="note-card-wrap p-3.5 bg-gray-50 rounded-xl border border-gray-100">
|
||||
<div class="flex items-center justify-between mb-1.5">
|
||||
<p class="text-xs text-gray-6">2026-02-05</p>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="star-rating" data-score="7"><span class="star"><svg class="star-empty" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg><span class="star-fill" style="width:100%"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg></span></span><span class="star"><svg class="star-empty" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg><span class="star-fill" style="width:100%"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg></span></span><span class="star"><svg class="star-empty" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg><span class="star-fill" style="width:100%"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg></span></span><span class="star"><svg class="star-empty" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg><span class="star-fill" style="width:50%"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg></span></span><span class="star"><svg class="star-empty" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg><span class="star-fill" style="width:0%"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg></span></span></div>
|
||||
<button onclick="confirmDeleteNote()" class="shrink-0 w-7 h-7 flex items-center justify-center rounded-lg bg-white border border-gray-200 text-gray-5">
|
||||
<svg class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button onclick="confirmDeleteNote()" class="shrink-0 w-7 h-7 flex items-center justify-center rounded-lg bg-white border border-gray-200 text-gray-5">
|
||||
<svg class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/></svg>
|
||||
</button>
|
||||
<p class="text-sm text-gray-9 leading-relaxed">已通过微信联系王先生,表示对新到的斯诺克球桌感兴趣,周末可能来体验。</p>
|
||||
</div>
|
||||
<div class="flex items-start gap-3 p-3.5 bg-gray-50 rounded-xl border border-gray-100">
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-xs text-gray-6 mb-1.5">2026-01-20</p>
|
||||
<p class="text-sm text-gray-9 leading-relaxed">王先生最近出差较多,到店频率降低。建议等他回来后再约。</p>
|
||||
<div class="note-card-wrap p-3.5 bg-gray-50 rounded-xl border border-gray-100">
|
||||
<div class="flex items-center justify-between mb-1.5">
|
||||
<p class="text-xs text-gray-6">2026-01-20</p>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="star-rating" data-score="7"><span class="star"><svg class="star-empty" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg><span class="star-fill" style="width:100%"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg></span></span><span class="star"><svg class="star-empty" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg><span class="star-fill" style="width:100%"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg></span></span><span class="star"><svg class="star-empty" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg><span class="star-fill" style="width:100%"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg></span></span><span class="star"><svg class="star-empty" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg><span class="star-fill" style="width:50%"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg></span></span><span class="star"><svg class="star-empty" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg><span class="star-fill" style="width:0%"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg></span></span></div>
|
||||
<button onclick="confirmDeleteNote()" class="shrink-0 w-7 h-7 flex items-center justify-center rounded-lg bg-white border border-gray-200 text-gray-5">
|
||||
<svg class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button onclick="confirmDeleteNote()" class="shrink-0 w-7 h-7 flex items-center justify-center rounded-lg bg-white border border-gray-200 text-gray-5">
|
||||
<svg class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/></svg>
|
||||
</button>
|
||||
<p class="text-sm text-gray-9 leading-relaxed">王先生最近出差较多,到店频率降低。建议等他回来后再约。</p>
|
||||
</div>
|
||||
<div class="flex items-start gap-3 p-3.5 bg-gray-50 rounded-xl border border-gray-100">
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-xs text-gray-6 mb-1.5">2026-01-08</p>
|
||||
<p class="text-sm text-gray-9 leading-relaxed">上次到店时推荐了会员续费活动,客户说考虑一下。</p>
|
||||
<div class="note-card-wrap p-3.5 bg-gray-50 rounded-xl border border-gray-100">
|
||||
<div class="flex items-center justify-between mb-1.5">
|
||||
<p class="text-xs text-gray-6">2026-01-08</p>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="star-rating" data-score="7"><span class="star"><svg class="star-empty" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg><span class="star-fill" style="width:100%"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg></span></span><span class="star"><svg class="star-empty" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg><span class="star-fill" style="width:100%"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg></span></span><span class="star"><svg class="star-empty" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg><span class="star-fill" style="width:100%"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg></span></span><span class="star"><svg class="star-empty" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg><span class="star-fill" style="width:50%"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg></span></span><span class="star"><svg class="star-empty" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg><span class="star-fill" style="width:0%"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg></span></span></div>
|
||||
<button onclick="confirmDeleteNote()" class="shrink-0 w-7 h-7 flex items-center justify-center rounded-lg bg-white border border-gray-200 text-gray-5">
|
||||
<svg class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button onclick="confirmDeleteNote()" class="shrink-0 w-7 h-7 flex items-center justify-center rounded-lg bg-white border border-gray-200 text-gray-5">
|
||||
<svg class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/></svg>
|
||||
</button>
|
||||
<p class="text-sm text-gray-9 leading-relaxed">上次到店时推荐了会员续费活动,客户说考虑一下。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="noteEmpty" class="text-center py-6 hidden">
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link href="../css/banner.css" rel="stylesheet">
|
||||
<link href="../css/ai-icons.css" rel="stylesheet">
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
@@ -514,7 +515,7 @@
|
||||
<span class="note-indicator" title="有备注">📝</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-7 leading-relaxed task-desc">最近到店:15天前 · 余额:非常多</p>
|
||||
<p class="text-sm text-gray-6 leading-relaxed task-desc">高流失风险,建议尽快联系</p>
|
||||
<p class="text-sm text-gray-6 leading-relaxed task-desc"><span class="ai-inline-icon"></span>高流失风险,建议尽快联系</p>
|
||||
</div>
|
||||
<svg class="w-5 h-5 text-gray-5 flex-shrink-0 ml-2 mt-1" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="9 18 15 12 9 6"/>
|
||||
@@ -532,7 +533,7 @@
|
||||
<span class="text-sm">🧡</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-7 leading-relaxed task-desc">最近到店:20天前 · 余额:非常多</p>
|
||||
<p class="text-sm text-gray-6 leading-relaxed task-desc">VIP客户,储值余额较多</p>
|
||||
<p class="text-sm text-gray-6 leading-relaxed task-desc"><span class="ai-inline-icon"></span>VIP客户,储值余额较多</p>
|
||||
</div>
|
||||
<svg class="w-5 h-5 text-gray-5 flex-shrink-0 ml-2 mt-1" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="9 18 15 12 9 6"/>
|
||||
@@ -560,7 +561,7 @@
|
||||
<span class="note-indicator" title="有备注">📝</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-7 leading-relaxed task-desc">最近到店:10天前 · 余额:一般</p>
|
||||
<p class="text-sm text-gray-6 leading-relaxed task-desc">消费频率下降,需关注</p>
|
||||
<p class="text-sm text-gray-6 leading-relaxed task-desc"><span class="ai-inline-icon"></span>消费频率下降,需关注</p>
|
||||
</div>
|
||||
<svg class="w-5 h-5 text-gray-5 flex-shrink-0 ml-2 mt-1" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="9 18 15 12 9 6"/>
|
||||
@@ -578,7 +579,7 @@
|
||||
<span class="text-sm">💙</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-7 leading-relaxed task-desc">最近到店:8天前 · 余额:一般</p>
|
||||
<p class="text-sm text-gray-6 leading-relaxed task-desc">偏好晚间时段,可推荐夜场套餐</p>
|
||||
<p class="text-sm text-gray-6 leading-relaxed task-desc"><span class="ai-inline-icon"></span>偏好晚间时段,可推荐夜场套餐</p>
|
||||
</div>
|
||||
<svg class="w-5 h-5 text-gray-5 flex-shrink-0 ml-2 mt-1" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="9 18 15 12 9 6"/>
|
||||
@@ -596,7 +597,7 @@
|
||||
<span class="text-sm">💙</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-7 leading-relaxed task-desc">最近到店:5天前 · 余额:无</p>
|
||||
<p class="text-sm text-gray-6 leading-relaxed task-desc">潜力客户,建议加强互动</p>
|
||||
<p class="text-sm text-gray-6 leading-relaxed task-desc"><span class="ai-inline-icon"></span>潜力客户,建议加强互动</p>
|
||||
</div>
|
||||
<svg class="w-5 h-5 text-gray-5 flex-shrink-0 ml-2 mt-1" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="9 18 15 12 9 6"/>
|
||||
@@ -746,7 +747,7 @@
|
||||
|
||||
card.addEventListener('touchend', function(e) {
|
||||
clearTimeout(pressTimer);
|
||||
if (!isLongPress && !isMoved && !this.classList.contains('abandoned')) {
|
||||
if (!isLongPress && !isMoved) {
|
||||
navigateCard(this);
|
||||
}
|
||||
});
|
||||
@@ -784,7 +785,7 @@
|
||||
|
||||
card.addEventListener('mouseup', function() {
|
||||
clearTimeout(pressTimer);
|
||||
if (!isLongPress && !isMoved && !this.classList.contains('abandoned')) {
|
||||
if (!isLongPress && !isMoved) {
|
||||
navigateCard(this);
|
||||
}
|
||||
});
|
||||
@@ -951,5 +952,3 @@
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
@@ -164,7 +164,7 @@
|
||||
| 功能 | 数据源 | 状态 |
|
||||
|------|--------|------|
|
||||
| 用户审核列表 | `zqyy_app.auth.user_applications` + `zqyy_app.auth.users` | 📱 新建 |
|
||||
| 用户-助教关联建议 | `dwd.dim_assistant`(通过球房ID+手机号匹配) | ✅ FDW |
|
||||
| 用户-助教关联建议 | `dwd.dim_assistant`(通过球房ID+手机号匹配)+ `dwd.dim_staff` / `dwd.dim_staff_ex`(员工信息表匹配) | ✅ FDW |
|
||||
| 球房ID映射 | `zqyy_app.auth.site_code_mapping` | 📱 新建 |
|
||||
| Excel 上传-财务支出 | `dws.dws_finance_expense_summary`(或新建 staging 表) | 🔧/📱 |
|
||||
| Excel 上传-团购收入 | `dws.dws_platform_settlement`(或新建 staging 表) | 🔧/📱 |
|
||||
@@ -263,7 +263,14 @@
|
||||
| `dws.cfg_performance_tier` | 定档配置 |
|
||||
| `dws.cfg_assistant_level_price` | 助教等级单价 |
|
||||
| `dws.cfg_bonus_rules` | 奖金规则 |
|
||||
| `dws.cfg_index_parameters` | 指数参数配置 |
|
||||
| `dws.cfg_index_parameters` | 指数参数配置(含 SPI 26 个参数 ✅) |
|
||||
| `dws.dws_order_summary` | 订单汇总 |
|
||||
|
||||
### 新增 FDW 映射(员工信息表)
|
||||
|
||||
| 来源表 | 用途 |
|
||||
|--------|------|
|
||||
| `dwd.dim_staff` | 员工基础维度(姓名、手机、岗位、在职状态等),用于用户申请人员匹配 |
|
||||
| `dwd.dim_staff_ex` | 员工扩展维度(工号、头像、职级、分组等),用于用户申请人员匹配 |
|
||||
|
||||
---
|
||||
|
||||
@@ -87,14 +87,15 @@ P11 部署与上线(环境配置 + 监控 + 灰度)
|
||||
### SPEC 名称建议:`miniapp-auth-system`
|
||||
|
||||
### 需求概述
|
||||
构建小程序的完整用户认证体系,包括微信登录、用户申请、审核流程、RBAC 权限、用户-助教绑定。
|
||||
构建小程序的完整用户认证体系,包括微信登录、用户申请、审核流程、RBAC 权限、用户-助教/员工绑定。
|
||||
|
||||
### 关键交付物
|
||||
1. 新建表:`auth.users`(重构,增加 wx_openid、status、site_id 等)、`auth.user_applications`、`auth.site_code_mapping`、`auth.user_assistant_binding`
|
||||
1. 新建表:`auth.users`(重构,增加 wx_openid、status、site_id 等)、`auth.user_applications`、`auth.site_code_mapping`、`auth.user_assistant_binding`(增加 `staff_id` 字段支持员工绑定)
|
||||
2. 重构现有 `public.roles`、`public.permissions`、`public.user_roles`、`public.role_permissions` 迁移至 `auth` Schema
|
||||
3. 后端 API:微信 code2Session 登录、用户申请提交、JWT 签发
|
||||
4. 后端 API:用户状态查询(审核中/通过/拒绝)
|
||||
5. 权限中间件:基于角色的 API 访问控制
|
||||
6. 人员匹配:用户申请时同时在助教表(`dim_assistant`)和员工信息表(`dim_staff` + `dim_staff_ex`)中匹配
|
||||
|
||||
### 依赖
|
||||
- P1(`auth` Schema 已创建)
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
- AC1:租户管理后台是独立 Web 应用,独立登录入口
|
||||
- AC2:租户管理员只能看到自己租户下的店铺和用户
|
||||
- AC3:用户审核页面:申请列表、状态筛选、球房ID+手机号关联建议、审核操作
|
||||
- AC3:用户审核页面:申请列表、状态筛选、球房ID+手机号关联建议(同时匹配助教表+员工信息表)、审核操作
|
||||
- AC4:Excel 上传校验:必填、金额精度、表头格式、类型合法
|
||||
- AC5:主键冲突时前端展示 diff 交互,逐条确认后保存
|
||||
- AC6:助教奖罚支持同一助教同月多笔
|
||||
@@ -130,4 +130,4 @@ biz.excel_upload_log
|
||||
- [ ] T6:实现 Excel 上传后端(解析 + 校验 + 冲突检测)
|
||||
- [ ] T7:实现 Excel 上传前端(模板下载 + 上传 + diff 交互 + 确认)
|
||||
- [ ] T8:实现 4 种 Excel 模板的校验规则
|
||||
- [ ] T9:实现助教姓名+编号匹配校验(对照 `dim_assistant`)
|
||||
- [ ] T9:实现人员姓名+编号匹配校验(同时对照 `dim_assistant` 助教表和 `dim_staff` + `dim_staff_ex` 员工信息表)
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
- AC3:`dws_member_consumption_summary` 新增字段有值(`recharge_count_30d/60d/90d`、`recharge_amount_30d/60d/90d`、`avg_ticket_amount`)
|
||||
- AC4:`dws_assistant_daily_detail` 新增字段在符合惩罚条件的订单上正确填充
|
||||
- AC5:新表的 RLS 视图和 FDW 映射已同步创建
|
||||
- AC6:`cfg_index_parameters` 中新增 `SPI` 类型的配置行
|
||||
- AC6:`cfg_index_parameters` 中新增 `SPI` 类型的配置行 ✅(已完成,26 个参数,effective_from=2026-02-23)
|
||||
|
||||
---
|
||||
|
||||
@@ -56,9 +56,9 @@
|
||||
|
||||
## 任务清单
|
||||
|
||||
- [ ] T1:设计并创建 `dws_member_spending_power_index` 表
|
||||
- [ ] T2:实现 `SpendingPowerIndexTask`(含 Level/Speed/Stability 子分)
|
||||
- [ ] T3:在 `cfg_index_parameters` 中插入 SPI 默认参数
|
||||
- [x] T1:设计并创建 `dws_member_spending_power_index` 表 ✅(已完成,24 字段 + 2 索引,迁移脚本 `2026-02-23_create_dws_member_spending_power_index.sql`,测试库已建表,0 行待跑数)
|
||||
- [x] T2:实现 `SpendingPowerIndexTask`(含 Level/Speed/Stability 子分)✅(已完成,代码 `tasks/dws/index/spending_power_index_task.py`,已注册 task_registry,含单元测试 + 属性测试 + BD_Manual 文档)
|
||||
- [x] T3:在 `cfg_index_parameters` 中插入 SPI 默认参数 ✅(已完成,27 个参数含 Level/Speed/Stability 权重、压缩基数、窗口天数等)
|
||||
- [ ] T4:设计并创建 `dws_assistant_order_contribution` 表
|
||||
- [ ] T5:实现 `AssistantOrderContributionTask`
|
||||
- [ ] T6:扩展 `dws_member_consumption_summary`(充值窗口 + 次均消费字段)
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
1. 作为球房工作人员,我需要通过微信登录小程序,首次登录时填写申请表单(球房ID、申请身份、手机号、编号、昵称)。
|
||||
2. 作为租户管理员,我需要审核用户申请,将用户关联到对应的助教/员工,并分配身份权限。
|
||||
3. 作为系统,我需要通过球房ID+手机号自动建议用户与助教的对应关系。
|
||||
3. 作为系统,我需要通过球房ID+手机号自动建议用户与助教/员工的对应关系(同时匹配 `dwd.dim_assistant` 助教表和 `dwd.dim_staff` + `dwd.dim_staff_ex` 员工信息表)。
|
||||
4. 作为用户,我需要看到自己的申请状态(审核中/通过/拒绝)。
|
||||
5. 作为用户,我可以同时属于多个店铺(连锁场景),权限按店铺独立。
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
- AC2:新用户首次登录后进入申请页面,提交后状态为 pending
|
||||
- AC3:管理员审核通过后,用户状态变为 approved,可正常访问小程序
|
||||
- AC4:球房ID 不存在时,申请仍可提交,管理端显示"未找到关联信息"
|
||||
- AC7:用户申请信息比对时,系统同时在助教表(`dwd.dim_assistant`)和员工信息表(`dwd.dim_staff` / `dwd.dim_staff_ex`)中匹配,返回所有匹配结果供管理员选择
|
||||
- AC5:权限中间件正确拦截无权请求(如助教不能看财务看板)
|
||||
- AC6:一个用户可关联多个店铺,切换店铺后数据正确隔离
|
||||
|
||||
@@ -51,10 +52,22 @@ auth.user_site_roles
|
||||
|
||||
auth.user_assistant_binding
|
||||
- id, user_id, site_id, assistant_id (ETL dim_assistant)
|
||||
- staff_id (ETL dim_staff,可选,员工绑定时填写)
|
||||
- binding_type (assistant/staff/manager)
|
||||
- created_at
|
||||
```
|
||||
|
||||
### 人员匹配逻辑(用户申请 → 信息比对)
|
||||
|
||||
用户提交申请时填写球房ID + 手机号(或编号),系统自动在以下两张表中匹配:
|
||||
|
||||
1. 助教表 `dwd.dim_assistant`(通过 FDW `fdw_etl.v_assistant`):按 `site_id` + `mobile` 或 `alias_name` 匹配
|
||||
2. 员工信息表 `dwd.dim_staff` + `dwd.dim_staff_ex`(通过 FDW):按 `site_id` + `mobile` 或 `staff_name` / `alias_name` / `job_num` 匹配
|
||||
|
||||
匹配结果合并后返回给管理端,管理员从候选列表中选择正确的关联对象。若两张表均无匹配,显示"未找到关联信息",管理员可手动填写。
|
||||
|
||||
> 员工信息表数据链路:上游 API → `ods.staff_info_master` → `dwd.dim_staff`(基础维度,SCD2)+ `dwd.dim_staff_ex`(扩展维度,SCD2)
|
||||
|
||||
### 权限列表(固定)
|
||||
|
||||
| 权限 code | 说明 |
|
||||
|
||||
85
docs/roadmap/2026-02-24__fdw-dwd-to-core-migration-plan.md
Normal file
85
docs/roadmap/2026-02-24__fdw-dwd-to-core-migration-plan.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# FDW 数据源迁移方案:DWD → Core
|
||||
|
||||
> 创建日期:2026-02-24
|
||||
> 状态:已决策(上线后迭代)
|
||||
> 关联 SPEC:miniapp-db-foundation
|
||||
|
||||
---
|
||||
|
||||
## 背景
|
||||
|
||||
当前 `miniapp-db-foundation` SPEC 中,FDW 外部表映射的数据源链路为:
|
||||
|
||||
```
|
||||
ETL 库 dwd.dim_* / dws.dws_*
|
||||
→ app.v_dim_* / app.v_dws_*(RLS 视图,site_id 过滤)
|
||||
→ 业务库 fdw_etl.v_dim_* / fdw_etl.v_dws_*(FDW 外部表)
|
||||
```
|
||||
|
||||
ETL 六层 Schema 中,`core` 层的定位是"跨门店标准化维度/事实",是 DWD 层的精简子集。长远来看,小程序读取的维度数据应从 `core` 层获取,而非直接读 `dwd` 层。
|
||||
|
||||
## 决策
|
||||
|
||||
上线时保持现状(`app` 视图指向 `dwd.*` / `dws.*`),上线后再迭代切换到 `core`。
|
||||
|
||||
## 理由
|
||||
|
||||
1. Core 层当前仅 7 张表(`dim_site`、`dim_member`、`dim_assistant`、`dim_table`、`dim_goods_category`、`fact_settlement`、`fact_payment`),小程序需要的 11 张 DWD + 24 张 DWS 远超 Core 覆盖范围
|
||||
2. Core 层字段是精简子集(如 `core.dim_member` 无 `card_balance`、`total_consumption` 等),小程序前期需要 DWD 的完整字段
|
||||
3. `app` 视图层本身就是抽象层——后端通过 `fdw_etl.v_dim_member` 访问数据,底层视图定义可随时切换,后端代码零改动
|
||||
4. 上线关键路径是 P0 阶段(基础设施 → 微信配置 → 后端核心功能),Core 层完善属于 P2 范畴
|
||||
|
||||
## 迁移路径
|
||||
|
||||
### Phase 0:上线前(当前)
|
||||
|
||||
按 SPEC 原样执行:
|
||||
- `app.v_dim_member` → `SELECT * FROM dwd.dim_member WHERE site_id = ...`
|
||||
- `app.v_dws_*` → `SELECT * FROM dws.dws_* WHERE site_id = ...`
|
||||
- FDW 通过 `IMPORT FOREIGN SCHEMA app` 映射
|
||||
|
||||
### Phase 1:上线后 — 补齐 Core 层表
|
||||
|
||||
- 盘点小程序实际用到的维度表,确认 Core 层需要补齐哪些字段
|
||||
- 对照 DWD 层的 `dim_member`、`dim_assistant` 等,在 Core 层补全小程序所需的业务字段
|
||||
- 新增 Core 层缺失的维度表(如 `core.dim_member_card_account`、`core.dim_staff` 等)
|
||||
|
||||
### Phase 2:上线后 — ETL 填充任务
|
||||
|
||||
- 在 ETL Connector 中新增 DWD → Core 的填充任务
|
||||
- 确保 Core 层数据与 DWD 层保持同步
|
||||
- 验证 Core 层数据完整性和正确性
|
||||
|
||||
### Phase 3:上线后 — 切换视图定义
|
||||
|
||||
- 修改 `app` 视图定义,将维度表从 `dwd.*` 切换到 `core.*`:
|
||||
```sql
|
||||
-- 修改前
|
||||
CREATE OR REPLACE VIEW app.v_dim_member AS
|
||||
SELECT * FROM dwd.dim_member
|
||||
WHERE site_id = current_setting('app.current_site_id')::bigint;
|
||||
|
||||
-- 修改后
|
||||
CREATE OR REPLACE VIEW app.v_dim_member AS
|
||||
SELECT * FROM core.dim_member
|
||||
WHERE site_id = current_setting('app.current_site_id')::bigint;
|
||||
```
|
||||
- DWS 汇总表保持不变(它们本身就在 DWS 层,Core 层不涉及汇总)
|
||||
- 重新执行 `IMPORT FOREIGN SCHEMA app INTO fdw_etl` 同步外部表
|
||||
- 后端和小程序代码无需任何改动
|
||||
|
||||
## 风险与缓解
|
||||
|
||||
| 风险 | 缓解措施 |
|
||||
|------|---------|
|
||||
| Core 层字段不全导致小程序功能缺失 | Phase 1 先盘点再补齐,确保字段覆盖 |
|
||||
| DWD → Core 填充任务引入数据延迟 | Core 填充任务纳入 ETL 调度链,与 DWD 同步执行 |
|
||||
| 切换视图时短暂不可用 | 使用 `CREATE OR REPLACE VIEW` 原子替换,无停机 |
|
||||
| Core 层数据与 DWD 不一致 | 切换前运行数据一致性校验脚本 |
|
||||
|
||||
## 关键设计优势
|
||||
|
||||
`app` 视图层是整个方案的"切换开关":
|
||||
- 后端只看到 `fdw_etl.v_dim_member`,不关心底层是 DWD 还是 Core
|
||||
- 切换只需修改视图 SQL + 重新 IMPORT,零代码改动
|
||||
- 可以逐表切换(先切 `dim_member`,验证通过后再切其他表),降低风险
|
||||
186
docs/roadmap/BACKLOG.md
Normal file
186
docs/roadmap/BACKLOG.md
Normal file
@@ -0,0 +1,186 @@
|
||||
# 项目待办总览(Backlog)
|
||||
|
||||
> 最后更新:2026-02-24
|
||||
> 本文档汇总项目中所有已识别的待办事项,按子系统和优先级分类。
|
||||
> 来源:LAUNCH-CHECKLIST、SPEC 文档、字段审计报告、PRD、代码注释等。
|
||||
|
||||
---
|
||||
|
||||
## 阅读指南
|
||||
|
||||
- P0 = 不做就上不了线
|
||||
- P1 = 上线前必须做
|
||||
- P2 = 可上线后迭代
|
||||
- ✅ = 已完成
|
||||
- 🔲 = 待办
|
||||
- 📋 = 有 SPEC 但未执行
|
||||
|
||||
---
|
||||
|
||||
## 一、小程序上线关键路径(P0)
|
||||
|
||||
来源:`docs/deployment/LAUNCH-CHECKLIST.md`
|
||||
|
||||
### 1.1 基础设施
|
||||
|
||||
| 状态 | 项目 | 说明 |
|
||||
|------|------|------|
|
||||
| ✅ | 服务器目录结构 + 仓库克隆 | 20260220 完成 |
|
||||
| 🔲 | 配置环境变量文件 | 服务器 `.env` 手动创建 |
|
||||
| 🔲 | 安装 Python 依赖 | `uv sync --all-packages` |
|
||||
| 🔲 | 运行 `setup-server-git.py` | Git 排除规则 |
|
||||
| 🔲 | 运行 `init-server-env.py` | 删除排除文件 + 创建 export 目录 |
|
||||
| 🔲 | bat 脚本部署到服务器 | `D:\NeoZQYY\scripts\` |
|
||||
| 🔲 | 确认 Nginx 反代规则 | 测试 8001 / 正式 8000 |
|
||||
| 🔲 | SSL 证书自动续期 | |
|
||||
| 🔲 | 数据库备份方案 | pg_dump + Windows 计划任务 |
|
||||
|
||||
### 1.2 微信侧配置
|
||||
|
||||
| 状态 | 项目 | 说明 |
|
||||
|------|------|------|
|
||||
| ✅ | 合法域名 + HTTPS | 已配置 |
|
||||
| 🔲 | 消息推送配置提交验证 | 需服务器后端在线 |
|
||||
| 🔲 | 用户隐私保护指引 | 微信后台填写 |
|
||||
| 🔲 | 小程序名称/图标/简介/类目 | 审核必需 |
|
||||
| 🔲 | 体验成员配置 | 内部测试必需 |
|
||||
|
||||
### 1.3 后端核心功能
|
||||
|
||||
| 状态 | 项目 | 说明 |
|
||||
|------|------|------|
|
||||
| 🔲 | 微信登录接口 | `POST /api/auth/wechat_login` |
|
||||
| 🔲 | 权限中间件 | JWT site_id + role 校验 |
|
||||
| 🔲 | 至少一个有实际功能的首页 | 审核要求 |
|
||||
| 🔲 | 密钥配置 | `WX_APP_ID`、`WX_APP_SECRET`、`JWT_SECRET_KEY` |
|
||||
|
||||
### 1.4 数据库基础设施
|
||||
|
||||
| 状态 | 项目 | 说明 |
|
||||
|------|------|------|
|
||||
| 📋 | miniapp-db-foundation SPEC | 6 个任务全部待执行 |
|
||||
| 🔲 | auth/biz Schema 创建 | SPEC 任务 2 |
|
||||
| 🔲 | ETL RLS 视图层(35 张) | SPEC 任务 1 |
|
||||
| 🔲 | FDW 跨库映射 | SPEC 任务 3 |
|
||||
| 🔲 | 端到端验证脚本 | SPEC 任务 5 |
|
||||
|
||||
---
|
||||
|
||||
## 二、安全与审计(P1 — 上线前必须做)
|
||||
|
||||
来源:`docs/deployment/LAUNCH-CHECKLIST.md` 第五阶段
|
||||
|
||||
| 状态 | 项目 | 说明 |
|
||||
|------|------|------|
|
||||
| 🔲 | 用户申请/审核流 | `user_application` 表 + 审核 API |
|
||||
| 🔲 | 审计日志 | `audit_log` 表 + 审计中间件 |
|
||||
| 🔲 | 后端结构化日志 | 替代 uvicorn 默认日志 |
|
||||
| 🔲 | 服务器防火墙确认 | Tailscale 网卡入站限制 |
|
||||
| 🔲 | PostgreSQL 监听确认 | 仅内网/本机 |
|
||||
| 🔲 | 消息推送切安全模式 | AES 加解密 |
|
||||
|
||||
---
|
||||
|
||||
## 三、审核准备(P1 — 提交审核前)
|
||||
|
||||
来源:`docs/deployment/LAUNCH-CHECKLIST.md` 第六阶段
|
||||
|
||||
| 状态 | 项目 | 说明 |
|
||||
|------|------|------|
|
||||
| 🔲 | 主要页面功能截图 | |
|
||||
| 🔲 | 测试账号 | |
|
||||
| 🔲 | 类目资质文件 | 营业执照等 |
|
||||
| 🔲 | 功能介绍文案 | |
|
||||
|
||||
---
|
||||
|
||||
## 四、ETL 待办
|
||||
|
||||
### 4.1 字段补全(来源:`field_review_for_user.md`)
|
||||
|
||||
| 状态 | 项目 | 优先级 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| 🔲 | 映射错误修复(4 处) | 高 | site_assistant_id、discount_price 列名、batch_stock_qty、provisional_total_cost |
|
||||
| 🔲 | 待新增/补映射字段(40 个) | 中 | 分布在 12 张 ODS 表,含 A/B/C 三类 |
|
||||
| 🔲 | 新建 DWD 表(2 张) | 中 | `dwd_goods_stock_movement`(优先)、`settlement_ticket`(待定) |
|
||||
| 🔲 | ODS 配置修改(1 处) | 中 | `goods_stock_summary` 改 `requires_window=True` |
|
||||
| 🔲 | ODS 平层化映射修复 | 低 | `table_area_id_list` 字段名映射错位 |
|
||||
|
||||
### 4.2 DWS 层待建表
|
||||
|
||||
| 状态 | 项目 | 来源 |
|
||||
|------|------|------|
|
||||
| 🔲 | `dws_member_spending_power_index` RLS 视图 | miniapp-db-foundation P2 预留 |
|
||||
| 🔲 | `dws_assistant_order_contribution` | miniapp-db-foundation P2 预留 |
|
||||
| 🔲 | `dws_goods_stock_summary`(日/周/月粒度) | dataflow-field-completion SPEC |
|
||||
|
||||
### 4.3 Core 层迁移
|
||||
|
||||
| 状态 | 项目 | 来源 |
|
||||
|------|------|------|
|
||||
| 🔲 | 补齐 Core 层维度表字段 | `docs/roadmap/2026-02-24__fdw-dwd-to-core-migration-plan.md` |
|
||||
| 🔲 | DWD → Core 填充任务 | 同上 |
|
||||
| 🔲 | app 视图从 DWD 切换到 Core | 同上 |
|
||||
|
||||
### 4.4 架构优化
|
||||
|
||||
| 状态 | 项目 | 来源 |
|
||||
|------|------|------|
|
||||
| 📋 | 冷数据归档(方案 5) | `ods_taskspec_refactor_proposal.md` 中长期待办 |
|
||||
| 🔲 | ETL SDK 抽象 | LAUNCH-CHECKLIST 7.4,飞球 Connector → 通用基类 |
|
||||
|
||||
---
|
||||
|
||||
## 五、后端待办(P2 — 上线后迭代)
|
||||
|
||||
来源:`docs/deployment/LAUNCH-CHECKLIST.md` 第七阶段
|
||||
|
||||
| 状态 | 项目 | 说明 |
|
||||
|------|------|------|
|
||||
| 🔲 | xlsx 导入/导出 | 上传、解析、校验、落库、错误报告 |
|
||||
| 🔲 | 运维监控系统 | BS 架构,集成管理后台,取代 bat 脚本 |
|
||||
| 🔲 | 租户模型 | tenant 层 + RLS Policy DDL |
|
||||
| 🔲 | 后端 API 集成测试 | |
|
||||
| 🔲 | 小程序端自动化测试 | |
|
||||
| 🔲 | 依赖版本 pin 上限 | 当前 `>=0.115` 等范围较宽松 |
|
||||
|
||||
---
|
||||
|
||||
## 六、SPEC 状态总览
|
||||
|
||||
| SPEC | 状态 | 说明 |
|
||||
|------|------|------|
|
||||
| `admin-web-console` | ✅ 已完成 | Web 管理后台 |
|
||||
| `assistant-abolish-cleanup` | ✅ 已完成 | 助教废除链路清理 |
|
||||
| `dataflow-field-completion` | ✅ 已完成 | 字段补全与联调 |
|
||||
| `dataflow-structure-audit` | ✅ 已完成 | 数据流结构分析重构 |
|
||||
| `dwd-phase1-refactor` | ✅ 已完成 | DWD 第一阶段重构 |
|
||||
| `etl-aggregation-fix` | ✅ 已完成 | ETL 聚合修复 |
|
||||
| `etl-dws-flow-refactor` | ✅ 已完成 | DWS 流程重构 |
|
||||
| `etl-fullstack-integration` | ✅ 已完成 | 全栈集成测试 |
|
||||
| `etl-pipeline-debug` | ✅ 已完成 | ETL 全链路调试 |
|
||||
| `etl-staff-dimension` | ✅ 已完成 | 员工维度表 |
|
||||
| `ods-dedup-standardize` | ✅ 已完成 | ODS 去重标准化 |
|
||||
| `spi-spending-power-index` | ✅ 已完成 | 消费力指数 |
|
||||
| `miniapp-db-foundation` | 📋 待执行 | 小程序数据库基础设施(6 个任务) |
|
||||
|
||||
---
|
||||
|
||||
## 七、文档待办
|
||||
|
||||
| 状态 | 项目 | 位置 |
|
||||
|------|------|------|
|
||||
| 🔲 | ADR(架构决策记录)模板 | `docs/architecture/` |
|
||||
| 🔲 | 数据字典完善 | `docs/contracts/data_dictionary/` |
|
||||
| 🔲 | 权限矩阵 | `docs/permission_matrix/` |
|
||||
| 🔲 | 运维手册 | `docs/ops/` |
|
||||
| ✅ | FDW DWD→Core 迁移方案 | `docs/roadmap/2026-02-24__fdw-dwd-to-core-migration-plan.md` |
|
||||
|
||||
---
|
||||
|
||||
## 维护说明
|
||||
|
||||
本文档为项目待办的单一汇总入口。新增待办时:
|
||||
1. 先在对应的来源文档中记录(LAUNCH-CHECKLIST、SPEC、字段审计报告等)
|
||||
2. 同步更新本文档对应分类
|
||||
3. 完成后标记 ✅ 并注明日期
|
||||
Reference in New Issue
Block a user