微信小程序页面迁移校验之前 P5任务处理之前

This commit is contained in:
Neo
2026-03-09 01:19:21 +08:00
parent 263bf96035
commit 6e20987d2f
1112 changed files with 153824 additions and 219694 deletions

View File

@@ -1,6 +1,7 @@
# NeoZQYY 文档地图
> 本文档记录项目中所有文档资产的位置、类型和内容概要,方便快速定位。
> 归档规则见末尾「文档归档规则」章节;程序输出路径规范见 `docs/deployment/EXPORT-PATHS.md`。
---
@@ -20,7 +21,6 @@
| 文件 | 内容 |
|------|------|
| `docs/README.md` | 项目架构图、模块文档索引、技术栈速览、认证体系概览、四库架构、常用命令 |
| `docs/etl-feiqiu-architecture.md` | ETL Connector 整体架构说明 |
### 2.2 数据库变更手册 `docs/database/`
@@ -29,6 +29,7 @@
| 文件 | 记录内容 |
|------|----------|
| `BD_Manual_auth_tables.md` | auth Schema 8 张认证表users、roles、permissions、user_applications 等) |
| `BD_Manual_biz_tables.md` | biz Schema 4 张核心业务表coach_tasks、coach_task_history、notes、trigger_jobs |
| `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 视图 |
@@ -46,25 +47,28 @@
| `BD_Manual_assistant_accounts_master.md` | 助教账户主表 |
| `BD_Manual_assistant_service_records.md` | 助教服务记录表 |
| `BD_Manual_site_tables_master.md` | 门店台桌主表 |
| `BD_Manual_20260301_cleanup_and_fixes.md` | 已删除表残留清理 + ODS 商品销售窗口修复 + dim_staff_ex 列名修复 |
| `README.md` | 数据库文档目录说明 |
子目录:
| 目录 | 内容 |
|------|------|
| `ddl/` | 9 个 DDL 基线文件,覆盖 etl_feiqiu 六层 Schemameta/ods/dwd/core/dws/app+ zqyy_app 个 Schemaauth/public+ FDW |
| `ddl/` | 10 个 DDL 基线文件,覆盖 etl_feiqiu 六层 Schemameta/ods/dwd/core/dws/app+ zqyy_app 个 Schemaauth/biz/public+ FDW |
| `_archived/` | 10 个已归档的历史变更文档(已废弃的表、已回滚的变更等) |
### 2.3 审计记录 `docs/audit/`
项目变更的完整审计追踪体系。
项目变更的完整审计追踪体系。审计追溯链Prompt 日志 ↔ Session 日志 ↔ 变更审计记录,通过 Prompt-ID 和 Session-ID 双向关联。
| 路径 | 内容 |
|------|------|
| `audit_dashboard.md` | 审计仪表盘,汇总所有变更审计记录 |
| `README.md` | 审计目录说明 |
| `changes/` | 31 份变更审计文档(`YYYY-MM-DD__<slug>.md` 格式),每份包含:变更原因、影响范围、回滚策略、验证 SQL |
| `SESSION-LOG-GUIDE.md` | Session 日志使用指南:索引字段说明、查询方法、典型场景、与其他审计产物的关系 |
| `changes/` | 38 份变更审计文档(`YYYY-MM-DD__<slug>.md` 格式),每份包含:变更原因、影响范围、回滚策略、验证 SQL |
| `prompt_logs/` | ~500 份 Prompt 日志(`prompt_log_YYYYMMDD_HHMMSS.md`),记录每次 AI 交互的输入输出 |
| `session_logs/` | 全量会话记录(按 `YYYY-MM/DD/` 分层),含双索引(`_session_index.json` / `_session_index_full.json`)、每轮 execution 的完整 Markdown 记录、LLM 生成的操作摘要 |
### 2.4 数据契约 `docs/contracts/`
@@ -105,7 +109,29 @@ H5 静态原型页面,用于小程序 UI 设计参考。
| `js/` | 8 个交互脚本 |
| `img/` | 图片资源 |
### 2.8 其他项目级文档目录
### 2.8 参考资料 `docs/reference/`
| 文件 | 内容 |
|------|------|
| `bailian-agent-guide.md` | 百炼 Agent 集成参考指南(由 v1 + v2 合并) |
| `bailian-dashscope-api.md` | 百炼 DashScope API 参考 |
### 2.9 数据分析报告 `docs/reports/`
一次性数据分析、调研产出的报告文档。与 `prd/specs/` 的区别specs 是需求规格reports 是基于数据的分析结论。
| 文件 | 内容 |
|------|------|
| `complex-orders-analysis.md` | 复杂订单结构分析ODS 全量扫描,关联键与复杂度定义) |
| `dwd-amount-duration-calibration.md` | DWD 层金额·绩效·时长字段口径全景(置信度与存疑项标注) |
### 2.10 架构文档 `docs/architecture/`
| 文件 | 内容 |
|------|------|
| `etl-feiqiu-architecture.md` | ETL Connector 整体架构说明数据流、DWS/INDEX 任务、调度编排、CLI |
### 2.11 其他项目级文档目录
| 路径 | 内容 |
|------|------|
@@ -117,7 +143,6 @@ H5 静态原型页面,用于小程序 UI 设计参考。
| `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 整体架构说明 |
---
@@ -128,8 +153,8 @@ H5 静态原型页面,用于小程序 UI 设计参考。
| 文件 | 内容 |
|------|------|
| `README.md` | 架构概览、双库连接、认证系统、13 个路由模块摘要、服务层、配置加载 |
| `docs/API-REFERENCE.md` | 完整 API 参考13 个路由模块的所有端点、请求/响应示例、认证要求、错误码 |
| `README.md` | 架构概览、双库连接、认证系统、17 个路由模块摘要、服务层、配置加载、触发器系统 |
| `docs/API-REFERENCE.md` | 完整 API 参考17 个路由模块的所有端点、请求/响应示例、认证要求、错误码 |
### 3.2 ETL Connector `apps/etl/connectors/feiqiu/`
@@ -150,13 +175,14 @@ H5 静态原型页面,用于小程序 UI 设计参考。
| 文件 | 内容 |
|------|------|
| `README.md` | 后端 API 集成、认证流程、权限模型、关键端点说明 |
| `README.md` | 后端 API 集成、认证流程(含开发模式)、权限模型、关键端点说明 |
| `doc/auth-integration-guide.md` | 认证集成详细指南 |
### 3.4 管理后台 `apps/admin-web/`
| 文件 | 内容 |
|------|------|
| `README.md` | 8 个页面、组件体系、API 层、状态管理、开发指南 |
| `README.md` | 8 个页面、组件体系(含营业日提示)、API 层、状态管理、开发指南 |
### 3.5 MCP Server `apps/mcp-server/`
@@ -168,7 +194,7 @@ H5 静态原型页面,用于小程序 UI 设计参考。
| 文件 | 内容 |
|------|------|
| `README.md` | 3 个模块enums / money / datetime_utils的 API 文档及用法示例 |
| `README.md` | 4 个模块enums / money / datetime_utils / 其他工具)的 API 文档及用法示例 |
---
@@ -227,4 +253,38 @@ H5 静态原型页面,用于小程序 UI 设计参考。
| `spi-spending-power-index` | SPI 消费力指数 |
| `dataflow-field-completion` | 数据流字段补全 |
| `dataflow-structure-audit` | 数据流结构审计 |
| `assistant-abolish-cleanup` | 助教废除清理 |
| `assistant-abolish-cleanup` | 助教废除清理 |
---
## 六、文档归档规则
新建文档时,按以下规则选择目标目录。禁止在 `docs/` 根目录散放文件(`README.md``DOCUMENTATION-MAP.md` 除外)。
| 文档类型 | 目标目录 | 说明 |
|----------|----------|------|
| 数据分析报告、调研产出 | `reports/` | 一次性分析结论,带"生成时间/数据来源"标记 |
| 架构设计文档 | `architecture/` | 系统/模块架构图与说明 |
| 数据库变更审计 | `database/` | `BD_Manual_*.md` 格式,含回滚与验证 SQL |
| 变更审计记录 | `audit/changes/` | `YYYY-MM-DD__<slug>.md` 格式 |
| 产品需求规格 | `prd/specs/` | P1-P11 等需求 spec不放分析报告 |
| 数据契约 | `contracts/` | OpenAPI spec、JSON Schema、数据字典 |
| 部署与运维配置 | `deployment/` | 启动清单、路径规范、安全指南 |
| 路线图与规划 | `roadmap/` | 迁移计划、BACKLOG |
| Spec 需求输入 | `spec-input/` | 问题汇总,供开启 Spec 流程 |
| 外部参考资料 | `reference/` | 第三方 API/SDK 指南 |
| 迁移记录 | `migrate/` | 仓库迁移总结、配置迁移记录 |
| MCP 相关 | `mcp/` | AI 工具集成文档 |
| UI 原型 | `h5_ui/` | H5 静态原型页面 |
| 运维手册 | `ops/` | 故障排查、日常运维流程 |
| 权限矩阵 | `permission_matrix/` | 角色-资源权限映射 |
### 判断流程
1. 是模块专属文档?→ 放模块内部 `docs/`(如 `apps/etl/.../docs/`
2. 是审计产物?→ 统一写 `docs/audit/`,禁止写入子模块
3. 按上表匹配文档类型 → 放对应子目录
4. 都不匹配?→ 先在上表新增分类,再创建子目录
> 本规则由 `.kiro/steering/export-paths.md` 强制执行。

View File

@@ -65,20 +65,23 @@ NeoZQYY 是面向台球门店业务的全栈数据平台,包含 6 个子系统
### 项目级文档
| 目录 | 说明 |
|------|------|
| `prd/` | 产品需求文档 |
| `contracts/` | 数据契约OpenAPI、JSON Schema、数据字典 |
| `permission_matrix/` | 权限矩阵 |
| `architecture/` | 架构设计文档 |
| `database/` | 数据库设计与变更文档BD 手册) |
| `deployment/` | 部署文档(启动清单、输出路径规范) |
| `h5_ui/` | 小程序原型与 UI 设计稿 |
| `ops/` | 运维手册 |
| `audit/` | 统一审计目录(变更记录 + Prompt 日志 |
| `roadmap/` | 路线图 |
| `migrate/` | 迁移指南 |
| `spec-input/` | Spec 输入文档 |
| 目录 | 说明 | 典型内容 |
|------|------|----------|
| `architecture/` | 架构设计文档 | 系统架构图、ETL 架构说明 |
| `audit/` | 统一审计目录(变更记录 + Prompt 日志) | `changes/` 变更审计、`prompt_logs/` 交互日志 |
| `contracts/` | 数据契约 | OpenAPI spec、JSON Schema、数据字典 |
| `database/` | 数据库设计与变更文档BD 手册) | `BD_Manual_*.md` 表结构变更审计、`ddl/` 基线 |
| `deployment/` | 部署与运维配置文档 | 启动清单、输出路径规范、隐私政策 |
| `h5_ui/` | 小程序 UI 原型H5 静态页面) | 页面原型、样式、交互脚本 |
| `mcp/` | MCP Server 相关文档 | AI 数据库查询手册 |
| `migrate/` | 迁移记录与指南 | Monorepo 迁移总结、旧配置迁移记录 |
| `ops/` | 运维手册 | 故障排查、日常运维流程(待填充 |
| `permission_matrix/` | 权限矩阵 | 角色-资源权限映射(待填充) |
| `prd/` | 产品需求文档 | PRD 审阅 Q&A、`specs/` 下 P1-P11 需求规格 |
| `reference/` | 外部参考资料 | 百炼 Agent 指南、DashScope API 参考 |
| `reports/` | 数据分析报告与调研产出 | 复杂订单分析、DWD 字段口径全景等一次性分析报告 |
| `roadmap/` | 路线图与待办 | 迁移计划、BACKLOG |
| `spec-input/` | Spec 流程的需求输入文档 | 用户提交的问题汇总,供开启 Spec 使用 |
## 技术栈速览

View File

@@ -136,7 +136,7 @@ BaseTask # 提供 E/T/L 模板方法 + 窗口分段
```
extract(context):
├── 从 dwd.dwd_assistant_service_log 提取助教服务记录
├── 从 dwd.dwd_assistant_trash_event 提取废除记录
├── 从 dwd.dwd_assistant_service_log_ex 获取废除标记is_trash
└── 加载配置缓存(技能类型映射等)
transform(extracted, context):

View File

@@ -8,11 +8,23 @@
|-----------|------|
| `changes/` | AI 逐次变更审计记录(`<YYYY-MM-DD>__<slug>.md` |
| `prompt_logs/` | Prompt 日志文件(每次 prompt 生成一个独立文件,按时间戳命名) |
| `session_logs/` | 全量会话记录(按 `YYYY-MM/DD/` 分层),含双索引和 LLM 操作摘要。详见 [`SESSION-LOG-GUIDE.md`](SESSION-LOG-GUIDE.md) |
| `audit_dashboard.md` | 审计一览表(自动生成,勿手动编辑) |
| `SESSION-LOG-GUIDE.md` | Session 日志使用指南(索引字段、查询方法、典型场景) |
## 维护约定
- `prompt_logs/``prompt-audit-log` Hook 自动管理,请勿手动编辑
- `session_logs/``agentStop` Hook 自动提取,索引由提取脚本自动更新
- `audit_dashboard.md``/audit` 流程自动刷新,也可通过 `python scripts/audit/gen_audit_dashboard.py` 手动重新生成
- 变更审计记录由 `/audit` 流程audit-writer 子代理)生成
- 变更审计记录由 `/audit` 流程audit-writer 子代理)生成,包含 `session_id` 字段与 Session 日志双向关联
- 历史记录(迁移前 ETL 子项目的审计)已合并至此目录
## 审计追溯链
```
Prompt 日志 ←→ Session 日志 ←→ 变更审计记录
(用户说了什么) (AI 做了什么) (正式变更文档)
```
通过 Prompt-ID 和 Session-ID 可在三者之间双向追溯。详见 [`SESSION-LOG-GUIDE.md`](SESSION-LOG-GUIDE.md)。

View File

@@ -1,12 +1,28 @@
# 审计一览表
> 自动生成于 2026-02-26 06:28:25,请勿手动编辑。
> 自动生成于 2026-03-08 03:05:13,请勿手动编辑。
## 时间线视图
| 日期 | 项目 | 需求摘要 | 变更类型 | 影响模块 | 风险 | 详情 |
|------|------|----------|----------|----------|------|------|
| 2026-03-08 | ETL-feiqiu, 后端 | 变更审计记录P5 AI 集成需求审视 — 7 项歧义修补 + category 枚举对齐 | 文档 | 其他, 文档, 脚本工具 | 未知 | [链接](changes/2026-03-08__p5-ai-spec-review-category-enum-align.md) |
| 2026-03-07 | 项目级 | 变更审计记录TASK 3 项目标签计算逻辑 — 文档与配置同步 | 文档 | 其他 | 低 | [链接](changes/2026-03-07__task3-project-tag-docs-sync.md) |
| 2026-03-06 | 项目级 | 变更审计记录:修复 RecordingAPIClient 缺少 post 方法 | bugfix | 其他 | 极低 | [链接](changes/2026-03-06__fix-api-client-post-method.md) |
| 2026-03-06 | 项目级 | 变更审计记录:修复 DatabaseOperations 缺少 _dsn 属性导致 DWD 并行装载全部失败 | bugfix | 其他 | 极低 | [链接](changes/2026-03-06__fix-db-operations-dsn-proxy.md) |
| 2026-03-04 | 项目级 | 变更审计记录全栈累积变更营业日配置、WebSocket 日志、微信认证、仓库清理) | 重构 | 其他 | 高 | [链接](changes/2026-03-04__fullstack-accumulated-changes.md) |
| 2026-03-03 | 项目级 | 变更审计记录:微信小程序开发调试面板 | 文档 | 其他 | 低 | [链接](changes/2026-03-03__miniprogram-dev-debug-panel.md) |
| 2026-03-02 | ETL-feiqiu, 项目级 | 变更审计:合并 ETL Hook 为统一分析入口 | 文档 | 其他, 脚本工具 | 未知 | [链接](changes/2026-03-02__etl-unified-analysis-hook-merge.md) |
| 2026-03-02 | 项目级 | SPI 基数校准改用非零样本中位数 | 功能 | 其他 | 未知 | [链接](changes/2026-03-02__spi-calibration-nonzero-median.md) |
| 2026-03-01 | 项目级 | 审计记录DWD 清理 + ODS 商品销售修复 + dim_staff_ex 修复 | bugfix | 其他 | 未知 | [链接](changes/2026-03-01__dwd-cleanup-ods-fix-dim-staff-repair.md) |
| 2026-03-01 | 项目级 | 变更审计DWS numeric 精度扩展 + ODS 库存 siteid 注入 | 文档 | 其他 | 低 | [链接](changes/2026-03-01__dws-numeric-precision-ods-siteid-fix.md) |
| 2026-02-28 | ETL-feiqiu, 后端, 管理后台 | 变更审计记录:多模块累积变更(营业日/核心业务/认证/ETL DWS 重构/参考文档合并) | 重构 | 其他 | 未知 | [链接](changes/2026-02-28__multi-module-accumulated-changes.md) |
| 2026-02-27 | ETL-feiqiu, 共享包, 跨库(FDW), 项目级 | 变更审计:营业日分割规则 PRD 同步检查 + 全栈集成收口 | bugfix | API 层, DWD 层, DWS 层, 其他, 调度, 质量校验, 配置 | 低 | [链接](changes/2026-02-27__biz-day-cutoff-prd-sync-check.md) |
| 2026-02-27 | 项目级 | 审计记录P4 小程序核心业务路由 + 触发器注册 | 重构 | 其他 | 未知 | [链接](changes/2026-02-27__p4-core-business-routes-triggers.md) |
| 2026-02-26 | 项目级 | 审计记录ETL Bug 修复 — dim_staff_ex 列映射 + assistant_daily table_area_name | bugfix | 其他 | 未知 | [链接](changes/2026-02-26__etl-bugfix-dim-staff-rankname-assistant-daily-table-area.md) |
| 2026-02-26 | 项目级 | 变更审计P1/P2/P3 全栈集成DB 基础 + ETL DWS 扩展 + 小程序鉴权) | bugfix | 其他 | 低 | [链接](changes/2026-02-26__p1-p2-p3-fullstack-integration.md) |
| 2026-02-26 | 项目级 | 审计记录维客线索重构member_birthday_manual → member_retention_clue | 重构 | 其他 | 未知 | [链接](changes/2026-02-26__retention-clue-refactor.md) |
| 2026-02-26 | 项目级 | 审计记录root-file — .gitignore 更新与 H5 UI / 临时文件清理 | 重构 | 其他 | 高 | [链接](changes/2026-02-26__root-file-gitignore-h5ui-cleanup.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) |
@@ -44,6 +60,10 @@
| 日期 | 需求摘要 | 变更类型 | 影响模块 | 风险 | 详情 |
|------|----------|----------|----------|------|------|
| 2026-03-08 | 变更审计记录P5 AI 集成需求审视 — 7 项歧义修补 + category 枚举对齐 | 文档 | 其他, 文档, 脚本工具 | 未知 | [链接](changes/2026-03-08__p5-ai-spec-review-category-enum-align.md) |
| 2026-03-02 | 变更审计:合并 ETL Hook 为统一分析入口 | 文档 | 其他, 脚本工具 | 未知 | [链接](changes/2026-03-02__etl-unified-analysis-hook-merge.md) |
| 2026-02-28 | 变更审计记录:多模块累积变更(营业日/核心业务/认证/ETL DWS 重构/参考文档合并) | 重构 | 其他 | 未知 | [链接](changes/2026-02-28__multi-module-accumulated-changes.md) |
| 2026-02-27 | 变更审计:营业日分割规则 PRD 同步检查 + 全栈集成收口 | bugfix | API 层, DWD 层, DWS 层, 其他, 调度, 质量校验, 配置 | 低 | [链接](changes/2026-02-27__biz-day-cutoff-prd-sync-check.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) |
| 2026-02-15 | 审计记录docs/index + docs/开发笔记 清理与路径整合 | 清理 | 其他, 文档, 脚本工具 | 低 | [链接](changes/2026-02-15__docs-devnotes-index-cleanup.md) |
@@ -74,19 +94,53 @@
| 日期 | 需求摘要 | 变更类型 | 影响模块 | 风险 | 详情 |
|------|----------|----------|----------|------|------|
| 2026-03-08 | 变更审计记录P5 AI 集成需求审视 — 7 项歧义修补 + category 枚举对齐 | 文档 | 其他, 文档, 脚本工具 | 未知 | [链接](changes/2026-03-08__p5-ai-spec-review-category-enum-align.md) |
| 2026-02-28 | 变更审计记录:多模块累积变更(营业日/核心业务/认证/ETL DWS 重构/参考文档合并) | 重构 | 其他 | 未知 | [链接](changes/2026-02-28__multi-module-accumulated-changes.md) |
| 2026-02-15 | 审计记录:后端依赖补全使 FastAPI 可启动 | bugfix | 其他 | 未知 | [链接](changes/2026-02-15__backend-deps-bootstrap.md) |
### 管理后台
| 日期 | 需求摘要 | 变更类型 | 影响模块 | 风险 | 详情 |
|------|----------|----------|----------|------|------|
| 2026-02-28 | 变更审计记录:多模块累积变更(营业日/核心业务/认证/ETL DWS 重构/参考文档合并) | 重构 | 其他 | 未知 | [链接](changes/2026-02-28__multi-module-accumulated-changes.md) |
### 桌面GUI
| 日期 | 需求摘要 | 变更类型 | 影响模块 | 风险 | 详情 |
|------|----------|----------|----------|------|------|
| 2026-02-13 | 审计记录移除旧版指数RECALL/INTIMACY+ ML last-touch 清理 | bugfix | DWS 层, GUI, 其他, 数据库, 文档, 测试, 调度 | 低 | [链接](changes/2026-02-13__remove-legacy-index-cleanup.md) |
### 共享包
| 日期 | 需求摘要 | 变更类型 | 影响模块 | 风险 | 详情 |
|------|----------|----------|----------|------|------|
| 2026-02-27 | 变更审计:营业日分割规则 PRD 同步检查 + 全栈集成收口 | bugfix | API 层, DWD 层, DWS 层, 其他, 调度, 质量校验, 配置 | 低 | [链接](changes/2026-02-27__biz-day-cutoff-prd-sync-check.md) |
### 跨库(FDW)
| 日期 | 需求摘要 | 变更类型 | 影响模块 | 风险 | 详情 |
|------|----------|----------|----------|------|------|
| 2026-02-27 | 变更审计:营业日分割规则 PRD 同步检查 + 全栈集成收口 | bugfix | API 层, DWD 层, DWS 层, 其他, 调度, 质量校验, 配置 | 低 | [链接](changes/2026-02-27__biz-day-cutoff-prd-sync-check.md) |
### 项目级
| 日期 | 需求摘要 | 变更类型 | 影响模块 | 风险 | 详情 |
|------|----------|----------|----------|------|------|
| 2026-03-07 | 变更审计记录TASK 3 项目标签计算逻辑 — 文档与配置同步 | 文档 | 其他 | 低 | [链接](changes/2026-03-07__task3-project-tag-docs-sync.md) |
| 2026-03-06 | 变更审计记录:修复 RecordingAPIClient 缺少 post 方法 | bugfix | 其他 | 极低 | [链接](changes/2026-03-06__fix-api-client-post-method.md) |
| 2026-03-06 | 变更审计记录:修复 DatabaseOperations 缺少 _dsn 属性导致 DWD 并行装载全部失败 | bugfix | 其他 | 极低 | [链接](changes/2026-03-06__fix-db-operations-dsn-proxy.md) |
| 2026-03-04 | 变更审计记录全栈累积变更营业日配置、WebSocket 日志、微信认证、仓库清理) | 重构 | 其他 | 高 | [链接](changes/2026-03-04__fullstack-accumulated-changes.md) |
| 2026-03-03 | 变更审计记录:微信小程序开发调试面板 | 文档 | 其他 | 低 | [链接](changes/2026-03-03__miniprogram-dev-debug-panel.md) |
| 2026-03-02 | 变更审计:合并 ETL Hook 为统一分析入口 | 文档 | 其他, 脚本工具 | 未知 | [链接](changes/2026-03-02__etl-unified-analysis-hook-merge.md) |
| 2026-03-02 | SPI 基数校准改用非零样本中位数 | 功能 | 其他 | 未知 | [链接](changes/2026-03-02__spi-calibration-nonzero-median.md) |
| 2026-03-01 | 审计记录DWD 清理 + ODS 商品销售修复 + dim_staff_ex 修复 | bugfix | 其他 | 未知 | [链接](changes/2026-03-01__dwd-cleanup-ods-fix-dim-staff-repair.md) |
| 2026-03-01 | 变更审计DWS numeric 精度扩展 + ODS 库存 siteid 注入 | 文档 | 其他 | 低 | [链接](changes/2026-03-01__dws-numeric-precision-ods-siteid-fix.md) |
| 2026-02-27 | 变更审计:营业日分割规则 PRD 同步检查 + 全栈集成收口 | bugfix | API 层, DWD 层, DWS 层, 其他, 调度, 质量校验, 配置 | 低 | [链接](changes/2026-02-27__biz-day-cutoff-prd-sync-check.md) |
| 2026-02-27 | 审计记录P4 小程序核心业务路由 + 触发器注册 | 重构 | 其他 | 未知 | [链接](changes/2026-02-27__p4-core-business-routes-triggers.md) |
| 2026-02-26 | 审计记录ETL Bug 修复 — dim_staff_ex 列映射 + assistant_daily table_area_name | bugfix | 其他 | 未知 | [链接](changes/2026-02-26__etl-bugfix-dim-staff-rankname-assistant-daily-table-area.md) |
| 2026-02-26 | 变更审计P1/P2/P3 全栈集成DB 基础 + ETL DWS 扩展 + 小程序鉴权) | bugfix | 其他 | 低 | [链接](changes/2026-02-26__p1-p2-p3-fullstack-integration.md) |
| 2026-02-26 | 审计记录维客线索重构member_birthday_manual → member_retention_clue | 重构 | 其他 | 未知 | [链接](changes/2026-02-26__retention-clue-refactor.md) |
| 2026-02-26 | 审计记录root-file — .gitignore 更新与 H5 UI / 临时文件清理 | 重构 | 其他 | 高 | [链接](changes/2026-02-26__root-file-gitignore-h5ui-cleanup.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) |
@@ -108,12 +162,14 @@
| 日期 | 需求摘要 | 变更类型 | 风险 | 详情 |
|------|----------|----------|------|------|
| 2026-02-27 | 变更审计:营业日分割规则 PRD 同步检查 + 全栈集成收口 | bugfix | 低 | [链接](changes/2026-02-27__biz-day-cutoff-prd-sync-check.md) |
| 2026-02-14 | 审计记录api/recording_client.py 默认时区修正 | 功能 | 极低 | [链接](changes/2026-02-14__recording-client-timezone-fix.md) |
### DWD 层
| 日期 | 需求摘要 | 变更类型 | 风险 | 详情 |
|------|----------|----------|------|------|
| 2026-02-27 | 变更审计:营业日分割规则 PRD 同步检查 + 全栈集成收口 | bugfix | 低 | [链接](changes/2026-02-27__biz-day-cutoff-prd-sync-check.md) |
| 2026-02-14 | 审计记录:删除 DWD 层 dwd_settlement_head_ex.settle_list 冗余列 | 清理 | 未知 | [链接](changes/2026-02-14__drop-dwd-settle-list.md) |
| 2026-02-14 | 审计记录:删除 ODS 层 settlelist 冗余列 | bugfix | 未知 | [链接](changes/2026-02-14__drop-ods-settlelist.md) |
@@ -121,6 +177,7 @@
| 日期 | 需求摘要 | 变更类型 | 风险 | 详情 |
|------|----------|----------|------|------|
| 2026-02-27 | 变更审计:营业日分割规则 PRD 同步检查 + 全栈集成收口 | bugfix | 低 | [链接](changes/2026-02-27__biz-day-cutoff-prd-sync-check.md) |
| 2026-02-14 | 审计记录DWS 基类 bugfix — 绩效档位兜底 + safe_decimal 异常捕获 | bugfix | 未知 | [链接](changes/2026-02-14__dws-bugfix-tier-safedecimal.md) |
| 2026-02-13 | 审计记录移除旧版指数RECALL/INTIMACY+ ML last-touch 清理 | bugfix | 低 | [链接](changes/2026-02-13__remove-legacy-index-cleanup.md) |
@@ -134,7 +191,23 @@
| 日期 | 需求摘要 | 变更类型 | 风险 | 详情 |
|------|----------|----------|------|------|
| 2026-03-08 | 变更审计记录P5 AI 集成需求审视 — 7 项歧义修补 + category 枚举对齐 | 文档 | 未知 | [链接](changes/2026-03-08__p5-ai-spec-review-category-enum-align.md) |
| 2026-03-07 | 变更审计记录TASK 3 项目标签计算逻辑 — 文档与配置同步 | 文档 | 低 | [链接](changes/2026-03-07__task3-project-tag-docs-sync.md) |
| 2026-03-06 | 变更审计记录:修复 RecordingAPIClient 缺少 post 方法 | bugfix | 极低 | [链接](changes/2026-03-06__fix-api-client-post-method.md) |
| 2026-03-06 | 变更审计记录:修复 DatabaseOperations 缺少 _dsn 属性导致 DWD 并行装载全部失败 | bugfix | 极低 | [链接](changes/2026-03-06__fix-db-operations-dsn-proxy.md) |
| 2026-03-04 | 变更审计记录全栈累积变更营业日配置、WebSocket 日志、微信认证、仓库清理) | 重构 | 高 | [链接](changes/2026-03-04__fullstack-accumulated-changes.md) |
| 2026-03-03 | 变更审计记录:微信小程序开发调试面板 | 文档 | 低 | [链接](changes/2026-03-03__miniprogram-dev-debug-panel.md) |
| 2026-03-02 | 变更审计:合并 ETL Hook 为统一分析入口 | 文档 | 未知 | [链接](changes/2026-03-02__etl-unified-analysis-hook-merge.md) |
| 2026-03-02 | SPI 基数校准改用非零样本中位数 | 功能 | 未知 | [链接](changes/2026-03-02__spi-calibration-nonzero-median.md) |
| 2026-03-01 | 审计记录DWD 清理 + ODS 商品销售修复 + dim_staff_ex 修复 | bugfix | 未知 | [链接](changes/2026-03-01__dwd-cleanup-ods-fix-dim-staff-repair.md) |
| 2026-03-01 | 变更审计DWS numeric 精度扩展 + ODS 库存 siteid 注入 | 文档 | 低 | [链接](changes/2026-03-01__dws-numeric-precision-ods-siteid-fix.md) |
| 2026-02-28 | 变更审计记录:多模块累积变更(营业日/核心业务/认证/ETL DWS 重构/参考文档合并) | 重构 | 未知 | [链接](changes/2026-02-28__multi-module-accumulated-changes.md) |
| 2026-02-27 | 变更审计:营业日分割规则 PRD 同步检查 + 全栈集成收口 | bugfix | 低 | [链接](changes/2026-02-27__biz-day-cutoff-prd-sync-check.md) |
| 2026-02-27 | 审计记录P4 小程序核心业务路由 + 触发器注册 | 重构 | 未知 | [链接](changes/2026-02-27__p4-core-business-routes-triggers.md) |
| 2026-02-26 | 审计记录ETL Bug 修复 — dim_staff_ex 列映射 + assistant_daily table_area_name | bugfix | 未知 | [链接](changes/2026-02-26__etl-bugfix-dim-staff-rankname-assistant-daily-table-area.md) |
| 2026-02-26 | 变更审计P1/P2/P3 全栈集成DB 基础 + ETL DWS 扩展 + 小程序鉴权) | bugfix | 低 | [链接](changes/2026-02-26__p1-p2-p3-fullstack-integration.md) |
| 2026-02-26 | 审计记录维客线索重构member_birthday_manual → member_retention_clue | 重构 | 未知 | [链接](changes/2026-02-26__retention-clue-refactor.md) |
| 2026-02-26 | 审计记录root-file — .gitignore 更新与 H5 UI / 临时文件清理 | 重构 | 高 | [链接](changes/2026-02-26__root-file-gitignore-h5ui-cleanup.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) |
@@ -166,6 +239,7 @@
| 日期 | 需求摘要 | 变更类型 | 风险 | 详情 |
|------|----------|----------|------|------|
| 2026-03-08 | 变更审计记录P5 AI 集成需求审视 — 7 项歧义修补 + category 枚举对齐 | 文档 | 未知 | [链接](changes/2026-03-08__p5-ai-spec-review-category-enum-align.md) |
| 2026-02-15 | 变更审计记录Change Audit Record | 文档 | 极低 | [链接](changes/2026-02-15__audit-consolidation-doc-reorg.md) |
| 2026-02-15 | 审计记录docs/bd_manual + docs/dictionary → docs/database 合并 | 清理 | 极低 | [链接](changes/2026-02-15__docs-database-merge.md) |
| 2026-02-15 | 审计记录docs/index + docs/开发笔记 清理与路径整合 | 清理 | 低 | [链接](changes/2026-02-15__docs-devnotes-index-cleanup.md) |
@@ -203,6 +277,8 @@
| 日期 | 需求摘要 | 变更类型 | 风险 | 详情 |
|------|----------|----------|------|------|
| 2026-03-08 | 变更审计记录P5 AI 集成需求审视 — 7 项歧义修补 + category 枚举对齐 | 文档 | 未知 | [链接](changes/2026-03-08__p5-ai-spec-review-category-enum-align.md) |
| 2026-03-02 | 变更审计:合并 ETL Hook 为统一分析入口 | 文档 | 未知 | [链接](changes/2026-03-02__etl-unified-analysis-hook-merge.md) |
| 2026-02-15 | 审计记录docs/bd_manual + docs/dictionary → docs/database 合并 | 清理 | 极低 | [链接](changes/2026-02-15__docs-database-merge.md) |
| 2026-02-15 | 审计记录docs/index + docs/开发笔记 清理与路径整合 | 清理 | 低 | [链接](changes/2026-02-15__docs-devnotes-index-cleanup.md) |
| 2026-02-14 | 审计记录API vs ODS 比对 v3-fixed | 文档 | 极低 | [链接](changes/2026-02-14__api-ods-comparison-v3-fixed.md) |
@@ -223,6 +299,7 @@
| 日期 | 需求摘要 | 变更类型 | 风险 | 详情 |
|------|----------|----------|------|------|
| 2026-02-27 | 变更审计:营业日分割规则 PRD 同步检查 + 全栈集成收口 | bugfix | 低 | [链接](changes/2026-02-27__biz-day-cutoff-prd-sync-check.md) |
| 2026-02-14 | 审计记录:废弃独立 ODS/DWD 任务代码清理 + 文档同步 | bugfix | 未知 | [链接](changes/2026-02-14__legacy-ods-dwd-cleanup.md) |
| 2026-02-13 | 审计记录移除旧版指数RECALL/INTIMACY+ ML last-touch 清理 | bugfix | 低 | [链接](changes/2026-02-13__remove-legacy-index-cleanup.md) |
@@ -230,4 +307,11 @@
| 日期 | 需求摘要 | 变更类型 | 风险 | 详情 |
|------|----------|----------|------|------|
| 2026-02-27 | 变更审计:营业日分割规则 PRD 同步检查 + 全栈集成收口 | bugfix | 低 | [链接](changes/2026-02-27__biz-day-cutoff-prd-sync-check.md) |
| 2026-02-15 | 变更审计记录Change Audit Record | 文档 | 极低 | [链接](changes/2026-02-15__audit-consolidation-doc-reorg.md) |
### 配置
| 日期 | 需求摘要 | 变更类型 | 风险 | 详情 |
|------|----------|----------|------|------|
| 2026-02-27 | 变更审计:营业日分割规则 PRD 同步检查 + 全栈集成收口 | bugfix | 低 | [链接](changes/2026-02-27__biz-day-cutoff-prd-sync-check.md) |

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,203 @@
# BD 手册assistant_trash_event 清理 + ODS_STORE_GOODS_SALES 修复 + dim_staff_ex 修复 + DWS 精度扩展 + ODS 库存 siteid 注入
> 日期2026-03-01
> Prompt 摘要:清理 assistant_trash_event 残留代码/DDL 文档;修复 ODS_STORE_GOODS_SALES 窗口配置以恢复商品销售数据拉取;修复 dim_staff_ex FACT_MAPPINGS 列名映射错误DWS 层 7 个 ratio/margin 字段 numeric 精度扩展P1ODS goods_stock_summary 加 siteid 列 + DWD 映射补全P2
> 直接原因:联调发现的 P1/P2 问题 + 三个独立问题的批量修复
---
## 一、变更说明
### 1.1 assistant_trash_event 残留清理
`dwd.dwd_assistant_trash_event``dwd.dwd_assistant_trash_event_ex` 已于 2026-02-22 DROP
上游 ODS 表 `ods.assistant_cancellation_records` 同步 DROP。本次清理残留的代码引用和 DDL 文档。
**DDL 文档删除项:**
| 文件 | 删除内容 |
|------|---------|
| `etl_feiqiu__ods.sql` | PK 约束 `assistant_cancellation_records_pkey`,索引 `idx_assistant_cancellation_records_fetched_at_*`2 条) |
| `etl_feiqiu__dwd.sql` | PK 约束 `dwd_assistant_trash_event_pkey``dwd_assistant_trash_event_ex_pkey`,索引 `idx_dwd_assistant_trash_event_*`2 条) |
| `dwd-amount-duration-calibration.md` | 章节 2.11(助教废除事件主表)、存疑字段 #15、数据新鲜度行 |
**代码删除项(前序已完成):**
- `utils/json_store.py` — API 路径映射
- `tasks/utility/manual_ingest_task.py` — FILE_MAPPING / TABLE_SPECS
- `quality/consistency_checker.py` — ODS_TABLE_TO_JSON_FILE / ODS_TABLE_TO_TASK_CODE
- `scripts/refresh_json_and_audit.py` — ACTUAL_LIST_KEY
- `scripts/run_compare_v3.py` / `run_compare_v3_fixed.py` — TABLES 列表
### 1.2 ODS_STORE_GOODS_SALES 窗口配置修复
**问题:** `ods_tasks.py``ODS_STORE_GOODS_SALES``requires_window=False`,导致 API `/TenantGoods/GetGoodsSalesList` 不传 `startTime/endTime` 参数,始终返回 0 条记录。
**修复:**
- `requires_window``True`
- 新增 `time_fields=("startTime", "endTime")`
**数据恢复:** 以 30 天窗口切分,回填 2025-07-07 ~ 2026-03-01ODS 新增 26,759 条DWD 层 `dwd_store_goods_sale` 从 17,563 → 26,759 条,时间范围延伸至 2026-02-25。
### 1.3 dim_staff_ex FACT_MAPPINGS 列名修复
**问题:** `dwd_load_task.py``dim_staff_ex` 的 FACT_MAPPINGS 使用驼峰列名(`cashierpointid``groupid` 等),但 ODS 表 `staff_info_master` 实际列名为下划线风格(`cashier_point_id``group_id` 等),导致 SCD2 合并 SQL 执行报错,整表被跳过。
**修复映射:**
| DWD 列 | 修复前(错误) | 修复后(正确) |
|--------|---------------|---------------|
| `cashier_point_id` | `cashierpointid` | `cashier_point_id` |
| `cashier_point_name` | `cashierpointname` | `cashier_point_name` |
| `group_id` | `groupid` | `group_id` |
| `group_name` | `groupname` | `group_name` |
| `system_user_id` | `systemuserid` | `system_user_id` |
| `tenant_org_id` | `tenantorgid` | `tenant_org_id` |
| `user_roles` | `userroles` | `user_roles` |
**数据恢复:** DWD 装载后 `dim_staff_ex` 从 0 行 → 15 行。
### 1.4 [P1] DWS 层 numeric 精度扩展(举一反三)
**问题:** `dws.dws_assistant_finance_analysis.gross_margin` 定义为 `numeric(5,4)`,只能存 ±0.9999。当 `cost_daily > revenue_total`(亏损场景),`gross_margin = gross_profit / revenue_total` 可能 < -1导致 INSERT 溢出报错。举一反三排查发现 7 个同类风险字段。
**修复:**
| 表 | 字段 | 修复前 | 修复后 |
|----|------|--------|--------|
| `dws.cfg_performance_tier` | `bonus_deduction_ratio` | `numeric(5,4)` | `numeric(7,4)` |
| `dws.dws_assistant_finance_analysis` | `gross_margin` | `numeric(5,4)` | `numeric(7,4)` |
| `dws.dws_assistant_recharge_commission` | `commission_ratio` | `numeric(5,4)` | `numeric(7,4)` |
| `dws.dws_assistant_salary_calc` | `bonus_deduction_ratio` | `numeric(5,4)` | `numeric(7,4)` |
| `dws.dws_finance_discount_detail` | `discount_ratio` | `numeric(5,4)` | `numeric(7,4)` |
| `dws.dws_finance_income_structure` | `income_ratio` | `numeric(5,4)` | `numeric(7,4)` |
| `dws.dws_member_assistant_intimacy` | `burst_multiplier` | `numeric(6,4)` | `numeric(7,4)` |
**依赖视图处理:** 7 个 `app.v_dws_*` RLS 视图先 DROP 再重建。
**Python 防御:** `assistant_finance_task.py``gross_margin` 计算加 clamp 到 ±999.9999。
**迁移脚本:** `db/etl_feiqiu/migrations/20260301_dws_numeric_precision_fix.sql`
### 1.5 [P2] ODS goods_stock_summary 加 siteid + DWD 映射补全
**问题:** `dwd.dwd_goods_stock_summary` DDL 定义了 `site_id bigint``tenant_id bigint`,但 FACT_MAPPINGS 缺少映射,导致 DWD 层 site_id 始终为 NULL。
**根因分析:**
- API `GetGoodsStockReport` 返回的记录不含 `siteId`/`tenantId`(已从 JSON 缓存确认)
- ODS 表 `ods.goods_stock_summary` 也没有 `siteid`
- 请求参数中有 `siteId`,但 ODS 入库时未注入到记录中
**修复(三层联动):**
1. ODS 表加列:`ALTER TABLE ods.goods_stock_summary ADD COLUMN siteid bigint`
2. ODS 入库通用注入:`_insert_records_schema_aware` 中,当 ODS 表有 `siteid` 列但记录不含时,从 `app.store_id` 配置注入
3. DWD FACT_MAPPINGS 补映射:`("site_id", '"siteid"', "bigint")`
4. 已有数据回填:从 `ods.goods_stock_movements` 推断 siteid 回填 3216 条
**迁移脚本:** `db/etl_feiqiu/migrations/20260301_ods_goods_stock_summary_add_siteid.sql`
---
## 二、兼容性影响
| 子系统 | 影响 |
|--------|------|
| ETL | `assistant_trash_event` 相关任务已无代码引用,无影响;`ODS_STORE_GOODS_SALES` 恢复正常窗口拉取;`dim_staff_ex` 恢复正常 SCD2 装载DWS ratio 字段精度扩展后不再溢出ODS 库存汇总入库时自动注入 siteid |
| 后端 API | 7 个 `app.v_dws_*` RLS 视图已重建,字段类型从 numeric(5,4) 变为 numeric(7,4)API 返回值精度不变(仍为 4 位小数),无破坏性影响 |
| 小程序 | 无影响 |
| 管理后台 | 商品销售相关报表数据将恢复完整;库存汇总将携带 site_id |
---
## 三、回滚策略
### 3.1 assistant_trash_event
纯文档清理,无需回滚。如需恢复,从 git 历史还原对应行即可。
### 3.2 ODS_STORE_GOODS_SALES
```python
# 回滚 ods_tasks.py
requires_window=True requires_window=False
# 删除 time_fields=("startTime", "endTime") 行
```
### 3.3 dim_staff_ex
```python
# 回滚 dwd_load_task.py FACT_MAPPINGS
("cashier_point_id", "cashier_point_id", "bigint") ("cashier_point_id", "cashierpointid", "bigint")
# 其余 6 个字段同理恢复驼峰写法
```
如需清空已装载数据:`TRUNCATE dwd.dim_staff_ex;`
### 3.4 [P1] DWS numeric 精度
```sql
-- 回滚脚本db/etl_feiqiu/migrations/20260301_dws_numeric_precision_fix_rollback.sql
-- 注意:如果已有数据超出原精度范围(如 gross_margin > 0.9999),回滚会失败
-- 需先清理超范围数据:
UPDATE dws.dws_assistant_finance_analysis SET gross_margin = LEAST(0.9999, GREATEST(-0.9999, gross_margin));
-- 然后执行回滚(同样需要先 DROP 再重建视图)
```
Python 回滚:删除 `assistant_finance_task.py` 中的 clamp 行。
### 3.5 [P2] ODS siteid 列
```sql
-- ODS 列不可逆删除(已有数据依赖),但可置 NULL
UPDATE ods.goods_stock_summary SET siteid = NULL;
-- DWD FACT_MAPPINGS 回滚:删除 ("site_id", '"siteid"', "bigint") 行
-- ODS 入库注入回滚:删除 ods_tasks.py 中 "通用 siteid 注入" 代码块
```
---
## 四、验证 SQL
```sql
-- 1. 确认 assistant_trash_event 表已不存在
SELECT count(*) FROM information_schema.tables
WHERE table_schema = 'dwd' AND table_name LIKE '%assistant_trash_event%';
-- 预期0
-- 2. 确认 ODS 商品销售数据已回填
SELECT count(*) as cnt, min(create_time) as min_time, max(create_time) as max_time
FROM ods.store_goods_sales_records WHERE fetched_at IS NOT NULL;
-- 预期cnt > 40000, max_time >= 2026-02-25
-- 3. 确认 DWD 商品销售数据已更新
SELECT count(*) as cnt, max(create_time) as max_time FROM dwd.dwd_store_goods_sale;
-- 预期cnt > 25000, max_time >= 2026-02-25
-- 4. 确认 dim_staff_ex 已有数据
SELECT count(*) as cnt FROM dwd.dim_staff_ex WHERE scd2_is_current = 1;
-- 预期cnt = 15与 dim_staff 一致)
-- 5. 确认 dim_staff_ex 关键字段非全 NULL
SELECT count(*) as has_system_user FROM dwd.dim_staff_ex
WHERE scd2_is_current = 1 AND system_user_id IS NOT NULL;
-- 预期:> 0
-- 6. [P1] 确认 DWS ratio 字段精度已扩展
SELECT table_name, column_name, numeric_precision, numeric_scale
FROM information_schema.columns
WHERE table_schema = 'dws'
AND column_name IN ('gross_margin', 'bonus_deduction_ratio', 'commission_ratio',
'discount_ratio', 'income_ratio', 'burst_multiplier')
ORDER BY table_name;
-- 预期:所有行 numeric_precision=7, numeric_scale=4
-- 7. [P1] 确认 RLS 视图已重建
SELECT table_schema || '.' || table_name FROM information_schema.views
WHERE table_schema = 'app'
AND table_name IN ('v_cfg_performance_tier', 'v_dws_assistant_finance_analysis',
'v_dws_assistant_recharge_commission', 'v_dws_assistant_salary_calc',
'v_dws_finance_discount_detail', 'v_dws_finance_income_structure',
'v_dws_member_assistant_intimacy');
-- 预期7 行
-- 8. [P2] 确认 ODS goods_stock_summary 有 siteid 列且已回填
SELECT count(*) as total, count(siteid) as filled, count(DISTINCT siteid) as distinct_sites
FROM ods.goods_stock_summary;
-- 预期filled = total, distinct_sites = 1
-- 9. [P2] 确认 DWD FACT_MAPPINGS 生效(下次 DWD_LOAD 后验证)
SELECT count(*) as has_site_id FROM dwd.dwd_goods_stock_summary WHERE site_id IS NOT NULL;
-- 预期:下次 ETL 运行后 > 0当前可能仍为 0需重跑 DWD_LOAD
```

View File

@@ -0,0 +1,166 @@
# BD_Manualbiz Schema AI 表(对话记录 + 消息 + 缓存)
> 目标库:`test_zqyy_app`(通过 `APP_DB_DSN` 连接)
> 迁移脚本:`db/zqyy_app/migrations/2026-03-08__create_ai_tables.sql`
> 关联 SPEC`05-miniapp-ai-integration`P5 AI 集成层)
---
## 1. 变更说明
### 新增表3 张)
| # | 表名 | 用途 | 字段数 |
|---|------|------|--------|
| 1 | `biz.ai_conversations` | AI 对话记录:每次 AI 调用(用户主动或系统自动)创建一条 | 8 |
| 2 | `biz.ai_messages` | AI 消息记录:对话中的每条消息(输入/输出/系统) | 6 |
| 3 | `biz.ai_cache` | AI 应用缓存:各应用的结构化输出结果 | 9 |
### 表字段明细
#### biz.ai_conversations8 字段)
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | BIGSERIAL | PK | 自增主键 |
| `user_id` | VARCHAR(50) | NOT NULL | 用户 ID系统自动调用时为 `system` |
| `nickname` | VARCHAR(100) | NOT NULL DEFAULT '' | 用户昵称 |
| `app_id` | VARCHAR(30) | NOT NULL | 应用标识app1_chat / app2_finance / app3_clue / app4_analysis / app5_tactics / app6_note / app7_customer / app8_consolidation |
| `site_id` | BIGINT | NOT NULL | 门店 ID多门店隔离 |
| `source_page` | VARCHAR(100) | 可空 | 来源页面标识 |
| `source_context` | JSONB | 可空 | 页面上下文 JSON |
| `created_at` | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 创建时间 |
#### biz.ai_messages6 字段)
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | BIGSERIAL | PK | 自增主键 |
| `conversation_id` | BIGINT | NOT NULL, FK → `biz.ai_conversations(id)` ON DELETE CASCADE | 关联对话 |
| `role` | VARCHAR(10) | NOT NULL, CHECK IN ('user', 'assistant', 'system') | 消息角色 |
| `content` | TEXT | NOT NULL | 消息内容 |
| `tokens_used` | INTEGER | 可空 | 本条消息消耗的 token 数 |
| `created_at` | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 创建时间 |
#### biz.ai_cache9 字段)
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | BIGSERIAL | PK | 自增主键 |
| `cache_type` | VARCHAR(30) | NOT NULL, CHECK 7 个枚举值 | 缓存类型(见下方枚举) |
| `site_id` | BIGINT | NOT NULL | 门店 ID多门店隔离 |
| `target_id` | VARCHAR(100) | NOT NULL | 目标 ID含义因 cache_type 而异 |
| `result_json` | JSONB | NOT NULL | 结构化输出结果 |
| `score` | INTEGER | 可空 | 评分:仅应用 6 使用1-10 分) |
| `triggered_by` | VARCHAR(100) | 可空 | 触发来源标识 |
| `created_at` | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 创建时间 |
| `expires_at` | TIMESTAMPTZ | 可空 | 可选过期时间 |
### cache_type 枚举值与 target_id 约定
| cache_type | target_id 格式 | 示例 |
|---|---|---|
| `app2_finance` | 时间维度编码 | `this_month`, `last_week` |
| `app3_clue` | member_id | `12345` |
| `app4_analysis` | `{assistant_id}_{member_id}` | `100_12345` |
| `app5_tactics` | `{assistant_id}_{member_id}` | `100_12345` |
| `app6_note_analysis` | member_id | `12345` |
| `app7_customer_analysis` | member_id | `12345` |
| `app8_clue_consolidated` | member_id | `12345` |
### 约束与索引
| 表 | 约束/索引名 | 类型 | 说明 |
|----|-----------|------|------|
| `ai_conversations` | `idx_ai_conv_user_site` | INDEX | `(user_id, site_id, created_at DESC)` — 用户历史对话列表查询 |
| `ai_conversations` | `idx_ai_conv_app_site` | INDEX | `(app_id, site_id, created_at DESC)` — 按应用查询对话 |
| `ai_messages` | FK `conversation_id` | FK | → `biz.ai_conversations(id)` ON DELETE CASCADE |
| `ai_messages` | `chk_ai_msg_role` | CHECK | `role IN ('user', 'assistant', 'system')` |
| `ai_messages` | `idx_ai_msg_conv` | INDEX | `(conversation_id, created_at)` — 对话消息列表 |
| `ai_cache` | `chk_ai_cache_type` | CHECK | 7 个枚举值 |
| `ai_cache` | `idx_ai_cache_lookup` | INDEX | `(cache_type, site_id, target_id, created_at DESC)` — 查询最新缓存 |
| `ai_cache` | `idx_ai_cache_cleanup` | INDEX | `(cache_type, site_id, target_id, created_at)` — 清理超限记录ASC 排序便于删除最旧) |
---
## 2. 兼容性影响
| 组件 | 影响 |
|------|------|
| ETL 任务 | 无影响。AI 表属于 `biz` Schema不参与 ETL 流程 |
| 后端 API | 直接依赖。P5 AI 模块(`apps/backend/app/ai/`将基于这三张表实现对话持久化、缓存读写、SSE 流式对话等功能 |
| 小程序 | 间接依赖。小程序通过后端 AI API 间接使用对话和缓存数据 |
| 管理后台 | 暂无影响。后续可能增加 AI 调用统计和缓存管理界面 |
| `member_retention_clue` | 间接关联。App8维客线索整理的结果同时写入 `ai_cache``member_retention_clue` 表 |
| 现有 `biz` Schema | 兼容。仅新增 3 张表不修改已有对象coach_tasks、notes、trigger_jobs 等) |
---
## 3. 回滚策略
按逆序 DROPCASCADE 处理外键依赖):
```sql
-- 删除索引
DROP INDEX IF EXISTS biz.idx_ai_cache_cleanup;
DROP INDEX IF EXISTS biz.idx_ai_cache_lookup;
DROP INDEX IF EXISTS biz.idx_ai_msg_conv;
DROP INDEX IF EXISTS biz.idx_ai_conv_app_site;
DROP INDEX IF EXISTS biz.idx_ai_conv_user_site;
-- 删除表ai_messages 依赖 ai_conversations需先删或用 CASCADE
DROP TABLE IF EXISTS biz.ai_cache CASCADE;
DROP TABLE IF EXISTS biz.ai_messages CASCADE;
DROP TABLE IF EXISTS biz.ai_conversations CASCADE;
```
注意:
- `ai_messages` 通过 FK 依赖 `ai_conversations`CASCADE 会级联删除消息
- 如果表中已有数据,需先备份再执行回滚
- 回滚不会删除 `biz` Schema 本身
---
## 4. 验证 SQL
```sql
-- 1. 验证 3 张 AI 表全部存在
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'biz'
AND table_name IN ('ai_conversations', 'ai_messages', 'ai_cache')
ORDER BY table_name;
-- 预期:返回 3 行ai_cache, ai_conversations, ai_messages
-- 2. 验证 ai_conversations 字段数量和关键字段
SELECT column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_schema = 'biz' AND table_name = 'ai_conversations'
ORDER BY ordinal_position;
-- 预期:返回 8 行,包含 id/user_id/nickname/app_id/site_id/source_page/source_context/created_at
-- 3. 验证 ai_messages 的外键和 CHECK 约束
SELECT conname, contype, pg_get_constraintdef(oid) AS constraint_def
FROM pg_constraint
WHERE conrelid = 'biz.ai_messages'::regclass
ORDER BY conname;
-- 预期:包含 chk_ai_msg_roleCHECK role IN ...)和 ai_messages_conversation_id_fkeyFK → ai_conversations
-- 4. 验证 ai_cache 的 CHECK 约束7 个枚举值)
SELECT conname, pg_get_constraintdef(oid) AS constraint_def
FROM pg_constraint
WHERE conrelid = 'biz.ai_cache'::regclass AND contype = 'c';
-- 预期:返回 1 行 chk_ai_cache_type包含 7 个枚举值
-- 5. 验证索引全部存在5 个)
SELECT indexname
FROM pg_indexes
WHERE schemaname = 'biz'
AND indexname IN (
'idx_ai_conv_user_site', 'idx_ai_conv_app_site',
'idx_ai_msg_conv',
'idx_ai_cache_lookup', 'idx_ai_cache_cleanup'
)
ORDER BY indexname;
-- 预期:返回 5 行
```

View File

@@ -94,6 +94,7 @@
| 日期 | 字段 | 修正内容 |
|------|------|---------|
| 2026-02-20 | `site_assistant_id` | ODS 源从 `order_assistant_id`(订单级 ID修正为 `site_assistant_id`(助教档案 ID |
| 2026-02-26 | (下游)`table_area_name` | DWS 任务 `_extract_service_records()` 原错误引用 `asl.table_area_name`(本表无此列),改为 JOIN `dwd.dim_table``site_table_area_name`。详见 `BD_Manual_fix_dws_assistant_daily_table_area.md` |
---

View File

@@ -0,0 +1,186 @@
# BD_Manualdws.biz_date() 函数与物化视图营业日重建
> 变更类型:新增函数 + 重建物化视图
> Schema`dws`
> 迁移脚本 1`db/etl_feiqiu/migrations/2026-02-27__add_biz_date_function.sql`
> 迁移脚本 2`db/etl_feiqiu/migrations/2026-02-27__rebuild_mv_with_biz_date.sql`
> DDL 基线:`docs/database/ddl/etl_feiqiu__dws.sql`
> 关联需求Requirements 9.1, 9.2, 9.3, 9.4
---
## 1. 变更说明
### 1.1 新增函数:`dws.biz_date(timestamptz, int)`
| 属性 | 值 |
|------|-----|
| 函数签名 | `dws.biz_date(ts timestamptz, cutoff_hour int DEFAULT 8) RETURNS date` |
| 语言 | SQL |
| 特性 | `IMMUTABLE PARALLEL SAFE` |
| 逻辑 | `(ts - make_interval(hours => cutoff_hour))::date` |
| 等价 Python | `neozqyy_shared.datetime_utils.business_date()` |
将时间戳减去 `cutoff_hour` 小时后取日期,实现营业日归属。默认 `cutoff_hour=8`,即 08:00 前的时间戳归属前一天。
### 1.2 重建物化视图8 个)
`CURRENT_DATE` 替换为 `dws.biz_date(NOW())`,使物化视图的数据范围与 DWS 任务的营业日口径一致。
| 物化视图 | 原条件 | 新条件 |
|---------|--------|--------|
| `mv_dws_assistant_daily_detail_l1` | `stat_date >= (CURRENT_DATE - '1 day')` | `stat_date >= (dws.biz_date(NOW()) - '1 day')` |
| `mv_dws_assistant_daily_detail_l2` | `stat_date >= (CURRENT_DATE - '30 days')` | `stat_date >= (dws.biz_date(NOW()) - '30 days')` |
| `mv_dws_assistant_daily_detail_l3` | `stat_date >= (CURRENT_DATE - '90 days')` | `stat_date >= (dws.biz_date(NOW()) - '90 days')` |
| `mv_dws_assistant_daily_detail_l4` | `date_trunc('month', CURRENT_DATE) ± 6 mons` | `date_trunc('month', dws.biz_date(NOW())) ± 6 mons` |
| `mv_dws_finance_daily_summary_l1` | `stat_date >= (CURRENT_DATE - '1 day')` | `stat_date >= (dws.biz_date(NOW()) - '1 day')` |
| `mv_dws_finance_daily_summary_l2` | `stat_date >= (CURRENT_DATE - '30 days')` | `stat_date >= (dws.biz_date(NOW()) - '30 days')` |
| `mv_dws_finance_daily_summary_l3` | `stat_date >= (CURRENT_DATE - '90 days')` | `stat_date >= (dws.biz_date(NOW()) - '90 days')` |
| `mv_dws_finance_daily_summary_l4` | `date_trunc('month', CURRENT_DATE) ± 6 mons` | `date_trunc('month', dws.biz_date(NOW())) ± 6 mons` |
索引在重建后重新创建,结构不变。
---
## 2. 兼容性说明
| 影响范围 | 说明 |
|---------|------|
| ETL 任务 | `MvRefreshTask``DWS_MV_REFRESH_*`)执行 `REFRESH MATERIALIZED VIEW` 不受影响,视图定义变更对刷新逻辑透明 |
| 后端 API | 无直接影响。后端通过 DWS 表查询,物化视图仅用于加速查询 |
| 管理后台 | 无影响。前端不直接查询物化视图 |
| 小程序 | 无影响 |
| 字段映射 | 物化视图列结构不变(`SELECT *`),仅 WHERE 条件变更 |
| `biz_date()` 函数 | 标记为 `IMMUTABLE`,可安全用于索引表达式和物化视图定义 |
---
## 3. 回滚策略
### 3.1 回滚函数
```sql
DROP FUNCTION IF EXISTS dws.biz_date(timestamptz, int);
```
> 注意需先回滚物化视图3.2),否则依赖此函数的视图定义会阻止删除。
### 3.2 回滚物化视图(恢复自然日口径)
使用 `scripts/migrate/migrate_finalize.py` 中的原始定义重建:
```sql
BEGIN;
DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_assistant_daily_detail_l1;
DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_assistant_daily_detail_l2;
DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_assistant_daily_detail_l3;
DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_assistant_daily_detail_l4;
DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_finance_daily_summary_l1;
DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_finance_daily_summary_l2;
DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_finance_daily_summary_l3;
DROP MATERIALIZED VIEW IF EXISTS dws.mv_dws_finance_daily_summary_l4;
-- 用原始定义重建CURRENT_DATE 版本)
CREATE MATERIALIZED VIEW dws.mv_dws_assistant_daily_detail_l1 AS
SELECT * FROM dws.dws_assistant_daily_detail
WHERE stat_date >= (CURRENT_DATE - '1 day'::interval) WITH DATA;
CREATE MATERIALIZED VIEW dws.mv_dws_assistant_daily_detail_l2 AS
SELECT * FROM dws.dws_assistant_daily_detail
WHERE stat_date >= (CURRENT_DATE - '30 days'::interval) WITH DATA;
CREATE MATERIALIZED VIEW dws.mv_dws_assistant_daily_detail_l3 AS
SELECT * FROM dws.dws_assistant_daily_detail
WHERE stat_date >= (CURRENT_DATE - '90 days'::interval) WITH DATA;
CREATE MATERIALIZED VIEW dws.mv_dws_assistant_daily_detail_l4 AS
SELECT * FROM dws.dws_assistant_daily_detail
WHERE stat_date >= (date_trunc('month', CURRENT_DATE::timestamptz) - '6 mons'::interval)
AND stat_date < date_trunc('month', CURRENT_DATE::timestamptz) WITH DATA;
CREATE MATERIALIZED VIEW dws.mv_dws_finance_daily_summary_l1 AS
SELECT * FROM dws.dws_finance_daily_summary
WHERE stat_date >= (CURRENT_DATE - '1 day'::interval) WITH DATA;
CREATE MATERIALIZED VIEW dws.mv_dws_finance_daily_summary_l2 AS
SELECT * FROM dws.dws_finance_daily_summary
WHERE stat_date >= (CURRENT_DATE - '30 days'::interval) WITH DATA;
CREATE MATERIALIZED VIEW dws.mv_dws_finance_daily_summary_l3 AS
SELECT * FROM dws.dws_finance_daily_summary
WHERE stat_date >= (CURRENT_DATE - '90 days'::interval) WITH DATA;
CREATE MATERIALIZED VIEW dws.mv_dws_finance_daily_summary_l4 AS
SELECT * FROM dws.dws_finance_daily_summary
WHERE stat_date >= (date_trunc('month', CURRENT_DATE::timestamptz) - '6 mons'::interval)
AND stat_date < date_trunc('month', CURRENT_DATE::timestamptz) WITH DATA;
-- 重建索引
CREATE INDEX idx_mv_assistant_daily_l1 ON dws.mv_dws_assistant_daily_detail_l1 USING btree (site_id, stat_date, assistant_id);
CREATE INDEX idx_mv_assistant_daily_l2 ON dws.mv_dws_assistant_daily_detail_l2 USING btree (site_id, stat_date, assistant_id);
CREATE INDEX idx_mv_assistant_daily_l3 ON dws.mv_dws_assistant_daily_detail_l3 USING btree (site_id, stat_date, assistant_id);
CREATE INDEX idx_mv_assistant_daily_l4 ON dws.mv_dws_assistant_daily_detail_l4 USING btree (site_id, stat_date, assistant_id);
CREATE INDEX idx_mv_finance_daily_l1 ON dws.mv_dws_finance_daily_summary_l1 USING btree (site_id, stat_date);
CREATE INDEX idx_mv_finance_daily_l2 ON dws.mv_dws_finance_daily_summary_l2 USING btree (site_id, stat_date);
CREATE INDEX idx_mv_finance_daily_l3 ON dws.mv_dws_finance_daily_summary_l3 USING btree (site_id, stat_date);
CREATE INDEX idx_mv_finance_daily_l4 ON dws.mv_dws_finance_daily_summary_l4 USING btree (site_id, stat_date);
COMMIT;
```
---
## 4. 验证 SQL
### 4.1 确认 `biz_date()` 函数存在且行为正确
```sql
-- 08:00 前归属前一天
SELECT dws.biz_date('2026-01-15 07:59:59+08'::timestamptz) AS should_be_0114;
-- 预期2026-01-14
-- 08:00 起归属当天
SELECT dws.biz_date('2026-01-15 08:00:00+08'::timestamptz) AS should_be_0115;
-- 预期2026-01-15
-- 月末边界
SELECT dws.biz_date('2026-02-01 07:00:00+08'::timestamptz, 8) AS should_be_0131;
-- 预期2026-01-31
```
### 4.2 确认 8 个物化视图已重建且定义包含 `biz_date`
```sql
SELECT matviewname, definition LIKE '%biz_date%' AS uses_biz_date
FROM pg_matviews
WHERE schemaname = 'dws'
AND matviewname LIKE 'mv_dws_%'
ORDER BY matviewname;
-- 预期8 行uses_biz_date 全部为 true
```
### 4.3 确认物化视图索引完整
```sql
SELECT indexname, tablename
FROM pg_indexes
WHERE schemaname = 'dws'
AND tablename LIKE 'mv_dws_%'
ORDER BY indexname;
-- 预期8 个索引assistant_daily l1-l4 + finance_daily l1-l4
```
### 4.4 确认物化视图有数据(刷新后)
```sql
SELECT 'assistant_l1' AS mv, COUNT(*) FROM dws.mv_dws_assistant_daily_detail_l1
UNION ALL
SELECT 'assistant_l2', COUNT(*) FROM dws.mv_dws_assistant_daily_detail_l2
UNION ALL
SELECT 'assistant_l3', COUNT(*) FROM dws.mv_dws_assistant_daily_detail_l3
UNION ALL
SELECT 'assistant_l4', COUNT(*) FROM dws.mv_dws_assistant_daily_detail_l4
UNION ALL
SELECT 'finance_l1', COUNT(*) FROM dws.mv_dws_finance_daily_summary_l1
UNION ALL
SELECT 'finance_l2', COUNT(*) FROM dws.mv_dws_finance_daily_summary_l2
UNION ALL
SELECT 'finance_l3', COUNT(*) FROM dws.mv_dws_finance_daily_summary_l3
UNION ALL
SELECT 'finance_l4', COUNT(*) FROM dws.mv_dws_finance_daily_summary_l4;
```

View File

@@ -0,0 +1,215 @@
# BD_Manualbiz Schema 业务表(助教任务系统 + 备注系统 + 触发器调度)
> 目标库:`test_zqyy_app`(通过 `APP_DB_DSN` 连接)
> 迁移脚本:
> - `db/zqyy_app/migrations/2026-02-27__p4_create_biz_tables.sql`(建表)
> - `db/zqyy_app/migrations/2026-02-27__p4_seed_trigger_jobs.sql`(种子数据)
> 关联 SPEC`04-miniapp-core-business`P4 小程序核心业务模块)
---
## 1. 变更说明
### 新增表4 张)
| # | 表名 | 用途 | 字段数 |
|---|------|------|--------|
| 1 | `biz.coach_tasks` | 助教任务表:存储任务分配、状态、有效期、置顶、放弃原因等 | 15 |
| 2 | `biz.coach_task_history` | 任务变更历史表:记录任务关闭/新建/置顶/放弃的追溯链 | 9 |
| 3 | `biz.notes` | 统一备注表:通过 `type` 字段区分普通备注/回访备注/放弃原因,含星星评分 | 14 |
| 4 | `biz.trigger_jobs` | 触发器配置表:存储 cron/interval/event 三种触发方式的配置与执行状态 | 9 |
### 表字段明细
#### biz.coach_tasks15 字段)
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | BIGSERIAL | PK | 自增主键 |
| `site_id` | BIGINT | NOT NULL | 门店 ID多门店隔离 |
| `assistant_id` | BIGINT | NOT NULL | 助教 ID |
| `member_id` | BIGINT | NOT NULL | 客户 ID |
| `task_type` | VARCHAR(50) | NOT NULL | 任务类型:`high_priority_recall` / `priority_recall` / `follow_up_visit` / `relationship_building` |
| `status` | VARCHAR(20) | NOT NULL DEFAULT 'active' | 状态:`active` / `inactive` / `completed` / `abandoned` |
| `priority_score` | NUMERIC(5,2) | 可空 | 优先级分数,取 `max(WBI, NCI)` 快照 |
| `expires_at` | TIMESTAMPTZ | 可空 | 有效期时间戳NULL 表示无限期 |
| `is_pinned` | BOOLEAN | DEFAULT FALSE | 是否置顶 |
| `abandon_reason` | TEXT | 可空 | 放弃原因(放弃时必填) |
| `completed_at` | TIMESTAMPTZ | 可空 | 完成时间 |
| `completed_task_type` | VARCHAR(50) | 可空 | 完成时的任务类型快照 |
| `parent_task_id` | BIGINT | FK → `biz.coach_tasks(id)`,可空 | 父任务 ID自引用 |
| `created_at` | TIMESTAMPTZ | DEFAULT NOW() | 创建时间 |
| `updated_at` | TIMESTAMPTZ | DEFAULT NOW() | 更新时间 |
#### biz.coach_task_history9 字段)
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | BIGSERIAL | PK | 自增主键 |
| `task_id` | BIGINT | NOT NULL, FK → `biz.coach_tasks(id)` | 关联任务 |
| `action` | VARCHAR(50) | NOT NULL | 操作类型:`created` / `type_changed` / `pinned` / `abandoned` / `cancel_abandon` / `expired` / `completed` |
| `old_status` | VARCHAR(20) | 可空 | 变更前状态 |
| `new_status` | VARCHAR(20) | 可空 | 变更后状态 |
| `old_task_type` | VARCHAR(50) | 可空 | 变更前任务类型 |
| `new_task_type` | VARCHAR(50) | 可空 | 变更后任务类型 |
| `detail` | JSONB | 可空 | 附加详情(如放弃原因等) |
| `created_at` | TIMESTAMPTZ | DEFAULT NOW() | 记录时间 |
#### biz.notes14 字段)
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | BIGSERIAL | PK | 自增主键 |
| `site_id` | BIGINT | NOT NULL | 门店 ID |
| `user_id` | INTEGER | NOT NULL | 小程序用户 ID |
| `target_type` | VARCHAR(50) | NOT NULL | 目标类型(如 `member` |
| `target_id` | BIGINT | NOT NULL | 目标 ID如 member_id |
| `type` | VARCHAR(20) | NOT NULL DEFAULT 'normal' | 备注类型:`normal` / `follow_up` / `abandon_reason` |
| `content` | TEXT | NOT NULL | 备注内容 |
| `rating_service_willingness` | SMALLINT | CHECK (1-5),可空 | 再次服务意愿评分 |
| `rating_revisit_likelihood` | SMALLINT | CHECK (1-5),可空 | 再来店可能性评分 |
| `task_id` | BIGINT | FK → `biz.coach_tasks(id)`,可空 | 关联任务 |
| `ai_score` | SMALLINT | 可空 | AI 应用 6 评分P5 实现) |
| `ai_analysis` | TEXT | 可空 | AI 分析结果P5 实现) |
| `created_at` | TIMESTAMPTZ | DEFAULT NOW() | 创建时间 |
| `updated_at` | TIMESTAMPTZ | DEFAULT NOW() | 更新时间 |
#### biz.trigger_jobs9 字段)
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | SERIAL | PK | 自增主键 |
| `job_type` | VARCHAR(100) | NOT NULL | 任务类型标识,映射到 Python handler |
| `job_name` | VARCHAR(100) | NOT NULL, UNIQUE | 任务名称(唯一) |
| `trigger_condition` | VARCHAR(20) | NOT NULL | 触发方式:`cron` / `interval` / `event` |
| `trigger_config` | JSONB | NOT NULL | 触发配置cron 表达式 / 间隔秒数 / 事件名) |
| `last_run_at` | TIMESTAMPTZ | 可空 | 上次运行时间 |
| `next_run_at` | TIMESTAMPTZ | 可空 | 下次运行时间event 类型为 NULL |
| `status` | VARCHAR(20) | NOT NULL DEFAULT 'enabled' | 状态:`enabled` / `disabled` |
| `created_at` | TIMESTAMPTZ | DEFAULT NOW() | 创建时间 |
### 约束与索引
| 表 | 约束/索引名 | 类型 | 说明 |
|----|-----------|------|------|
| `coach_tasks` | `idx_coach_tasks_site_assistant_member_type` | UNIQUE INDEX (partial) | `(site_id, assistant_id, member_id, task_type) WHERE status = 'active'`,保证同一组合下活跃任务最多一条 |
| `coach_tasks` | `idx_coach_tasks_assistant_status` | INDEX | `(site_id, assistant_id, status)`,助教任务列表查询加速 |
| `coach_tasks` | FK `parent_task_id` | FK | → `biz.coach_tasks(id)`,自引用 |
| `coach_task_history` | FK `task_id` | FK | → `biz.coach_tasks(id)` |
| `notes` | `idx_notes_target` | INDEX | `(site_id, target_type, target_id)`,按目标查询备注加速 |
| `notes` | CHECK `rating_service_willingness` | CHECK | `BETWEEN 1 AND 5` |
| `notes` | CHECK `rating_revisit_likelihood` | CHECK | `BETWEEN 1 AND 5` |
| `notes` | FK `task_id` | FK | → `biz.coach_tasks(id)` |
| `trigger_jobs` | UNIQUE `job_name` | UNIQUE | 触发器名称唯一 |
### 种子数据4 条触发器配置)
| job_name | job_type | trigger_condition | trigger_config | next_run_at | 说明 |
|----------|----------|-------------------|----------------|-------------|------|
| `task_generator` | `task_generator` | `cron` | `{"cron_expression": "0 4 * * *"}` | 次日 04:00 | 每日凌晨 4:00 运行任务生成器 |
| `task_expiry_check` | `task_expiry_check` | `interval` | `{"interval_seconds": 3600}` | NOW() + 1h | 每小时检查过期任务 |
| `recall_completion_check` | `recall_completion_check` | `event` | `{"event_name": "etl_data_updated"}` | NULL | ETL 数据更新后触发召回完成检测 |
| `note_reclassify_backfill` | `note_reclassify_backfill` | `event` | `{"event_name": "recall_completed"}` | NULL | 召回完成后触发备注回溯重分类 |
---
## 2. 兼容性影响
| 组件 | 影响 |
|------|------|
| ETL 任务 | 无直接影响。`biz` Schema 表不参与 ETL 流程,但任务生成器通过 FDW 只读访问 ETL 库的 WBI/NCI/RS 指数数据 |
| 后端 API | 直接依赖。FastAPI 后端将基于这些表实现任务 CRUD`/api/xcx/tasks`)、备注 CRUD`/api/xcx/notes`)、触发器调度等功能 |
| 小程序 | 间接依赖。小程序通过后端 API 间接使用任务列表、备注功能 |
| 管理后台 | 暂无影响。后续可能增加任务监控和触发器管理界面 |
| FDW 配置 | 无影响。`fdw_etl` Schema 独立于 `biz`,任务生成器和召回检测器通过 FDW 只读查询 ETL 库 |
| `auth` Schema | 间接依赖。任务生成器通过 `auth.user_assistant_binding` 确定助教与小程序用户的映射关系 |
| `public` Schema | 无影响。`member_retention_clue` 表独立于本次变更 |
| 现有 `biz` Schema | 兼容。`biz` Schema 已由 P1 迁移脚本创建,本次仅在其中新增 4 张表,不修改已有对象 |
---
## 3. 回滚策略
按逆序 `DROP TABLE IF EXISTS CASCADE`(迁移脚本末尾已包含注释形式的回滚语句):
```sql
-- 先删除种子数据(如需保留表结构)
DELETE FROM biz.trigger_jobs
WHERE job_name IN (
'task_generator',
'task_expiry_check',
'recall_completion_check',
'note_reclassify_backfill'
);
-- 删除索引
DROP INDEX IF EXISTS biz.idx_notes_target;
DROP INDEX IF EXISTS biz.idx_coach_tasks_assistant_status;
DROP INDEX IF EXISTS biz.idx_coach_tasks_site_assistant_member_type;
-- 删除表按逆序CASCADE 处理外键依赖)
DROP TABLE IF EXISTS biz.trigger_jobs CASCADE;
DROP TABLE IF EXISTS biz.notes CASCADE;
DROP TABLE IF EXISTS biz.coach_task_history CASCADE;
DROP TABLE IF EXISTS biz.coach_tasks CASCADE;
```
注意:
- `CASCADE` 会级联删除依赖对象(外键引用的子表数据)
- 如果表中已有业务数据,需先备份再执行回滚
- 回滚不会删除 `biz` Schema 本身(由 P1 创建,其他表可能依赖)
---
## 4. 验证 SQL
```sql
-- 1. 验证 biz Schema 下 4 张业务表全部存在
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'biz'
AND table_name IN ('coach_tasks', 'coach_task_history', 'notes', 'trigger_jobs')
ORDER BY table_name;
-- 预期:返回 4 行coach_task_history, coach_tasks, notes, trigger_jobs
-- 2. 验证 coach_tasks 表字段数量和关键字段
SELECT column_name, data_type, is_nullable, column_default
FROM information_schema.columns
WHERE table_schema = 'biz' AND table_name = 'coach_tasks'
ORDER BY ordinal_position;
-- 预期:返回 15 行,包含 id/site_id/assistant_id/member_id/task_type/status 等
-- 3. 验证部分唯一索引存在
SELECT indexname, indexdef
FROM pg_indexes
WHERE schemaname = 'biz' AND indexname = 'idx_coach_tasks_site_assistant_member_type';
-- 预期:返回 1 行indexdef 包含 "WHERE ((status)::text = 'active'::text)"
-- 4. 验证 notes 表的 CHECK 约束(评分 1-5
SELECT conname, pg_get_constraintdef(oid) AS constraint_def
FROM pg_constraint
WHERE conrelid = 'biz.notes'::regclass AND contype = 'c';
-- 预期:返回 2 行,分别约束 rating_service_willingness 和 rating_revisit_likelihood 在 1-5 范围
-- 5. 验证种子数据4 条触发器配置
SELECT job_name, job_type, trigger_condition,
trigger_config->>'cron_expression' AS cron,
trigger_config->>'interval_seconds' AS interval_sec,
trigger_config->>'event_name' AS event
FROM biz.trigger_jobs
WHERE job_name IN ('task_generator', 'task_expiry_check', 'recall_completion_check', 'note_reclassify_backfill')
ORDER BY job_name;
-- 预期:返回 4 行
-- note_reclassify_backfill | event | recall_completed
-- recall_completion_check | event | etl_data_updated
-- task_expiry_check | interval| interval_seconds=3600
-- task_generator | cron | 0 4 * * *
-- 6. 验证查询索引存在
SELECT indexname
FROM pg_indexes
WHERE schemaname = 'biz'
AND indexname IN ('idx_coach_tasks_assistant_status', 'idx_notes_target')
ORDER BY indexname;
-- 预期:返回 2 行
```

View File

@@ -0,0 +1,88 @@
# BD_Manualdim_groupbuy_package_ex 新增团购详情字段
> 日期2026-03-05
> 涉及库:`etl_feiqiu` / `test_etl_feiqiu`
> 迁移脚本:`db/etl_feiqiu/migrations/2026-03-05__add_detail_fields_to_dim_groupbuy_package_ex.sql`
> 直接原因整合团购详情接口QueryPackageCouponInfo需在 DWD 扩展表中存储可用台区、助教服务、关联门店等维度信息
> Prompt 摘要etl-coupon-detail spec — 需求 4 验收标准 1
---
## 1. 变更说明
### 变更内容
`dwd.dim_groupbuy_package_ex` 表新增 4 个 JSONB 列,用于存储从团购详情接口提取的维度数据。
| Schema | 表 | 新增列 | 类型 | 说明 |
|--------|-----|--------|------|------|
| dwd | dim_groupbuy_package_ex | table_area_ids | JSONB | 可用台区 ID 列表(来自详情接口 tableAreaId |
| dwd | dim_groupbuy_package_ex | table_area_names | JSONB | 可用台区名称列表(来自详情接口 tableAreaNameList |
| dwd | dim_groupbuy_package_ex | assistant_services | JSONB | 助教服务关联(来自详情接口 packageCouponAssistants |
| dwd | dim_groupbuy_package_ex | groupon_site_infos | JSONB | 关联门店信息(来自详情接口 grouponSiteInfos |
所有列均为 NULLABLE使用 `ADD COLUMN IF NOT EXISTS` 确保幂等性。
### 数据来源
ODS 层 `ods.group_buy_package_details` 表(由 `ODS_GROUP_PACKAGE` 任务的详情拉取子流程写入),通过 `coupon_id = groupbuy_package_id` 关联后在 DWD 加载时合并。
---
## 2. 兼容性影响
| 组件 | 影响 | 说明 |
|------|------|------|
| ETL DWD 加载 | 需配合修改 | `dwd_load_task.py` 需新增 LEFT JOIN 逻辑从 ODS 详情表读取并映射到这 4 个字段Task 4.2/4.3 |
| ETL SCD2 | 自动兼容 | 新增 JSONB 字段自动纳入 `_is_row_changed` 变更检测 |
| 后端 API | 无影响 | 当前无接口直接查询 dim_groupbuy_package_ex 的详情字段 |
| 小程序 | 无影响 | 不直接使用 DWD 层表 |
| RLS 视图 | 无影响 | dim_groupbuy_package_ex 无 RLS 视图 |
---
## 3. 回滚策略
```sql
ALTER TABLE dwd.dim_groupbuy_package_ex
DROP COLUMN IF EXISTS table_area_ids,
DROP COLUMN IF EXISTS table_area_names,
DROP COLUMN IF EXISTS assistant_services,
DROP COLUMN IF EXISTS groupon_site_infos;
```
回滚后需同步撤销 `dwd_load_task.py` 中对应的 LEFT JOIN 和字段映射逻辑。
---
## 4. 验证 SQL
```sql
-- 1. 确认 4 个新列存在且类型正确
SELECT column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_schema = 'dwd'
AND table_name = 'dim_groupbuy_package_ex'
AND column_name IN ('table_area_ids', 'table_area_names', 'assistant_services', 'groupon_site_infos')
ORDER BY ordinal_position;
-- 预期4 行data_type 均为 'jsonb'is_nullable 均为 'YES'
-- 2. 确认列 COMMENT 已写入
SELECT a.attname AS column_name, d.description AS comment
FROM pg_catalog.pg_attribute a
JOIN pg_catalog.pg_class c ON a.attrelid = c.oid
JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
LEFT JOIN pg_catalog.pg_description d ON d.objoid = c.oid AND d.objsubid = a.attnum
WHERE n.nspname = 'dwd'
AND c.relname = 'dim_groupbuy_package_ex'
AND a.attname IN ('table_area_ids', 'table_area_names', 'assistant_services', 'groupon_site_infos')
ORDER BY a.attnum;
-- 预期4 行,每行 comment 非 NULL
-- 3. 确认表主键和索引未受影响
SELECT indexname, indexdef
FROM pg_indexes
WHERE schemaname = 'dwd'
AND tablename = 'dim_groupbuy_package_ex';
-- 预期:原有 4 个索引不变pkey + 3 个辅助索引)
```

View File

@@ -16,8 +16,8 @@
| 列名 | 类型 | 默认值 | 业务含义 | 取值范围/示例 |
|------|------|--------|---------|-------------|
| `contribution_id` | BIGINT (SERIAL) | nextval 序列 | 自增主键PK | 自增 |
| `site_id` | INTEGER NOT NULL | — | 门店 ID | 飞球门店 ID |
| `tenant_id` | INTEGER NOT NULL | — | 租户 ID | 飞球租户 ID |
| `site_id` | BIGINT NOT NULL | — | 门店 ID | 飞球门店 ID |
| `tenant_id` | BIGINT NOT NULL | — | 租户 ID | 飞球租户 ID |
| `assistant_id` | BIGINT NOT NULL | — | 助教 ID | 飞球助教 ID |
| `assistant_nickname` | VARCHAR(100) | NULL | 助教昵称 | 中文昵称 |
| `stat_date` | DATE NOT NULL | — | 统计日期 | `2025-01-15` |

View File

@@ -0,0 +1,94 @@
# BD_Manual修复 dim_staff_ex 列映射 rankname → rank_name
> 影响表:`dwd.dim_staff_ex`
> ODS 源表:`ods.staff_info_master`
> 修复日期2026-02-26
> 代码位置:`apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.py`
> 触发场景:`FLOW_API_FULL` 执行时 `DWD_LOAD_FROM_ODS` 阶段 dim_staff_ex 加载失败
---
## 1. 变更说明
DWD 加载任务 `DwdLoadTask` 中,`dwd.dim_staff_ex` 的列映射定义错误:
```python
# 修复前(错误)
("rank_name", "rankname", None)
# 修复后(正确)
("rank_name", "rank_name", None)
```
映射元组含义:`(dwd_列名, ods_源列名, 类型转换)`
ODS 表 `ods.staff_info_master` 的实际列名为 `rank_name`(带下划线),而非 `rankname`。此错误导致 PostgreSQL 报 `UndefinedColumn: 字段 "rankname" 不存在`dim_staff_ex 的 SCD2 合并在每个窗口段均失败(共 4 次)。
---
## 2. 兼容性影响
| 组件 | 影响 |
|------|------|
| ETL DWD 层 | dim_staff_ex 恢复正常加载rank_name 字段将正确从 ODS 映射 |
| DWS 层 | 无直接影响(当前无 DWS 任务依赖 dim_staff_ex.rank_name |
| 后端 API | 无影响(后端通过 FDW 读取,表结构未变) |
| 小程序 | 无影响 |
| DDL | 无变更,表结构不变 |
---
## 3. 回滚策略
此修复仅涉及 Python 代码中的列映射字符串,无 DDL 变更。
回滚步骤:
1.`dwd_load_task.py` 中 dim_staff_ex 映射恢复为 `("rank_name", "rankname", None)`
2. 注意:回滚后 dim_staff_ex 将再次无法加载 rank_name 字段
已加载的数据无需回滚——修复前该字段从未成功写入。
---
## 4. 验证 SQL
```sql
-- 验证 1确认 ODS 源表列名为 rank_name非 rankname
SELECT column_name
FROM information_schema.columns
WHERE table_schema = 'ods'
AND table_name = 'staff_info_master'
AND column_name IN ('rank_name', 'rankname');
-- 预期:仅返回 rank_name
-- 验证 2确认 DWD 目标表存在 rank_name 列
SELECT column_name
FROM information_schema.columns
WHERE table_schema = 'dwd'
AND table_name = 'dim_staff_ex'
AND column_name = 'rank_name';
-- 预期:返回 1 行
-- 验证 3修复后重跑 ETL检查 dim_staff_ex 是否有 rank_name 非空数据
SELECT COUNT(*) AS total,
COUNT(rank_name) AS has_rank_name
FROM dwd.dim_staff_ex
WHERE scd2_is_current = 1;
-- 预期has_rank_name > 0取决于上游数据是否有值
-- 验证 4对比 ODS 与 DWD 的 rank_name 一致性
SELECT s.id AS staff_id, s.rank_name AS ods_rank_name, d.rank_name AS dwd_rank_name
FROM ods.staff_info_master s
JOIN dwd.dim_staff_ex d ON s.id = d.staff_id AND d.scd2_is_current = 1
WHERE s.rank_name IS DISTINCT FROM d.rank_name
LIMIT 10;
-- 预期:修复并重跑后返回 0 行
```
---
## 5. 映射修正记录
| 日期 | 字段 | 修正内容 |
|------|------|---------|
| 2026-02-26 | `rank_name` | ODS 源列名从 `rankname` 修正为 `rank_name`,与 `ods.staff_info_master` DDL 一致 |

View File

@@ -0,0 +1,132 @@
# BD_Manual修复 DWS_ASSISTANT_DAILY 缺失 table_area_name 列
> 影响任务:`DWS_ASSISTANT_DAILY`
> 涉及表:`dwd.dwd_assistant_service_log`(读取)、`dwd.dim_table`(新增 JOIN、`dws.dws_assistant_daily`(写入)
> 修复日期2026-02-26
> 代码位置:`apps/etl/connectors/feiqiu/tasks/dws/assistant_daily_task.py` → `_extract_service_records()`
> 触发场景:`FLOW_API_FULL` 执行时 DWS 阶段首个任务失败,级联导致后续 13 个 DWS 任务全部 `InFailedSqlTransaction`
---
## 1. 变更说明
`_extract_service_records()` 方法的 SQL 原先直接从 `dwd.dwd_assistant_service_log` 读取 `asl.table_area_name`但该表实际不存在此列DDL 可确认)。
修复方式:通过 LEFT JOIN `dwd.dim_table` 获取台区名称。
```sql
-- 修复前(错误)
SELECT ...
asl.table_area_name,
...
FROM dwd.dwd_assistant_service_log asl
LEFT JOIN dwd.dwd_assistant_service_log_ex ex ...
-- 修复后(正确)
SELECT ...
COALESCE(dt.site_table_area_name, '') AS table_area_name,
...
FROM dwd.dwd_assistant_service_log asl
LEFT JOIN dwd.dwd_assistant_service_log_ex ex ...
LEFT JOIN dwd.dim_table dt
ON asl.site_table_id = dt.table_id
AND dt.scd2_is_current = 1
```
JOIN 条件说明:
- `asl.site_table_id = dt.table_id`:通过台桌 ID 关联维度表
- `dt.scd2_is_current = 1`:仅取当前有效的 SCD2 版本
- `COALESCE(..., '')`dim_table 无匹配时回退空字符串,避免 NULL 传播
---
## 2. 级联失败说明
此 bug 不仅导致 `DWS_ASSISTANT_DAILY` 本身失败,还因 DWS 阶段共享同一数据库连接且无逐任务 rollback使 psycopg2 进入 `InFailedSqlTransaction` 状态,后续 13 个 DWS/INDEX 任务全部失败:
- DWS_ASSISTANT_CUSTOMER, DWS_ASSISTANT_SALARY, DWS_ASSISTANT_FINANCE, DWS_ASSISTANT_MONTHLY
- DWS_MEMBER_CONSUMPTION, DWS_MEMBER_VISIT
- DWS_FINANCE_DAILY, DWS_FINANCE_RECHARGE, DWS_FINANCE_INCOME_STRUCTURE, DWS_FINANCE_DISCOUNT_DETAIL
- DWS_WINBACK_INDEX, DWS_NEWCONV_INDEX, DWS_RELATION_INDEX
修复此根因后,上述任务均可恢复正常执行。
---
## 3. 兼容性影响
| 组件 | 影响 |
|------|------|
| ETL DWS 层 | `dws_assistant_daily` 恢复正常写入,`table_area_name` 来源从不存在的列改为 dim_table 维度表 |
| 后续 DWS 任务 | 级联失败消除,所有 DWS 任务可正常执行 |
| 后端 API | 无影响DWS 聚合表结构未变) |
| 管理后台 | 助教日报表将正确显示台区名称 |
| DDL | 无变更,无新增表/列 |
---
## 4. 回滚策略
此修复仅涉及 Python 代码中的 SQL 查询,无 DDL 变更。
回滚步骤:
1.`assistant_daily_task.py``_extract_service_records()` 的 SQL 恢复为 `asl.table_area_name`,移除 `LEFT JOIN dwd.dim_table`
2. 注意:回滚后 DWS_ASSISTANT_DAILY 将再次失败
已写入的 DWS 数据如需回滚:
```sql
-- 清除修复后写入的 dws_assistant_daily 数据(按需执行)
DELETE FROM dws.dws_assistant_daily
WHERE stat_date >= '2025-11-01';
```
---
## 5. 验证 SQL
```sql
-- 验证 1确认 dwd_assistant_service_log 确实没有 table_area_name 列
SELECT column_name
FROM information_schema.columns
WHERE table_schema = 'dwd'
AND table_name = 'dwd_assistant_service_log'
AND column_name = 'table_area_name';
-- 预期:返回 0 行
-- 验证 2确认 dim_table 存在 site_table_area_name 列
SELECT column_name
FROM information_schema.columns
WHERE table_schema = 'dwd'
AND table_name = 'dim_table'
AND column_name = 'site_table_area_name';
-- 预期:返回 1 行
-- 验证 3检查 JOIN 关联覆盖率service_log 的 site_table_id 在 dim_table 中的匹配率)
SELECT
COUNT(*) AS total_records,
COUNT(dt.table_id) AS matched_dim_table,
ROUND(COUNT(dt.table_id)::numeric / NULLIF(COUNT(*), 0) * 100, 1) AS match_pct
FROM dwd.dwd_assistant_service_log asl
LEFT JOIN dwd.dim_table dt
ON asl.site_table_id = dt.table_id
AND dt.scd2_is_current = 1
WHERE asl.is_delete = 0;
-- 预期match_pct 接近 100%
-- 验证 4修复后重跑 ETL检查 dws_assistant_daily 是否有数据
SELECT stat_date, COUNT(*) AS rows
FROM dws.dws_assistant_daily
WHERE stat_date >= '2025-11-01'
GROUP BY stat_date
ORDER BY stat_date
LIMIT 10;
-- 预期:有数据行返回
```
---
## 6. 代码引用
- 修复文件:`apps/etl/connectors/feiqiu/tasks/dws/assistant_daily_task.py``_extract_service_records()`
- 关联 BD Manual`BD_Manual_assistant_service_records.md`dwd_assistant_service_log 字段映射文档)
- dim_table DDL`docs/database/ddl/etl_feiqiu__dwd.sql`(含 `site_table_area_name` 列定义)

View File

@@ -0,0 +1,92 @@
# BD_Manualods.group_buy_package_details 团购套餐详情表
> 日期2026-03-05
> 涉及库:`etl_feiqiu` / `test_etl_feiqiu`
> DDL 路径:`db/etl_feiqiu/ods/group_buy_package_details.sql`
> 直接原因整合团购详情接口QueryPackageCouponInfo新建 ODS 详情表存储每个团购套餐的详情数据
> Prompt 摘要etl-coupon-detail spec — 需求 3 验收标准 1-4
---
## 1. 变更说明
### 变更内容
新建 `ods.group_buy_package_details` 表,用于存储 `QueryPackageCouponInfo` 详情接口的原始数据。
| Schema | 表 | 操作 | 说明 |
|--------|-----|------|------|
| ods | group_buy_package_details | 新建 | 团购套餐详情,主键 `coupon_id`,含 12 个结构化字段 + 6 个 JSONB 数组字段 + 3 个 ETL 元数据字段 |
### 数据获取方式
通过 `ODS_GROUP_PACKAGE` 任务的 `detail_endpoint` 二级详情拉取子流程:
- 主流程拉取团购列表 → `ods.group_buy_packages`
- 子流程遍历每个 `id`,串行调用 `QueryPackageCouponInfo` → 本表
- 全量快照模式UPSERT on `coupon_id`
### 关键字段
| 字段 | 类型 | 说明 |
|------|------|------|
| `coupon_id` | BIGINT PK | 团购套餐 ID= group_buy_packages.id |
| `table_area_ids` | JSONB | 可用台区 ID 列表 |
| `table_area_names` | JSONB | 可用台区名称列表 |
| `assistant_services` | JSONB | 助教服务关联数组 |
| `groupon_site_infos` | JSONB | 关联门店信息数组 |
| `package_services` | JSONB | 套餐服务数组(待调研) |
| `coupon_details_list` | JSONB | 券明细数组(待调研) |
| `content_hash` | TEXT | 内容哈希,用于变更检测 |
| `payload` | JSONB | 完整原始 JSON 响应 |
---
## 2. 兼容性影响
| 组件 | 影响 | 说明 |
|------|------|------|
| ETL ODS 层 | 新增表 | `ODS_GROUP_PACKAGE` 任务通过 `detail_endpoint` 配置自动写入 |
| ETL DWD 层 | 需配合修改 | `dwd_load_task.py` 需 LEFT JOIN 本表将 4 个 JSONB 字段合并到 `dim_groupbuy_package_ex` |
| 后端 API | 无影响 | 当前无接口直接查询本表 |
| 小程序 | 无影响 | 不直接使用 ODS 层表 |
---
## 3. 回滚策略
```sql
DROP TABLE IF EXISTS ods.group_buy_package_details;
```
回滚后需同步移除 `ODS_GROUP_PACKAGE` 任务中的 `detail_endpoint` 相关配置。
---
## 4. 验证 SQL
```sql
-- 1. 确认表存在且主键正确
SELECT column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_schema = 'ods'
AND table_name = 'group_buy_package_details'
ORDER BY ordinal_position;
-- 预期22 列coupon_id 为 BIGINT NOT NULL
-- 2. 确认主键约束
SELECT constraint_name, constraint_type
FROM information_schema.table_constraints
WHERE table_schema = 'ods'
AND table_name = 'group_buy_package_details'
AND constraint_type = 'PRIMARY KEY';
-- 预期1 行pk_group_buy_package_details
-- 3. 确认 JSONB 列存在
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_schema = 'ods'
AND table_name = 'group_buy_package_details'
AND data_type = 'jsonb'
ORDER BY ordinal_position;
-- 预期8 行table_area_ids, table_area_names, assistant_services, groupon_site_infos, package_services, coupon_details_list, payload
```

View File

@@ -0,0 +1,159 @@
# BD 手册维客线索表member_retention_clue
## 概述
`zqyy_app` / `test_zqyy_app` 业务库中新建 `member_retention_clue` 表,替代原 `member_birthday_manual` 表。维客线索是助教为会员记录的销售/维护线索,采用"大类 + 摘要 + 详情"三层结构,覆盖客户基础、消费习惯、玩法偏好、促销偏好、社交关系、重要反馈六个维度。
生日信息不再单独建表,作为"客户基础"大类下的一条线索记录。
## 变更说明
| 库 | Schema | 表 | 变更类型 | 说明 |
|----|--------|---|---------|------|
| zqyy_app / test_zqyy_app | public | member_birthday_manual | 删除 | 旧表,生日单独记录方案废弃 |
| zqyy_app / test_zqyy_app | public | member_retention_clue | 新建 | 维客线索表 |
| zqyy_app / test_zqyy_app | public | member_retention_clue.source | 新增列 | 2026-02-27 补齐线索来源字段 |
| zqyy_app / test_zqyy_app | public | member_retention_clue.category | 约束变更 | 2026-03-08 枚举对齐:`客户基础信息``客户基础` |
### 表结构
| 列名 | 类型 | 约束 | 说明 |
|------|------|------|------|
| id | BIGSERIAL | PRIMARY KEY | 自增主键 |
| member_id | BIGINT | NOT NULL | 会员 ID |
| category | VARCHAR(20) | NOT NULL, CHECK | 线索大类枚举6 值,见下方枚举表) |
| summary | VARCHAR(200) | NOT NULL | 摘要:重点信息 |
| detail | TEXT | 可为空 | 详情:分析及扩展说明 |
| recorded_by_assistant_id | BIGINT | — | 记录助教 ID |
| recorded_by_name | VARCHAR(50) | — | 记录助教姓名 |
| recorded_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 记录时间 |
| site_id | BIGINT | NOT NULL | 门店 ID多门店隔离 |
| source | VARCHAR(20) | NOT NULL DEFAULT 'manual' | 线索来源2026-02-27 新增) |
### category 枚举值
> 2026-03-08 枚举对齐:`客户基础信息` → `客户基础`P5 spec 评审决定,与 AI 应用 Prompt 统一)
| 值 | 说明 |
|----|------|
| 客户基础 | 生日、职业、偏好时段等基础画像 |
| 消费习惯 | 消费频次、客单价、消费时段等 |
| 玩法偏好 | 中式/斯诺克/美式偏好、技术水平等 |
| 促销偏好 | 对储值活动、套餐、折扣的敏感度 |
| 社交关系 | 常带朋友、固定球搭子、社交圈等 |
| 重要反馈 | 客户提出的需求、投诉、建议等 |
### source 枚举值2026-02-27 新增)
| 值 | 说明 |
|----|------|
| manual | 助教手动录入(默认值) |
| ai_consumption | 应用 3消费分析自动生成 |
| ai_note | 应用 6备注分析自动提取 |
### 约束与索引
| 名称 | 类型 | 列 | 说明 |
|------|------|---|------|
| member_retention_clue_pkey | PRIMARY KEY | id | 主键 |
| chk_retention_clue_category | CHECK | category | 限制大类枚举值 |
| idx_retention_clue_member | INDEX (btree) | member_id | 按会员查询 |
| idx_retention_clue_site | INDEX (btree) | site_id | 按门店查询 |
| idx_retention_clue_category | INDEX (btree) | (member_id, category) | 按会员+大类查询 |
## 兼容性
- **后端 API**`POST /api/member-birthday` 废弃,替换为 `POST /api/retention-clue``GET /api/retention-clue/{member_id}``DELETE /api/retention-clue/{clue_id}`
- **source 字段**2026-02-27`POST /api/retention-clue` 接受可选 `source` 参数,默认 `manual``GET` 返回中包含 `source` 字段。已有数据自动填充 `DEFAULT 'manual'`,向后兼容
- **ETL Connector**DWS 任务移除 FDW 读取 `member_birthday_manual` 的逻辑,生日仅从 `dim_member.birthday`API 来源)读取
- **FDW**`fdw_app.member_birthday_manual` 外部表需在 ETL 库侧同步更新为 `fdw_app.member_retention_clue`(含 `source` 列)
- **小程序**:助教端调用新 API 提交维客线索
- **H5 原型**customer-detail 和 task-detail 页面"消费习惯"板块改为"维客线索"
## 回滚策略
### 仅回滚 source 列2026-02-27 变更)
```sql
BEGIN;
ALTER TABLE member_retention_clue DROP COLUMN IF EXISTS source;
COMMIT;
```
### 完整回滚(整表)
```sql
BEGIN;
DROP TABLE IF EXISTS member_retention_clue CASCADE;
-- 如需恢复旧表,执行归档的 2026-02-22__C2_member_birthday_manual.sql
COMMIT;
```
## 验证步骤
```sql
-- 1. 确认旧表已删除
SELECT table_name FROM information_schema.tables
WHERE table_schema = 'public' AND table_name = 'member_birthday_manual';
-- 预期0 行
-- 2. 确认新表存在
SELECT table_name FROM information_schema.tables
WHERE table_schema = 'public' AND table_name = 'member_retention_clue';
-- 预期1 行
-- 3. 确认列结构完整10 列)
SELECT column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = 'member_retention_clue'
ORDER BY ordinal_position;
-- 预期id, member_id, category, summary, detail,
-- recorded_by_assistant_id, recorded_by_name, recorded_at, site_id, source
-- 4. 确认 CHECK 约束
SELECT conname FROM pg_constraint
WHERE conrelid = 'member_retention_clue'::regclass AND contype = 'c';
-- 预期chk_retention_clue_category
-- 5. 确认索引3 + 主键)
SELECT indexname FROM pg_indexes
WHERE tablename = 'member_retention_clue';
-- 预期4 行
-- 6. 确认表注释
SELECT obj_description('member_retention_clue'::regclass, 'pg_class');
-- 预期:包含"维客线索"
-- 7. 确认 source 列存在且默认值正确2026-02-27
SELECT column_name, data_type, column_default, is_nullable
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 'member_retention_clue'
AND column_name = 'source';
-- 预期1 行varchar, 'manual'::character varying, NO
-- 8. 确认 source 列注释
SELECT col_description(
(SELECT oid FROM pg_class WHERE relname = 'member_retention_clue'),
(SELECT ordinal_position FROM information_schema.columns
WHERE table_name = 'member_retention_clue' AND column_name = 'source')
);
-- 预期:包含 'manual' / 'ai_consumption' / 'ai_note'
-- 9. 确认已有数据的 source 分布
SELECT source, COUNT(*) FROM member_retention_clue GROUP BY source;
-- 预期:全部为 'manual'(或空表)
```
## 关联文件
- 迁移脚本(建表):`db/zqyy_app/migrations/2026-02-26__refactor_birthday_to_retention_clue.sql`
- 迁移脚本source 列):`db/zqyy_app/migrations/2026-02-27__add_source_to_retention_clue.sql`
- 迁移脚本category 枚举对齐):`db/zqyy_app/migrations/2026-03-08__align_retention_clue_category_enum.sql`
- FDW 反向映射(生产):`db/fdw/setup_fdw_reverse.sql`
- FDW 反向映射(测试):`db/fdw/setup_fdw_reverse_test.sql`
- 后端路由:`apps/backend/app/routers/member_retention_clue.py`
- 后端模型:`apps/backend/app/schemas/member_retention_clue.py`
- H5 原型:`docs/h5_ui/pages/customer-detail.html``docs/h5_ui/pages/task-detail.html`
- 旧表文档(已归档):`docs/database/_archived/BD_Manual_member_birthday_manual.md`
- 旧 FDW 文档(已归档):`docs/database/_archived/BD_Manual_fdw_reverse_member_birthday.md`

View File

@@ -0,0 +1,128 @@
# BD_Manualtenant_id INTEGER → BIGINT 迁移
> 日期2026-03-03
> 涉及库:`etl_feiqiu` / `test_etl_feiqiu`、`zqyy_app` / `test_zqyy_app`
> 迁移脚本:
> - `db/etl_feiqiu/migrations/2026-03-03__alter_tenant_id_int_to_bigint.sql`
> - `db/zqyy_app/migrations/2026-03-03__alter_tenant_id_int_to_bigint.sql`
> 直接原因:飞球 tenant_id如 2790683160709957远超 int4 上限2,147,483,647导致写入溢出
> Prompt 摘要:修复 tenant_id int4 溢出问题,迁移为 bigint
---
## 1. 变更说明
### 变更前
| 库 | Schema | 表 | 列 | 类型 |
|----|--------|----|----|------|
| etl_feiqiu | dws | dws_assistant_order_contribution | tenant_id | INTEGER (int4) NOT NULL |
| zqyy_app | auth | site_code_mapping | tenant_id | INTEGER (int4) NULL |
### 变更后
| 库 | Schema | 表 | 列 | 类型 |
|----|--------|----|----|------|
| etl_feiqiu | dws | dws_assistant_order_contribution | tenant_id | BIGINT (int8) NOT NULL |
| zqyy_app | auth | site_code_mapping | tenant_id | BIGINT (int8) NULL |
### 级联影响
| 对象 | 类型 | 处理方式 |
|------|------|---------|
| `app.v_dws_assistant_order_contribution` (ETL 库) | RLS 视图 | DROP → ALTER → 重建SELECT * |
| `fdw_etl.v_dws_assistant_order_contribution` (App 库) | FDW 外部表 | DROP → IMPORT FOREIGN SCHEMA 重新导入 |
| `app.v_dws_assistant_order_contribution` (App 库) | RLS 视图 | 自动继承 FDW 外部表类型 |
---
## 2. 兼容性影响
| 组件 | 影响 | 说明 |
|------|------|------|
| ETL 任务 | 无影响 | `assistant_order_contribution_task.py` 从 DWD 层读取 tenant_id已是 bigint写入 DWS 现在类型匹配 |
| 后端 API | 无影响 | 通过 FDW 视图读取,类型自动跟随源表 |
| 小程序 | 无影响 | 不直接使用 tenant_id |
| `init_test_user.py` | 已更新 | 移除 `_safe_tenant_id()` 中的 int4 范围检查降级逻辑 |
---
## 3. 回滚策略
### ETL 库回滚
```sql
BEGIN;
DROP VIEW IF EXISTS app.v_dws_assistant_order_contribution CASCADE;
ALTER TABLE dws.dws_assistant_order_contribution ALTER COLUMN tenant_id TYPE integer;
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;
GRANT SELECT ON app.v_dws_assistant_order_contribution TO app_reader;
COMMIT;
```
### App 库回滚
```sql
BEGIN;
ALTER TABLE auth.site_code_mapping ALTER COLUMN tenant_id TYPE integer;
COMMIT;
```
### 代码回滚
恢复 `scripts/ops/init_test_user.py``_safe_tenant_id()` 的 int4 范围检查逻辑。
---
## 4. 验证 SQL
### ETL 库test_etl_feiqiu
```sql
-- 1. 确认 dws 表 tenant_id 类型
SELECT column_name, data_type, udt_name
FROM information_schema.columns
WHERE table_schema = 'dws'
AND table_name = 'dws_assistant_order_contribution'
AND column_name = 'tenant_id';
-- 预期data_type = 'bigint', udt_name = 'int8'
-- 2. 确认 RLS 视图存在且类型正确
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_schema = 'app'
AND table_name = 'v_dws_assistant_order_contribution'
AND column_name = 'tenant_id';
-- 预期data_type = 'bigint'
-- 3. 确认全库无残留 int4 tenant_id
SELECT table_schema, table_name
FROM information_schema.columns
WHERE column_name = 'tenant_id' AND udt_name = 'int4';
-- 预期0 行
```
### App 库test_zqyy_app
```sql
-- 1. 确认 auth 表 tenant_id 类型
SELECT column_name, data_type, udt_name
FROM information_schema.columns
WHERE table_schema = 'auth'
AND table_name = 'site_code_mapping'
AND column_name = 'tenant_id';
-- 预期data_type = 'bigint', udt_name = 'int8'
-- 2. 确认 FDW 外部表类型正确
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_schema = 'fdw_etl'
AND table_name = 'v_dws_assistant_order_contribution'
AND column_name = 'tenant_id';
-- 预期data_type = 'bigint'
-- 3. 确认全库无残留 int4 tenant_id
SELECT table_schema, table_name
FROM information_schema.columns
WHERE column_name = 'tenant_id' AND udt_name = 'int4';
-- 预期0 行
```

View File

@@ -14,6 +14,7 @@
| `etl_feiqiu__app.sql` | etl_feiqiu | app | RLS 视图层43 视图,无表) |
| `zqyy_app__public.sql` | zqyy_app | public | 小程序业务表12 表) |
| `zqyy_app__auth.sql` | zqyy_app | auth | 用户认证与权限8 表) |
| `zqyy_app__biz.sql` | zqyy_app | biz | 核心业务表(任务/备注/触发器4 表) |
| `fdw.sql` | — | — | FDW 正向跨库映射配置etl→app |
## 数据字典BD_Manual — ODS→DWD 字段映射)

View File

@@ -1,6 +1,6 @@
-- =============================================================================
-- etl_feiqiu / appRLS 视图层)
-- 生成日期2026-02-25
-- 生成日期2026-02-27
-- 来源:测试库(通过脚本自动导出)
-- =============================================================================
@@ -322,6 +322,9 @@ SELECT recharge_order_id,
WHERE (site_id = (current_setting('app.current_site_id'::text))::bigint);
;
-- ⚠️ consume_money 透传自 DWD 层存在三种历史口径A/B/CAPI 消费端不应直接展示或参与计算。
-- 应使用 items_sum = table_charge_money + goods_money + assistant_pd_money + assistant_cx_money + electricity_money
-- settle_type 枚举1=台桌结账, 3=商城订单, 6=退货订单, 7=退款订单(本表无 is_delete 字段)
CREATE OR REPLACE VIEW app.v_dwd_settlement_head AS
SELECT order_settle_id,
tenant_id,
@@ -683,7 +686,7 @@ SELECT id,
platform_fee_amount,
recharge_cash_inflow,
card_consume_total,
cash_card_consume,
recharge_card_consume,
gift_card_consume,
cash_outflow_total,
cash_balance_change,
@@ -992,7 +995,8 @@ SELECT id,
total_discount,
actual_pay,
cash_pay,
cash_card_pay,
balance_pay,
recharge_card_pay,
gift_card_pay,
groupbuy_pay,
table_duration_min,

View File

@@ -1,6 +1,6 @@
-- =============================================================================
-- etl_feiqiu / core跨门店标准化维度/事实)
-- 生成日期2026-02-25
-- 生成日期2026-02-27
-- 来源:测试库(通过脚本自动导出)
-- =============================================================================

View File

@@ -1,6 +1,6 @@
-- =============================================================================
-- etl_feiqiu / dwd明细数据层
-- 生成日期2026-02-25
-- 生成日期2026-02-27
-- 来源:测试库(通过脚本自动导出)
-- =============================================================================
@@ -616,27 +616,6 @@ CREATE TABLE dwd.dwd_assistant_service_log_ex (
operator_name text
);
CREATE TABLE dwd.dwd_assistant_trash_event (
assistant_trash_event_id bigint NOT NULL,
site_id bigint,
table_id bigint,
table_area_id bigint,
assistant_no character varying(32),
assistant_name character varying(64),
charge_minutes_raw integer,
abolish_amount numeric(18,2),
trash_reason character varying(255),
create_time timestamp with time zone,
tenant_id bigint
);
CREATE TABLE dwd.dwd_assistant_trash_event_ex (
assistant_trash_event_id bigint NOT NULL,
table_name character varying(64),
table_area_name character varying(64),
assistant_no_int integer
);
CREATE TABLE dwd.dwd_goods_stock_movement (
site_goods_stock_id bigint NOT NULL,
tenant_id bigint,
@@ -1170,8 +1149,6 @@ ALTER TABLE dwd.dim_tenant_goods ADD CONSTRAINT dim_tenant_goods_pkey PRIMARY KE
ALTER TABLE dwd.dim_tenant_goods_ex ADD CONSTRAINT dim_tenant_goods_ex_pkey PRIMARY KEY (tenant_goods_id, scd2_start_time);
ALTER TABLE dwd.dwd_assistant_service_log ADD CONSTRAINT dwd_assistant_service_log_pkey PRIMARY KEY (assistant_service_id);
ALTER TABLE dwd.dwd_assistant_service_log_ex ADD CONSTRAINT dwd_assistant_service_log_ex_pkey PRIMARY KEY (assistant_service_id);
ALTER TABLE dwd.dwd_assistant_trash_event ADD CONSTRAINT dwd_assistant_trash_event_pkey PRIMARY KEY (assistant_trash_event_id);
ALTER TABLE dwd.dwd_assistant_trash_event_ex ADD CONSTRAINT dwd_assistant_trash_event_ex_pkey PRIMARY KEY (assistant_trash_event_id);
ALTER TABLE dwd.dwd_goods_stock_movement ADD CONSTRAINT dwd_goods_stock_movement_pkey PRIMARY KEY (site_goods_stock_id);
ALTER TABLE dwd.dwd_goods_stock_summary ADD CONSTRAINT dwd_goods_stock_summary_pkey PRIMARY KEY (site_goods_id, fetched_at);
ALTER TABLE dwd.dwd_groupbuy_redemption ADD CONSTRAINT dwd_groupbuy_redemption_pkey PRIMARY KEY (redemption_id);
@@ -1262,8 +1239,6 @@ CREATE INDEX idx_dwd_assistant_service_log_time_create_time ON dwd.dwd_assistant
CREATE INDEX idx_dwd_assistant_service_log_time_pk_118fd0d3 ON dwd.dwd_assistant_service_log USING btree (create_time, assistant_service_id);
CREATE INDEX idx_dwd_assistant_service_log_time_pk_3fb2dede ON dwd.dwd_assistant_service_log USING btree (start_use_time, assistant_service_id);
CREATE INDEX idx_dwd_assistant_service_log_time_start_use_time ON dwd.dwd_assistant_service_log USING btree (start_use_time);
CREATE INDEX idx_dwd_assistant_trash_event_time_create_time ON dwd.dwd_assistant_trash_event USING btree (create_time);
CREATE INDEX idx_dwd_assistant_trash_event_time_pk_0b64af2a ON dwd.dwd_assistant_trash_event USING btree (create_time, assistant_trash_event_id);
CREATE INDEX idx_dwd_groupbuy_redemption_time_create_time ON dwd.dwd_groupbuy_redemption USING btree (create_time);
CREATE INDEX idx_dwd_groupbuy_redemption_time_pk_create_time_redemption_id ON dwd.dwd_groupbuy_redemption USING btree (create_time, redemption_id);
CREATE INDEX idx_dwd_payment_time_create_time ON dwd.dwd_payment USING btree (create_time);

View File

@@ -1,6 +1,6 @@
-- =============================================================================
-- etl_feiqiu / dws汇总数据层
-- 生成日期2026-02-25
-- 生成日期2026-02-27
-- 来源:测试库(通过脚本自动导出)
-- =============================================================================
@@ -42,8 +42,11 @@ CREATE SEQUENCE IF NOT EXISTS dws.dws_platform_settlement_id_seq AS bigint;
CREATE TABLE dws.cfg_area_category (
category_id integer DEFAULT nextval('dws.cfg_area_category_category_id_seq'::regclass) NOT NULL,
source_area_name character varying(100) NOT NULL,
source_table_name character varying(100) DEFAULT NULL,
category_code character varying(20) NOT NULL,
category_name character varying(50) NOT NULL,
display_name character varying(50) DEFAULT NULL,
short_name character varying(20) DEFAULT NULL,
match_type character varying(10) DEFAULT 'EXACT'::character varying NOT NULL,
match_priority integer DEFAULT 100 NOT NULL,
is_active boolean DEFAULT true NOT NULL,
@@ -51,6 +54,9 @@ CREATE TABLE dws.cfg_area_category (
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL
);
-- 唯一约束:(source_area_name, source_table_name) 支持同一台区下按台桌细分
CREATE UNIQUE INDEX uk_cfg_area_category
ON dws.cfg_area_category (source_area_name, COALESCE(source_table_name, ''));
CREATE TABLE dws.cfg_assistant_level_price (
price_id integer DEFAULT nextval('dws.cfg_assistant_level_price_price_id_seq'::regclass) NOT NULL,
@@ -102,7 +108,7 @@ CREATE TABLE dws.cfg_performance_tier (
min_hours numeric(10,2) NOT NULL,
max_hours numeric(10,2),
base_deduction numeric(10,2) DEFAULT 0 NOT NULL,
bonus_deduction_ratio numeric(5,4) DEFAULT 0 NOT NULL,
bonus_deduction_ratio numeric(7,4) DEFAULT 0 NOT NULL,
vacation_days integer DEFAULT 0 NOT NULL,
vacation_unlimited boolean DEFAULT false NOT NULL,
is_new_hire_tier boolean DEFAULT false NOT NULL,
@@ -215,7 +221,7 @@ CREATE TABLE dws.dws_assistant_finance_analysis (
revenue_room numeric(14,2) DEFAULT 0 NOT NULL,
cost_daily numeric(14,2) DEFAULT 0 NOT NULL,
gross_profit numeric(14,2) DEFAULT 0 NOT NULL,
gross_margin numeric(5,4) DEFAULT 0 NOT NULL,
gross_margin numeric(7,4) DEFAULT 0 NOT NULL,
service_count integer DEFAULT 0 NOT NULL,
service_hours numeric(10,2) DEFAULT 0 NOT NULL,
room_service_count integer DEFAULT 0 NOT NULL,
@@ -265,8 +271,8 @@ CREATE TABLE dws.dws_assistant_monthly_summary (
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,
site_id bigint NOT NULL,
tenant_id bigint NOT NULL,
assistant_id bigint NOT NULL,
assistant_nickname character varying(100),
stat_date date NOT NULL,
@@ -291,7 +297,7 @@ CREATE TABLE dws.dws_assistant_recharge_commission (
recharge_order_no character varying(50),
recharge_amount numeric(12,2) DEFAULT 0 NOT NULL,
commission_amount numeric(12,2) DEFAULT 0 NOT NULL,
commission_ratio numeric(5,4),
commission_ratio numeric(7,4),
import_batch_no character varying(50),
import_file_name character varying(200),
import_time timestamp with time zone,
@@ -323,7 +329,7 @@ CREATE TABLE dws.dws_assistant_salary_calc (
base_course_price numeric(10,2) DEFAULT 0 NOT NULL,
bonus_course_price numeric(10,2) DEFAULT 0 NOT NULL,
base_deduction numeric(10,2) DEFAULT 0 NOT NULL,
bonus_deduction_ratio numeric(5,4) DEFAULT 0 NOT NULL,
bonus_deduction_ratio numeric(7,4) DEFAULT 0 NOT NULL,
base_income numeric(12,2) DEFAULT 0 NOT NULL,
bonus_income numeric(12,2) DEFAULT 0 NOT NULL,
room_income numeric(12,2) DEFAULT 0 NOT NULL,
@@ -366,7 +372,7 @@ CREATE TABLE dws.dws_finance_daily_summary (
platform_fee_amount numeric(14,2) DEFAULT 0 NOT NULL,
recharge_cash_inflow numeric(14,2) DEFAULT 0 NOT NULL,
card_consume_total numeric(14,2) DEFAULT 0 NOT NULL,
cash_card_consume numeric(14,2) DEFAULT 0 NOT NULL,
recharge_card_consume numeric(14,2) DEFAULT 0 NOT NULL,
gift_card_consume numeric(14,2) DEFAULT 0 NOT NULL,
cash_outflow_total numeric(14,2) DEFAULT 0 NOT NULL,
cash_balance_change numeric(14,2) DEFAULT 0 NOT NULL,
@@ -394,7 +400,7 @@ CREATE TABLE dws.dws_finance_discount_detail (
discount_type_code character varying(30) NOT NULL,
discount_type_name character varying(50) NOT NULL,
discount_amount numeric(14,2) DEFAULT 0 NOT NULL,
discount_ratio numeric(5,4) DEFAULT 0 NOT NULL,
discount_ratio numeric(7,4) DEFAULT 0 NOT NULL,
usage_count integer DEFAULT 0 NOT NULL,
affected_orders integer DEFAULT 0 NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
@@ -429,7 +435,7 @@ CREATE TABLE dws.dws_finance_income_structure (
category_code character varying(30) NOT NULL,
category_name character varying(50) NOT NULL,
income_amount numeric(14,2) DEFAULT 0 NOT NULL,
income_ratio numeric(5,4) DEFAULT 0 NOT NULL,
income_ratio numeric(7,4) DEFAULT 0 NOT NULL,
order_count integer DEFAULT 0 NOT NULL,
duration_minutes integer DEFAULT 0 NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
@@ -564,7 +570,7 @@ CREATE TABLE dws.dws_member_assistant_intimacy (
score_recency numeric(10,4),
score_recharge numeric(10,4),
score_duration numeric(10,4),
burst_multiplier numeric(6,4),
burst_multiplier numeric(7,4),
raw_score numeric(14,6),
display_score numeric(4,2),
calc_time timestamp with time zone DEFAULT now() NOT NULL,
@@ -720,7 +726,7 @@ CREATE TABLE dws.dws_member_recall_index (
CREATE TABLE dws.dws_member_spending_power_index (
spi_id bigint DEFAULT nextval('dws.dws_member_spending_power_index_spi_id_seq'::regclass) NOT NULL,
site_id integer NOT NULL,
site_id bigint NOT NULL,
member_id bigint NOT NULL,
spend_30 numeric(14,2) DEFAULT 0,
spend_90 numeric(14,2) DEFAULT 0,
@@ -767,7 +773,8 @@ CREATE TABLE dws.dws_member_visit_detail (
total_discount numeric(12,2) DEFAULT 0 NOT NULL,
actual_pay numeric(12,2) DEFAULT 0 NOT NULL,
cash_pay numeric(12,2) DEFAULT 0 NOT NULL,
cash_card_pay numeric(12,2) DEFAULT 0 NOT NULL,
balance_pay numeric(12,2) DEFAULT 0 NOT NULL,
recharge_card_pay numeric(12,2) DEFAULT 0 NOT NULL,
gift_card_pay numeric(12,2) DEFAULT 0 NOT NULL,
groupbuy_pay numeric(12,2) DEFAULT 0 NOT NULL,
table_duration_min integer DEFAULT 0 NOT NULL,
@@ -1310,7 +1317,7 @@ SELECT id,
platform_fee_amount,
recharge_cash_inflow,
card_consume_total,
cash_card_consume,
recharge_card_consume,
gift_card_consume,
cash_outflow_total,
cash_balance_change,
@@ -1357,7 +1364,7 @@ SELECT id,
platform_fee_amount,
recharge_cash_inflow,
card_consume_total,
cash_card_consume,
recharge_card_consume,
gift_card_consume,
cash_outflow_total,
cash_balance_change,
@@ -1404,7 +1411,7 @@ SELECT id,
platform_fee_amount,
recharge_cash_inflow,
card_consume_total,
cash_card_consume,
recharge_card_consume,
gift_card_consume,
cash_outflow_total,
cash_balance_change,
@@ -1451,7 +1458,7 @@ SELECT id,
platform_fee_amount,
recharge_cash_inflow,
card_consume_total,
cash_card_consume,
recharge_card_consume,
gift_card_consume,
cash_outflow_total,
cash_balance_change,
@@ -1483,3 +1490,69 @@ CREATE INDEX idx_mv_finance_daily_l2 ON dws.mv_dws_finance_daily_summary_l2 USIN
CREATE INDEX idx_mv_finance_daily_l3 ON dws.mv_dws_finance_daily_summary_l3 USING btree (site_id, stat_date);
CREATE INDEX idx_mv_finance_daily_l4 ON dws.mv_dws_finance_daily_summary_l4 USING btree (site_id, stat_date);
-- =============================================================================
-- 项目标签表2026-03-07 新增)
-- =============================================================================
CREATE SEQUENCE IF NOT EXISTS dws.dws_assistant_project_tag_id_seq AS bigint;
CREATE SEQUENCE IF NOT EXISTS dws.dws_member_project_tag_id_seq AS bigint;
CREATE TABLE dws.dws_assistant_project_tag (
id BIGSERIAL NOT NULL,
site_id BIGINT NOT NULL,
tenant_id BIGINT NOT NULL,
assistant_id BIGINT NOT NULL,
time_window VARCHAR(40) NOT NULL,
category_code VARCHAR(30) NOT NULL,
category_name VARCHAR(50) NOT NULL,
short_name VARCHAR(10) NOT NULL,
duration_seconds BIGINT NOT NULL DEFAULT 0,
total_seconds BIGINT NOT NULL DEFAULT 0,
percentage NUMERIC(5,4) NOT NULL DEFAULT 0,
is_tagged BOOLEAN NOT NULL DEFAULT FALSE,
computed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT pk_dws_assistant_project_tag PRIMARY KEY (id),
CONSTRAINT uk_dws_assistant_project_tag
UNIQUE (site_id, assistant_id, time_window, category_code)
);
COMMENT ON TABLE dws.dws_assistant_project_tag IS '助教项目标签按时间窗口计算各项目时长占比≥25%分配标签';
COMMENT ON COLUMN dws.dws_assistant_project_tag.time_window IS '时间窗口THIS_MONTH/THIS_QUARTER/LAST_MONTH/LAST_3_MONTHS_EXCL_CURRENT/LAST_QUARTER/LAST_6_MONTHS';
COMMENT ON COLUMN dws.dws_assistant_project_tag.is_tagged IS '占比≥0.25时为TRUE表示该助教拥有此项目标签';
CREATE TABLE dws.dws_member_project_tag (
id BIGSERIAL NOT NULL,
site_id BIGINT NOT NULL,
tenant_id BIGINT NOT NULL,
member_id BIGINT NOT NULL,
time_window VARCHAR(40) NOT NULL,
category_code VARCHAR(30) NOT NULL,
category_name VARCHAR(50) NOT NULL,
short_name VARCHAR(10) NOT NULL,
duration_seconds BIGINT NOT NULL DEFAULT 0,
total_seconds BIGINT NOT NULL DEFAULT 0,
percentage NUMERIC(5,4) NOT NULL DEFAULT 0,
is_tagged BOOLEAN NOT NULL DEFAULT FALSE,
computed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT pk_dws_member_project_tag PRIMARY KEY (id),
CONSTRAINT uk_dws_member_project_tag
UNIQUE (site_id, member_id, time_window, category_code)
);
COMMENT ON TABLE dws.dws_member_project_tag IS '客户项目标签按时间窗口计算各项目消费时长占比≥25%分配标签';
COMMENT ON COLUMN dws.dws_member_project_tag.time_window IS '时间窗口LAST_30_DAYS/LAST_60_DAYS';
COMMENT ON COLUMN dws.dws_member_project_tag.is_tagged IS '占比≥0.25时为TRUE表示该客户拥有此项目标签';
-- 部分索引(加速看板查询)
CREATE INDEX idx_apt_site_window_tagged
ON dws.dws_assistant_project_tag (site_id, time_window)
WHERE is_tagged = TRUE;
CREATE INDEX idx_mpt_site_window_tagged
ON dws.dws_member_project_tag (site_id, time_window)
WHERE is_tagged = TRUE;

View File

@@ -1,6 +1,6 @@
-- =============================================================================
-- etl_feiqiu / metaETL 调度元数据)
-- 生成日期2026-02-25
-- 生成日期2026-02-27
-- 来源:测试库(通过脚本自动导出)
-- =============================================================================

View File

@@ -1,6 +1,6 @@
-- =============================================================================
-- etl_feiqiu / ods原始数据层
-- 生成日期2026-02-25
-- 生成日期2026-02-27
-- 来源:测试库(通过脚本自动导出)
-- =============================================================================
@@ -77,28 +77,6 @@ CREATE TABLE ods.assistant_accounts_master (
payload jsonb NOT NULL
);
CREATE TABLE ods.assistant_cancellation_records (
id bigint NOT NULL,
siteid bigint,
siteprofile jsonb,
assistantname text,
assistantabolishamount numeric(18,2),
assistanton integer,
pdchargeminutes integer,
tableareaid bigint,
tablearea text,
tableid bigint,
tablename text,
trashreason text,
createtime timestamp without time zone,
tenant_id bigint,
content_hash text NOT NULL,
source_file text,
source_endpoint text,
fetched_at timestamp with time zone DEFAULT now(),
payload jsonb NOT NULL
);
CREATE TABLE ods.assistant_service_records (
id bigint NOT NULL,
tenant_id bigint,
@@ -215,6 +193,7 @@ CREATE TABLE ods.goods_stock_summary (
rangesalemoney numeric(18,2),
rangeinventory numeric(18,4),
currentstock numeric(18,4),
siteid bigint,
content_hash text NOT NULL,
source_file text,
source_endpoint text,
@@ -1066,7 +1045,6 @@ CREATE TABLE ods.tenant_goods_master (
-- 约束(主键 / 唯一 / 外键)
ALTER TABLE ods.assistant_accounts_master ADD CONSTRAINT assistant_accounts_master_pkey PRIMARY KEY (id, content_hash);
ALTER TABLE ods.assistant_cancellation_records ADD CONSTRAINT assistant_cancellation_records_pkey PRIMARY KEY (id, content_hash);
ALTER TABLE ods.assistant_service_records ADD CONSTRAINT assistant_service_records_pkey PRIMARY KEY (id, content_hash);
ALTER TABLE ods.goods_stock_movements ADD CONSTRAINT goods_stock_movements_pkey PRIMARY KEY (sitegoodsstockid, content_hash);
ALTER TABLE ods.goods_stock_summary ADD CONSTRAINT goods_stock_summary_pkey PRIMARY KEY (sitegoodsid, content_hash);
@@ -1091,8 +1069,6 @@ ALTER TABLE ods.tenant_goods_master ADD CONSTRAINT tenant_goods_master_pkey PRIM
-- 索引
CREATE INDEX idx_assistant_accounts_master_fetched_at_fetched_at ON ods.assistant_accounts_master USING btree (fetched_at);
CREATE INDEX idx_assistant_accounts_master_fetched_pk_d986993f ON ods.assistant_accounts_master USING btree (fetched_at, id, content_hash);
CREATE INDEX idx_assistant_cancellation_records_fetched_at_fetched_at ON ods.assistant_cancellation_records USING btree (fetched_at);
CREATE INDEX idx_assistant_cancellation_records_fetched_pk_258b411c ON ods.assistant_cancellation_records USING btree (fetched_at, id, content_hash);
CREATE INDEX idx_assistant_service_records_fetched_at_fetched_at ON ods.assistant_service_records USING btree (fetched_at);
CREATE INDEX idx_assistant_service_records_fetched_pk_e200787c ON ods.assistant_service_records USING btree (fetched_at, id, content_hash);
CREATE INDEX idx_goods_stock_movements_fetched_at_fetched_at ON ods.goods_stock_movements USING btree (fetched_at);

View File

@@ -1,6 +1,6 @@
-- =============================================================================
-- FDW 跨库映射(在 zqyy_app 中执行)
-- 生成日期2026-02-25
-- 生成日期2026-02-27
-- 来源db/fdw/setup_fdw.sql
-- =============================================================================

View File

@@ -1,6 +1,6 @@
-- =============================================================================
-- zqyy_app / auth用户认证与权限
-- 生成日期2026-02-25
-- 生成日期2026-02-27
-- 来源:测试库(通过脚本自动导出)
-- =============================================================================
@@ -42,7 +42,7 @@ CREATE TABLE auth.site_code_mapping (
site_code character varying(10) NOT NULL,
site_id bigint NOT NULL,
site_name character varying(200),
tenant_id integer,
tenant_id bigint,
created_at timestamp with time zone DEFAULT now() NOT NULL
);
@@ -86,7 +86,7 @@ CREATE TABLE auth.users (
wx_avatar_url text,
nickname character varying(100),
phone character varying(20),
status character varying(20) DEFAULT 'pending'::character varying NOT NULL,
status character varying(20) DEFAULT 'new'::character varying NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL
);

View File

@@ -0,0 +1,89 @@
-- =============================================================================
-- zqyy_app / biz核心业务表任务/备注/触发器))
-- 生成日期2026-02-27
-- 来源:测试库(通过脚本自动导出)
-- =============================================================================
CREATE SCHEMA IF NOT EXISTS biz;
-- 序列
CREATE SEQUENCE IF NOT EXISTS biz.coach_task_history_id_seq AS bigint;
CREATE SEQUENCE IF NOT EXISTS biz.coach_tasks_id_seq AS bigint;
CREATE SEQUENCE IF NOT EXISTS biz.notes_id_seq AS bigint;
CREATE SEQUENCE IF NOT EXISTS biz.trigger_jobs_id_seq AS integer;
-- 表
CREATE TABLE biz.coach_task_history (
id bigint DEFAULT nextval('biz.coach_task_history_id_seq'::regclass) NOT NULL,
task_id bigint NOT NULL,
action character varying(50) NOT NULL,
old_status character varying(20),
new_status character varying(20),
old_task_type character varying(50),
new_task_type character varying(50),
detail jsonb,
created_at timestamp with time zone DEFAULT now()
);
CREATE TABLE biz.coach_tasks (
id bigint DEFAULT nextval('biz.coach_tasks_id_seq'::regclass) NOT NULL,
site_id bigint NOT NULL,
assistant_id bigint NOT NULL,
member_id bigint NOT NULL,
task_type character varying(50) NOT NULL,
status character varying(20) DEFAULT 'active'::character varying NOT NULL,
priority_score numeric(5,2),
expires_at timestamp with time zone,
is_pinned boolean DEFAULT false,
abandon_reason text,
completed_at timestamp with time zone,
completed_task_type character varying(50),
parent_task_id bigint,
created_at timestamp with time zone DEFAULT now(),
updated_at timestamp with time zone DEFAULT now()
);
CREATE TABLE biz.notes (
id bigint DEFAULT nextval('biz.notes_id_seq'::regclass) NOT NULL,
site_id bigint NOT NULL,
user_id integer NOT NULL,
target_type character varying(50) NOT NULL,
target_id bigint NOT NULL,
type character varying(20) DEFAULT 'normal'::character varying NOT NULL,
content text NOT NULL,
rating_service_willingness smallint,
rating_revisit_likelihood smallint,
task_id bigint,
ai_score smallint,
ai_analysis text,
created_at timestamp with time zone DEFAULT now(),
updated_at timestamp with time zone DEFAULT now()
);
CREATE TABLE biz.trigger_jobs (
id integer DEFAULT nextval('biz.trigger_jobs_id_seq'::regclass) NOT NULL,
job_type character varying(100) NOT NULL,
job_name character varying(100) NOT NULL,
trigger_condition character varying(20) NOT NULL,
trigger_config jsonb NOT NULL,
last_run_at timestamp with time zone,
next_run_at timestamp with time zone,
status character varying(20) DEFAULT 'enabled'::character varying NOT NULL,
created_at timestamp with time zone DEFAULT now()
);
-- 约束(主键 / 唯一 / 外键)
ALTER TABLE biz.coach_task_history ADD CONSTRAINT coach_task_history_task_id_fkey FOREIGN KEY (task_id) REFERENCES biz.coach_tasks(id);
ALTER TABLE biz.coach_task_history ADD CONSTRAINT coach_task_history_pkey PRIMARY KEY (id);
ALTER TABLE biz.coach_tasks ADD CONSTRAINT coach_tasks_parent_task_id_fkey FOREIGN KEY (parent_task_id) REFERENCES biz.coach_tasks(id);
ALTER TABLE biz.coach_tasks ADD CONSTRAINT coach_tasks_pkey PRIMARY KEY (id);
ALTER TABLE biz.notes ADD CONSTRAINT notes_task_id_fkey FOREIGN KEY (task_id) REFERENCES biz.coach_tasks(id);
ALTER TABLE biz.notes ADD CONSTRAINT notes_pkey PRIMARY KEY (id);
ALTER TABLE biz.trigger_jobs ADD CONSTRAINT trigger_jobs_pkey PRIMARY KEY (id);
ALTER TABLE biz.trigger_jobs ADD CONSTRAINT trigger_jobs_job_name_key UNIQUE (job_name);
-- 索引
CREATE INDEX idx_coach_tasks_assistant_status ON biz.coach_tasks USING btree (site_id, assistant_id, status);
CREATE UNIQUE INDEX idx_coach_tasks_site_assistant_member_type ON biz.coach_tasks USING btree (site_id, assistant_id, member_id, task_type) WHERE ((status)::text = 'active'::text);
CREATE INDEX idx_notes_target ON biz.notes USING btree (site_id, target_type, target_id);

View File

@@ -1,6 +1,6 @@
-- =============================================================================
-- zqyy_app / public小程序业务表
-- 生成日期2026-02-25
-- 生成日期2026-02-27
-- 来源:测试库(通过脚本自动导出)
-- =============================================================================
@@ -9,7 +9,7 @@ CREATE SCHEMA IF NOT EXISTS public;
-- 序列
CREATE SEQUENCE IF NOT EXISTS public.admin_users_id_seq AS integer;
CREATE SEQUENCE IF NOT EXISTS public.approvals_id_seq AS bigint;
CREATE SEQUENCE IF NOT EXISTS public.member_birthday_manual_id_seq AS bigint;
CREATE SEQUENCE IF NOT EXISTS public.member_retention_clue_id_seq AS bigint;
CREATE SEQUENCE IF NOT EXISTS public.permissions_id_seq AS integer;
CREATE SEQUENCE IF NOT EXISTS public.roles_id_seq AS integer;
CREATE SEQUENCE IF NOT EXISTS public.tasks_id_seq AS bigint;
@@ -37,15 +37,17 @@ CREATE TABLE public.approvals (
created_at timestamp with time zone DEFAULT now()
);
CREATE TABLE public.member_birthday_manual (
id bigint DEFAULT nextval('member_birthday_manual_id_seq'::regclass) NOT NULL,
CREATE TABLE public.member_retention_clue (
id bigint DEFAULT nextval('member_retention_clue_id_seq'::regclass) NOT NULL,
member_id bigint NOT NULL,
birthday_value date NOT NULL,
category character varying(20) NOT NULL,
summary character varying(200) NOT NULL,
detail text,
recorded_by_assistant_id bigint,
recorded_by_name character varying(50),
recorded_at timestamp with time zone DEFAULT now() NOT NULL,
source character varying(20) DEFAULT 'assistant'::character varying,
site_id bigint NOT NULL
site_id bigint NOT NULL,
source character varying(20) DEFAULT 'manual'::character varying NOT NULL
);
CREATE TABLE public.permissions (
@@ -149,8 +151,10 @@ ALTER TABLE admin_users ADD CONSTRAINT admin_users_username_key UNIQUE (username
ALTER TABLE approvals ADD CONSTRAINT approvals_approver_id_fkey FOREIGN KEY (approver_id) REFERENCES users(id);
ALTER TABLE approvals ADD CONSTRAINT approvals_task_id_fkey FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE;
ALTER TABLE approvals ADD CONSTRAINT approvals_pkey PRIMARY KEY (id);
ALTER TABLE member_birthday_manual ADD CONSTRAINT member_birthday_manual_pkey PRIMARY KEY (id);
ALTER TABLE member_birthday_manual ADD CONSTRAINT uk_member_birthday_manual UNIQUE (member_id, recorded_by_assistant_id);
ALTER TABLE member_retention_clue ADD CONSTRAINT member_retention_clue_pkey PRIMARY KEY (id);
ALTER TABLE member_retention_clue ADD CONSTRAINT chk_retention_clue_category CHECK (
category IN ('客户基础', '消费习惯', '玩法偏好', '促销偏好', '社交关系', '重要反馈')
);
ALTER TABLE permissions ADD CONSTRAINT permissions_pkey PRIMARY KEY (id);
ALTER TABLE permissions ADD CONSTRAINT permissions_resource_action_key UNIQUE (resource, action);
ALTER TABLE role_permissions ADD CONSTRAINT role_permissions_permission_id_fkey FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE;
@@ -175,8 +179,9 @@ ALTER TABLE users ADD CONSTRAINT users_wx_openid_key UNIQUE (wx_openid);
CREATE INDEX idx_admin_users_site ON public.admin_users USING btree (site_id);
CREATE INDEX idx_approvals_site_id ON public.approvals USING btree (site_id);
CREATE INDEX idx_approvals_task_id ON public.approvals USING btree (task_id);
CREATE INDEX idx_mbd_member ON public.member_birthday_manual USING btree (member_id);
CREATE INDEX idx_mbd_site_id ON public.member_birthday_manual USING btree (site_id);
CREATE INDEX idx_retention_clue_category ON public.member_retention_clue USING btree (member_id, category);
CREATE INDEX idx_retention_clue_member ON public.member_retention_clue USING btree (member_id);
CREATE INDEX idx_retention_clue_site ON public.member_retention_clue USING btree (site_id);
CREATE INDEX idx_roles_site_id ON public.roles USING btree (site_id);
CREATE INDEX idx_scheduled_tasks_next_run ON public.scheduled_tasks USING btree (next_run_at) WHERE (enabled = true);
CREATE INDEX idx_scheduled_tasks_site ON public.scheduled_tasks USING btree (site_id);

View File

@@ -222,9 +222,9 @@ pause
|------|------|
| 已完成 | 跳板机已配置好(用户确认) |
| 已完成 | Tailscale 内网已配置DB_HOST=100.64.0.4 |
| | 确认 Nginx 将 `api.langlangzhuoqiu.cn` 反代到 Tailscale IP:8000正式 |
| | 确认 Nginx 将测试环境反代到 Tailscale IP:8001如需区分域名 |
| | 确认 SSL 证书有效且自动续期 |
| 完成 20260224 | 确认 Nginx 将 `api.langlangzhuoqiu.cn` 反代到 Tailscale IP:8000正式 |
| 完成 20260224 | 确认 Nginx 将测试环境反代到 Tailscale IP:8001如需区分域名 |
| 完成 20260224 | 确认 SSL 证书有效且自动续期 |
> 跳板机本身已配好,这里只需确认反代规则指向了正确的后端端口。
> 如果测试和正式共用 `api.langlangzhuoqiu.cn`,则体验版和正式版会打到同一个后端。
@@ -282,9 +282,9 @@ Get-ChildItem $backupDir -Filter "*.dump" | Where-Object { $_.LastWriteTime -lt
| 状态 | 项目 |
|------|------|
| 已完成 | 后端接口 `GET/POST /api/wx/callback` 已实现(`wx_callback.py` |
| | 在 `apps/backend/.env.local` 中配置 `WX_CALLBACK_TOKEN` |
| | 服务器上部署最新代码并重启后端 |
| | 微信后台填写消息推送配置并提交验证 |
| 已完成 | 在 `apps/backend/.env.local` 中配置 `WX_CALLBACK_TOKEN` |
| 已完成 | 服务器上部署最新代码并重启后端 |
| 未完成 | 微信后台填写消息推送配置并提交验证 |
> 消息推送配置必须在服务器后端已启动、跳板机反代已就绪之后才能操作。
> 微信会向你的 URL 发 GET 请求验签,后端必须在线才能通过。
@@ -304,29 +304,42 @@ Get-ChildItem $backupDir -Filter "*.dump" | Where-Object { $_.LastWriteTime -lt
- Nginx 反代未指向正确端口
- Token 两边不一致
**需要支持加密模式,见增补文档路径下的文档**
### 2.3 隐私协议 / 用户隐私保护指引
| 状态 | 项目 |
|------|------|
| | 在微信后台填写用户隐私保护指引 |
| | 小程序端实现隐私授权弹窗组件 |
操作路径:微信后台 - 设置 - 基本设置 - 服务内容声明 - 用户隐私保护指引
操作路径:微信后台 → 账号设置 服务内容声明 用户隐私保护指引 → 去完善
(也可在提交审核时填写,入口:管理 → 版本管理 → 提交代码审核)
需要声明你收集了哪些用户信息
- 微信昵称、头像(如果用到 `wx.getUserProfile`
- 用户标识openid`wx.login` 必然涉及)
- 设备信息(如果用到 `wx.getSystemInfo`
根据你实际使用的隐私接口声明,对照官方列表
https://developers.weixin.qq.com/miniprogram/dev/framework/user-privacy/miniprogram-intro.html
> 2023 年 9 月起微信强制要求。不填写的话,调用 `wx.login` 等隐私相关 API 会直接报错。
> 即使你当前只用了 `wx.login`,也需要声明"用户标识"这一项。
当前项目可能涉及的声明项:
- 收集你的手机号(如果用了 `<button open-type="getPhoneNumber">`
- 收集你的昵称、头像(如果用了 `<button open-type="chooseAvatar">``<input type="nickname">`
> 注意:`wx.getUserProfile` 和 `wx.getUserInfo` 已被微信回收,不再可用。
> `wx.login` 和 `wx.getSystemInfo` 不在隐私接口列表中,无需声明。
小程序端适配(必须):
- 2023 年 10 月 17 日起微信强制启用隐私保护功能
- 需使用 `wx.getPrivacySetting` / `wx.onNeedPrivacyAuthorization` 处理隐私授权
- 需在页面中放置 `<button open-type="agreePrivacyAuthorization">` 供用户同意
- 用户未同意前,所有隐私相关 API 和组件都无法调用
- 官方开发指南及 Demo
https://developers.weixin.qq.com/miniprogram/dev/framework/user-privacy/PrivacyAuthorize.html
### 2.4 小程序基本信息
| 状态 | 项目 |
|------|------|
| 已完成 | AppID 已配置:`wx7c07793d82732921` |
| | 确认小程序名称、图标、简介已填写完整 |
| | 确认小程序类目已选择(建议"工具 - 企业管理"或"生活服务" |
| 已完成 | 确认小程序名称、图标、简介已填写完整 |
| 已完成 | 确认小程序类目已选择(建议"工具 - 企业管理"或"生活服务" |
> 审核时会检查这些基本信息。类目选择需注意:部分类目需要上传营业执照等资质文件,提前确认。

View File

@@ -0,0 +1,194 @@
# 桌球运营助手 小程序隐私保护指引
> 本文档用于在微信小程序后台「用户隐私保护指引」中填写。
> 操作路径:微信后台 → 账号设置 → 服务内容声明 → 用户隐私保护指引 → 去完善
> 对应 LAUNCH-CHECKLIST.md 第 2.3 章节。
> 最后更新2026-02-26
---
## 开发者信息
- 小程序名称:桌球运营助手
- 开发者:北京塞伯汀科技有限公司(以下简称"开发者"
---
## 开发者处理的信息
根据法律规定,开发者仅处理实现小程序功能所必要的信息。
### 需要声明的信息类型及用途
| 信息类型 | 是否需要明示同意 | 用途(填入微信后台) |
|---------|:---:|------|
| 微信昵称、头像 | 是 | 用于在小程序内展示你的个人资料,便于门店管理员识别你的身份 |
| 手机号 | 是 | 用于账号申请时填写联系方式,便于门店管理员审核你的身份并与门店人员信息进行匹配 |
| 麦克风 | 是 | 用于智能助手对话功能中的"按住说话"语音输入,将语音转换为文字后发送给助手 |
| 操作日志 | 否 | 用于记录你在小程序内的关键操作(如任务状态变更、备注提交等),以便排查问题和保障服务稳定性 |
| 设备信息 | 否 | 用于适配不同机型的页面显示效果,以及排查兼容性问题,保障小程序正常运行 |
### 建议移除的信息类型
以下信息类型在当前小程序功能中**未使用**,建议在微信后台隐私指引中**不要勾选**,避免过度收集:
| 信息类型 | 不需要的原因 |
|---------|------------|
| 地址 | 小程序不涉及收货地址或地址填写功能 |
| 磁场传感器 | 小程序无指南针、金属探测等功能 |
| 方向传感器 | 小程序无方向导航功能 |
| 陀螺仪传感器 | 小程序无体感交互或 AR 功能 |
| 加速传感器 | 小程序无摇一摇或运动检测功能 |
| 位置信息 | 小程序不需要获取用户地理位置(门店信息通过球房 ID 关联,非 GPS 定位) |
| 所关注账号 | 小程序不涉及公众号关注或社交关系功能 |
| 剪切板 | 小程序不读取剪切板内容 |
> **重要**:微信后台只需勾选实际使用的信息类型。勾选了未使用的类型,审核时可能被要求说明用途,且违反最小必要原则。
---
## 微信后台填写参考(逐项复制)
以下为微信后台各字段的建议填写内容,可直接复制粘贴:
### 微信昵称、头像
```
用于在小程序内展示你的个人资料,便于门店管理员识别你的身份。
```
### 手机号
```
用于账号申请时填写联系方式,便于门店管理员审核你的身份并与门店人员信息进行匹配。
```
### 麦克风
```
用于智能助手对话功能中的"按住说话"语音输入,将语音转换为文字后发送给助手。
```
### 操作日志
```
用于记录你在小程序内的关键操作(如任务状态变更、备注提交等),以便排查问题和保障服务稳定性。
```
### 设备信息
```
用于适配不同机型的页面显示效果,以及排查兼容性问题,保障小程序正常运行。
```
---
## 第三方插件/SDK 信息
桌球运营助手小程序接入的第三方插件/SDK 信息如下:
| SDK/服务名称 | 提供方 | 用途 | 涉及的个人信息 |
|------------|-------|------|-------------|
| TDesign 小程序组件库 | 腾讯 | 提供 UI 组件(按钮、表单、弹窗等),不涉及个人信息收集 | 无 |
| 阿里云百炼DashScope API | 阿里云计算有限公司 | 提供智能助手对话、运营数据分析、备注含金量评分等 AI 能力,后端服务器调用百炼 API 时会传入用户标识和对话内容 | 用户标识user_id、用户身份角色、用户昵称、用户输入的对话内容和备注内容 |
> 说明:
> - TDesign 为纯前端 UI 组件库,不采集任何用户数据。
> - 阿里云百炼为后端调用的 AI 服务(非小程序端 SDK后端将用户标识、身份、昵称及对话内容传给百炼 API 以实现智能助手功能。百炼平台根据传入参数做数据隔离,不同用户只能查询自己有权限的数据。
> - 百炼 API 的个人信息处理规则请参阅:[阿里云百炼隐私政策](https://terms.alicdn.com/legal-agreement/terms/suit_bu1_ali_cloud/suit_bu1_ali_cloud202112071754_83380.html)
> - 如后续接入微信同声传译插件(语音转文字)或其他 SDK需在此处补充。
---
## 未成年人保护
根据相关法律法规的规定,若你是 14 周岁以下的未成年人,你需要和你的监护人共同仔细阅读本指引,并在征得监护人明示同意后继续使用小程序服务。
开发者将根据相关法律法规的规定及本指引内容,处理经监护人同意而收集的未成年人用户信息,并通过本指引「你的权益」部分披露的内容保障未成年人在个人信息处理活动中的各项权利。
---
## 你的权益
关于你的个人信息,你可以通过以下方式与开发者联系,行使查阅、复制、更正、删除等法定权利。
若你在小程序中注册了账号,你可以通过以下方式与开发者联系,申请注销你在小程序中使用的账号。在受理你的申请后,开发者承诺在十五个工作日内完成核查和处理,并按照法律法规要求处理你的相关信息。
### 联系方式(在微信后台选择并填写)
| 联系方式 | 内容 |
|---------|------|
| 电子邮箱 | privacy@saiboting.com |
> 请在微信后台「你的权益」部分点击「增加联系方式」,选择「电子邮箱」,填入上述邮箱地址。
> 如有其他联系方式(如客服电话),也可一并添加。
---
## 开发者对信息的存储
### 存储期限
在微信后台选择:**固定存储期限**
建议填写天数:**365** 天
> 说明:用户数据保留 365 天1 年),超过存储期限后将依法删除或匿名化处理。
> 如用户主动注销账号,开发者将在 15 个工作日内完成数据删除。
开发者承诺,除法律法规另有规定外,开发者对你的信息的保存期限应当为实现处理目的所必要的最短时间。
---
## 信息的使用规则
开发者将会在本指引所明示的用途内使用收集的信息。
如开发者使用你的信息超出本指引目的或合理范围,开发者必须在变更使用目的或范围前,再次以弹窗方式告知并征得你的明示同意。
---
## 信息对外提供
开发者承诺,不会主动共享或转让你的信息至任何第三方,如存在确需共享或转让时,开发者应当直接征得或确认第三方征得你的单独同意。
开发者承诺,不会对外公开披露你的信息,如必须公开披露时,开发者应当向你告知公开披露的目的、披露信息的类型及可能涉及的信息,并征得你的单独同意。
---
## 投诉与建议
你认为开发者未遵守上述约定,或有其他的投诉建议、或未成年人个人信息保护相关问题,可通过以下方式与开发者联系;或者向微信进行投诉。
- 电子邮箱privacy@saiboting.com
---
## 补充文档
了解更多个人信息处理规则可查看补充文档。
> 微信后台支持上传 .txt 格式补充文档(大小不超过 100KB
> 如需上传,可将本文档的核心内容导出为 .txt 文件上传。
---
## 附:微信后台填写操作指南
### 操作步骤
1. 登录 [微信公众平台](https://mp.weixin.qq.com/)
2. 进入「账号设置」→「服务内容声明」→「用户隐私保护指引」→「去完善」
3. 按照上方表格,**仅勾选实际使用的 5 项信息类型**(微信昵称头像、手机号、麦克风、操作日志、设备信息)
4. 逐项填写用途(复制上方「微信后台填写参考」中的文本)
5. 在「第三方插件/SDK 信息」中填写 TDesign 信息(或选择"无"
6. 在「你的权益」中添加联系方式(电子邮箱)
7. 存储期限选择「固定存储期限」,填写 365 天
8. 信息使用规则中告知方式选择「弹窗」
9. 预览确认后提交
### 注意事项
- 隐私保护指引提交后,小程序端需实现隐私授权弹窗组件(见 LAUNCH-CHECKLIST 2.3 第二项)
- 每次新增隐私接口调用时,需回来更新隐私保护指引
- 邮箱地址 `privacy@saiboting.com` 需确保可正常接收邮件,请确认后再填写;如暂无此邮箱,可先使用公司其他可用邮箱

View File

@@ -0,0 +1,35 @@
## 以下内容添加到 LAUNCH-CHECKLIST.md 的 5.4 安全加固之后
### 5.5 微信 API 安全(证书签名)(依赖 3.4
| 状态 | 项目 |
|------|------|
| 已完成 | 微信后台申请 API 安全证书 |
| | 将 RSA 私钥文件放到服务器 `D:\NeoZQYY\secrets\wx_api_private_key.pem` |
| | 将平台证书放到服务器 `D:\NeoZQYY\secrets\wx_api_cert.cer` |
| | 在 `.env.local` 中配置 `WX_API_CERT_SN``WX_API_PRIVATE_KEY_PATH``WX_API_CERT_PATH``WX_API_SYMMETRIC_KEY` |
| | 安装依赖 `pip install cryptography` |
| | 实现签名工具类 `app/utils/wx_api_sign.py` |
| | 在需要签名的 API 调用中集成签名逻辑 |
> 详细使用说明见 [`docs/deployment/wx-api-security-guide.md`](wx-api-security-guide.md)
微信 API 安全提供了四样材料:
| 材料 | 用途 | 存放位置 |
|------|------|----------|
| 证书编号SN | 请求头标识 | `.env.local``WX_API_CERT_SN` |
| `.cer` 证书文件 | 验证微信响应签名 | `D:\NeoZQYY\secrets\wx_api_cert.cer` |
| 对称密钥AES Key | 解密敏感数据(如手机号) | `.env.local``WX_API_SYMMETRIC_KEY` |
| 非对称密钥RSA 私钥) | 对请求签名 | `D:\NeoZQYY\secrets\wx_api_private_key.pem` |
安全要求:
- 私钥文件和对称密钥绝对不能提交到 Git
- `D:\NeoZQYY\secrets\` 目录已在 `.gitignore``server-exclude.txt` 中排除
- 证书有有效期,到期前需在微信后台重新申请
## 同时更新 3.4 密钥配置,补充以下条目:
| | `WX_API_CERT_SN` 写入服务器 `.env.local` |
| | `WX_API_SYMMETRIC_KEY` 写入服务器 `.env.local` |
| | RSA 私钥文件放到 `D:\NeoZQYY\secrets\` |

View File

@@ -0,0 +1,451 @@
# 微信小程序 API 安全(证书 + 签名 + 加密)使用说明
> 校验依据https://developers.weixin.qq.com/miniprogram/dev/server/getting_started/api_signature.html
## 背景
微信小程序部分敏感 API如获取手机号等支持服务通信二次加密和签名
可有效防止数据篡改与泄露。开发者在小程序管理后台「开发 → 开发管理 → 开发设置 → API 安全」
进行密钥配置。
## 你拿到的四样材料
| 材料 | 微信后台对应位置 | 用途 |
|------|-----------------|------|
| 开放平台证书编号SN | API 安全页面显示 | 放在请求头 `Wechatmp-Serial` 中标识证书 |
| `.cer` 平台证书文件 | 配置完公钥后下载 | 验证微信 API 响应的签名RSAwithSHA256 |
| 对称密钥AES256 Key | API 对称密钥处配置 | 加密请求数据 + 解密响应数据AES256_GCM |
| 非对称密钥RSA 私钥) | API 非对称密钥处配置 | 对请求签名RSAwithSHA256 + PSS 填充) |
## 支持的算法组合
微信支持以下算法,在管理后台配置:
| 类型 | 可选算法 |
|------|---------|
| 加密算法 | AES256_GCM、SM4_GCM |
| 签名算法 | RSAwithSHA256、SM2withSM3 |
以下以 AES256_GCM + RSAwithSHA256 为例说明。
## 完整流程概览
```
请求:原始数据 → AES256_GCM 加密 → RSAwithSHA256 签名 → 发送
响应:接收 → RSAwithSHA256 验签 → AES256_GCM 解密 → 原始数据
```
## 配置步骤
### 1. 将密钥文件放到服务器
```powershell
New-Item -ItemType Directory -Path D:\NeoZQYY\secrets -Force
```
将以下文件放入 `D:\NeoZQYY\secrets\`
- `wx_api_private_key.pem` — 应用私钥RSA
- `wx_api_platform_cert.cer` — 平台证书(微信的公钥,用于验签响应)
### 2. 配置环境变量
`apps/backend/.env.local` 中添加:
```env
# API 安全 - 对称密钥编号(微信后台 API 安全页面显示的 SN
WX_API_SYM_SN=你的对称密钥编号
# API 安全 - 对称密钥AES256 KeyBase64 编码的 32 字节密钥)
WX_API_SYMMETRIC_KEY=你的对称密钥明文
# API 安全 - 非对称密钥编号
WX_API_ASYM_SN=你的非对称密钥编号
# API 安全 - RSA 私钥文件路径
WX_API_PRIVATE_KEY_PATH=D:/NeoZQYY/secrets/wx_api_private_key.pem
# API 安全 - 平台证书文件路径(微信的公钥,用于验签响应)
WX_API_PLATFORM_CERT_PATH=D:/NeoZQYY/secrets/wx_api_platform_cert.cer
# API 安全 - 平台证书编号(微信后台显示,非证书内序列号)
WX_API_PLATFORM_CERT_SN=平台证书编号
```
### 3. 后端工具类
`apps/backend/app/utils/` 下新建 `wx_api_sign.py`
```python
"""
微信小程序 API 安全 — 请求加密 + 签名 + 响应验签 + 解密
校验依据:
https://developers.weixin.qq.com/miniprogram/dev/server/getting_started/api_signature.html
流程:
1. 请求加密AES256_GCM 加密请求数据
2. 请求签名RSAwithSHA256 + PSS 填充salt=32对密文签名
3. 响应验签:用平台证书公钥验证响应签名
4. 响应解密AES256_GCM 解密响应数据
"""
import base64
import json
import os
import time
from pathlib import Path
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding as asym_padding
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.x509 import load_pem_x509_certificate
from app.config import get
class WxApiSecurity:
"""微信 API 安全:加密 + 签名 + 验签 + 解密"""
def __init__(self):
self.app_id: str = get("WX_APP_ID", "")
self.sym_sn: str = get("WX_API_SYM_SN", "")
self.asym_sn: str = get("WX_API_ASYM_SN", "")
self.platform_cert_sn: str = get("WX_API_PLATFORM_CERT_SN", "")
# 对称密钥AES256Base64 编码)
sym_key_b64 = get("WX_API_SYMMETRIC_KEY", "")
self.sym_key = base64.b64decode(sym_key_b64) if sym_key_b64 else None
# 加载 RSA 私钥(应用私钥,用于签名请求)
key_path = get("WX_API_PRIVATE_KEY_PATH", "")
if key_path and Path(key_path).exists():
with open(key_path, "rb") as f:
self.private_key = serialization.load_pem_private_key(
f.read(), password=None
)
else:
self.private_key = None
# 加载平台证书(微信公钥,用于验签响应)
cert_path = get("WX_API_PLATFORM_CERT_PATH", "")
if cert_path and Path(cert_path).exists():
with open(cert_path, "rb") as f:
cert = load_pem_x509_certificate(f.read())
self.platform_public_key = cert.public_key()
else:
self.platform_public_key = None
# ========== 请求加密AES256_GCM==========
def encrypt_request(
self, url_path: str, req_data: dict, timestamp: int
) -> dict:
"""
加密请求数据。
参数:
url_path: 完整 API URL含协议不含 query 参数),
"https://api.weixin.qq.com/wxa/business/getuserphonenumber"
req_data: 原始请求参数字典
timestamp: 时间戳
返回:
加密后的请求体 {"iv": "...", "data": "...", "authtag": "..."}
"""
if not self.sym_key:
raise RuntimeError("对称密钥未配置")
# 在原数据内添加安全字段
req_data["_n"] = base64.b64encode(os.urandom(16)).decode()
req_data["_appid"] = self.app_id
req_data["_timestamp"] = timestamp
plaintext = json.dumps(req_data, ensure_ascii=False).encode("utf-8")
# GCM 额外认证数据AADurlpath|appid|timestamp|sn
aad = f"{url_path}|{self.app_id}|{timestamp}|{self.sym_sn}".encode()
# 12 字节随机 IV
iv = os.urandom(12)
aesgcm = AESGCM(self.sym_key)
# encrypt 返回 ciphertext + tag最后 16 字节是 tag
ct_with_tag = aesgcm.encrypt(iv, plaintext, aad)
ciphertext = ct_with_tag[:-16]
authtag = ct_with_tag[-16:]
return {
"iv": base64.b64encode(iv).decode(),
"data": base64.b64encode(ciphertext).decode(),
"authtag": base64.b64encode(authtag).decode(),
}
# ========== 请求签名RSAwithSHA256 + PSS==========
def sign_request(
self, url_path: str, post_data: str, timestamp: int
) -> dict[str, str]:
"""
生成签名请求头。
参数:
url_path: 完整 API URL含协议不含 query 参数)
post_data: 加密后的请求体 JSON 字符串
timestamp: 时间戳(与加密时一致)
返回:
签名相关的请求头字典
"""
if not self.private_key:
raise RuntimeError("RSA 私钥未配置")
# 待签名串格式urlpath\nappid\ntimestamp\npostdata
# 字段之间用换行符分隔,末尾无额外换行符
payload = f"{url_path}\n{self.app_id}\n{timestamp}\n{post_data}"
# RSAwithSHA256 + PSS 填充salt 长度为 32
signature = self.private_key.sign(
payload.encode("utf-8"),
asym_padding.PSS(
mgf=asym_padding.MGF1(hashes.SHA256()),
salt_length=32,
),
hashes.SHA256(),
)
return {
"Wechatmp-Appid": self.app_id,
"Wechatmp-TimeStamp": str(timestamp),
"Wechatmp-Signature": base64.b64encode(signature).decode(),
}
# ========== 响应验签 ==========
def verify_response(
self, url_path: str, resp_data: str, timestamp: str,
signature_b64: str, serial: str
) -> bool:
"""
验证微信 API 响应签名。
参数:
url_path: 请求的 API URL
resp_data: 响应体字符串
timestamp: 响应头 Wechatmp-TimeStamp
signature_b64: 响应头 Wechatmp-SignatureBase64
serial: 响应头 Wechatmp-Serial平台证书编号
"""
if not self.platform_public_key:
raise RuntimeError("平台证书未配置")
# 待验签串urlpath\nappid\ntimestamp\nrespdata
payload = f"{url_path}\n{self.app_id}\n{timestamp}\n{resp_data}"
signature = base64.b64decode(signature_b64)
try:
self.platform_public_key.verify(
signature,
payload.encode("utf-8"),
asym_padding.PSS(
mgf=asym_padding.MGF1(hashes.SHA256()),
salt_length=asym_padding.PSS.AUTO,
),
hashes.SHA256(),
)
return True
except Exception:
return False
# ========== 响应解密AES256_GCM==========
def decrypt_response(
self, resp_body: dict, url_path: str, timestamp: str
) -> dict:
"""
解密微信 API 响应数据。
参数:
resp_body: 响应体 {"iv": "...", "data": "...", "authtag": "..."}
url_path: 请求的 API URL
timestamp: 响应头 Wechatmp-TimeStamp
"""
if not self.sym_key:
raise RuntimeError("对称密钥未配置")
iv = base64.b64decode(resp_body["iv"])
ciphertext = base64.b64decode(resp_body["data"])
authtag = base64.b64decode(resp_body["authtag"])
# AAD 格式与请求时一致
aad = f"{url_path}|{self.app_id}|{timestamp}|{self.sym_sn}".encode()
aesgcm = AESGCM(self.sym_key)
plaintext = aesgcm.decrypt(iv, ciphertext + authtag, aad)
return json.loads(plaintext)
# ========== 便捷方法:加密 + 签名一步到位 ==========
def prepare_request(
self, url_path: str, req_data: dict
) -> tuple[str, dict[str, str]]:
"""
一步完成加密 + 签名,返回 (加密后的body字符串, 请求头字典)。
"""
timestamp = int(time.time())
# 1. 加密
encrypted = self.encrypt_request(url_path, req_data, timestamp)
body_str = json.dumps(encrypted, ensure_ascii=False)
# 2. 签名
headers = self.sign_request(url_path, body_str, timestamp)
headers["Content-Type"] = "application/json"
return body_str, headers
# 模块级单例
wx_security = WxApiSecurity()
```
### 4. 使用示例:获取手机号
```python
import httpx
import json
from app.utils.wx_api_sign import wx_security
async def get_user_phone_number(access_token: str, code: str) -> dict:
"""
调用微信接口获取用户手机号(带加密 + 签名)。
code 来自小程序端 getPhoneNumber 按钮回调。
接口文档:
https://developers.weixin.qq.com/miniprogram/dev/server/API/user-info/phone-number/api_getphonenumber.html
"""
url_path = "https://api.weixin.qq.com/wxa/business/getuserphonenumber"
full_url = f"{url_path}?access_token={access_token}"
# 加密 + 签名
body_str, headers = wx_security.prepare_request(
url_path, {"code": code}
)
async with httpx.AsyncClient() as client:
resp = await client.post(full_url, content=body_str, headers=headers)
# 验签(从响应头获取签名信息)
resp_sig = resp.headers.get("Wechatmp-Signature", "")
resp_ts = resp.headers.get("Wechatmp-TimeStamp", "")
resp_serial = resp.headers.get("Wechatmp-Serial", "")
if resp_sig:
verified = wx_security.verify_response(
url_path, resp.text, resp_ts, resp_sig, resp_serial
)
if not verified:
raise ValueError("微信响应签名验证失败")
# 解密响应
resp_body = resp.json()
if "iv" in resp_body and "data" in resp_body:
return wx_security.decrypt_response(resp_body, url_path, resp_ts)
else:
# 未加密的响应(错误码等)
return resp_body
```
### 不使用加密的简单调用(仅签名)
如果某些接口只需要签名不需要加密,可以只用签名部分:
```python
async def call_wx_api_sign_only(
access_token: str, url_path: str, data: dict
) -> dict:
"""仅签名,不加密请求体"""
full_url = f"{url_path}?access_token={access_token}"
timestamp = int(time.time())
body_str = json.dumps(data, ensure_ascii=False)
headers = wx_security.sign_request(url_path, body_str, timestamp)
headers["Content-Type"] = "application/json"
async with httpx.AsyncClient() as client:
resp = await client.post(full_url, content=body_str, headers=headers)
return resp.json()
```
## 依赖安装
```bash
pip install cryptography httpx
```
- `cryptography` — RSA 签名PSS 填充)+ AES256_GCM 加解密 + X.509 证书解析
- `httpx` — 异步 HTTP 客户端
注意API 安全使用的是 AES256_GCM不是 CBC`cryptography` 库原生支持,不需要 `pycryptodome`
## 请求头说明
| Header | 说明 |
|--------|------|
| `Wechatmp-Appid` | 当前小程序的 AppID |
| `Wechatmp-TimeStamp` | 签名时的时间戳 |
| `Wechatmp-Signature` | 签名数据Base64 编码) |
## 响应头说明
| Header | 说明 |
|--------|------|
| `Wechatmp-Appid` | 小程序 AppID |
| `Wechatmp-TimeStamp` | 签名时间戳 |
| `Wechatmp-Serial` | 平台证书编号(在 MP 管理页获取,非证书内序列号) |
| `Wechatmp-Signature` | 平台证书签名Base64 |
| `Wechatmp-Serial-Deprecated` | 即将失效的证书编号(仅证书更换周期内出现) |
| `Wechatmp-Signature-Deprecated` | 即将失效的证书签名(仅证书更换周期内出现) |
> 若 `Wechatmp-Serial-Deprecated` 出现,说明当前使用的平台证书即将过期,请尽快到 MP 后台更新。
## 签名算法关键细节
1. 签名使用 PSS 填充方式salt 长度为 32
2. 待签名串格式:`urlpath\nappid\ntimestamp\npostdata`,字段间用 `\n` 分隔,末尾无额外换行
3. urlpath 需要包含 HTTP 协议头(如 `https://api.weixin.qq.com/wxa/...`),不包含 URL 参数
4. PSS 签名包含随机因子,每次签名结果都会变化,这是正常的
## 加密算法关键细节
1. 使用 AES256_GCM 模式(不是 CBC
2. IV 为 12 字节随机字符串
3. AAD额外认证数据格式`urlpath|appid|timestamp|sn`,用竖线 `|` 分隔
4. 加密后的 data 明文需要额外包含 `_n`(随机串)、`_appid``_timestamp` 三个安全字段
5. 不包含 AccessTokenAccessToken 放在 URL 参数中)
## 错误码
| 错误码 | 说明 |
|--------|------|
| 40230 | 缺少 Wechatmp-Serial |
| 40231 | 缺少 Wechatmp-Timestamp |
| 40232 | 缺少 Wechatmp-Signature |
| 40233 | 缺少 Wechatmp-Appid |
| 40234 | 签名错误 |
| 40235 | 加密错误 |
| 40236 | 无效的 Wechatmp-Appid |
| 40237 | Wechatmp-Appid 和 Token 不匹配 |
| 40238 | 开发者未设置对称密钥 |
| 40239 | 开发者未设置公钥 |
| 40240 | 超时的数据 |
## 安全注意事项
- RSA 私钥文件绝对不能提交到 Git放在 `D:\NeoZQYY\secrets\` 并确保 `.gitignore` 排除
- 对称密钥只放在 `.env.local` 中,不要硬编码
- 平台证书有有效期,注意响应头中的 `Wechatmp-Serial-Deprecated` 提示
- 测试环境和正式环境使用同一套密钥API 安全是 AppID 级别的)
## 参考文档
- [API 安全签名加密指南(官方)](https://developers.weixin.qq.com/miniprogram/dev/server/getting_started/api_signature.html)
- [获取手机号接口](https://developers.weixin.qq.com/miniprogram/dev/server/API/user-info/phone-number/api_getphonenumber.html)

View File

@@ -0,0 +1,283 @@
# 微信消息推送 — 安全模式AES 加解密)说明
> 校验依据https://developers.weixin.qq.com/miniprogram/dev/framework/server-ability/message-push.html
## 当前状态
- GET 验签:已通过,不受加密模式影响
- POST 消息接收:选择了「安全模式」,微信推送的消息体为 AES 加密,需要解密后才能读取
## 需要的配置
`apps/backend/.env.local` 中添加:
```env
# 微信后台「消息推送」页面的 EncodingAESKey43位字符串
WX_ENCODING_AES_KEY=你在微信后台生成的那个43位字符串
# 小程序的 AppID
WX_APP_ID=你的小程序AppID
```
## 安全模式与明文模式的关键区别
安全模式下,微信 POST 推送的 URL 参数会多出两个字段:
| 参数 | 说明 |
|------|------|
| `signature` | 普通签名token + timestamp + nonce仅用于明文模式 |
| `msg_signature` | 消息签名token + timestamp + nonce + Encrypt安全模式必须用这个验签 |
| `encrypt_type` | 值为 `aes`,表示安全模式 |
| `timestamp` | 时间戳 |
| `nonce` | 随机数 |
**重要:安全模式下验签必须使用 `msg_signature`,不要使用 `signature`**
验签算法:将 token、timestamp、nonce、Encrypt包体内的字段四个参数字典序排序后拼接做 SHA1`msg_signature` 比对。
## 解密原理
安全模式下,微信 POST 推送的 JSON 结构为:
```json
{
"Encrypt": "加密后的密文字符串"
}
```
解密流程:
1. 从请求体取出 `Encrypt` 字段
2. AESKey = Base64_Decode(EncodingAESKey + "="),得到 32 字节 AES 密钥
3. 将 Encrypt 密文进行 Base64 解码,得到 TmpMsg
4. 用 AESKey 对 TmpMsg 进行 AES-256-CBC 解密IV 取 AESKey 前 16 字节),去除 PKCS#7 填充K=32
5. 解密后的明文格式:`random(16B) + msg_len(4B) + msg + appid`
- random(16B)16 字节随机字符串
- msg_len消息长度4 字节网络字节序(大端)
- msg解密后的明文消息
- appid小程序 AppID需验证是否与自身一致
## 回包加密(安全模式下需要加密回包)
如果需要回复非空内容(非 `success`),安全模式下回包也需要加密:
1. 构造 FullStr = random(16B) + msg_len(4B) + msg + appid
2. 用 AESKey 进行 AES-256-CBC 加密PKCS#7 填充K=32
3. Base64 编码得到 Encrypt
4. 生成 MsgSignature将 token、TimeStamp、Nonce、Encrypt 四个参数字典序排序拼接后 SHA1
5. 回包格式JSON
```json
{
"Encrypt": "加密后的密文",
"MsgSignature": "签名",
"TimeStamp": ,
"Nonce": "随机数"
}
```
> 如果只需回复 `success`,无需加密,直接返回纯文本 `success` 即可。
## 代码改动
### 方案 A使用微信官方示例代码推荐
微信官方提供了多种语言的加解密示例代码PHP、Java、C++、Python、C#
建议优先使用官方示例https://developers.weixin.qq.com/miniprogram/dev/framework/server-ability/message-push.html
(页面底部「示例下载」链接)
安装依赖:
```bash
pip install pycryptodome # AES 加解密
```
### 方案 B手动实现解密工具
`apps/backend/app/utils/` 下新建 `wx_crypto.py`
```python
"""
微信消息加解密工具
校验依据:
https://developers.weixin.qq.com/miniprogram/dev/framework/server-ability/message-push.html
PKCS#7 填充说明(微信官方规范):
- K 为秘钥字节数(采用 32
- Buf 为待加密的内容N 为其字节数
- Buf 需要被填充为 K 的整数倍
- 在 Buf 的尾部填充 (K - N%K) 个字节,每个字节的内容是 (K - N%K)
"""
import base64
import hashlib
import struct
from Crypto.Cipher import AES
BLOCK_SIZE = 32 # 微信规范PKCS#7 填充使用 K=32
class WXBizMsgCrypt:
"""微信消息加解密"""
def __init__(self, token: str, encoding_aes_key: str, app_id: str):
self.token = token
self.app_id = app_id
# AESKey = Base64_Decode(EncodingAESKey + "=")
self.aes_key = base64.b64decode(encoding_aes_key + "=")
self.iv = self.aes_key[:16] # 初始向量取密钥前 16 字节
def check_msg_signature(
self, msg_signature: str, timestamp: str, nonce: str, encrypt: str
) -> bool:
"""
安全模式验签。
注意:安全模式下必须用 msg_signature 验签,参与排序的是四个参数:
token、timestamp、nonce、Encrypt包体内的字段
"""
items = sorted([self.token, timestamp, nonce, encrypt])
hash_str = hashlib.sha1("".join(items).encode("utf-8")).hexdigest()
return hash_str == msg_signature
def decrypt(self, encrypt_text: str) -> str:
"""
解密微信推送的加密消息。
返回解密后的消息明文JSON 字符串)。
"""
cipher = AES.new(self.aes_key, AES.MODE_CBC, self.iv)
decrypted = cipher.decrypt(base64.b64decode(encrypt_text))
# 去除 PKCS#7 填充K=32
pad_len = decrypted[-1]
if pad_len < 1 or pad_len > BLOCK_SIZE:
raise ValueError(f"无效的 PKCS#7 填充: {pad_len}")
content = decrypted[:-pad_len]
# FullStr = random(16B) + msg_len(4B) + msg + appid
msg_len = struct.unpack("!I", content[16:20])[0]
msg = content[20 : 20 + msg_len].decode("utf-8")
from_app_id = content[20 + msg_len :].decode("utf-8")
# 校验 AppID
if from_app_id != self.app_id:
raise ValueError(
f"AppID 不匹配: 期望 {self.app_id}, 实际 {from_app_id}"
)
return msg
def encrypt(self, msg: str) -> str:
"""
加密回包消息(安全模式下回复非 success 内容时需要)。
返回 Base64 编码的密文。
"""
import os
# 构造 FullStr = random(16B) + msg_len(4B) + msg + appid
msg_bytes = msg.encode("utf-8")
app_id_bytes = self.app_id.encode("utf-8")
full_str = (
os.urandom(16)
+ struct.pack("!I", len(msg_bytes))
+ msg_bytes
+ app_id_bytes
)
# PKCS#7 填充K=32
pad_len = BLOCK_SIZE - (len(full_str) % BLOCK_SIZE)
full_str += bytes([pad_len] * pad_len)
cipher = AES.new(self.aes_key, AES.MODE_CBC, self.iv)
encrypted = cipher.encrypt(full_str)
return base64.b64encode(encrypted).decode("utf-8")
def generate_msg_signature(
self, timestamp: str, nonce: str, encrypt: str
) -> str:
"""生成回包的 MsgSignature"""
items = sorted([self.token, timestamp, nonce, encrypt])
return hashlib.sha1("".join(items).encode("utf-8")).hexdigest()
```
### 改动 `wx_callback.py` 的 POST 处理
```python
# 在文件顶部新增导入
import json
from app.utils.wx_crypto import WXBizMsgCrypt
from app.config import get
# 初始化解密器(模块级别)
wx_crypt = WXBizMsgCrypt(
token=get("WX_CALLBACK_TOKEN", ""),
encoding_aes_key=get("WX_ENCODING_AES_KEY", ""),
app_id=get("WX_APP_ID", ""),
)
@router.post("/callback")
async def receive_message(
request: Request,
msg_signature: str = Query("", alias="msg_signature"),
signature: str = Query(""),
timestamp: str = Query(""),
nonce: str = Query(""),
encrypt_type: str = Query(""),
):
body = await request.body()
raw = json.loads(body)
# 安全模式:用 msg_signature 验签四参数token + timestamp + nonce + Encrypt
if encrypt_type == "aes" and "Encrypt" in raw:
if not wx_crypt.check_msg_signature(
msg_signature, timestamp, nonce, raw["Encrypt"]
):
logger.warning("安全模式消息推送验签失败")
return Response(content="signature mismatch", status_code=403)
decrypted_str = wx_crypt.decrypt(raw["Encrypt"])
data = json.loads(decrypted_str)
else:
# 明文模式 / 兼容模式:用 signature 验签(三参数)
if not _check_signature(signature, timestamp, nonce):
logger.warning("明文模式消息推送验签失败")
return Response(content="signature mismatch", status_code=403)
data = raw
logger.info(
"收到微信推送: MsgType=%s, Event=%s",
data.get("MsgType", "?"),
data.get("Event", "?"),
)
# TODO: 根据 MsgType/Event 分发处理
# 回复 success 无需加密
return Response(content="success", media_type="text/plain")
```
## 依赖安装
```bash
pip install pycryptodome
```
注意:是 `pycryptodome` 不是 `pycrypto`(后者已废弃)。
## 测试方法
部署后可以用微信开发者工具发送客服消息,或者关注/取关小程序触发事件推送,
查看后端日志确认消息是否正确解密:
```bash
# 在 Windows 服务器上查看后端日志
# 应该能看到 "收到微信推送: MsgType=event, Event=subscribe" 之类的输出
```
微信也提供了调试工具,可在消息推送配置页面使用「请求构造」和「调试工具」进行测试。
## 注意事项
- `EncodingAESKey` 必须和微信后台配置的完全一致,一个字符都不能错
- 如果后续在微信后台重新生成了 `EncodingAESKey`,服务器端也要同步更新并重启
- GET 验签逻辑不需要改动,安全模式只影响 POST 消息体
- PKCS#7 填充的 K 值为 32不是常见的 16这是微信的特殊规范
- 安全模式下验签用 `msg_signature`(四参数),不要用 `signature`(三参数)

View File

@@ -0,0 +1,56 @@
[
{
"region_index": 0,
"region_name": "经营一览",
"scroll_mode": "scroll_into_view",
"scroll_into_view_id": "section-overview",
"wait_ms": 800,
"screenshot_name": "mp-board-finance--seg-0.png",
"notes": "scroll-into-view -> section-overview"
},
{
"region_index": 1,
"region_name": "预收资产",
"scroll_mode": "scroll_into_view",
"scroll_into_view_id": "section-recharge",
"wait_ms": 1000,
"screenshot_name": "mp-board-finance--seg-1.png",
"notes": "scroll-into-view -> section-recharge"
},
{
"region_index": 2,
"region_name": "应计收入确认",
"scroll_mode": "scroll_into_view",
"scroll_into_view_id": "section-revenue",
"wait_ms": 1000,
"screenshot_name": "mp-board-finance--seg-2.png",
"notes": "scroll-into-view -> section-revenue"
},
{
"region_index": 3,
"region_name": "现金流入",
"scroll_mode": "scroll_into_view",
"scroll_into_view_id": "section-cashflow",
"wait_ms": 1000,
"screenshot_name": "mp-board-finance--seg-3.png",
"notes": "scroll-into-view -> section-cashflow"
},
{
"region_index": 4,
"region_name": "现金流出",
"scroll_mode": "scroll_into_view",
"scroll_into_view_id": "section-expense",
"wait_ms": 1000,
"screenshot_name": "mp-board-finance--seg-4.png",
"notes": "scroll-into-view -> section-expense"
},
{
"region_index": 5,
"region_name": "助教分析",
"scroll_mode": "scroll_into_view",
"scroll_into_view_id": "section-coach",
"wait_ms": 1000,
"screenshot_name": "mp-board-finance--seg-5.png",
"notes": "scroll-into-view -> section-coach"
}
]

View File

@@ -0,0 +1,129 @@
{
"pageHeight": 4976,
"viewportHeight": 752,
"anchorPositions": [
{
"name": "经营一览",
"selector": "#section-overview",
"found": true,
"top": 132,
"bottom": 736,
"height": 604
},
{
"name": "预收资产",
"selector": "#section-recharge",
"found": true,
"top": 784,
"bottom": 1466.796875,
"height": 682.796875
},
{
"name": "应计收入确认",
"selector": "#section-revenue",
"found": true,
"top": 1508.796875,
"bottom": 2857.09375,
"height": 1348.296875
},
{
"name": "现金流入",
"selector": "#section-cashflow",
"found": true,
"top": 2873.09375,
"bottom": 3333.390625,
"height": 460.296875
},
{
"name": "现金流出",
"selector": "#section-expense",
"found": true,
"top": 3349.390625,
"bottom": 4141.6875,
"height": 792.296875
},
{
"name": "助教分析",
"selector": "#section-coach",
"found": true,
"top": 4157.6875,
"bottom": 4679.984375,
"height": 522.296875
}
],
"stickyTotalHeight": 116,
"stickyDetails": [
{
"selector": ".safe-area-top",
"height": 45
},
{
"selector": "#filterBar",
"height": 71
}
],
"fixedBottomHeight": 56,
"fixedDetails": [
{
"selector": ".ai-float-btn-container",
"height": 56
}
],
"segments": [
{
"index": 0,
"name": "经营一览",
"scroll_y": 16,
"screenshot": "docs\\h5_ui\\screenshots\\h5-board-finance--seg-0.png",
"width": 1290,
"height": 2256
},
{
"index": 1,
"name": "预收资产",
"scroll_y": 668,
"screenshot": "docs\\h5_ui\\screenshots\\h5-board-finance--seg-1.png",
"width": 1290,
"height": 2256
},
{
"index": 2,
"name": "应计收入确认",
"scroll_y": 1392.796875,
"screenshot": "docs\\h5_ui\\screenshots\\h5-board-finance--seg-2.png",
"width": 1290,
"height": 2256
},
{
"index": 3,
"name": "现金流入",
"scroll_y": 2757.09375,
"screenshot": "docs\\h5_ui\\screenshots\\h5-board-finance--seg-3.png",
"width": 1290,
"height": 2256
},
{
"index": 4,
"name": "现金流出",
"scroll_y": 3233.390625,
"screenshot": "docs\\h5_ui\\screenshots\\h5-board-finance--seg-4.png",
"width": 1290,
"height": 2256
},
{
"index": 5,
"name": "助教分析",
"scroll_y": 4041.6875,
"screenshot": "docs\\h5_ui\\screenshots\\h5-board-finance--seg-5.png",
"width": 1290,
"height": 2256
}
],
"page_name": "board-finance",
"viewport": {
"width": 430,
"height": 752
},
"dpr": 3,
"timestamp": "2026-03-08 23:15:32"
}

View File

@@ -0,0 +1,32 @@
{
"extractedAt": "2026-03-06",
"viewport": "375x667",
"fontFamily": "\"Noto Sans SC\", -apple-system, BlinkMacSystemFont, sans-serif",
"pages": {
"task-list": {
"Banner": { "fontSize": "16px", "fontWeight": "400", "color": "#fff", "backgroundColor": "transparent", "borderRadius": "0", "padding": "0 0 16px", "lineHeight": "24px" },
"TaskCard": { "fontSize": "16px", "fontWeight": "400", "color": "#000", "backgroundColor": "#fff", "borderRadius": "12px", "padding": "16px", "lineHeight": "24px" },
"ContextMenu": { "fontSize": "16px", "fontWeight": "400", "color": "#000", "backgroundColor": "#fff", "borderRadius": "14px", "padding": "6px 0", "lineHeight": "24px" },
"SectionTitle": { "fontSize": "16px", "fontWeight": "600", "color": "rgb(36,36,36)", "lineHeight": "24px" },
"ModalCard": { "fontSize": "16px", "fontWeight": "400", "color": "#000", "backgroundColor": "#fff", "borderRadius": "16px", "padding": "24px 20px 20px", "lineHeight": "24px" }
},
"task-detail": {
"Banner": { "fontSize": "16px", "fontWeight": "400", "color": "#fff", "backgroundColor": "transparent", "padding": "0", "lineHeight": "24px" },
"SectionTitle": { "fontSize": "14px", "fontWeight": "600", "color": "rgb(36,36,36)", "padding": "0 0 0 12px", "lineHeight": "20px" },
"SpeechBubble": { "fontSize": "14px", "fontWeight": "400", "color": "rgb(94,94,94)", "backgroundColor": "rgb(240,244,255)", "borderRadius": "12px", "padding": "12px 16px", "lineHeight": "23.8px" },
"PrimaryButton": { "fontSize": "12px", "fontWeight": "500", "color": "rgb(0,82,217)", "lineHeight": "16px" }
},
"board-finance": {
"TabActive": { "fontSize": "14px", "fontWeight": "500", "color": "rgb(0,82,217)", "padding": "12px 0", "lineHeight": "20px" },
"CompareToggle": { "fontSize": "16px", "backgroundColor": "rgb(220,220,220)", "borderRadius": "12px" }
},
"chat": {
"MessageBubble": { "fontSize": "16px", "fontWeight": "400", "color": "#fff", "backgroundColor": "rgb(0,82,217)", "borderRadius": "16px 2px 16px 16px", "padding": "12px 16px", "lineHeight": "24px", "maxWidth": "80%" }
},
"login": {
"LoginButton": { "fontSize": "16px", "fontWeight": "500", "color": "#fff", "backgroundColor": "rgb(220,220,220)", "borderRadius": "12px", "padding": "16px 0", "lineHeight": "24px" },
"Title": { "fontSize": "24px", "fontWeight": "700", "color": "rgb(36,36,36)", "lineHeight": "32px" },
"Subtitle": { "fontSize": "14px", "fontWeight": "400", "color": "rgb(139,139,139)", "lineHeight": "22.75px" }
}
}
}

View File

@@ -1,5 +1,10 @@
/* ========== AI 标识通用样式 ========== */
/* --- 默认配色(无 JS 时的 fallback靛色 --- */
.ai-inline-icon, .ai-title-badge {
--ai-from: #667eea; --ai-to: #a78bfa; --ai-from-deep: #4a5fc7; --ai-to-deep: #667eea;
}
/* --- 配色系渐变背景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; }

View File

@@ -47,3 +47,84 @@ body {
padding: 16px;
margin-top: 8px;
}
/* ===== 备注弹窗 — 展开评价按钮 ===== */
.note-expand-btn {
font-size: 12px;
color: #0052d9;
background: none;
border: none;
cursor: pointer;
padding: 2px 0;
white-space: nowrap;
}
.note-expand-btn:active { opacity: 0.6; }
/* ===== 备注弹窗 — 星星打分组件 ===== */
.note-rating-group {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 20px;
padding: 14px 16px;
background: #f8f9fb;
border-radius: 14px;
border: 1px solid #eee;
}
.note-rating-label {
font-size: 13px;
font-weight: 600;
color: #393939;
line-height: 1.4;
white-space: nowrap;
flex-shrink: 0;
min-width: 48px;
}
.note-rating-right {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
align-items: flex-end;
}
.note-rating-row {
display: flex;
align-items: center;
gap: 6px;
user-select: none;
-webkit-user-select: none;
touch-action: none;
}
.note-rating-row .nr-star {
position: relative;
width: 32px;
height: 32px;
cursor: pointer;
flex-shrink: 0;
transition: transform 0.15s ease;
}
.note-rating-row .nr-star:active { transform: scale(0.85); }
.note-rating-row .nr-star svg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.note-rating-row .nr-star .nr-empty { color: #d9d9d9; }
.note-rating-row .nr-star .nr-filled { color: #faad14; clip-path: inset(0 100% 0 0); transition: clip-path 0.12s ease; }
.note-rating-row .nr-star.active .nr-filled { clip-path: inset(0 0 0 0); }
/* 1/3/5 星下方提示文字 */
.note-rating-hints {
display: flex;
margin-top: 4px;
padding: 0;
}
.note-rating-hints span {
width: 32px;
flex-shrink: 0;
font-size: 10px;
color: #a6a6a6;
text-align: center;
line-height: 1;
}
.note-rating-hints span + span { margin-left: 6px; }

View File

@@ -0,0 +1,49 @@
{
"colors": {
"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"
},
"spacing": {
"comment": "Tailwind 默认 1 unit = 4px缩放公式 rpx = px × 2 × 0.875,取偶数",
"base": 8,
"unit": "rpx"
},
"borderRadius": {
"comment": "简单 ×2 缩放A/B 对比验证差异 <0.02%,选择更整洁的 ×2 方案)",
"sm": "8rpx",
"md": "16rpx",
"lg": "24rpx",
"xl": "32rpx",
"2xl": "32rpx",
"3xl": "48rpx"
},
"fontSize": {
"comment": "已应用 87.5% 缩放H5 px × 2 × 0.875,取偶数",
"xs": "22rpx",
"sm": "24rpx",
"base": "28rpx",
"lg": "32rpx",
"xl": "36rpx",
"2xl": "42rpx"
},
"shadows": {
"lg": "0 8rpx 32rpx rgba(0,0,0,0.06)",
"xl": "0 16rpx 48rpx rgba(0,0,0,0.08)"
}
}

114
docs/h5_ui/icon-mapping.md Normal file
View File

@@ -0,0 +1,114 @@
# 图标映射表
> 全局图标映射TDesign 优先。自定义图标放 `apps/miniprogram/miniprogram/assets/icons/`。
> 复杂内联 SVG 统一导出为图片资源,避免小程序中维护大段 SVG 代码。
## 全局通用图标
| H5 中的图标描述 | 处理方式 | 小程序引用 |
|----------------|----------|-----------|
| Logo 台球图标 | 自定义 SVG → 文件 | `/assets/icons/logo-billiard.svg` |
| 微信图标 | 自定义 SVG → 文件 | `/assets/icons/icon-wechat.svg` |
| 返回箭头 | TDesign | `<t-icon name="chevron-left" />` |
| 右箭头 | TDesign | `<t-icon name="chevron-right" />` |
| 关闭 | TDesign | `<t-icon name="close" />` |
| 搜索 | TDesign | `<t-icon name="search" />` |
| 更多(三点) | TDesign | `<t-icon name="ellipsis" />` |
## Banner 背景处理方案(统一)
原型中所有 Banner 使用 `banner.css` 的 CSS 渐变 + `texture-aurora` SVG 丝带纹理,结构复杂(多层渐变+伪元素+内联 SVG path
**处理方式:** 统一用 Playwright 截取各主题 Banner 区域,导出为图片资源。
| Banner 主题 | 使用页面 | 导出文件 |
|-------------|----------|----------|
| `theme-blue texture-aurora` | task-list, performance | `/assets/images/banner-blue.png` |
| `theme-red texture-aurora` | task-detail高优先召回 | `/assets/images/banner-red.png` |
| `theme-orange texture-aurora` | task-detail优先召回 | `/assets/images/banner-orange.png` |
| `theme-pink texture-aurora` | task-detail-relationship关系构建 | `/assets/images/banner-pink.png` |
| `theme-teal texture-aurora` | task-detail-callback客户回访 | `/assets/images/banner-teal.png` |
| `theme-coral texture-aurora` | coach-detail | `/assets/images/banner-coral.png` |
| `theme-dark-gold` | customer-detail | `/assets/images/banner-dark-gold.png` |
> 小程序中 Banner 区域使用 `<image>` 组件 + 绝对定位文字叠加,替代 CSS 渐变方案。
## 状态页图标(复杂 SVG → 图片)
| H5 中的图标 | 实际 SVG 内容 | 处理方式 | 小程序引用 |
|-------------|--------------|----------|-----------|
| reviewing 主图标 | amber→orange 渐变方块 + 白色时钟(圆形表盘+时针分针) | 导出 SVG | `/assets/icons/icon-clock-circle.svg` |
| reviewing 进度对勾 | 绿色圆圈内白色对勾 polyline | TDesign | `<t-icon name="check-circle-filled" />` |
| reviewing 信息图标 | Material 风格 info圆+感叹号) | TDesign | `<t-icon name="info-circle-filled" />` |
| reviewing 聊天气泡 | 聊天气泡 path | TDesign | `<t-icon name="chat" />` |
| reviewing 登出箭头 | 右箭头+门框 path | TDesign | `<t-icon name="logout" />` |
| no-permission 主图标 | rose→red 渐变方块 + 白色禁止符号(圆+对角线) | 导出 SVG | `/assets/icons/icon-forbidden.svg` |
| no-permission 问号 | 问号圆圈 path | TDesign | `<t-icon name="help-circle-filled" />` |
## TabBar 图标
| Tab | 处理方式 | 小程序引用 |
|-----|----------|-----------|
| 任务(未选中) | 自定义 SVG剪贴板+勾选stroke 灰色) | `/assets/icons/tab-task.png` |
| 任务(选中) | 自定义 SVG剪贴板+勾选fill 蓝色) | `/assets/icons/tab-task-active.png` |
| 看板(未选中) | 自定义 SVG三柱状图stroke 灰色) | `/assets/icons/tab-board.png` |
| 看板(选中) | 自定义 SVG三柱状图fill 蓝色) | `/assets/icons/tab-board-active.png` |
| 我的(未选中) | 自定义 SVG人物头像stroke 灰色) | `/assets/icons/tab-my.png` |
| 我的(选中) | 自定义 SVG人物头像fill 蓝色) | `/assets/icons/tab-my-active.png` |
> TabBar 图标由 `bottom-nav.js` 定义,包含完整的 active/inactive SVG。建议统一导出为 PNG 图片对。
## AI 助手图标
| H5 中的图标描述 | 实际 SVG 内容 | 处理方式 | 小程序引用 |
|----------------|--------------|----------|-----------|
| AI 悬浮按钮 | 可爱机器人(圆角矩形身体+天线+紫蓝色大眼睛+微笑+粉色腮红+小耳朵),渐变动画背景 | 截图导出按钮整体 | `/assets/icons/icon-ai-float.png` |
| AI 内联图标(卡片中) | 同上机器人 SVG小尺寸 | 截图导出 | `/assets/icons/icon-ai-inline.png` |
| AI 标题徽章 | 同上机器人 + 随机配色 class | 截图导出 | `/assets/icons/icon-ai-badge.png` |
| 对话发送 | TDesign | TDesign | `<t-icon name="send" />` |
| 语音输入 | TDesign | TDesign | `<t-icon name="sound" />` |
> AI 图标由 `ai-icons.js` 统一管理页面加载时随机分配配色red/orange/yellow/blue/indigo/purple。小程序中可固定一种配色或实现随机逻辑。
## 任务模块图标
| H5 中的图标描述 | 处理方式 | 小程序引用 |
|----------------|----------|-----------|
| 置顶 📌 | Emoji 文本 | `📌` |
| 放弃 ❌ | Emoji 文本 | `❌` |
| 备注指示器 📝 | Emoji 文本 | `📝` |
| 爱心(💖>8.5 | Emoji 文本 | `💖` |
| 爱心(🧡>7 | Emoji 文本 | `🧡` |
| 爱心(💛>5 | Emoji 文本 | `💛` |
| 爱心(💙<5 | Emoji 文本 | `💙` |
| 喜好-中式 | Emoji 文本 | `🎱` |
| 喜好-斯诺克 | 文本 | `斯` |
| 喜好-麻将 | Emoji 文本 | `🀅` |
| 喜好-团建 | Emoji 文本 | `🎤` |
| "与我的关系"图标 | 可爱机器人 SVG同 AI 图标),可视为心形 ICON 扩展 | 截图导出 | `/assets/icons/icon-ai-relationship.png` |
## 看板模块图标
| H5 中的图标描述 | 处理方式 | 小程序引用 |
|----------------|----------|-----------|
| 筛选下拉箭头 | TDesign | `<t-icon name="caret-down-small" />` |
| 环比上升 | TDesign + 绿色 | `<t-icon name="arrow-up" />` |
| 环比下降 | TDesign + 红色 | `<t-icon name="arrow-down" />` |
| 目录导航按钮 | TDesign | `<t-icon name="view-list" />` |
| 指标帮助"?" | TDesign | `<t-icon name="help-circle" />` |
## 我的模块图标
| H5 中的图标描述 | 处理方式 | 小程序引用 |
|----------------|----------|-----------|
| 备注记录 | TDesign | `<t-icon name="edit-1" />` |
| 对话记录 | TDesign | `<t-icon name="chat" />` |
| 退出账号 | TDesign | `<t-icon name="poweroff" />` |
## 星级评价组件
原型中星级评价由 `ai-icons.js` 渲染:读取 `data-score`0-10转换为 5 颗星(支持半星)。
- 空星:灰色 SVG 五角星
- 满星/半星:彩色 SVG 五角星(通过 `clip-path` 实现半星)
- 小程序建议使用 TDesign `<t-rate>` 组件替代

View File

@@ -0,0 +1,60 @@
# 页面名apply账号申请页
> PRD 参考:`apps/miniprogram/doc/prd.md` 第七节 7.2P3 认证系统
> 已实现:是(`apps/miniprogram/miniprogram/pages/apply/`
## 页面说明
新用户提交入驻申请。顶部整合欢迎信息与审核流程步骤条,下方为结构化表单。
## 页面结构
1. 顶部蓝色卡片:欢迎语 + 四步审核流程(提交申请 → 等待审核 → 审核通过 → 开始使用)
2. 表单区域5 个独立文本输入字段
3. 底部固定提交按钮
## 状态变量
| 变量名 | 类型 | 初始值 | 必填 | 说明 |
|--------|------|--------|------|------|
| siteId | string | "" | 是 | 球房ID |
| role | string | "" | 是 | 申请身份(如:助教、店长等) |
| phone | string | "" | 是 | 手机号 |
| staffNo | string | "" | 否 | 编号(选填) |
| nickname | string | "" | 是 | 昵称 |
| loading | boolean | false | — | 提交请求中 |
## 用户操作 → 响应
| 操作 | 触发条件 | 响应行为 | 目标状态 |
|------|----------|----------|----------|
| 填写表单字段 | 无 | 更新对应状态变量;清除该字段错误状态 | 字段值更新 |
| 点击"提交申请" | 表单校验通过 | POST /api/xcx-auth/apply | loading=true |
| 点击"提交申请" | 表单校验失败 | 必填空字段高亮红框 + 下方红色提示文字,滚动到第一个错误字段 | 错误态 |
| 提交成功 | API 返回 | 跳转 reviewing 页面 | redirectTo reviewing |
| 提交失败 | API 报错 | Toast 提示错误信息 | loading=false |
## 表单校验规则
| 字段 | 规则 | 错误提示 |
|------|------|----------|
| siteId | 必填 | 请输入球房ID |
| role | 必填 | 请输入申请身份 |
| phone | 必填 | 请输入手机号 |
| staffNo | 选填,无校验 | — |
| nickname | 必填 | 请输入昵称 |
## 页面状态枚举
| 状态名 | 视觉表现 | 触发条件 |
|--------|----------|----------|
| 默认态 | 空表单,提交按钮可用 | 初始 |
| 校验错误态 | 必填空字段红色边框 + 下方红色提示文字 | 校验失败 |
| 加载中 | 提交按钮 loading | loading=true |
## 后端 API 依赖
| API | 方法 | 说明 |
|-----|------|------|
| `/api/xcx-auth/apply` | POST | 提交入驻申请 |
## 页面导航
- 来源loginstatus=new
- 去向reviewing提交成功
## 全局组件
- 无底部 TabBar
- 无 AI 悬浮按钮

View File

@@ -0,0 +1,104 @@
# 页面名board-coach助教看板
> PRD 参考P8 `docs/prd/specs/P8-miniapp-fe-boards.md``apps/miniprogram/doc/prd.md` 第九节 9.3
> 已实现:否
## 页面说明
按排序×技能×时间三重筛选查看助教排名列表。不同排序维度下卡片结构和突出数据不同。
## 状态变量
| 变量名 | 类型 | 初始值 | 说明 |
|--------|------|--------|------|
| selectedSort | string | "定档业绩最高" | 排序筛选 |
| selectedSkill | string | "不限" | 技能筛选 |
| selectedTime | string | "本月" | 时间筛选 |
| sortDropdownVisible | boolean | false | 排序筛选下拉展开 |
| skillDropdownVisible | boolean | false | 技能筛选下拉展开 |
| timeDropdownVisible | boolean | false | 时间筛选下拉展开 |
| coaches | array | [] | 助教列表 |
| loading | boolean | true | 数据加载中 |
| error | boolean | false | 加载失败 |
## 用户操作 → 响应
| 操作 | 触发条件 | 响应行为 | 目标状态 |
|------|----------|----------|----------|
| 页面加载 | 进入页面 | GET /api/board/coaches | loading→false |
| 点击排序筛选 | 无 | 展开排序下拉,关闭其他 | sortDropdownVisible=true |
| 选择排序 | 下拉展开 | 切换对应 dim-container 显隐,重新请求数据 | selectedSort 更新 |
| 点击技能筛选 | 无 | 展开技能下拉,关闭其他 | skillDropdownVisible=true |
| 选择技能 | 下拉展开 | 重新请求数据 | selectedSkill 更新 |
| 点击时间筛选 | 无 | 展开时间下拉,关闭其他 | timeDropdownVisible=true |
| 选择时间 | 下拉展开 | 重新请求数据 | selectedTime 更新 |
| 点击助教卡片 | 无 | navigateTo coach-detail | — |
| 切换看板 Tab | 顶部 Tab 栏 | navigateTo board-finance / board-customer | — |
| 滚动页面 | 持续 | 下滑隐藏筛选栏,上滑显示 | — |
| 点击"重试" | error=true | 重新请求数据 | loading=true |
## 筛选器选项(忠于原型 HTML
### 排序筛选6 个)
1. 定档业绩最高
2. 定档业绩最低
3. 工资最高
4. 工资最低
5. 客源储值最高
6. 任务完成最多
### 技能筛选
不限 / 🎱 / 斯 / 🀄 / 🎤
### 时间筛选
本月 / 本季度 / 上月 / 前3个月不含本月/ 上季度 / 最近6个月不含本月不支持客源储值最高
## 排序切换 → 卡片结构映射(忠于原型 HTML
切换排序时,通过 `selectSort(value)` 函数切换对应的 `dim-container` 显隐。每种排序的卡片右侧突出数据不同:
| 排序 | container id | 卡片右侧突出数据 |
|------|-------------|-----------------|
| 定档业绩最高/最低 | `dim-perf` | 定档 Xh + 折前 Xh + 距升档 Xh或 ✅ 已达标) |
| 工资最高/最低 | `dim-salary` | 预估标签 + ¥金额(大字)+ 定档/折前 |
| 客源储值最高 | `dim-sv` | 储值 ¥金额(大字)+ 客户 X人 + 消耗 ¥X |
> 高客源储值定义PRD 补充RS > 2 的客户的会员卡储值合计RS = 关系强度指数,> 2 表示有一定服务关系的客户)
| 任务完成最多 | `dim-task` | 召回 X蓝色大字+ 回访 X |
### 所有卡片共有元素
- 助教头像圆形 + 姓名
- 等级标签(星级/高级/中级/初级)— 不同等级不同颜色
- 技能标签(🎱/斯/🀄/🎤)
- 底部 TOP3 客户 emoji 列表
## 等级标签颜色
| 等级 | 颜色 |
|------|------|
| 星级 | 金色/amber |
| 高级 | 蓝色/primary |
| 中级 | 绿色/success |
| 初级 | 灰色/gray |
## 筛选栏滚动行为
- 下滑时筛选栏隐藏,上滑时重新显示
-`initFilterBarScrollBehavior()` 实现
## 页面状态枚举
| 状态名 | 视觉表现 | 触发条件 |
|--------|----------|----------|
| 加载中 | 区域文案"加载中..." | loading=true |
| 正常态 | 助教列表 | 有数据 |
| 空数据态 | "暂无数据" | coaches 为空 |
| 错误态 | "加载失败,请点击重试" + 重试按钮 | error=true |
## 后端 API 依赖
| API | 方法 | 说明 |
|-----|------|------|
| `GET /api/board/coaches` | GET | 助教看板(排序×技能×时间三重筛选) |
## 页面导航
- 来源board-finance / board-customer看板 Tab 切换)
- 去向coach-detail / chat
## 全局组件
- 底部 TabBar看板 active
- AI 悬浮按钮(右下角)
- 看板顶部 Tab 栏(财务 / 客户 / 助教 active

View File

@@ -0,0 +1,90 @@
# 页面名board-customer客户看板
> PRD 参考P8 `docs/prd/specs/P8-miniapp-fe-boards.md``apps/miniprogram/doc/prd.md` 第九节 9.2
> 已实现:否
## 页面说明
按 8 个维度查看前 100 名客户。支持维度切换和项目筛选,不同维度下卡片结构和展示内容不同。
## 状态变量
| 变量名 | 类型 | 初始值 | 说明 |
|--------|------|--------|------|
| selectedType | string | "最应召回" | 维度/类型筛选8 个维度) |
| selectedProject | string | "全部" | 项目筛选 |
| customers | array | [] | 客户列表(前 100 名) |
| typeDropdownVisible | boolean | false | 维度筛选下拉展开 |
| projectDropdownVisible | boolean | false | 项目筛选下拉展开 |
| loading | boolean | true | 数据加载中 |
| error | boolean | false | 加载失败 |
## 用户操作 → 响应
| 操作 | 触发条件 | 响应行为 | 目标状态 |
|------|----------|----------|----------|
| 页面加载 | 进入页面 | GET /api/board/customers | loading→false |
| 点击维度筛选 | 无 | 展开维度下拉,关闭其他 | typeDropdownVisible=true |
| 选择维度 | 下拉展开 | 切换对应 dim-container 显隐,重新请求数据 | selectedType 更新 |
| 点击项目筛选 | 无 | 展开项目下拉,关闭其他 | projectDropdownVisible=true |
| 选择项目 | 下拉展开 | 重新请求数据 | selectedProject 更新 |
| 点击客户卡片 | 无 | navigateTo customer-detail | — |
| 切换看板 Tab | 顶部 Tab 栏 | navigateTo board-finance / board-coach | — |
| 滚动页面 | 持续 | 下滑隐藏筛选栏,上滑显示 | — |
| 点击"重试" | error=true | 重新请求数据 | loading=true |
## 筛选器选项(忠于原型 HTML
### 维度/类型筛选8 个)
1. 最应召回 — WBI加权流失指数综合到店间隔、消费频率、余额等
2. 最大消费潜力 — 综合权重(频率×客单×余额×活跃度)
3. 最高余额
4. 最近充值
5. 最近到店
6. 最高消费 近60天
7. 最频繁 近60天
8. 最专一 近60天 — 亲密度最大值(该客户对某助教的服务集中度)
### 项目筛选
全部 / 中🎱 / 斯诺克 / 麻将 / 团建
## 维度切换 → 卡片结构映射(忠于原型 HTML
切换维度时,通过 `selectType(value)` 函数切换对应的 `dim-container` 显隐。每个维度的卡片结构不同:
| 维度 | container id | 卡片特有内容 |
|------|-------------|-------------|
| 最应召回 | `dim-recall` | 理想天数 / 已过天数 / 超期天数(红色标签)+ 30天到店次数 / 余额 / 召回指数 |
| 最大消费潜力 | `dim-potential` | 频率标签(高频/中频)+ 客单标签 + 余额标签 + 4列网格30天消费/月均到店/余额/次均消费 |
| 最高余额 | `dim-balance` | 最近到店/理想天数 + 3列网格余额大字橙色/月均消耗/可用月数 |
| 最近充值 | `dim-recharge` | 最近到店/理想天数 + 4列网格最后充值/充值金额/60天充值次数/当前余额 |
| 最近到店 | `dim-recent` | 到店天数 + 到店频率 + 余额 |
| 最高消费 近60天 | `dim-spend60` | 高消费标签 + 3列网格60天消费/次均消费/到店次数 |
| 最频繁 近60天 | `dim-freq60` | 频率标签 + 到店次数/消费金额/次均消费 |
| 最专一 近60天 | `dim-loyal` | 专一度指标 + 服务助教分布 |
### 所有卡片共有元素
- 客户昵称 + 爱心 emoji💖/🧡/💛/💙)+ 喜好标签(🎱/斯/🀅/🎤)
- 底部助教行:助教名 + 关系 emoji + 跟/弃 badge
- 助教标记样式:有助教跟进时显示助教名+关系状态
## 筛选栏滚动行为
- 下滑时筛选栏隐藏,上滑时重新显示
-`initFilterBarScrollBehavior()` 实现
## 助教身份隐式过滤PRD 补充)
- 助教角色登录时,后台默认只显示该助教 14 天内服务过的客户
- 店长角色无此限制,可查看全部客户
## 页面状态枚举
## 后端 API 依赖
| API | 方法 | 说明 |
|-----|------|------|
| `GET /api/board/customers` | GET | 客户看板(维度+项目筛选) |
## 页面导航
- 来源board-finance / board-coach看板 Tab 切换)
- 去向customer-detail / chat
## 全局组件
- 底部 TabBar看板 active
- AI 悬浮按钮(右下角)
- 看板顶部 Tab 栏(财务 / 客户 active / 助教)

View File

@@ -0,0 +1,115 @@
# 页面名board-finance财务看板
> PRD 参考P8 `docs/prd/specs/P8-miniapp-fe-boards.md``apps/miniprogram/doc/prd.md` 第九节 9.1
> 已实现:否
## 页面说明
看板 Tab 的默认页面。展示多维度交叉筛选的财务数据,含环比开关、目录导航、吸顶板块头、指标解释弹窗。顶部有时间和区域两组筛选器。
## 状态变量
| 变量名 | 类型 | 初始值 | 说明 |
|--------|------|--------|------|
| selectedTime | string | "本月" | 时间筛选 |
| selectedArea | string | "全部区域" | 区域筛选 |
| compareEnabled | boolean | false | 环比对比开关 |
| timeDropdownVisible | boolean | false | 时间筛选下拉展开 |
| areaDropdownVisible | boolean | false | 区域筛选下拉展开 |
| tocVisible | boolean | false | 目录导航面板展开 |
| tipVisible | boolean | false | 指标解释弹窗显示 |
| tipType | string | "" | 当前显示的指标解释类型 |
| currentSection | number | 0 | 当前可视板块索引(用于吸顶和目录高亮) |
| loading | boolean | true | 数据加载中 |
| error | boolean | false | 加载失败 |
## 用户操作 → 响应
| 操作 | 触发条件 | 响应行为 | 目标状态 |
|------|----------|----------|----------|
| 页面加载 | 进入页面 | GET /api/board/finance | loading→false |
| 点击时间筛选 | 无 | 展开时间下拉面板,关闭其他面板 | timeDropdownVisible=true |
| 选择时间选项 | 下拉展开 | 重新请求数据,关闭下拉 | selectedTime 更新 |
| 点击区域筛选 | 无 | 展开区域下拉面板,关闭其他面板 | areaDropdownVisible=true |
| 选择区域选项 | 下拉展开 | 重新请求数据,关闭下拉 | selectedArea 更新 |
| 切换环比开关 | 无 | 切换所有 `[data-compare]` 元素显隐 | compareEnabled=!compareEnabled |
| 点击目录按钮 | 无 | 展开/关闭目录导航面板 | tocVisible=!tocVisible |
| 点击目录项 | tocVisible=true | 滚动到对应板块,关闭目录 | currentSection 更新 |
| 点击指标"?"图标 | 无 | 底部弹出指标解释弹窗+遮罩 | tipVisible=true |
| 点击遮罩/关闭 | tipVisible=true | 关闭指标解释弹窗 | tipVisible=false |
| 滚动页面 | 持续 | 检测当前可视板块 → 更新吸顶头+目录高亮;下滑隐藏筛选栏,上滑显示 | currentSection 更新 |
| 切换看板 Tab | 顶部 Tab 栏 | navigateTo board-customer / board-coach | — |
| 点击"重试" | error=true | 重新请求数据 | loading=true |
## 筛选器选项(忠于原型 HTML
### 时间筛选
本月 / 上月 / 本周 / 上周 / 前3个月 不含本月 / 本季度 / 上季度 / 最近6个月不含本月
### 区域筛选
全部区域 / 大厅(含 A区/B区/C区/ 麻将房 / 团建房
### 筛选联动规则
- 选择非"全部区域"时,预收资产板块隐藏
- 营业日以 08:00 为分割点
## 环比开关
- 滑块开关样式44×24px默认灰色 `#dcdcdc`,激活后蓝色 `#0052d9`
- 控制所有 `[data-compare]` 元素的显隐
- 环比数据:上升绿色↑、下降红色↓
## 目录导航6 个板块)
| 序号 | 图标 | 板块名 | section id |
|------|------|--------|------------|
| 1 | 📈 | 经营一览 | section-overview |
| 2 | 💳 | 预收资产 | section-recharge |
| 3 | 💰 | 【记账】应计收入确认 | section-revenue |
| 4 | 🧾 | 【现金流水】流入 | section-cashflow |
| 5 | 📤 | 【现金流水】流出 | section-expense |
| 6 | 🎱 | 助教分析 | section-coach |
## 吸顶板块头
- 滚动时检测当前可视板块,在顶部显示对应板块标题
-`updateStickyHeader()` + `getCurrentSection()` + `initSectionScrollBehavior()` 实现
## 筛选栏滚动行为
- 下滑时筛选栏隐藏(向上收起)
- 上滑时筛选栏重新显示
-`initFilterBarScrollBehavior()` 实现
## 指标解释弹窗
- 底部弹出式弹窗 + 半透明遮罩
- 点击指标旁的"?"图标触发
- 展示对应指标的计算口径和说明
## 数据板块
1. 经营一览(副标题:快速了解收入与现金流的整体健康度)
2. 预收资产(仅"全部区域"时显示)
3. 【记账】应计收入确认
4. 【现金流水】流入
5. 【现金流水】流出
6. 助教分析
## AI 智能洞察PRD 补充)
- 来源AI 应用 2`cache_type=app2_finance`
- 数据格式JSON 数组,每条含洞察标题+内容
- 缓存策略Redis / 内存缓存key = `site_id` + 筛选条件(时间+区域ETL 更新后失效,约 1 小时刷新
- 展示位置:经营一览板块底部或独立洞察卡片区域
## 页面状态枚举
| 状态名 | 视觉表现 | 触发条件 |
|--------|----------|----------|
| 加载中 | 区域文案"加载中..." | loading=true |
| 正常态 | 完整财务数据 + 环比 | 有数据 |
| 错误态 | "加载失败,请点击重试" + 重试按钮 | error=true |
## 后端 API 依赖
| API | 方法 | 说明 |
|-----|------|------|
| `GET /api/board/finance` | GET | 财务看板数据(交叉筛选) |
## 页面导航
- 来源TabBar 切换 / loginapproved首页设置为看板
- 去向board-customer / board-coach / chat
## 全局组件
- 底部 TabBar看板 active
- AI 悬浮按钮
- 看板顶部 Tab 栏(财务 active / 客户 / 助教)— sticky 定位

View File

@@ -0,0 +1,47 @@
# 页面名chat-history对话历史
> PRD 参考P9 `docs/prd/specs/P9-miniapp-fe-details.md``apps/miniprogram/doc/prd.md` 第十节
> 已实现:否
## 页面说明
展示历史 AI 对话记录列表,按时间倒序排列。点击可进入继续对话。
## 状态变量
| 变量名 | 类型 | 初始值 | 说明 |
|--------|------|--------|------|
| conversations | array | [] | 历史对话列表 |
| loading | boolean | true | 数据加载中 |
| error | boolean | false | 加载失败 |
## 用户操作 → 响应
| 操作 | 触发条件 | 响应行为 | 目标状态 |
|------|----------|----------|----------|
| 页面加载 | 进入页面 | GET /api/chat/conversations | loading→false |
| 点击对话记录 | 无 | navigateTo chat带 sessionId继续对话 | — |
| 点击返回 | 顶部导航栏 | navigateBack | — |
| 点击"重试" | error=true | 重新请求数据 | loading=true |
## 列表展示
- 按时间倒序排列
- 每条记录:对话标题/摘要 + 最后消息时间 + 消息数量
## 页面状态枚举
| 状态名 | 视觉表现 | 触发条件 |
|--------|----------|----------|
| 加载中 | 区域文案"加载中..." | loading=true |
| 正常态 | 对话列表 | 有数据 |
| 空数据态 | "暂无数据" | conversations 为空 |
| 错误态 | "加载失败,请点击重试" + 重试按钮 | error=true |
## 后端 API 依赖
| API | 方法 | 说明 |
|-----|------|------|
| `GET /api/chat/conversations` | GET | 对话历史列表 |
## 页面导航
- 来源my-profile点击"助手对话记录"
- 去向chat点击对话记录继续对话
## 全局组件
- 自定义顶部导航栏(返回按钮 + "对话记录"
- AI 悬浮按钮

View File

@@ -0,0 +1,81 @@
# 页面名chatAI 对话)
> PRD 参考P9 `docs/prd/specs/P9-miniapp-fe-details.md``apps/miniprogram/doc/prd.md` 第十节
> 已实现:否
## 页面说明
与 AI 助手进行对话。支持文本输入和语音输入AI 回复流式展示SSE。从其他页面进入时携带上下文引用。
> 原型 HTML 为纯静态展示(对话内容硬编码),仅有顶部返回 `history.back()` 一个交互事件。以下交互逻辑基于 PRD 设计。
## 状态变量
| 变量名 | 类型 | 初始值 | 说明 |
|--------|------|--------|------|
| messages | array | [] | 对话消息列表 |
| inputText | string | "" | 输入框内容 |
| isRecording | boolean | false | 录音状态 |
| referenceContent | object | null | 引用内容(来源页面 title + 基本信息) |
| sessionId | string | "" | 会话 ID |
| isStreaming | boolean | false | AI 正在流式回复 |
| loading | boolean | false | 加载历史消息中 |
## 用户操作 → 响应
| 操作 | 触发条件 | 响应行为 | 目标状态 |
|------|----------|----------|----------|
| 页面加载(新对话) | 从其他页面进入 | 创建新会话,第一条消息为页面上下文 | sessionId 设置 |
| 页面加载(继续对话) | 从 chat-history 进入 | 加载历史消息 | messages 填充 |
| 输入文本 + 点击发送 | inputText 非空 | POST /api/chat/conversations/:id/messagesSSE | isStreaming=true |
| 按住说话 | 无 | 开始录音 | isRecording=true |
| 松开说话 | isRecording=true | 语音转文字 → 填充输入框 | isRecording=false |
| AI 回复完成 | SSE 流结束 | 消息列表更新 | isStreaming=false |
| 上拉加载 | 有更早记录 | 加载更早消息 | loading=true→false |
| 距最后消息 >1 小时 | 时间判断 | 显示"新对话主题"和"继续对话"按钮 | — |
| 点击"新对话主题" | 提示条显示 | 创建新会话 | sessionId 更新 |
| 点击"继续对话" | 提示条显示 | 在当前会话继续 | — |
| 点击返回 | 顶部导航栏 | navigateBack / history.back() | — |
## 消息展示规则
- IM 风格:左侧 AI 气泡,右侧用户气泡
- AI 回复支持流式展示(逐字输出)
- 引用内容:灰底卡片,含来源类型、标题、摘要
- 发送后自动滚动到底部
## AI 对话来源展示规则PRD 补充)
从不同入口进入 chat 页面时,引用卡片的来源 title 定义:
| 入口页面 | 来源 title | 携带上下文 |
|----------|-----------|-----------|
| task-list 长按菜单"问问AI助手" | 任务:{客户昵称} | 任务类型 + 客户基本信息 |
| task-detail "问问助手" | 任务详情:{客户昵称} | 任务详情 + 客户信息 + AI 分析 |
| customer-detail "问问助手" | 客户:{客户昵称} | 客户信息 + 消费记录 + 指数 |
| AI 悬浮按钮(任意页面) | 当前页面名称 | 当前页面上下文 |
| chat-history 继续对话 | 历史对话 | 历史消息 |
## 会话管理规则PRD 补充)
- 距最后一条消息超过 1 小时:页面顶部显示提示条,含两个按钮:
- 「新对话主题」→ 创建新 sessionId清空消息区
- 「继续对话」→ 保持当前 sessionId在原会话追加
- 每个会话有独立 sessionId用于后端关联上下文
## 页面状态枚举
| 状态名 | 视觉表现 | 触发条件 |
|--------|----------|----------|
| 空对话 | 引用卡片 + 空消息区 | 新对话,未发送 |
| 对话中 | 消息列表 + 输入区 | 有消息 |
| AI 回复中 | 最后一条消息逐字输出 | isStreaming=true |
| 录音中 | 输入区显示录音动画 | isRecording=true |
## 后端 API 依赖
| API | 方法 | 说明 |
|-----|------|------|
| `POST /api/chat/conversations` | POST | 创建对话 |
| `POST /api/chat/conversations/:id/messages` | POST | 发送消息SSE 流式返回) |
| `GET /api/chat/conversations/:id/messages` | GET | 对话消息列表 |
## 页面导航
- 来源任意业务页面AI 悬浮按钮 / "问问助手"按钮)/ chat-history
- 去向:无(末端页面)
## 全局组件
- 自定义顶部导航栏(返回按钮 + "智能助手"
- 无底部 TabBar
- 无 AI 悬浮按钮(本页即为 AI 对话)

View File

@@ -0,0 +1,100 @@
# 页面名coach-detail助教详情
> PRD 参考P9 `docs/prd/specs/P9-miniapp-fe-details.md`
> 已实现:否
## 页面说明
展示助教完整信息:绩效概览、收入明细(本月/上月 Tab、任务执行展开/收起)、客户关系 TOP5、近期服务明细、更多信息。支持备注弹窗。
## Banner 主题
`banner-bg theme-coral texture-aurora`(珊瑚粉主题 + 极光丝带纹理)
> ⚠️ Banner 背景使用 CSS 渐变+SVG 纹理,小程序实现时建议导出为图片资源 `/assets/images/banner-coral.png`
## 页面区域结构(忠于原型 HTML
1. Banner助教信息花名/等级/技能标签/工龄/客户数)
- 工龄计算PRD 补充):入职时间到当前日期的年数(精确到 0.1 年)
2. 绩效概览(`st blue`)— 4宫格本月定档业绩/本月工资预估/客源储值余额/本月任务完成 + 绩效档位进度条
3. 收入明细(`st green`)— Tab 切换:本月预估 / 上月
4. 任务执行(`st orange`)— 任务列表 + 展开/收起
5. 客户关系 TOP5`st pink`)— 近60天
6. 近期服务明细(`st purple`
7. 更多信息(`st teal`)— 入职日期 + 历史月份表格
## 状态变量
| 变量名 | 类型 | 初始值 | 说明 |
|--------|------|--------|------|
| coachInfo | object | null | 助教信息(花名、级别、技能、工龄、客户数) |
| incomeTab | string | "this" | 收入明细当前 Tabthis=本月/last=上月) |
| tasksExpanded | boolean | false | 任务列表展开状态 |
| noteModalVisible | boolean | false | 备注弹窗显示状态 |
| noteText | string | "" | 备注输入内容 |
| notesPopupVisible | boolean | false | 备注列表弹窗显示状态 |
| loading | boolean | true | 数据加载中 |
| error | boolean | false | 加载失败 |
## 用户操作 → 响应
| 操作 | 触发条件 | 响应行为 | 目标状态 |
|------|----------|----------|----------|
| 页面加载 | 进入页面 | GET /api/coaches/:id含全部区域数据 | loading→false |
| 切换收入明细 Tab | 点击"本月"/"上月" | 切换对应内容显隐 | incomeTab 更新 |
| 点击"展开全部 ↓" | tasksExpanded=false | 显示隐藏的任务列表(含已放弃任务) | tasksExpanded=true |
| 点击"收起 ↑" | tasksExpanded=true | 隐藏多余任务 | tasksExpanded=false |
| 点击"备注"按钮 | 无 | 打开备注弹窗 | noteModalVisible=true |
| 备注弹窗-提交 | noteText 非空 | POST /api/xcx/notes | Toast "备注已保存",弹窗关闭 |
| 点击客户卡片 | 客户关系 TOP5 区域 | navigateTo customer-detail | — |
| 点击客户卡片的备注图标 | 无 | 弹出该客户的备注列表弹窗(阻止冒泡,不触发卡片跳转) | notesPopupVisible=true |
| 点击返回 | 顶部导航栏 | navigateBack / history.back() | — |
| 点击"重试" | error=true | 重新请求数据 | loading=true |
## 收入明细 Tab
- 本月(`id="incomeTab_this"`含「预估」标签active 状态
- 上月(`id="incomeTab_last"`
- 明细项:基础课时费、激励课时费、充值提成、酒水提成、合计
-`switchIncomeTab(tab)` 控制切换
## 任务执行区域
- 默认显示前 6 项任务
- 隐藏区域 `id="hiddenTasks"`:更多任务 + 已放弃任务
- 已放弃任务样式opacity 0.55 + 删除线 + 放弃原因文字
- 展开/收起按钮 `id="toggleTasksBtn"`:文字在「展开全部 ↓」/「收起 ↑」间切换
-`toggleAllTasks()` 控制
## 客户关系 TOP5
- 标题右侧标注「近60天」
- 5 个客户卡片,每个含:
- 头像圆形 + 姓名 + 关系 emoji/💛)+ 关系分数(彩色)
- 服务次数 / 储值 / 消费
- 前 2 名有渐变背景pink/amber后 3 名灰色背景
## 备注弹窗
- 底部弹出式弹窗
- 包含:多行文本输入 + 提交按钮
- 空值校验:内容为空时 Toast 提示
-`showNoteModal()` / `hideNoteModal()` / `saveNote()` 控制
## 备注列表弹窗
- 点击客户卡片的备注图标触发
- 动态渲染该客户的备注列表
-`showNotesPopup(name, notes)` / `hideNotesPopup()` 控制
## 页面状态枚举
| 状态名 | 视觉表现 | 触发条件 |
|--------|----------|----------|
| 加载中 | 区域文案"加载中..." | loading=true |
| 正常态 | 完整助教详情 | 有数据 |
| 错误态 | "加载失败,请点击重试" + 重试按钮 | error=true |
## 后端 API 依赖
| API | 方法 | 说明 |
|-----|------|------|
| `GET /api/coaches/:id` | GET | 助教详情(含绩效、收入、任务、客户关系) |
| `POST /api/xcx/notes` | POST | 创建备注 |
## 页面导航
- 来源board-coach点击助教卡片
- 去向task-detail*(点击客户卡片)/ chat通过悬浮按钮
## 全局组件
- 自定义顶部导航栏(返回按钮 + "助教详情"
- AI 悬浮按钮

View File

@@ -0,0 +1,71 @@
# 页面名customer-detail客户详情
> PRD 参考P9 `docs/prd/specs/P9-miniapp-fe-details.md`
> 已实现:否
## 页面说明
展示客户全信息、消费记录三种样式、指数总览、备注、AI 维客线索、AI 客户分析。纯静态展示页(原型中无复杂交互函数)。
## 状态变量
| 变量名 | 类型 | 初始值 | 说明 |
|--------|------|--------|------|
| customerInfo | object | null | 客户信息(昵称、手机、会员卡等级、余额、注册日期) |
| indexes | object | null | 指数总览WBI/NCI/SPI + 爱心 icon |
| consumptionRecords | array | [] | 消费记录列表 |
| consumptionPage | number | 1 | 消费记录当前页 |
| hasMoreRecords | boolean | true | 是否还有更多消费记录 |
| retentionClues | array | [] | AI 维客线索(应用 8 |
| customerAnalysis | object | null | AI 客户分析(应用 7 |
| notes | array | [] | 备注列表 |
| loading | boolean | true | 数据加载中 |
| error | boolean | false | 加载失败 |
| loadingMore | boolean | false | 加载更多消费记录中 |
## 用户操作 → 响应(忠于原型 HTML
| 操作 | 触发条件 | 响应行为 | 目标状态 |
|------|----------|----------|----------|
| 页面加载 | 进入页面 | 请求客户详情+指数+消费记录+AI 数据 | loading→false |
| 滚动到底部 | hasMoreRecords=true | GET 消费记录下一页(每次 10 条) | loadingMore=true→false |
| 点击"问问助手" | 底部按钮 | navigateTo chat`window.location.href='chat.html'` | — |
| 点击返回 | 顶部导航栏 | navigateBack`history.back()` | — |
> 原型 HTML 中仅有两个内联事件:顶部返回 `history.back()` 和底部"问问助手" `window.location.href='chat.html'`,无其他动态交互函数。
## 消费记录三种样式
1. 台桌结账:下沉到 `dwd_table_fee_log`,每条台费详情,关联总金额汇总
2. 商城订单:助教列表(花名+级别+课程类型+服务时长+定档绩效)、支付金额、食品酒水总金额
3. 充值:充值金额、支付方式
## 消费记录展示规则
- 默认 10 条,拉到底懒加载(每次 10 条)
- 金额为 0 的项不展示
- 有团购/折扣时展示正价+实付
- 总金额仅在消费条目 >1 时出现
## AI 区域展示
- 维客线索(应用 8Emoji 二级标签提供者逗号分隔By:系统/By:备注)
- 客户分析(应用 7`cache_type=app7_customer_analysis`):运营策略数组 + 总结
- 触发时机PRD 补充):客户结账单出现后自动生成
- 应用 3 与应用 7 共用同一 cache_type应用 3 为简版摘要,应用 7 为完整分析
## 页面状态枚举
| 状态名 | 视觉表现 | 触发条件 |
|--------|----------|----------|
| 加载中 | 区域文案"加载中..." | loading=true |
| 正常态 | 完整客户详情 | 有数据 |
| 错误态 | "加载失败,请点击重试" + 重试按钮 | error=true |
## 后端 API 依赖
| API | 方法 | 说明 |
|-----|------|------|
| `GET /api/customers/:id` | GET | 客户详情 |
| `GET /api/customers/:id/consumption-records` | GET | 消费记录(分页,懒加载) |
| `GET /api/customers/:id/indexes` | GET | 客户指数总览 |
## 页面导航
- 来源board-customer点击客户卡片/ task-detail / performance
- 去向chat问问助手
## 全局组件
- 自定义顶部导航栏(返回按钮 + "客户详情"
- AI 悬浮按钮

View File

@@ -0,0 +1,54 @@
# 页面名customer-service-records客户服务记录
> PRD 参考P9 `docs/prd/specs/P9-miniapp-fe-details.md`
> 已实现:否
## 页面说明
展示某客户的服务记录列表,支持月份前后切换。
## 状态变量
| 变量名 | 类型 | 初始值 | 说明 |
|--------|------|--------|------|
| records | array | [] | 服务记录列表 |
| currentMonth | number | 当前月 | 当前显示月份 |
| loading | boolean | true | 数据加载中 |
| error | boolean | false | 加载失败 |
## 用户操作 → 响应
| 操作 | 触发条件 | 响应行为 | 目标状态 |
|------|----------|----------|----------|
| 页面加载 | 进入页面 | GET /api/customers/:id/service-records | loading→false |
| 点击"←"(上月) | currentMonth > 最小月 | 切换到上一月,更新月份标签 | currentMonth-- |
| 点击"→"(下月) | currentMonth < 最大月 | 切换到下一月,更新月份标签 | currentMonth++ |
| 点击返回 | 顶部导航栏 | navigateBack | — |
| 点击"重试" | error=true | 重新请求数据 | loading=true |
## 月份切换器(忠于原型 HTML
- 样式:← 2026年X月 →
- 到达边界时对应箭头按钮变灰禁用opacity 0.3
-`customer-service-records.js``switchMonth(direction)` 控制
## 记录展示
- 每条记录:服务时间 + 持续时长
- 按时间倒序排列
## 页面状态枚举
| 状态名 | 视觉表现 | 触发条件 |
|--------|----------|----------|
| 加载中 | 区域文案"加载中..." | loading=true |
| 正常态 | 服务记录列表 | 有数据 |
| 空数据态 | "暂无数据" | records 为空 |
| 错误态 | "加载失败,请点击重试" + 重试按钮 | error=true |
## 后端 API 依赖
| API | 方法 | 说明 |
|-----|------|------|
| `GET /api/customers/:id/service-records` | GET | 客户服务记录(支持月份参数) |
## 页面导航
- 来源customer-detail / task-detail
- 去向:无(末端页面)
## 全局组件
- 自定义顶部导航栏(返回按钮 + "服务记录"
- AI 悬浮按钮

View File

@@ -0,0 +1,52 @@
# 页面名login登录页
> PRD 参考:`apps/miniprogram/doc/prd.md` 第七节 7.1P3 认证系统
> 已实现:是(`apps/miniprogram/miniprogram/pages/login/`
## 页面说明
微信授权登录入口页。用户需勾选协议后才能点击登录按钮。登录后根据用户状态跳转到不同页面。
## 视觉元素(忠于原型 HTML
- 顶部 Logo台球图标 SVG需转为图片资源 `/assets/icons/logo-billiard.svg`
- 应用名称文字
- 微信登录按钮:渐变背景,含微信图标 SVG
- 底部协议勾选区域:勾选框 + 协议链接文字
## 状态变量
| 变量名 | 类型 | 初始值 | 说明 |
|--------|------|--------|------|
| agreed | boolean | false | 协议勾选状态 |
| loading | boolean | false | 登录请求中 |
## 用户操作 → 响应
| 操作 | 触发条件 | 响应行为 | 目标状态 |
|------|----------|----------|----------|
| 点击协议勾选框 | 无 | 切换 agreed | agreed=!agreed |
| 点击"使用微信登录" | agreed=true | 调用 wx.login() 获取 code → POST /api/xcx-auth/login | loading=true |
| 点击"使用微信登录" | agreed=false | 无响应(按钮禁用态) | 不变 |
| 登录成功status=new | API 返回 | 跳转 apply 页面 | redirectTo apply |
| 登录成功status=pending | API 返回 | 跳转 reviewing 页面 | redirectTo reviewing |
| 登录成功status=approved | API 返回 | 跳转默认首页task-list 或 board-finance | switchTab |
| 登录成功status=rejected | API 返回 | 跳转 no-permission 页面 | redirectTo no-permission |
| 登录成功status=disabled | API 返回 | 跳转 no-permission 页面 | redirectTo no-permission |
| 登录失败 | API 报错 | Toast 提示错误信息 | loading=false |
## 页面状态枚举
| 状态名 | 视觉表现 | 触发条件 |
|--------|----------|----------|
| 默认态 | 按钮灰色禁用,协议未勾选 | agreed=false |
| 可登录态 | 按钮蓝色渐变+阴影,协议已勾选 | agreed=true |
| 加载中 | 按钮显示 loading 动画 | loading=true |
## 后端 API 依赖
| API | 方法 | 说明 |
|-----|------|------|
| `/api/xcx-auth/login` | POST | 微信登录code → JWT + user_status |
## 页面导航
- 来源:小程序启动 / 退出账号后 / reviewing/no-permission 点击"更换登录账号"
- 去向apply / reviewing / no-permission / task-list / board-finance
## 全局组件
- 无底部 TabBar
- 无 AI 悬浮按钮

View File

@@ -0,0 +1,48 @@
# 页面名my-profile我的首页
> PRD 参考:`apps/miniprogram/doc/prd.md` 第十一节
> 已实现:否
## 页面说明
用户个人中心菜单页Tab 3。展示用户信息和功能入口列表。
## 状态变量
| 变量名 | 类型 | 初始值 | 说明 |
|--------|------|--------|------|
| userInfo | object | null | 用户信息(用户名、身份、门店) |
| logoutConfirmVisible | boolean | false | 退出账号确认弹窗 |
## 用户操作 → 响应
| 操作 | 触发条件 | 响应行为 | 目标状态 |
|------|----------|----------|----------|
| 页面加载 | 进入页面 | 读取缓存用户信息 | — |
| 点击"备注记录" | 无 | navigateTo notes | — |
| 点击"助手对话记录" | 无 | navigateTo chat-history | — |
| 点击"退出账号" | 无 | 显示确认弹窗 | logoutConfirmVisible=true |
| 确认退出 | 弹窗确认 | 清除登录态 + redirectTo login | — |
| 取消退出 | 弹窗取消 | 关闭弹窗 | logoutConfirmVisible=false |
## 菜单列表
| 菜单项 | 图标 | 跳转目标 |
|--------|------|----------|
| 备注记录 | `<t-icon name="edit-1" />` | notes |
| 助手对话记录 | `<t-icon name="chat" />` | chat-history |
| 退出账号 | `<t-icon name="poweroff" />` | 确认弹窗 → login |
## 页面状态枚举
| 状态名 | 视觉表现 | 触发条件 |
|--------|----------|----------|
| 默认态 | 用户信息 + 菜单列表 | 始终 |
## 后端 API 依赖
| API | 方法 | 说明 |
|-----|------|------|
| `GET /api/xcx-auth/status` | GET | 获取用户信息(可从缓存读取) |
## 页面导航
- 来源TabBar 切换
- 去向notes / chat-history / login退出
## 全局组件
- 底部 TabBar我的 active— 由 `bottom-nav.js` 自动注入
- AI 悬浮按钮

View File

@@ -0,0 +1,44 @@
# 页面名no-permission无权限页
> PRD 参考:`apps/miniprogram/doc/prd.md` 第七节 7.4
> 已实现:是(`apps/miniprogram/miniprogram/pages/no-permission/`
## 页面说明
纯展示页,告知用户申请被拒或账号无权限。包含可能原因列表和联系管理员信息。底部可切换登录账号。
## 状态变量
| 变量名 | 类型 | 初始值 | 说明 |
|--------|------|--------|------|
| (无) | — | — | 纯展示页,无交互状态 |
## 用户操作 → 响应
| 操作 | 触发条件 | 响应行为 | 目标状态 |
|------|----------|----------|----------|
| 点击"更换登录账号" | 底部按钮 | 清除登录态 → redirectTo login | — |
## 视觉元素(忠于原型 HTML
- 主图标rose→red 渐变圆角方块内,白色描边的禁止符号 SVG圆形 + 对角线)
- ⚠️ 此 SVG 较复杂,小程序实现时建议转为图片资源 `/assets/images/icon-no-permission.png`
- 标题:「无访问权限」
- 副标题:「很抱歉,您的访问申请未通过审核,或当前账号无访问权限」
- 原因卡片:
- 标题「可能的原因」Material 风格 info 图标)
- 列表项:申请信息不完整或不符合要求 / 非本店授权员工账号 / 账号权限已被管理员收回
- 联系管理员:问号圆圈图标 + 「如有疑问,请联系管理员重新申请」
- 底部按钮:登出箭头图标 + 「更换登录账号」
## 页面状态枚举
| 状态名 | 视觉表现 | 触发条件 |
|--------|----------|----------|
| 默认态 | 完整无权限展示(图标+原因+联系提示+底部按钮) | 始终 |
## 后端 API 依赖
无(纯展示页)
## 页面导航
- 来源loginstatus=rejected / disabled
- 去向login点击"更换登录账号"/ 用户再次打开小程序时重新判断状态
## 全局组件
- 无底部 TabBar
- 无 AI 悬浮按钮

View File

@@ -0,0 +1,56 @@
# 页面名notes备注记录
> PRD 参考P6 `docs/prd/specs/P6-miniapp-fe-tasks.md``apps/miniprogram/doc/prd.md` 第十一节
> 已实现:否
## 页面说明
展示用户所有备注记录,支持按 Tab 切换「客户备注」和「助教备注」两个分类。
## 状态变量
| 变量名 | 类型 | 初始值 | 说明 |
|--------|------|--------|------|
| activeTab | string | "customer" | 当前 Tabcustomer/coach |
| customerNotes | array | [] | 客户备注列表 |
| coachNotes | array | [] | 助教备注列表 |
| loading | boolean | true | 数据加载中 |
| error | boolean | false | 加载失败 |
## 用户操作 → 响应
| 操作 | 触发条件 | 响应行为 | 目标状态 |
|------|----------|----------|----------|
| 页面加载 | 进入页面 | GET /api/xcx/notes | loading→false |
| 点击"客户备注" Tab | activeTab≠customer | 切换显示客户备注列表,隐藏助教备注 | activeTab=customer |
| 点击"助教备注" Tab | activeTab≠coach | 切换显示助教备注列表,隐藏客户备注 | activeTab=coach |
| 点击返回 | 顶部导航栏 | navigateBack | — |
| 点击"重试" | error=true | 重新请求数据 | loading=true |
## Tab 切换(忠于原型 HTML
- 两个 Tab客户备注`id="tabCustomer"`/ 助教备注(`id="tabCoach"`
- 切换时互斥显隐对应列表容器(`id="customerNotes"` / `id="coachNotes"`
-`notes.js``switchTab(tab)` 控制
## 列表展示规则
- 按时间倒序(由近到远)平铺
- 每条记录:备注全文 + 关联对象(客户/助教)+ 创建时间
- 不进入详情页,本页即为详情展示
## 页面状态枚举
| 状态名 | 视觉表现 | 触发条件 |
|--------|----------|----------|
| 加载中 | 区域文案"加载中..." | loading=true |
| 正常态 | 备注列表 | 有数据 |
| 空数据态 | "暂无数据" | 当前 Tab 列表为空 |
| 错误态 | "加载失败,请点击重试" + 重试按钮 | error=true |
## 后端 API 依赖
| API | 方法 | 说明 |
|-----|------|------|
| `GET /api/xcx/notes` | GET | 获取备注列表(含客户和助教两类) |
## 页面导航
- 来源my-profile点击"备注记录"
- 去向:无(末端页面)
## 全局组件
- 自定义顶部导航栏(返回按钮 + "备注记录"
- AI 悬浮按钮

View File

@@ -0,0 +1,64 @@
# 页面名performance-records业绩明细 / 服务记录)
> PRD 参考P7 `docs/prd/specs/P7-miniapp-fe-performance.md`
> 已实现:否
## 页面说明
展示按月份切换的服务记录明细列表。顶部有统计概览(总记录/总时长/收入),下方按日期分组展示服务记录。
## 状态变量
| 变量名 | 类型 | 初始值 | 说明 |
|--------|------|--------|------|
| records | array | [] | 服务记录列表 |
| currentMonth | number | 当前月 | 当前显示月份 |
| loading | boolean | true | 数据加载中 |
| error | boolean | false | 加载失败 |
## 用户操作 → 响应
| 操作 | 触发条件 | 响应行为 | 目标状态 |
|------|----------|----------|----------|
| 页面加载 | 进入页面 | GET /api/performance/records | loading→false |
| 点击"←"(上月) | currentMonth > 最小月 | 切换到上一月,更新月份标签,重新请求数据 | currentMonth-- |
| 点击"→"(下月) | currentMonth < 最大月 | 切换到下一月,更新月份标签,重新请求数据 | currentMonth++ |
| 点击返回 | 顶部导航栏 | navigateBack / history.back() | — |
| 点击"重试" | error=true | 重新请求数据 | loading=true |
## 月份切换器(忠于原型 HTML
- 样式:← 2026年X月 →
- 到达边界时对应箭头按钮变灰禁用opacity 0.3
-`customer-service-records.js``switchMonth(direction)` 控制
## 统计概览
- 总记录数 / 总时长 / 收入合计
- 当月数据标记"预估"
## 记录展示规则
- 按日期分组,每组显示日期标题
- 每条记录:服务时间、课程类型、台桌/房间、会员昵称、开始/结束时间、业绩分钟
- 有定档折算惩罚时展示「120分钟定档折算30分钟」格式
- 营业日以 08:00 为分割点
## 记录点击交互
- 原型 HTML 中记录条目无 onclick 跳转
- 产品意图:点击记录可跳转对应任务详情页(需后续确认是否实现)
## 页面状态枚举
| 状态名 | 视觉表现 | 触发条件 |
|--------|----------|----------|
| 加载中 | 区域文案"加载中..." | loading=true |
| 正常态 | 统计概览 + 明细列表 | 有数据 |
| 空数据态 | "暂无数据" | records 为空 |
| 错误态 | "加载失败,请点击重试" + 重试按钮 | error=true |
## 后端 API 依赖
| API | 方法 | 说明 |
|-----|------|------|
| `GET /api/performance/records` | GET | 业绩明细(支持月份参数) |
## 页面导航
- 来源performance点击"查看全部"
- 去向:无(末端页面,原型中无跳转)
## 全局组件
- 自定义顶部导航栏(返回按钮 + "服务记录"
- AI 悬浮按钮

View File

@@ -0,0 +1,88 @@
# 页面名performance我的业绩
> PRD 参考P7 `docs/prd/specs/P7-miniapp-fe-performance.md`
> 已实现:否
## 页面说明
展示助教业绩全貌:收入档位、本月/上月业绩明细(含服务记录)、新客和常客列表。多个区域支持展开/收起和"查看全部"跳转。
## Banner 主题
`banner-bg theme-blue texture-aurora`(蓝色主题)
## 状态变量
| 变量名 | 类型 | 初始值 | 说明 |
|--------|------|--------|------|
| performanceSummary | object | null | 业绩汇总(收入、档位、工资预估) |
| thisMonthRecordsExpanded | boolean | false | 本月服务记录展开状态 |
| lastMonthRecordsExpanded | boolean | false | 上月服务记录展开状态 |
| newCustomerExpanded | boolean | false | 新客列表展开状态 |
| regularCustomerExpanded | boolean | false | 常客列表展开状态 |
| loading | boolean | true | 数据加载中 |
| error | boolean | false | 加载失败 |
## 页面区域结构(忠于原型 HTML
1. Banner个人信息花名/身份/门店 + 核心数据:本月预计收入 + 上月收入)
2. 收入情况(`bg-primary` 圆点)— 当前档位 + 下一阶段目标 + 升级提示(「距下一阶段需完成 X 小时,到达即得 Y 元」)
- 跳档激励计算公式PRD 补充YYY = max(跳档线差值最小值, 实际差值)
- 各档差值Tier0→1: 1200元 / Tier1→2: 750元 / Tier2→3: 540元 / Tier3→4: 420元
3. 本月业绩 预估(`bg-success` 圆点)— 基础课时费/激励课时费/充值激励/TOP3销冠奖/合计 + 我的服务记录明细
4. 上月收入(`bg-warning` 圆点)— 同结构 + 我的服务记录明细
5. 我的新客(`bg-cyan-500` 圆点)— 按时间顺序
6. 我的常客(`bg-pink-500` 圆点)— 近2月贡献TOP20
## 用户操作 → 响应
| 操作 | 触发条件 | 响应行为 | 目标状态 |
|------|----------|----------|----------|
| 页面加载 | 进入页面 | 请求业绩汇总+服务记录+新客+常客 | loading→false |
| 点击"展开更多"(本月服务记录) | thisMonthRecordsExpanded=false | 显示隐藏的更多记录条目,按钮文字变「收起」 | thisMonthRecordsExpanded=true |
| 点击"收起"(本月服务记录) | thisMonthRecordsExpanded=true | 隐藏多余记录条目,按钮文字变「展开更多」 | thisMonthRecordsExpanded=false |
| 点击"展开更多"(上月服务记录) | lastMonthRecordsExpanded=false | 同上 | lastMonthRecordsExpanded=true |
| 点击"收起"(上月服务记录) | lastMonthRecordsExpanded=true | 同上 | lastMonthRecordsExpanded=false |
| 点击"查看全部"(本月) | 服务记录区域 | navigateTo performance-records本月口径 | — |
| 点击"查看全部"(上月) | 服务记录区域 | navigateTo performance-records上月口径 | — |
| 点击"查看更多 ↓"(新客列表) | newCustomerExpanded=false | 展开完整新客列表 | newCustomerExpanded=true |
| 点击"收起 ↑"(新客列表) | newCustomerExpanded=true | 收起新客列表 | newCustomerExpanded=false |
| 点击"查看更多 ↓"(常客列表) | regularCustomerExpanded=false | 展开完整常客列表 | regularCustomerExpanded=true |
| 点击"收起 ↑"(常客列表) | regularCustomerExpanded=true | 收起常客列表 | regularCustomerExpanded=false |
| 点击新客/常客卡片 | 无 | navigateTo task-detail按该客户当前任务类型跳转对应详情页 | — |
| 点击返回 | 顶部导航栏 | navigateBack / history.back() | — |
| 点击"重试" | error=true | 重新请求数据 | loading=true |
## 服务记录展示
- 按天归总,每天显示服务次数和总时长
- 默认显示前几条,其余折叠
- 展开/收起按钮:`id="thisMonthRecordsToggle"` / `id="lastMonthRecordsToggle"`
- "查看全部"链接跳转 `performance-records.html`
## 新客/常客展示
- 我的新客:该助教首次服务 + 2 月内 + 服务次数 ≤2按最后服务时间排列
- 我的常客近2月贡献TOP20按服务次数降序展示次数、小时数、工资合计
- 两个列表均支持展开/收起(`toggleNewCustomer()` / `toggleRegularCustomer()`
## 业绩数据说明
- 营业日以 08:00 为分割点("本月"= 当月1日 08:00 ~ 次月1日 08:00
- 当月数据标记"预估"
## 页面状态枚举
| 状态名 | 视觉表现 | 触发条件 |
|--------|----------|----------|
| 加载中 | 区域文案"加载中..." | loading=true |
| 正常态 | 完整业绩展示 | 有数据 |
| 空数据态 | 各区域"暂无数据" | 对应列表为空 |
| 错误态 | "加载失败,请点击重试" + 重试按钮 | error=true |
## 后端 API 依赖
| API | 方法 | 说明 |
|-----|------|------|
| `GET /api/performance/summary` | GET | 当月绩效汇总 |
| `GET /api/performance/service-records` | GET | 服务记录明细(分页) |
| `GET /api/performance/my-new-customers` | GET | 我的新客列表 |
| `GET /api/performance/my-regulars` | GET | 我的常客列表 |
## 页面导航
- 来源task-list点击 Banner 业绩区域)
- 去向performance-records查看全部/ task-detail*(点击新客/常客卡片)
## 全局组件
- 自定义顶部导航栏(返回按钮 + "我的业绩"
- AI 悬浮按钮

View File

@@ -0,0 +1,44 @@
# 页面名reviewing审核中页
> PRD 参考:`apps/miniprogram/doc/prd.md` 第七节 7.3
> 已实现:是(`apps/miniprogram/miniprogram/pages/reviewing/`
## 页面说明
纯展示页,告知用户申请正在审核中。包含审核进度展示和联系管理员提示。底部可切换登录账号。
## 状态变量
| 变量名 | 类型 | 初始值 | 说明 |
|--------|------|--------|------|
| (无) | — | — | 纯展示页,无交互状态 |
## 用户操作 → 响应
| 操作 | 触发条件 | 响应行为 | 目标状态 |
|------|----------|----------|----------|
| 点击"更换登录账号" | 底部按钮 | 清除登录态 → redirectTo login | — |
## 视觉元素(忠于原型 HTML
- 主图标amber→orange 渐变圆角方块内,白色描边的圆形时钟 SVG圆形表盘 + 时针分针折线),带上下浮动动画
- ⚠️ 此 SVG 较复杂,小程序实现时建议转为图片资源 `/assets/images/icon-reviewing.png`
- 标题:「申请审核中」
- 副标题:「您的访问申请已提交成功,正在等待管理员审核,请耐心等待」
- 审核进度卡片:
- 标题「审核进度」+ 副文字「通常需要 1-3 个工作日」
- 三步进度条:已提交(绿色对勾圆圈)→ 审核中(高亮)→ 通过(灰色)
- 联系提示:聊天气泡图标 + 「如有疑问,请联系管理员」
- 底部按钮:登出箭头图标 + 「更换登录账号」
## 页面状态枚举
| 状态名 | 视觉表现 | 触发条件 |
|--------|----------|----------|
| 默认态 | 完整审核中展示(图标+进度+联系提示+底部按钮) | 始终 |
## 后端 API 依赖
无(纯展示页)
## 页面导航
- 来源loginstatus=pending/ apply提交成功
- 去向login点击"更换登录账号"/ 用户再次打开小程序时重新判断状态
## 全局组件
- 无底部 TabBar
- 无 AI 悬浮按钮

View File

@@ -0,0 +1,26 @@
# 页面名task-detail-callback任务详情 - 客户回访)
> 本页为 task-detail 系列的变体页面,完整交互说明见 `task-detail.md`。
> 此文件仅记录与 task-detail.html 的差异。
## 与 task-detail.html 的差异
| 差异点 | task-detail高优先召回 | task-detail-callback客户回访 |
|--------|--------------------------|----------------------------------|
| Banner 主题 | `theme-red` 红色 | `theme-teal` 青绿色 |
| 区域顺序 | ①与我的关系 → ②任务建议 → ③维客线索 → ④备注 → ⑤近期服务记录 | ①维客线索 → ②与我的关系(含近期服务记录)→ ③任务建议 → ④备注 |
| 话术样式 | 气泡样式(`.speech-bubble`,带复制按钮) | 竖线样式(`border-l-2`,左侧竖线+左内边距) |
| 任务建议标题 | 💡 建议执行 | 📞 常规回访要点 |
| 底部按钮颜色 | `from-primary to-blue-500` | `from-teal-500 to-cyan-500` |
| 导航栏"放弃"按钮 | 有 | 无 |
| 近期服务记录 | 独立区域第5区域 | 嵌入"与我的关系"卡片内(`.svc-section-bg` |
| ratingExpanded 初始值 | false需点击"展开评价" | true默认展开 |
## 实现建议
在小程序中task-detail 系列不需要 4 个独立页面。可以在同一页面通过路由参数 `taskType` 控制:
- Banner 主题色
- 区域排列顺序
- 话术样式(气泡 vs 竖线)
- 任务建议标题文案
- 是否显示"放弃"按钮
- ratingExpanded 初始值

View File

@@ -0,0 +1,17 @@
# 页面名task-detail-priority任务详情 - 优先召回)
> 本页为 task-detail 系列的变体页面,完整交互说明见 `task-detail.md`。
> 此文件仅记录与 task-detail.html高优先召回的差异。
## 与 task-detail.html 的差异
| 差异点 | task-detail高优先召回 | task-detail-priority优先召回 |
|--------|--------------------------|----------------------------------|
| Banner 主题 | `theme-red` 红色 | `theme-orange` 橙色 |
| 任务类型标签 | 高优先召回 | 优先召回 |
| 标签颜色 | 红色系 | 橙色系 |
其余结构(区域顺序、话术样式、底部操作栏、放弃按钮等)与 task-detail.html 完全一致。
## 实现建议
在小程序中task-detail 系列不需要 4 个独立页面。可以在同一页面通过路由参数 `taskType` 控制 Banner 主题色和标签样式。

View File

@@ -0,0 +1,26 @@
# 页面名task-detail-relationship任务详情 - 关系构建)
> 本页为 task-detail 系列的变体页面,完整交互说明见 `task-detail.md`。
> 此文件仅记录与 task-detail.html高优先召回的差异。
## 与 task-detail.html 的差异
| 差异点 | task-detail高优先召回 | task-detail-relationship关系构建 |
|--------|--------------------------|--------------------------------------|
| Banner 主题 | `theme-red` 红色 | `theme-pink` 粉色 |
| 区域顺序 | ①与我的关系 → ②任务建议 → ③维客线索 → ④备注 → ⑤近期服务记录 | ①维客线索 → ②与我的关系(含近期服务记录)→ ③任务建议 → ④备注 |
| 话术样式 | 气泡样式(`.speech-bubble`,带复制按钮) | 竖线样式(`border-l-2`,左侧竖线+左内边距) |
| 任务建议标题 | 💡 建议执行 | 💝 关系构建重点 |
| 任务建议背景 | 蓝色系 | `from-pink-50 to-rose-50` |
| 底部按钮颜色 | `from-primary to-blue-500` | `from-pink-500 to-rose-500` |
| 导航栏"放弃"按钮 | 有 | 无 |
| 近期服务记录 | 独立区域第5区域 | 嵌入"与我的关系"卡片内 |
| 备注区域 | 有已有备注展示 | 可能为空状态(提示「快点击下方备注按钮,添加客人备注!」) |
## 实现建议
在小程序中task-detail 系列不需要 4 个独立页面。可以在同一页面通过路由参数 `taskType` 控制:
- Banner 主题色
- 区域排列顺序
- 话术样式(气泡 vs 竖线)
- 任务建议标题和背景色
- 是否显示"放弃"按钮

View File

@@ -0,0 +1,148 @@
# 页面名task-detail 系列(任务详情)
> PRD 参考P6 `docs/prd/specs/P6-miniapp-fe-tasks.md``apps/miniprogram/doc/prd.md` 第八节 8.2
> 已实现:否
## 页面说明
展示客户详细信息、消费习惯、AI 分析(关系分析+任务建议+话术参考+备注评分)、维客线索、备注入口。底部固定操作栏。
task-detail 系列共 4 个页面,卡片内容相同(以 task-detail.html 为准),差异仅在:头部 Banner 主题/配色、区域排列顺序、话术样式、部分文案。
## Banner 主题映射表
| 页面 | Banner class | 主题色 | 任务类型 |
|------|-------------|--------|----------|
| task-detail.html | `banner-bg theme-red texture-aurora` | 红色 | 高优先召回 |
| task-detail-priority.html | (同 task-detail橙色变体 | 橙色 | 优先召回 |
| task-detail-relationship.html | `banner-bg theme-pink texture-aurora` | 粉色 | 关系构建 |
| task-detail-callback.html | `banner-bg theme-teal texture-aurora` | 青绿色 | 客户回访 |
> ⚠️ Banner 背景使用 CSS 渐变+SVG 纹理(`banner.css` 中的 `texture-aurora`),内含复杂的 SVG 丝带效果。小程序实现时建议将各主题 Banner 背景导出为图片资源,统一放 `/assets/images/banner-{theme}.png`。
## 页面区域顺序差异
| 区域 | task-detail高优先/优先) | task-detail-relationship关系构建 | task-detail-callback客户回访 |
|------|--------------------------|--------------------------------------|----------------------------------|
| 1 | 与我的关系 | 维客线索 | 维客线索 |
| 2 | 任务建议 | 与我的关系(含近期服务记录) | 与我的关系(含近期服务记录) |
| 3 | 维客线索 | 任务建议 | 任务建议 |
| 4 | 我给TA的备注 | 我给TA的备注 | 我给TA的备注 |
| 5 | 近期服务记录 | 嵌入区域2 | 嵌入区域2 |
## 话术样式差异
| 页面 | 话术样式 | 说明 |
|------|----------|------|
| task-detail高优先/优先) | 气泡样式 `.speech-bubble` | 浅蓝背景+边框+圆角+右下角尖角,每条带「复制」按钮 |
| task-detail-relationship | 竖线样式 `border-l-2` | 左侧 2px 竖线 + 左内边距 |
| task-detail-callback | 竖线样式 `border-l-2` | 同上 |
## 任务建议标题差异
| 页面 | 标题 | 背景色 |
|------|------|--------|
| task-detail | 💡 建议执行 | 蓝色系 |
| task-detail-relationship | 💝 关系构建重点 | pink→rose |
| task-detail-callback | 📞 常规回访要点 | teal→cyan |
## 状态变量
| 变量名 | 类型 | 初始值 | 说明 |
|--------|------|--------|------|
| taskDetail | object | null | 任务详情数据 |
| customerInfo | object | null | 客户基本信息 |
| serviceRecords | array | [] | 近期服务记录 |
| aiAnalysis | object | null | AI 分析(应用 4关系分析+任务建议+一句话总结) |
| retentionClues | array | [] | 维客线索(应用 8 整合线索+人工) |
| customerAnalysis | object | null | 客户分析(应用 7运营策略+总结) |
| talkingPoints | object | null | 话术参考(应用 5 缓存) |
| noteScore | number | null | 备注分析评分(应用 61-10 分) |
| noteModalVisible | boolean | false | 备注弹窗显示状态 |
| noteText | string | "" | 备注输入内容 |
| noteRating | object | {serviceWill: 0, returnChance: 0} | 星星评分(再次服务意愿+再来店可能性,各 1-5 星) |
| ratingExpanded | boolean | false | 评分区域是否展开(回访任务默认 true |
| abandonModalVisible | boolean | false | 放弃客户弹窗显示状态(仅高优先/优先召回有) |
| abandonReason | string | "" | 放弃原因 |
| loading | boolean | true | 数据加载中 |
| error | boolean | false | 加载失败 |
## 用户操作 → 响应
| 操作 | 触发条件 | 响应行为 | 目标状态 |
|------|----------|----------|----------|
| 页面加载 | 进入页面 | GET /api/xcx/tasks/{id} | loading→false |
| 点击"问问助手" | 底部操作栏 | navigateTo chat带任务+客户上下文引用) | — |
| 点击"备注" | 底部操作栏 | 打开备注弹窗 | noteModalVisible=true |
| 点击"展开评价" | noteModalVisible=true, ratingExpanded=false | 展开星星评分区域,隐藏展开按钮 | ratingExpanded=true |
| 拖动/点击星星 | 评分区域展开 | 更新对应评分值(支持触摸拖动+鼠标拖动+点击) | noteRating 更新 |
| 备注弹窗-提交 | noteText 非空 | POST /api/xcx/notes含评分 | Toast "备注已保存",弹窗关闭 |
| 点击"复制"(话术气泡) | 气泡样式页面 | 复制话术文本到剪贴板,按钮变"已复制" | — |
| 点击"放弃" | 顶部导航栏右侧(仅高优先/优先) | 打开放弃确认弹窗 | abandonModalVisible=true |
| 放弃弹窗-确认 | abandonReason 非空 | POST /api/xcx/tasks/{id}/abandon | Toast "已放弃该客户的维护" |
| 点击返回 | 顶部导航栏 | navigateBack / history.back() | — |
| 点击"重试" | error=true | 重新请求数据 | loading=true |
## "与我的关系"区域
- 标题带 section-title pink 样式
- AI 机器人图标(可爱机器人 SVG圆角矩形身体+天线+紫蓝色大眼睛+微笑+粉色腮红+小耳朵)
- 此图标可视为心形 ICON 的扩展,小程序中用 `/assets/icons/icon-ai-relationship.svg`
- 关系等级色系:💖 非常好(>8.5 粉色渐变) / 🧡 良好(>7) / 💛 一般(>5) / 💙 待发展(<5 蓝色渐变)
- 备注评分星级展示score ÷ 2 = 星数,支持半星,由 `ai-icons.js` 渲染)
## AI 应用编号映射PRD 补充)
| 应用编号 | 功能 | cache_type | 展示位置 |
|----------|------|------------|----------|
| 应用 4 | 关系分析 + 任务建议 + 一句话总结summary | `app4_analysis` | 与我的关系 / 任务建议 / 卡片底部 AI 文字 |
| 应用 5 | 话术参考5 条话术) | — | 话术区域 |
| 应用 6 | 备注评分1-10 分6 分为标准分) | `app6_note_analysis` | 备注弹窗提交后 |
| 应用 7 | 客户分析(运营策略 + 总结) | `app7_customer_analysis` | 客户分析区域 |
| 应用 8 | 维客线索整合(`member_retention_clue` 表) | — | 维客线索区域 |
## AI 区域展示规则
- 维客线索(应用 8Emoji 作为二级标签,提供者逗号分隔展示
- source=ai_consumption → "By:系统"
- source=ai_note → "By:备注"
- 客户分析(应用 7运营策略数组 + 总结文本
- 关系分析+任务建议+一句话总结(应用 4
- 话术参考(应用 55 条话术,气泡/竖线样式展示
- 备注评分(应用 61-10 分6 分为标准分
## 备注弹窗规则
- 底部弹出式弹窗(`.modal-overlay` + `.modal-card`
- 包含:多行文本输入 + 星星评分(再次服务意愿 + 再来店可能性)
- 回访任务task-detail-callback评分区域默认展开
- 其他任务类型:评分区域默认隐藏,通过"展开评价"按钮手动打开
- 星星打分交互:支持点击、触摸拖动、鼠标拖动(由 `task-detail-notes.js` 实现)
### 备注评分与回访完成判定PRD 补充)
- 提交备注后,后端调用应用 6 进行评分1-10 分6 分为标准分)
- 评分细则:综合备注文本质量、服务意愿星级、再来店可能性星级
- 回访任务完成条件:备注评分 ≥ 5 → 任务标记完成;< 5 → 不算完成
- 只计第一个备注的评分(后续备注不影响完成判定)
- 回访任务有效期 2 天:超过 2 天未完成则标签跳变(回到任务列表重新分配)
## 放弃客户弹窗(仅高优先/优先召回)
- 遮罩层 + 居中弹窗
- 包含:多行文本输入(放弃原因)+ 提交按钮(红色 `.danger`
- 空值校验:原因为空时显示错误提示
- 关系构建和客户回访页面无"放弃"按钮
## 底部操作栏
- 「问问助手」按钮:渐变背景(颜色随主题变化),聊天气泡图标
- 「备注」按钮:灰色背景,编辑笔图标
## 页面状态枚举
| 状态名 | 视觉表现 | 触发条件 |
|--------|----------|----------|
| 加载中 | 区域文案"加载中..." | loading=true |
| 正常态 | 完整详情展示 | 有数据 |
| 错误态 | "加载失败,请点击重试" + 重试按钮 | error=true |
## 后端 API 依赖
| API | 方法 | 说明 |
|-----|------|------|
| `GET /api/xcx/tasks/{id}` | GET | 获取任务详情(含客户信息+AI 分析) |
| `POST /api/xcx/notes` | POST | 创建备注(含评分,触发回访完成判定) |
| `POST /api/xcx/tasks/{id}/abandon` | POST | 放弃任务(仅高优先/优先) |
## 页面导航
- 来源task-list点击任务卡片按 task-type 跳转对应页面)
- 去向chat问问助手
## 全局组件
- 自定义顶部导航栏(返回按钮 + "任务详情",高优先/优先召回右侧有"放弃"按钮)
- AI 悬浮按钮

View File

@@ -0,0 +1,134 @@
# 页面名task-list任务列表 + 业绩概览)
> PRD 参考P6 `docs/prd/specs/P6-miniapp-fe-tasks.md``apps/miniprogram/doc/prd.md` 第八节 8.1
> 已实现:否
## 页面说明
小程序默认首页Tab 1。顶部 Banner 展示用户信息和业绩概览,下方为按优先级分组排序的任务列表。支持长按弹出操作菜单(置顶/放弃/问AI/备注)。
## Banner 主题
`banner-bg theme-blue texture-aurora`(蓝色主题 + 极光丝带纹理)
## 状态变量
| 变量名 | 类型 | 初始值 | 说明 |
|--------|------|--------|------|
| tasks | array | [] | 任务列表(按优先级分组) |
| userInfo | object | null | 用户信息(花名、身份、门店) |
| performanceData | object | null | 业绩概览(当月业绩、档位、工资预估、跳档激励) |
| contextMenuVisible | boolean | false | 长按菜单显示状态 |
| contextMenuPosition | object | {x,y} | 菜单弹出坐标 |
| contextMenuTarget | element | null | 长按的目标卡片 |
| remarkModalVisible | boolean | false | 备注/放弃弹窗显示状态 |
| loading | boolean | true | 数据加载中 |
| error | boolean | false | 加载失败 |
## 用户操作 → 响应
| 操作 | 触发条件 | 响应行为 | 目标状态 |
|------|----------|----------|----------|
| 页面加载 | 进入页面 | GET /api/xcx/tasks + 业绩数据 | loading→false |
| 点击 Banner 业绩区域 | 无 | navigateTo performance 页面 | — |
| 点击任务卡片 | 无 | 根据 task-type 跳转对应详情页(见跳转映射表) | — |
| 长按任务卡片500ms | 触摸/鼠标按住 | 在按压坐标处弹出黑底浮层+白色菜单 | contextMenuVisible=true |
| 菜单-「📌 置顶任务」 | 菜单显示 | POST /api/xcx/tasks/{id}/pin | 列表刷新,菜单关闭 |
| 菜单-「❌ 放弃任务」 | 菜单显示 | 打开备注/放弃弹窗 | remarkModalVisible=true |
| 菜单-「🤖 问问AI助手」 | 菜单显示 | navigateTo chat带任务上下文引用 | 菜单关闭 |
| 菜单-「📝 备注」 | 菜单显示 | 打开备注弹窗 | remarkModalVisible=true |
| 弹窗-提交 | 文本非空 | POST 对应接口 | Toast 提示,弹窗关闭 |
| 点击遮罩层 | contextMenuVisible=true | 关闭菜单 | contextMenuVisible=false |
| 下拉刷新 | 无 | 重新请求任务列表 | loading=true |
| 点击"重试" | error=true | 重新请求数据 | loading=true |
## 任务卡片跳转映射
| data-task-type | 跳转目标 |
|----------------|----------|
| high-priority | task-detail.html高优先召回 |
| priority | task-detail-priority.html优先召回 |
| relationship | task-detail-relationship.html关系构建 |
| callback | task-detail-callback.html客户回访 |
## 任务优先级定义PRD 补充)
| 优先级 | 类型 | 触发条件 | 标签颜色 |
|--------|------|----------|----------|
| P0 高优先召回 | high-priority | WBI + NCI > 7 | 红色 |
| P0 优先召回 | priority | WBI + NCI > 5 | 橙色 |
| P1 客户回访 | callback | 已完成召回但未备注(或备注评分 < 5 | 蓝色 |
| P2 关系构建 | relationship | RS < 6 | 粉色 |
> 列表内按优先级分组 → 组内按指数分数降序排列
## 任务完成条件PRD 补充)
| 任务类型 | 完成条件 | 说明 |
|----------|----------|------|
| 召回P0 | 客户出现新的服务订单 | 系统自动判定 |
| 回访P1 | 备注评分 ≥ 5应用 6 评分) | 评分 < 5 不算完成只计第一个备注2 天有效期 |
| 关系构建P2 | RS 指数提升至 ≥ 6 | 系统自动判定 |
## 任务卡片结构(忠于原型 HTML
- 外层 `.task-card`:左侧 4px 彩色边框(颜色随任务类型变化)
- 置顶卡片额外 `.pinned` class微亮边框阴影
- 内部结构:
- 第一行:任务类型标签(渐变背景圆角矩形)+ 客户昵称 + 关系 emoji + 备注指示器(📝,有备注时显示)
- 第二行「最近到店X天前 · 余额XXX」
- 第三行AI 机器人小图标 + AI 一句话智能建议文字来源AI 应用 4 的 `summary` 字段,`cache_type=app4_analysis`
- 右侧:向右箭头 chevron
## 列表分区结构
1. 📌 置顶区域amber 背景标签):用户手动置顶的任务
2. 一般任务区域(灰色背景标签):按优先级分组 → 组内按分数降序
3. ❌ 已放弃区域(灰色背景标签):
- 卡片 `.abandoned`opacity 0.55,左边框变灰,标签背景变灰,客户名变灰
- 无右侧箭头
- 显示放弃原因文字
## 长按菜单样式
- 遮罩层 `.context-overlay`(半透明黑色)
- 白色菜单 `.context-menu`min-width 192px圆角 14px阴影
- 4 个菜单项,项间 1px 分隔线:
- 📌 置顶任务(已置顶时显示「取消置顶」)
- ❌ 放弃任务
- 🤖 问问AI助手
- 📝 备注
## 业绩 Banner 展示
- 用户花名 + 身份
- 当月业绩进度条
- 预计收入(标记"预估"
- 跳档激励:「到达 XXX 即得 YYY」
- YYY = max(跳档线差值最小值, 实际差值)
- 各档差值Tier0→1: 1200元 / Tier1→2: 750元 / Tier2→3: 540元 / Tier3→4: 420元
## 跟/弃 icon 规则PRD 补充)
- 跟:助教主动跟进中(绿色标记)
- 弃:助教已放弃该客户(灰色标记)
- 无标记:未分配助教或无跟进状态
## 爱心 icon 规则
💖(>8.5) / 🧡(>7) / 💛(>5) / 💙(<5)
## 喜好标签
🎱(中式) / 斯(斯诺克) / 🀅(麻将) / 🎤(团建)
## 页面状态枚举
| 状态名 | 视觉表现 | 触发条件 |
|--------|----------|----------|
| 加载中 | 区域文案"加载中..." | loading=true |
| 正常态 | 任务列表 + 业绩 Banner | 有数据 |
| 空数据态 | "暂无任务" | tasks 为空 |
| 错误态 | "加载失败,请点击重试" + 重试按钮 | error=true |
## 后端 API 依赖
| API | 方法 | 说明 |
|-----|------|------|
| `GET /api/xcx/tasks` | GET | 获取任务列表 |
| `POST /api/xcx/tasks/{id}/pin` | POST | 置顶/取消置顶 |
| `POST /api/xcx/tasks/{id}/abandon` | POST | 放弃任务 |
| `POST /api/xcx/notes` | POST | 创建备注 |
| `GET /api/performance/summary` | GET | 业绩概览 |
## 页面导航
- 来源loginapproved/ TabBar 切换
- 去向task-detail* / performance / chat
## 全局组件
- 底部 TabBar任务 active— 由 `bottom-nav.js` 自动注入
- AI 悬浮按钮(右下角,渐变动画背景,可爱机器人 SVG 图标,点击 → chat

View File

@@ -20,13 +20,6 @@
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);

View File

@@ -1,8 +1,84 @@
/* task-detail 系列页面共享 — 备注弹窗 + Toast */
/* task-detail 系列页面共享 — 备注弹窗 + Toast + 星星打分 */
/* ---- 星星打分交互 ---- */
function initNoteRatings() {
document.querySelectorAll('.note-rating-row').forEach(function (row) {
var stars = row.querySelectorAll('.nr-star');
var dragging = false;
function setScore(idx) {
row.dataset.score = idx + 1;
stars.forEach(function (s, i) {
s.classList.toggle('active', i <= idx);
});
}
/* 点击 */
stars.forEach(function (star, idx) {
star.addEventListener('click', function () { setScore(idx); });
});
/* 触摸拖动 */
row.addEventListener('touchstart', function (e) { dragging = true; handleTouch(e); }, { passive: false });
row.addEventListener('touchmove', function (e) { if (dragging) { e.preventDefault(); handleTouch(e); } }, { passive: false });
row.addEventListener('touchend', function () { dragging = false; });
function handleTouch(e) {
var touch = e.touches[0];
for (var i = 0; i < stars.length; i++) {
var rect = stars[i].getBoundingClientRect();
if (touch.clientX >= rect.left && touch.clientX < rect.right) {
setScore(i);
break;
}
}
}
/* 鼠标拖动 */
row.addEventListener('mousedown', function (e) { dragging = true; handleMouse(e); });
document.addEventListener('mousemove', function (e) { if (dragging) handleMouse(e); });
document.addEventListener('mouseup', function () { dragging = false; });
function handleMouse(e) {
for (var i = 0; i < stars.length; i++) {
var rect = stars[i].getBoundingClientRect();
if (e.clientX >= rect.left && e.clientX < rect.right) {
setScore(i);
break;
}
}
}
});
}
function resetNoteRatings() {
document.querySelectorAll('.note-rating-row').forEach(function (row) {
row.dataset.score = '0';
row.querySelectorAll('.nr-star').forEach(function (s) { s.classList.remove('active'); });
});
}
/* 页面加载后初始化 */
document.addEventListener('DOMContentLoaded', initNoteRatings);
/* ---- 弹窗控制 ---- */
function showNoteModal() {
document.getElementById('noteModal').classList.remove('hidden');
document.getElementById('noteModal').classList.add('flex');
document.getElementById('noteText').value = '';
resetNoteRatings();
/* 重置展开状态 */
var sec = document.getElementById('noteRatingSection');
var btn = document.getElementById('noteExpandBtn');
if (sec) sec.classList.add('hidden');
if (btn) btn.classList.remove('hidden');
}
function expandNoteRating() {
var sec = document.getElementById('noteRatingSection');
var btn = document.getElementById('noteExpandBtn');
if (sec) sec.classList.remove('hidden');
if (btn) btn.classList.add('hidden');
}
function hideNoteModal() {

View File

@@ -53,27 +53,27 @@
<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>
<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="currentColor" opacity="0.12" stroke="currentColor" stroke-width="0.7"/><path d="M12 7V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="currentColor"/><circle cx="9" cy="11.5" r="1.8" fill="white" stroke="currentColor" stroke-width="0.6"/><circle cx="15" cy="11.5" r="1.8" fill="white" stroke="currentColor" stroke-width="0.6"/><circle cx="9.4" cy="11.2" r="0.7" fill="currentColor"/><circle cx="15.4" cy="11.2" r="0.7" fill="currentColor"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.5"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.5"/><rect x="3" y="10" width="2" height="4" rx="1" fill="currentColor" opacity="0.15" stroke="currentColor" stroke-width="0.5"/><rect x="19" y="10" width="2" height="4" rx="1" fill="currentColor" opacity="0.15" stroke="currentColor" stroke-width="0.5"/></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>
<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="currentColor" opacity="0.12" stroke="currentColor" stroke-width="0.7"/><path d="M12 7V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="currentColor"/><circle cx="9" cy="11.5" r="1.8" fill="white" stroke="currentColor" stroke-width="0.6"/><circle cx="15" cy="11.5" r="1.8" fill="white" stroke="currentColor" stroke-width="0.6"/><circle cx="9.4" cy="11.2" r="0.7" fill="currentColor"/><circle cx="15.4" cy="11.2" r="0.7" fill="currentColor"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.5"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.5"/><rect x="3" y="10" width="2" height="4" rx="1" fill="currentColor" opacity="0.15" stroke="currentColor" stroke-width="0.5"/><rect x="19" y="10" width="2" height="4" rx="1" fill="currentColor" opacity="0.15" stroke="currentColor" stroke-width="0.5"/></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>
<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="currentColor" opacity="0.12" stroke="currentColor" stroke-width="0.7"/><path d="M12 7V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="currentColor"/><circle cx="9" cy="11.5" r="1.8" fill="white" stroke="currentColor" stroke-width="0.6"/><circle cx="15" cy="11.5" r="1.8" fill="white" stroke="currentColor" stroke-width="0.6"/><circle cx="9.4" cy="11.2" r="0.7" fill="currentColor"/><circle cx="15.4" cy="11.2" r="0.7" fill="currentColor"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.5"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.5"/><rect x="3" y="10" width="2" height="4" rx="1" fill="currentColor" opacity="0.15" stroke="currentColor" stroke-width="0.5"/><rect x="19" y="10" width="2" height="4" rx="1" fill="currentColor" opacity="0.15" stroke="currentColor" stroke-width="0.5"/></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>
<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="currentColor" opacity="0.12" stroke="currentColor" stroke-width="0.7"/><path d="M12 7V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="currentColor"/><circle cx="9" cy="11.5" r="1.8" fill="white" stroke="currentColor" stroke-width="0.6"/><circle cx="15" cy="11.5" r="1.8" fill="white" stroke="currentColor" stroke-width="0.6"/><circle cx="9.4" cy="11.2" r="0.7" fill="currentColor"/><circle cx="15.4" cy="11.2" r="0.7" fill="currentColor"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.5"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.5"/><rect x="3" y="10" width="2" height="4" rx="1" fill="currentColor" opacity="0.15" stroke="currentColor" stroke-width="0.5"/><rect x="19" y="10" width="2" height="4" rx="1" fill="currentColor" opacity="0.15" stroke="currentColor" stroke-width="0.5"/></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>
<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="currentColor" opacity="0.12" stroke="currentColor" stroke-width="0.7"/><path d="M12 7V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="currentColor"/><circle cx="9" cy="11.5" r="1.8" fill="white" stroke="currentColor" stroke-width="0.6"/><circle cx="15" cy="11.5" r="1.8" fill="white" stroke="currentColor" stroke-width="0.6"/><circle cx="9.4" cy="11.2" r="0.7" fill="currentColor"/><circle cx="15.4" cy="11.2" r="0.7" fill="currentColor"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.5"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.5"/><rect x="3" y="10" width="2" height="4" rx="1" fill="currentColor" opacity="0.15" stroke="currentColor" stroke-width="0.5"/><rect x="19" y="10" width="2" height="4" rx="1" fill="currentColor" opacity="0.15" stroke="currentColor" stroke-width="0.5"/></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>
<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="currentColor" opacity="0.12" stroke="currentColor" stroke-width="0.7"/><path d="M12 7V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="currentColor"/><circle cx="9" cy="11.5" r="1.8" fill="white" stroke="currentColor" stroke-width="0.6"/><circle cx="15" cy="11.5" r="1.8" fill="white" stroke="currentColor" stroke-width="0.6"/><circle cx="9.4" cy="11.2" r="0.7" fill="currentColor"/><circle cx="15.4" cy="11.2" r="0.7" fill="currentColor"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.5"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.5"/><rect x="3" y="10" width="2" height="4" rx="1" fill="currentColor" opacity="0.15" stroke="currentColor" stroke-width="0.5"/><rect x="19" y="10" width="2" height="4" rx="1" fill="currentColor" opacity="0.15" stroke="currentColor" stroke-width="0.5"/></svg></span>AI智能洞察</span>
</div>
<p class="note">每个页面所有 Title AI 标识统一使用一种配色,刷新后随机分配</p>
</body>

View File

@@ -64,9 +64,9 @@
<!-- 主体内容 -->
<div class="flex-1 p-4">
<!-- 欢迎卡片 -->
<!-- 欢迎卡片 + 审核流程整合 -->
<div class="bg-gradient-to-br from-primary to-blue-400 rounded-2xl p-5 mb-4 text-white shadow-lg shadow-primary/20">
<div class="flex items-center gap-4 mb-3">
<div class="flex items-center gap-4 mb-4">
<div class="w-12 h-12 bg-white/20 backdrop-blur-sm rounded-xl flex items-center justify-center">
<svg class="w-6 h-6 text-white" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
@@ -77,66 +77,83 @@
<p class="text-white/80 text-sm">请填写申请信息,等待管理员审核</p>
</div>
</div>
</div>
<!-- 说明文字 -->
<div class="bg-primary/5 border border-primary/10 rounded-xl p-4 mb-4">
<div class="flex items-start gap-3">
<svg class="w-5 h-5 text-primary flex-shrink-0 mt-0.5" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/>
</svg>
<div>
<p class="text-sm text-primary font-medium mb-1">申请说明</p>
<p class="text-xs text-gray-7 leading-relaxed">
请填写您的真实信息,包括姓名、岗位和所属门店等,以便管理员快速审核您的申请。
</p>
<!-- 审核流程 -->
<div class="bg-white/10 backdrop-blur-sm rounded-xl p-4">
<div class="flex items-center justify-between">
<div class="flex flex-col items-center">
<div class="w-7 h-7 bg-white rounded-full flex items-center justify-center text-primary text-xs font-semibold mb-1.5">1</div>
<span class="text-xs text-white/90">提交申请</span>
</div>
<div class="flex-1 h-0.5 bg-white/30 mx-2"></div>
<div class="flex flex-col items-center">
<div class="w-7 h-7 bg-white/20 rounded-full flex items-center justify-center text-white/70 text-xs font-medium mb-1.5">2</div>
<span class="text-xs text-white/60">等待审核</span>
</div>
<div class="flex-1 h-0.5 bg-white/30 mx-2"></div>
<div class="flex flex-col items-center">
<div class="w-7 h-7 bg-white/20 rounded-full flex items-center justify-center text-white/70 text-xs font-medium mb-1.5">3</div>
<span class="text-xs text-white/60">审核通过</span>
</div>
<div class="flex-1 h-0.5 bg-white/30 mx-2"></div>
<div class="flex flex-col items-center">
<div class="w-7 h-7 bg-white/20 rounded-full flex items-center justify-center text-white/70 text-xs font-medium mb-1.5">4</div>
<span class="text-xs text-white/60">开始使用</span>
</div>
</div>
</div>
</div>
<!-- 表单区域 -->
<div class="bg-white rounded-2xl p-5 shadow-lg shadow-gray-200/50">
<div class="mb-3 flex items-center gap-1">
<span class="text-error text-sm">*</span>
<span class="text-sm font-medium text-gray-13">申请说明</span>
<div class="bg-white rounded-2xl shadow-lg shadow-gray-200/50 overflow-hidden">
<!-- 球房ID -->
<div class="px-5 py-4 border-b border-gray-100">
<div class="flex items-center mb-2">
<span class="text-error text-sm mr-1">*</span>
<span class="text-sm font-medium text-gray-13">球房ID</span>
</div>
<input type="text" id="siteId" placeholder="请输入球房ID"
class="w-full px-4 py-3 bg-gray-50 rounded-xl border border-gray-100 text-sm text-gray-13 placeholder-gray-5 focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary/30 transition-all" />
</div>
<textarea
id="applyReason"
class="w-full h-36 p-4 bg-gray-50 rounded-xl border border-gray-100 resize-none text-sm text-gray-13 placeholder-gray-5 focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary/30 transition-all"
placeholder="点击填写申请信息:&#10;1. 您的姓名&#10;2. 您的岗位(如:助教、店长等)&#10;3. 所属门店&#10;4. 其他说明(可选)"
></textarea>
<p id="errorTip" class="text-error text-xs mt-2 hidden">申请说明不能为空</p>
<div class="flex items-center justify-between mt-3">
<span class="text-xs text-gray-5">请认真填写,信息不完整可能导致审核不通过</span>
<span class="text-xs text-gray-6"><span id="charCount">0</span>/200</span>
<!-- 申请身份 -->
<div class="px-5 py-4 border-b border-gray-100">
<div class="flex items-center mb-2">
<span class="text-error text-sm mr-1">*</span>
<span class="text-sm font-medium text-gray-13">申请身份</span>
</div>
<input type="text" id="role" placeholder="请输入申请身份(如:助教、店长等)"
class="w-full px-4 py-3 bg-gray-50 rounded-xl border border-gray-100 text-sm text-gray-13 placeholder-gray-5 focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary/30 transition-all" />
</div>
<!-- 手机号 -->
<div class="px-5 py-4 border-b border-gray-100">
<div class="flex items-center mb-2">
<span class="text-error text-sm mr-1">*</span>
<span class="text-sm font-medium text-gray-13">手机号</span>
</div>
<input type="text" id="phone" placeholder="请输入手机号"
class="w-full px-4 py-3 bg-gray-50 rounded-xl border border-gray-100 text-sm text-gray-13 placeholder-gray-5 focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary/30 transition-all" />
</div>
<!-- 编号(选填) -->
<div class="px-5 py-4 border-b border-gray-100">
<div class="flex items-center mb-2">
<span class="text-sm font-medium text-gray-13">编号</span>
<span class="text-xs text-gray-6 ml-2">选填</span>
</div>
<input type="text" id="staffNo" placeholder="请输入编号"
class="w-full px-4 py-3 bg-gray-50 rounded-xl border border-gray-100 text-sm text-gray-13 placeholder-gray-5 focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary/30 transition-all" />
</div>
<!-- 昵称 -->
<div class="px-5 py-4">
<div class="flex items-center mb-2">
<span class="text-error text-sm mr-1">*</span>
<span class="text-sm font-medium text-gray-13">昵称</span>
</div>
<input type="text" id="nickname" placeholder="请输入昵称"
class="w-full px-4 py-3 bg-gray-50 rounded-xl border border-gray-100 text-sm text-gray-13 placeholder-gray-5 focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary/30 transition-all" />
</div>
</div>
<!-- 审核流程说明 -->
<div class="mt-4 bg-white rounded-2xl p-5 shadow-lg shadow-gray-200/50">
<h3 class="text-sm font-medium text-gray-13 mb-4">审核流程</h3>
<div class="flex items-center justify-between">
<div class="flex flex-col items-center">
<div class="w-8 h-8 bg-primary rounded-full flex items-center justify-center text-white text-xs font-medium mb-2">1</div>
<span class="text-xs text-gray-7">提交申请</span>
</div>
<div class="flex-1 h-0.5 bg-gray-200 mx-2"></div>
<div class="flex flex-col items-center">
<div class="w-8 h-8 bg-gray-200 rounded-full flex items-center justify-center text-gray-6 text-xs font-medium mb-2">2</div>
<span class="text-xs text-gray-6">等待审核</span>
</div>
<div class="flex-1 h-0.5 bg-gray-200 mx-2"></div>
<div class="flex flex-col items-center">
<div class="w-8 h-8 bg-gray-200 rounded-full flex items-center justify-center text-gray-6 text-xs font-medium mb-2">3</div>
<span class="text-xs text-gray-6">审核通过</span>
</div>
<div class="flex-1 h-0.5 bg-gray-200 mx-2"></div>
<div class="flex flex-col items-center">
<div class="w-8 h-8 bg-gray-200 rounded-full flex items-center justify-center text-gray-6 text-xs font-medium mb-2">4</div>
<span class="text-xs text-gray-6">开始使用</span>
</div>
</div>
</div>
<!-- 提示 -->
<p class="text-xs text-gray-6 text-center mt-3">请认真填写,信息不完整可能导致审核不通过</p>
</div>
<!-- 底部按钮 -->
@@ -150,31 +167,49 @@
</div>
<script>
const textarea = document.getElementById('applyReason');
const charCount = document.getElementById('charCount');
const errorTip = document.getElementById('errorTip');
const submitBtn = document.getElementById('submitBtn');
textarea.addEventListener('input', function() {
const len = this.value.length;
charCount.textContent = len;
if (len > 200) {
this.value = this.value.slice(0, 200);
charCount.textContent = 200;
}
if (this.value.trim()) {
errorTip.classList.add('hidden');
}
});
// 必填字段
const requiredFields = [
{ id: 'siteId', label: '球房ID' },
{ id: 'role', label: '申请身份' },
{ id: 'phone', label: '手机号' },
{ id: 'nickname', label: '昵称' },
];
submitBtn.addEventListener('click', function() {
if (!textarea.value.trim()) {
errorTip.classList.remove('hidden');
let firstEmpty = null;
// 清除之前的错误样式
document.querySelectorAll('.field-error').forEach(el => el.remove());
requiredFields.forEach(f => {
const input = document.getElementById(f.id);
input.classList.remove('ring-2', 'ring-error/30', 'border-error/40');
if (!input.value.trim()) {
input.classList.add('ring-2', 'ring-error/30', 'border-error/40');
const tip = document.createElement('p');
tip.className = 'field-error text-error text-xs mt-1';
tip.textContent = `请输入${f.label}`;
input.parentElement.appendChild(tip);
if (!firstEmpty) firstEmpty = input;
}
});
if (firstEmpty) {
firstEmpty.scrollIntoView({ behavior: 'smooth', block: 'center' });
return;
}
alert('申请已提交,请等待审核');
window.location.href = 'reviewing.html';
});
// 输入时清除错误状态
requiredFields.forEach(f => {
document.getElementById(f.id).addEventListener('input', function() {
if (this.value.trim()) {
this.classList.remove('ring-2', 'ring-error/30', 'border-error/40');
const err = this.parentElement.querySelector('.field-error');
if (err) err.remove();
}
});
});
</script>
</body>
</html>

View File

@@ -64,18 +64,22 @@
</div>
</div>
<div class="bg-white/10 rounded-xl backdrop-blur-sm">
<div class="grid grid-cols-3">
<div class="grid grid-cols-4">
<div class="text-center py-3 border-r border-white/10">
<p class="font-medium text-emerald-300 text-sm">¥8,600</p>
<p class="text-white/60 text-xs mt-1">储值余额</p>
</div>
<div class="text-center py-3 border-r border-white/10">
<p class="font-medium text-sm">¥2,800</p>
<p class="text-white/60 text-xs mt-1">60天累计消费</p>
<p class="text-white/60 text-xs mt-1">60天消费</p>
</div>
<div class="text-center py-3 border-r border-white/10">
<p class="font-medium text-sm">7天</p>
<p class="text-white/60 text-xs mt-1">理想间隔</p>
</div>
<div class="text-center py-3">
<p class="font-medium text-sm">广州朗朗桌球</p>
<p class="text-white/60 text-xs mt-1"></p>
<p class="font-medium text-amber-300 text-sm">12天</p>
<p class="text-white/60 text-xs mt-1">距今到</p>
</div>
</div>
</div>
@@ -83,19 +87,158 @@
</div>
<div class="p-4 space-y-4">
<!-- 消费习惯 -->
<!-- AI 智能洞察(客户全貌总结) -->
<div class="rounded-2xl overflow-hidden shadow-sm">
<div class="bg-gradient-to-br from-[#667eea] to-[#764ba2] p-5 text-white">
<div class="flex items-center gap-2 mb-3">
<div class="w-6 h-6 bg-white/20 rounded-lg flex items-center justify-center">
<svg width="16" height="16" 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>
</div>
<span class="text-sm font-medium text-white/90">AI 智能洞察</span>
</div>
<!-- 客户画像总结 -->
<p class="text-sm text-white/90 leading-relaxed mb-3">高价值 VIP 客户,月均到店 4-5 次,偏好夜场中式台球,近期对斯诺克产生兴趣。社交属性强,常带固定球搭子,有拉新能力。储值余额充足,对促销活动响应积极。</p>
<!-- 营销策略建议 -->
<div class="bg-white/10 rounded-xl p-3 backdrop-blur-sm">
<p class="text-xs text-white/60 mb-2">📋 当前推荐策略</p>
<div class="space-y-1.5">
<div class="flex items-start gap-2">
<span class="shrink-0 w-1.5 h-1.5 bg-emerald-300 rounded-full mt-1.5"></span>
<p class="text-xs text-white/85 leading-relaxed">最后到店距今 12 天,超出理想间隔 7 天,建议尽快安排助教小燕主动联系召回</p>
</div>
<div class="flex items-start gap-2">
<span class="shrink-0 w-1.5 h-1.5 bg-amber-300 rounded-full mt-1.5"></span>
<p class="text-xs text-white/85 leading-relaxed">客户提到想练斯诺克走位,可推荐斯诺克专项课程包,结合储值优惠提升客单价</p>
</div>
<div class="flex items-start gap-2">
<span class="shrink-0 w-1.5 h-1.5 bg-pink-300 rounded-full mt-1.5"></span>
<p class="text-xs text-white/85 leading-relaxed">社交属性强,可邀请参加门店球友赛事活动,带动球搭子到店消费</p>
</div>
</div>
</div>
</div>
</div>
<!-- 维客线索 -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<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>
<h2 class="st green text-sm font-semibold text-gray-13">维客线索</h2>
<span class="ai-title-badge"><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>
<span class="px-3 py-1.5 bg-gradient-to-r from-orange-50 to-amber-50 text-warning text-xs rounded-full border border-orange-100">💰 高客单价</span>
<span class="px-3 py-1.5 bg-gradient-to-r from-pink-50 to-rose-50 text-error text-xs rounded-full border border-pink-100">🍷 爱点酒水</span>
<!-- 线索列表 -->
<div class="space-y-2.5">
<!-- 客户基础 -->
<div class="flex items-start gap-3 p-3 bg-gray-50 rounded-xl border border-gray-100">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-primary/10 text-primary text-[11px] font-medium rounded-sm leading-normal tracking-wide">客户<br>基础</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">🎂 生日 3月15日 · VIP会员 · 注册2年</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<!-- 消费习惯 -->
<div class="flex items-start gap-3 p-3 bg-gray-50 rounded-xl border border-gray-100">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-success/10 text-success text-[11px] font-medium rounded-sm leading-normal tracking-wide">消费<br>习惯</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">🌙 常来夜场 · 月均4-5次</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<div class="bg-gray-50 rounded-xl border border-gray-100 p-3">
<div class="flex items-start gap-3">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-success/10 text-success text-[11px] font-medium rounded-sm leading-normal tracking-wide">消费<br>习惯</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">💰 高客单价</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<p class="text-xs text-gray-7 mt-2 leading-relaxed">近60天场均消费 ¥420高于门店均值 ¥180偏好夜场时段酒水附加消费占比 35%</p>
</div>
<!-- 玩法偏好 -->
<div class="flex items-start gap-3 p-3 bg-gray-50 rounded-xl border border-gray-100">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-purple-500/10 text-purple-600 text-[11px] font-medium rounded-sm leading-normal tracking-wide">玩法<br>偏好</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">🎱 偏爱中式 · 斯诺克进阶中</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<!-- 促销接受 -->
<div class="bg-gray-50 rounded-xl border border-gray-100 p-3">
<div class="flex items-start gap-3">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-warning/10 text-warning text-[11px] font-medium rounded-sm leading-normal tracking-wide">促销<br>接受</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">🍷 爱点酒水套餐 · 对储值活动敏感</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<p class="text-xs text-gray-7 mt-2 leading-relaxed">最近3次到店均点了酒水套餐上次 ¥5000 储值活动当天即充值,对满赠类活动响应率高</p>
</div>
<!-- 社交关系 -->
<div class="bg-gray-50 rounded-xl border border-gray-100 p-3">
<div class="flex items-start gap-3">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-pink-500/10 text-pink-600 text-[11px] font-medium rounded-sm leading-normal tracking-wide">社交<br>关系</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">👥 常带朋友 · 固定球搭子2人</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<p class="text-xs text-gray-7 mt-2 leading-relaxed">近60天 80% 的到店为多人局常与「李哥」「阿杰」同行曾介绍2位新客办卡</p>
</div>
<!-- 重要反馈 -->
<div class="flex items-start gap-3 p-3 bg-gray-50 rounded-xl border border-gray-100">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-error/10 text-error text-[11px] font-medium rounded-sm leading-normal tracking-wide">重要<br>反馈</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">⚠️ 上次提到想练斯诺克走位对球桌维护质量比较在意建议优先安排VIP房</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:小燕</span>
</div>
</div>
</div>
</div>
<!-- 助教任务 -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<div class="flex items-center justify-between mb-4">
<h2 class="st blue text-sm font-semibold text-gray-13">助教任务</h2>
<span class="text-xs text-gray-6">当前进行中</span>
</div>
<div class="space-y-3">
<!-- 小燕 - 高优先召回 -->
<div class="p-3 bg-gradient-to-br from-red-50/80 to-rose-50/60 rounded-xl border border-red-100/60">
<div class="flex items-center justify-between mb-2">
<div class="flex items-center gap-2">
<span class="text-sm font-semibold text-gray-13">小燕</span>
<span class="px-2 py-0.5 bg-pink-100 text-pink-700 text-xs rounded-full font-medium">高级助教</span>
</div>
<span class="px-2 py-0.5 bg-red-100 text-red-700 text-xs rounded-full font-medium">高优先召回</span>
</div>
<div class="flex items-center justify-between text-xs text-gray-7 mb-2">
<span>上次服务02-20 21:30 · 2.5h</span>
</div>
<div class="grid grid-cols-3 gap-2">
<div class="bg-white/80 rounded-lg py-1.5 text-center"><p class="text-[10px] text-gray-6 mb-0.5">近60天次数</p><p class="text-sm font-bold text-primary">18次</p></div>
<div class="bg-white/80 rounded-lg py-1.5 text-center"><p class="text-[10px] text-gray-6 mb-0.5">总时长</p><p class="text-sm font-bold text-gray-13">17h</p></div>
<div class="bg-white/80 rounded-lg py-1.5 text-center"><p class="text-[10px] text-gray-6 mb-0.5">次均时长</p><p class="text-sm font-bold text-warning">0.9h</p></div>
</div>
</div>
<!-- 泡芙 - 关系构建 -->
<div class="p-3 bg-gradient-to-br from-pink-50/80 to-fuchsia-50/60 rounded-xl border border-pink-100/60">
<div class="flex items-center justify-between mb-2">
<div class="flex items-center gap-2">
<span class="text-sm font-semibold text-gray-13">泡芙</span>
<span class="px-2 py-0.5 bg-purple-100 text-purple-700 text-xs rounded-full font-medium">中级助教</span>
</div>
<span class="px-2 py-0.5 bg-pink-100 text-pink-700 text-xs rounded-full font-medium">关系构建</span>
</div>
<div class="flex items-center justify-between text-xs text-gray-7 mb-2">
<span>上次服务02-15 14:00 · 1.5h</span>
</div>
<div class="grid grid-cols-3 gap-2">
<div class="bg-white/80 rounded-lg py-1.5 text-center"><p class="text-[10px] text-gray-6 mb-0.5">近60天次数</p><p class="text-sm font-bold text-primary">12次</p></div>
<div class="bg-white/80 rounded-lg py-1.5 text-center"><p class="text-[10px] text-gray-6 mb-0.5">总时长</p><p class="text-sm font-bold text-gray-13">11h</p></div>
<div class="bg-white/80 rounded-lg py-1.5 text-center"><p class="text-[10px] text-gray-6 mb-0.5">次均时长</p><p class="text-sm font-bold text-warning">0.9h</p></div>
</div>
</div>
</div>
<p class="text-sm text-gray-7 leading-relaxed">偏好晚间 21:00 后到店,喜欢中式台球和斯诺克。平均消费 350 元/次,月均到店 4-5 次。近 60 天到店 8 次,消费金额 2,800 元。</p>
</div>
<!-- 最喜欢的助教 -->
@@ -299,5 +442,6 @@
备注
</button>
</div>
<script src="../js/ai-icons.js"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -77,27 +77,69 @@
<!-- 主体内容 -->
<div class="p-4 space-y-4">
<!-- 消费习惯 -->
<!-- 维客线索 -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<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>
<h2 class="section-title green text-sm font-semibold text-gray-13">维客线索</h2>
<span class="ai-title-badge"><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>
<span class="px-3 py-1.5 bg-gradient-to-r from-purple-50 to-violet-50 text-purple-600 text-xs rounded-full border border-purple-100">🍷 爱点酒水</span>
<div class="space-y-2.5">
<!-- 客户基础 -->
<div class="flex items-start gap-3 p-3 bg-gray-50 rounded-xl border border-gray-100">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-primary/10 text-primary text-[11px] font-medium rounded-sm leading-normal tracking-wide">客户<br>基础</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">👩 女性 · VIP会员 · 入会1年半 · 忠实老客户</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<!-- 消费习惯 -->
<div class="flex items-start gap-3 p-3 bg-gray-50 rounded-xl border border-gray-100">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-success/10 text-success text-[11px] font-medium rounded-sm leading-normal tracking-wide">消费<br>习惯</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">☀️ 偏好周末下午 · 月均6-8次</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<div class="bg-gray-50 rounded-xl border border-gray-100 p-3">
<div class="flex items-start gap-3">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-success/10 text-success text-[11px] font-medium rounded-sm leading-normal tracking-wide">消费<br>习惯</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">💰 高客单价 · 爱点酒水</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<p class="text-xs text-gray-7 mt-2 leading-relaxed">场均消费 ¥420高于门店均值 ¥180酒水小食附加消费占比 40%偏好VIP包厢</p>
</div>
<!-- 玩法偏好 -->
<div class="bg-gray-50 rounded-xl border border-gray-100 p-3">
<div class="flex items-start gap-3">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-purple-500/10 text-purple-600 text-[11px] font-medium rounded-sm leading-normal tracking-wide">玩法<br>偏好</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">🎱 斯诺克爱好者 · 技术中上 · 喜欢研究杆法</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<p class="text-xs text-gray-7 mt-2 leading-relaxed">斯诺克占比 85%,偶尔玩中式八球;对高级杆法有浓厚兴趣,正在学习走位技巧</p>
</div>
<!-- 重要反馈 -->
<div class="bg-gray-50 rounded-xl border border-gray-100 p-3">
<div class="flex items-start gap-3">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-error/10 text-error text-[11px] font-medium rounded-sm leading-normal tracking-wide">重要<br>反馈</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">⭐ 上次服务好评,新球杆手感满意</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:小燕</span>
</div>
</div>
<p class="text-xs text-gray-7 mt-2 leading-relaxed">2月4日到店时表示新球杆手感很好希望下次能提前预留VIP包厢满意度持续保持高位</p>
</div>
</div>
<p class="text-sm text-gray-7 leading-relaxed">
忠实老客户,入会 1 年半。偏好周末下午时段,喜欢斯诺克。平均消费 420 元/次,月均到店 6-8 次,经常点酒水和小食。上次服务好评。
</p>
</div>
<!-- 与我的关系 -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<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>
<span class="ai-title-badge"><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">
@@ -173,7 +215,7 @@
<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 class="ai-title-badge"><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">
@@ -188,7 +230,7 @@
<div class="mt-4 p-4 bg-gray-50 rounded-xl border border-gray-100">
<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>
<span class="ai-title-badge"><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">
@@ -269,7 +311,10 @@
<div id="noteModal" class="fixed inset-0 bg-black/50 z-50 hidden items-end">
<div class="w-full bg-white rounded-t-3xl p-5 pb-8">
<div class="flex items-center justify-between mb-4">
<span class="text-base font-semibold text-gray-13">添加备注</span>
<div class="flex items-center gap-2">
<span class="text-base font-semibold text-gray-13">添加备注</span>
<button id="noteExpandBtn" onclick="expandNoteRating()" class="note-expand-btn">展开评价 ▾</button>
</div>
<button onclick="hideNoteModal()" class="w-8 h-8 flex items-center justify-center rounded-full bg-gray-100">
<svg class="w-4 h-4 text-gray-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"/>
@@ -277,6 +322,36 @@
</svg>
</button>
</div>
<div id="noteRatingSection" class="hidden">
<!-- 星星打分:再次服务意愿 -->
<div class="note-rating-group">
<span class="note-rating-label">再次服务此客户</span>
<div class="note-rating-right">
<div class="note-rating-row" data-score="0">
<span class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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>
</div>
<div class="note-rating-hints"><span>不想</span><span></span><span>一般</span><span></span><span>非常想</span></div>
</div>
</div>
<!-- 星星打分:再来店可能性 -->
<div class="note-rating-group">
<span class="note-rating-label">再来店可能性</span>
<div class="note-rating-right">
<div class="note-rating-row" data-score="0">
<span class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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>
</div>
<div class="note-rating-hints"><span>不可能</span><span></span><span>不好说</span><span></span><span>肯定能</span></div>
</div>
</div>
</div>
<textarea id="noteText" class="w-full h-32 p-4 bg-gray-50 rounded-xl resize-none text-sm text-gray-13 placeholder-gray-5 focus:outline-none focus:ring-2 focus:ring-primary/20 border border-gray-100" placeholder="请输入备注内容..."></textarea>
<button onclick="saveNote()" class="w-full h-12 bg-gradient-to-r from-teal-500 to-cyan-500 text-white font-medium rounded-xl mt-4 shadow-lg shadow-teal-500/30">保存</button>
</div>
@@ -304,6 +379,7 @@
<div id="toast" class="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-gray-13/80 text-white text-sm px-6 py-3 rounded-xl z-[100] hidden backdrop-blur-sm"></div>
<script src="../js/task-detail-notes.js"></script>
<script src="../js/ai-icons.js"></script>
</body>
</html>

View File

@@ -77,27 +77,69 @@
<!-- 主体内容 -->
<div class="p-4 space-y-4">
<!-- 消费习惯 -->
<!-- 维客线索 -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<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>
<h2 class="section-title green text-sm font-semibold text-gray-13">维客线索</h2>
<span class="ai-title-badge"><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>
<span class="px-3 py-1.5 bg-gradient-to-r from-purple-50 to-violet-50 text-purple-600 text-xs rounded-full border border-purple-100">👥 爱组局</span>
<div class="space-y-2.5">
<!-- 客户基础 -->
<div class="flex items-start gap-3 p-3 bg-gray-50 rounded-xl border border-gray-100">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-primary/10 text-primary text-[11px] font-medium rounded-sm leading-normal tracking-wide">客户<br>基础</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">👤 普通会员 · 注册10个月 · 近期活跃度下降</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<!-- 消费习惯 -->
<div class="flex items-start gap-3 p-3 bg-gray-50 rounded-xl border border-gray-100">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-success/10 text-success text-[11px] font-medium rounded-sm leading-normal tracking-wide">消费<br>习惯</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">🌙 偏好夜场 20:00-23:00 · 之前月均3-4次</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<div class="bg-gray-50 rounded-xl border border-gray-100 p-3">
<div class="flex items-start gap-3">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-success/10 text-success text-[11px] font-medium rounded-sm leading-normal tracking-wide">消费<br>习惯</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">📉 频率下降 · 爱组局</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<p class="text-xs text-gray-7 mt-2 leading-relaxed">场均消费 ¥220近月到店仅 1 次(之前月均 3-4 次);喜欢和朋友组局,酒水消费占比 25%</p>
</div>
<!-- 玩法偏好 -->
<div class="bg-gray-50 rounded-xl border border-gray-100 p-3">
<div class="flex items-start gap-3">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-purple-500/10 text-purple-600 text-[11px] font-medium rounded-sm leading-normal tracking-wide">玩法<br>偏好</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">🎱 中式八球为主 · 喜欢组局对战 · 想练组合球</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<p class="text-xs text-gray-7 mt-2 leading-relaxed">中式八球占比 90%,技术水平中等;喜欢 3-4 人组局对战,社交属性强</p>
</div>
<!-- 重要反馈 -->
<div class="bg-gray-50 rounded-xl border border-gray-100 p-3">
<div class="flex items-start gap-3">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-error/10 text-error text-[11px] font-medium rounded-sm leading-normal tracking-wide">重要<br>反馈</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">⚠️ 换了工作,下班时间不固定</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:小燕</span>
</div>
</div>
<p class="text-xs text-gray-7 mt-2 leading-relaxed">2月3日沟通时提到最近换了工作下班时间不固定周末可能更方便建议调整联系时段为周末</p>
</div>
</div>
<p class="text-sm text-gray-7 leading-relaxed">
偏好晚间 20:00-23:00 时段,喜欢中式八球。平均消费 220 元/次,之前月均到店 3-4 次,近期明显减少。喜欢和朋友组局打球。
</p>
</div>
<!-- 与我的关系 -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<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>
<span class="ai-title-badge"><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">
@@ -173,7 +215,7 @@
<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 class="ai-title-badge"><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">
@@ -188,7 +230,7 @@
<div class="mt-4 p-4 bg-gray-50 rounded-xl border border-gray-100">
<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>
<span class="ai-title-badge"><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">
@@ -269,7 +311,10 @@
<div id="noteModal" class="fixed inset-0 bg-black/50 z-50 hidden items-end">
<div class="w-full bg-white rounded-t-3xl p-5 pb-8">
<div class="flex items-center justify-between mb-4">
<span class="text-base font-semibold text-gray-13">添加备注</span>
<div class="flex items-center gap-2">
<span class="text-base font-semibold text-gray-13">添加备注</span>
<button id="noteExpandBtn" onclick="expandNoteRating()" class="note-expand-btn">展开评价 ▾</button>
</div>
<button onclick="hideNoteModal()" class="w-8 h-8 flex items-center justify-center rounded-full bg-gray-100">
<svg class="w-4 h-4 text-gray-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"/>
@@ -277,6 +322,36 @@
</svg>
</button>
</div>
<div id="noteRatingSection" class="hidden">
<!-- 星星打分:再次服务意愿 -->
<div class="note-rating-group">
<span class="note-rating-label">再次服务此客户</span>
<div class="note-rating-right">
<div class="note-rating-row" data-score="0">
<span class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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>
</div>
<div class="note-rating-hints"><span>不想</span><span></span><span>一般</span><span></span><span>非常想</span></div>
</div>
</div>
<!-- 星星打分:再来店可能性 -->
<div class="note-rating-group">
<span class="note-rating-label">再来店可能性</span>
<div class="note-rating-right">
<div class="note-rating-row" data-score="0">
<span class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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>
</div>
<div class="note-rating-hints"><span>不可能</span><span></span><span>不好说</span><span></span><span>肯定能</span></div>
</div>
</div>
</div>
<textarea id="noteText" class="w-full h-32 p-4 bg-gray-50 rounded-xl resize-none text-sm text-gray-13 placeholder-gray-5 focus:outline-none focus:ring-2 focus:ring-orange-500/20 border border-gray-100" placeholder="请输入备注内容..."></textarea>
<button onclick="saveNote()" class="w-full h-12 bg-gradient-to-r from-orange-500 to-amber-500 text-white font-medium rounded-xl mt-4 shadow-lg shadow-orange-500/30">保存</button>
</div>
@@ -304,6 +379,7 @@
<div id="toast" class="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-gray-13/80 text-white text-sm px-6 py-3 rounded-xl z-[100] hidden backdrop-blur-sm"></div>
<script src="../js/task-detail-notes.js"></script>
<script src="../js/ai-icons.js"></script>
</body>
</html>

View File

@@ -77,27 +77,69 @@
<!-- 主体内容 -->
<div class="p-4 space-y-4">
<!-- 消费习惯 -->
<!-- 维客线索 -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<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>
<h2 class="section-title green text-sm font-semibold text-gray-13">维客线索</h2>
<span class="ai-title-badge"><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>
<span class="px-3 py-1.5 bg-gradient-to-r from-pink-50 to-rose-50 text-pink-600 text-xs rounded-full border border-pink-100">💎 消费潜力大</span>
<div class="space-y-2.5">
<!-- 客户基础 -->
<div class="flex items-start gap-3 p-3 bg-gray-50 rounded-xl border border-gray-100">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-primary/10 text-primary text-[11px] font-medium rounded-sm leading-normal tracking-wide">客户<br>基础</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">🆕 新客户 · 入会2个月 · 消费潜力大</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<!-- 消费习惯 -->
<div class="flex items-start gap-3 p-3 bg-gray-50 rounded-xl border border-gray-100">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-success/10 text-success text-[11px] font-medium rounded-sm leading-normal tracking-wide">消费<br>习惯</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">☀️ 偏好下午 14:00-18:00 · 到店2次</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<div class="bg-gray-50 rounded-xl border border-gray-100 p-3">
<div class="flex items-start gap-3">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-success/10 text-success text-[11px] font-medium rounded-sm leading-normal tracking-wide">消费<br>习惯</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">💎 消费潜力大</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<p class="text-xs text-gray-7 mt-2 leading-relaxed">单次消费 ¥180高于新客均值 ¥120消费能力较强有提升空间适合推荐课程套餐</p>
</div>
<!-- 玩法偏好 -->
<div class="bg-gray-50 rounded-xl border border-gray-100 p-3">
<div class="flex items-start gap-3">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-purple-500/10 text-purple-600 text-[11px] font-medium rounded-sm leading-normal tracking-wide">玩法<br>偏好</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">🎱 初学者 · 对台球感兴趣 · 技术待提升</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<p class="text-xs text-gray-7 mt-2 leading-relaxed">尚未形成明确偏好,对各类玩法都有兴趣;技术水平初级,适合推荐入门课程和基础训练</p>
</div>
<!-- 重要反馈 -->
<div class="bg-gray-50 rounded-xl border border-gray-100 p-3">
<div class="flex items-start gap-3">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-error/10 text-error text-[11px] font-medium rounded-sm leading-normal tracking-wide">重要<br>反馈</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">💬 上次课后反馈良好,想继续学习</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:小燕</span>
</div>
</div>
<p class="text-xs text-gray-7 mt-2 leading-relaxed">2月5日基础课后表示很有收获希望能有更多练习机会建议安排球友交流活动帮助融入</p>
</div>
</div>
<p class="text-sm text-gray-7 leading-relaxed">
新客户,入会 2 个月。偏好下午 14:00-18:00 时段,对台球感兴趣但技术一般。消费能力较强,单次消费 180 元,有提升空间。
</p>
</div>
<!-- 与我的关系 -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<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>
<span class="ai-title-badge"><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">
@@ -173,7 +215,7 @@
<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 class="ai-title-badge"><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">
@@ -188,7 +230,7 @@
<div class="mt-4 p-4 bg-gray-50 rounded-xl border border-gray-100">
<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>
<span class="ai-title-badge"><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">
@@ -248,7 +290,10 @@
<div id="noteModal" class="fixed inset-0 bg-black/50 z-50 hidden items-end">
<div class="w-full bg-white rounded-t-3xl p-5 pb-8">
<div class="flex items-center justify-between mb-4">
<span class="text-base font-semibold text-gray-13">添加备注</span>
<div class="flex items-center gap-2">
<span class="text-base font-semibold text-gray-13">添加备注</span>
<button id="noteExpandBtn" onclick="expandNoteRating()" class="note-expand-btn">展开评价 ▾</button>
</div>
<button onclick="hideNoteModal()" class="w-8 h-8 flex items-center justify-center rounded-full bg-gray-100">
<svg class="w-4 h-4 text-gray-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"/>
@@ -256,6 +301,36 @@
</svg>
</button>
</div>
<div id="noteRatingSection" class="hidden">
<!-- 星星打分:再次服务意愿 -->
<div class="note-rating-group">
<span class="note-rating-label">再次服务此客户</span>
<div class="note-rating-right">
<div class="note-rating-row" data-score="0">
<span class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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>
</div>
<div class="note-rating-hints"><span>不想</span><span></span><span>一般</span><span></span><span>非常想</span></div>
</div>
</div>
<!-- 星星打分:再来店可能性 -->
<div class="note-rating-group">
<span class="note-rating-label">再来店可能性</span>
<div class="note-rating-right">
<div class="note-rating-row" data-score="0">
<span class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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>
</div>
<div class="note-rating-hints"><span>不可能</span><span></span><span>不好说</span><span></span><span>肯定能</span></div>
</div>
</div>
</div>
<textarea id="noteText" class="w-full h-32 p-4 bg-gray-50 rounded-xl resize-none text-sm text-gray-13 placeholder-gray-5 focus:outline-none focus:ring-2 focus:ring-pink-500/20 border border-gray-100" placeholder="请输入备注内容..."></textarea>
<button onclick="saveNote()" class="w-full h-12 bg-gradient-to-r from-pink-500 to-rose-500 text-white font-medium rounded-xl mt-4 shadow-lg shadow-pink-500/30">保存</button>
</div>
@@ -283,6 +358,7 @@
<div id="toast" class="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-gray-13/80 text-white text-sm px-6 py-3 rounded-xl z-[100] hidden backdrop-blur-sm"></div>
<script src="../js/task-detail-notes.js"></script>
<script src="../js/ai-icons.js"></script>
</body>
</html>

View File

@@ -44,27 +44,44 @@
/* 话术气泡 */
.speech-bubble {
position: relative;
background: #eef2ff;
border: 1px solid #b4c0f0;
border-radius: 14px;
background: #f0f4ff;
border: 1px solid #c5c5c5;
border-radius: 12px;
padding: 12px 16px;
font-size: 14px;
line-height: 1.7;
color: #5e5e5e;
margin-bottom: 16px;
}
/* 引出角:底部偏右,用旋转正方形模拟 */
/* 尖角:旋转正方形,右下角伸出 */
.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;
bottom: -8px;
right: 24px;
width: 14px;
height: 14px;
background: #f0f4ff;
border-right: 1px solid #c5c5c5;
border-bottom: 1px solid #c5c5c5;
transform: rotate(45deg);
}
/* 复制按钮 */
.copy-btn {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 0;
font-size: 12px;
color: #a6a6a6;
background: none;
border: none;
cursor: pointer;
transition: color 0.2s;
}
.copy-btn:active { color: #0052d9; }
.copy-btn svg { width: 14px; height: 14px; }
.copy-btn.copied { color: #00a870; }
</style>
</head>
<body class="bg-gray-1 min-h-screen">
@@ -104,27 +121,11 @@
<!-- 主体内容 -->
<div class="p-4 space-y-4">
<!-- 消费习惯 -->
<!-- 与我的关系(置顶) -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<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>
<span class="px-3 py-1.5 bg-gradient-to-r from-orange-50 to-amber-50 text-warning text-xs rounded-full border border-orange-100">💰 储值 高客单价</span>
</div>
<p class="text-sm text-gray-7 leading-relaxed">
偏好晚间 21:00 后到店,喜欢中式台球和斯诺克。平均消费 350 元/次,月均到店 4-5 次。经常点套餐和饮品,倾向于包厢消费。
</p>
</div>
<!-- 与我的关系 -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<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>
<h2 class="section-title pink text-sm font-semibold text-gray-13">与我的关系</h2>
<span class="ai-title-badge"><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">
@@ -137,60 +138,9 @@
</div>
<span class="text-lg font-bold text-pink-500">0.85</span>
</div>
<p class="text-sm text-gray-7 leading-relaxed mb-4">
<p class="text-sm text-gray-7 leading-relaxed">
最近 3 个月每周均有 1-2 次课程互动,客户反馈良好。上次服务评价 5 星,多次指定您为服务助教。
</p>
<!-- 最近服务记录 -->
<div class="mt-3 -mx-5 -mb-5 px-5 pt-5 pb-5 bg-gray-100/70 rounded-b-2xl">
<p class="text-base font-semibold text-gray-13 mb-3">📋 近期服务记录</p>
<div>
<div class="svc-record">
<div class="flex items-center justify-between mb-1.5">
<div class="flex items-center gap-2">
<span class="svc-table">A12号台</span>
<span class="svc-type basic">基础课</span>
<span class="svc-duration text-base">2.5h</span>
</div>
<span class="svc-income text-base">¥200</span>
</div>
<div class="flex items-center justify-between">
<span class="svc-drinks">🍷 百威x2 红牛x1</span>
<span class="svc-date text-xs">2026-02-07 21:30</span>
</div>
</div>
<div class="svc-record">
<div class="flex items-center justify-between mb-1.5">
<div class="flex items-center gap-2">
<span class="svc-table">3号台</span>
<span class="svc-type basic">基础课</span>
<span class="svc-duration text-base">2.0h</span>
</div>
<span class="svc-income text-base">¥160</span>
</div>
<div class="flex items-center justify-between">
<span class="svc-drinks">🍷 可乐x1</span>
<span class="svc-date text-xs">2026-02-01 20:30</span>
</div>
</div>
<div class="svc-record">
<div class="flex items-center justify-between mb-1.5">
<div class="flex items-center gap-2">
<span class="svc-table">VIP1号房</span>
<span class="svc-type incentive">激励课</span>
<span class="svc-duration text-base">1.5h</span>
</div>
<span class="svc-income text-base">¥150</span>
</div>
<div class="flex items-center justify-between">
<span class="svc-drinks">🍷 芝华士x1 矿泉水x2</span>
<span class="svc-date text-xs">2026-01-28 19:00</span>
</div>
</div>
</div>
<div class="mt-3 text-center">
<button onclick="window.location.href='customer-service-records.html'" class="text-xs text-primary font-medium">查看全部服务记录 →</button>
</div>
</div>
</div>
<!-- 任务建议 -->
@@ -200,7 +150,7 @@
<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 class="ai-title-badge"><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">
@@ -215,14 +165,71 @@
<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 class="speech-bubble"><span class="ai-inline-icon"><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>王哥您好,好久不见!最近店里新到了几张国际标准的斯诺克球桌,知道您是斯诺克爱好者,想邀请您有空来体验一下~<div class="flex justify-end mt-0"><button class="copy-btn" onclick="copySpeech(this)"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg><span>复制</span></button></div></div>
<div class="speech-bubble"><span class="ai-inline-icon"><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>王哥,最近忙吗?这周末我们有个老客户专属的球友交流赛,奖品还挺丰富的,您要不要来参加?<div class="flex justify-end mt-0"><button class="copy-btn" onclick="copySpeech(this)"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg><span>复制</span></button></div></div>
<div class="speech-bubble"><span class="ai-inline-icon"><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>王哥好呀,上次您提到想练练斯诺克的走位,我最近研究了一些新的训练方法,下次来的时候可以一起试试~<div class="flex justify-end mt-0"><button class="copy-btn" onclick="copySpeech(this)"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg><span>复制</span></button></div></div>
<div class="speech-bubble"><span class="ai-inline-icon"><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>王哥,好久没见您了,您的老位置 A12 号台一直给您留着呢!最近晚上人不多,环境特别好,随时欢迎您来~<div class="flex justify-end mt-0"><button class="copy-btn" onclick="copySpeech(this)"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg><span>复制</span></button></div></div>
<div class="speech-bubble"><span class="ai-inline-icon"><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>王哥您好,我们这个月推出了储值会员专属的夜场优惠套餐,包含球台+酒水,性价比很高,给您留意着呢~<div class="flex justify-end mt-0"><button class="copy-btn" onclick="copySpeech(this)"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg><span>复制</span></button></div></div>
</div>
</div>
</div>
<!-- 维客线索 -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<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"><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-2.5">
<!-- 客户基础 -->
<div class="flex items-start gap-3 p-3 bg-gray-50 rounded-xl border border-gray-100">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-primary/10 text-primary text-[11px] font-medium rounded-sm leading-normal tracking-wide">客户<br>基础</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">🎂 生日 3月15日 · VIP会员 · 注册2年</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<!-- 消费习惯 -->
<div class="flex items-start gap-3 p-3 bg-gray-50 rounded-xl border border-gray-100">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-success/10 text-success text-[11px] font-medium rounded-sm leading-normal tracking-wide">消费<br>习惯</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">🌙 常来夜场 · 月均4-5次</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<div class="bg-gray-50 rounded-xl border border-gray-100 p-3">
<div class="flex items-start gap-3">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-success/10 text-success text-[11px] font-medium rounded-sm leading-normal tracking-wide">消费<br>习惯</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">💰 高客单价</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<p class="text-xs text-gray-7 mt-2 leading-relaxed">近60天场均消费 ¥420高于门店均值 ¥180偏好夜场时段酒水附加消费占比 35%</p>
</div>
<!-- 玩法偏好 -->
<div class="bg-gray-50 rounded-xl border border-gray-100 p-3">
<div class="flex items-start gap-3">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-purple-500/10 text-purple-600 text-[11px] font-medium rounded-sm leading-normal tracking-wide">玩法<br>偏好</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">🎱 偏爱中式八球 · 斯诺克进阶中 · 最近对花式九球也有兴趣</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<p class="text-xs text-gray-7 mt-2 leading-relaxed">中式八球占比 60%,斯诺克 30%近2周开始尝试花式九球技术水平中等偏上</p>
</div>
<!-- 重要反馈 -->
<div class="bg-gray-50 rounded-xl border border-gray-100 p-3">
<div class="flex items-start gap-3">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-error/10 text-error text-[11px] font-medium rounded-sm leading-normal tracking-wide">重要<br>反馈</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">⚠️ 上次提到想练斯诺克走位</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:小燕</span>
</div>
</div>
<p class="text-xs text-gray-7 mt-2 leading-relaxed">2月7日到店时主动提及希望有针对性的走位训练建议下次安排斯诺克专项课程</p>
</div>
</div>
</div>
@@ -279,6 +286,77 @@
<p class="text-sm text-gray-5">暂无备注</p>
</div>
</div>
<!-- 近期服务记录(置底) -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<div class="flex items-center justify-between mb-4">
<h2 class="section-title blue text-sm font-semibold text-gray-13">近期服务记录</h2>
<span class="text-xs text-gray-6">共 3 次</span>
</div>
<!-- 汇总统计 -->
<div class="flex items-center gap-3 mb-4">
<div class="flex-1 text-center py-2.5 bg-primary/5 rounded-xl">
<p class="text-lg font-bold text-primary">6.0<span class="text-xs font-normal text-gray-6 ml-0.5">h</span></p>
<p class="text-[11px] text-gray-6 mt-0.5">总时长</p>
</div>
<div class="flex-1 text-center py-2.5 bg-success/5 rounded-xl">
<p class="text-lg font-bold text-success">¥510</p>
<p class="text-[11px] text-gray-6 mt-0.5">总收入</p>
</div>
<div class="flex-1 text-center py-2.5 bg-warning/5 rounded-xl">
<p class="text-lg font-bold text-warning">¥170</p>
<p class="text-[11px] text-gray-6 mt-0.5">场均</p>
</div>
</div>
<!-- 记录列表 -->
<div class="space-y-2.5">
<div class="svc-record">
<div class="flex items-center justify-between mb-1.5">
<div class="flex items-center gap-2">
<span class="svc-table">A12号台</span>
<span class="svc-type basic">基础课</span>
<span class="svc-duration text-base">2.5h</span>
</div>
<span class="svc-income text-base">¥200</span>
</div>
<div class="flex items-center justify-between">
<span class="svc-drinks">🍷 百威x2 红牛x1</span>
<span class="svc-date text-xs">2026-02-07 21:30</span>
</div>
</div>
<div class="svc-record">
<div class="flex items-center justify-between mb-1.5">
<div class="flex items-center gap-2">
<span class="svc-table">3号台</span>
<span class="svc-type basic">基础课</span>
<span class="svc-duration text-base">2.0h</span>
</div>
<span class="svc-income text-base">¥160</span>
</div>
<div class="flex items-center justify-between">
<span class="svc-drinks">🍷 可乐x1</span>
<span class="svc-date text-xs">2026-02-01 20:30</span>
</div>
</div>
<div class="svc-record">
<div class="flex items-center justify-between mb-1.5">
<div class="flex items-center gap-2">
<span class="svc-table">VIP1号房</span>
<span class="svc-type incentive">激励课</span>
<span class="svc-duration text-base">1.5h</span>
</div>
<span class="svc-income text-base">¥150</span>
</div>
<div class="flex items-center justify-between">
<span class="svc-drinks">🍷 芝华士x1 矿泉水x2</span>
<span class="svc-date text-xs">2026-01-28 19:00</span>
</div>
</div>
</div>
<div class="mt-4 text-center">
<button onclick="window.location.href='customer-service-records.html'" class="text-xs text-primary font-medium">查看全部服务记录 →</button>
</div>
</div>
</div>
<!-- 底部操作栏 -->
@@ -302,7 +380,10 @@
<div id="noteModal" class="fixed inset-0 bg-black/50 z-50 hidden items-end">
<div class="w-full bg-white rounded-t-3xl p-5 pb-8">
<div class="flex items-center justify-between mb-4">
<span class="text-base font-semibold text-gray-13">添加备注</span>
<div class="flex items-center gap-2">
<span class="text-base font-semibold text-gray-13">添加备注</span>
<button id="noteExpandBtn" onclick="expandNoteRating()" class="note-expand-btn">展开评价 ▾</button>
</div>
<button onclick="hideNoteModal()" class="w-8 h-8 flex items-center justify-center rounded-full bg-gray-100">
<svg class="w-4 h-4 text-gray-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"/>
@@ -310,6 +391,36 @@
</svg>
</button>
</div>
<div id="noteRatingSection" class="hidden">
<!-- 星星打分:再次服务意愿 -->
<div class="note-rating-group">
<span class="note-rating-label">再次服务此客户</span>
<div class="note-rating-right">
<div class="note-rating-row" data-score="0">
<span class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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>
</div>
<div class="note-rating-hints"><span>不想</span><span></span><span>一般</span><span></span><span>非常想</span></div>
</div>
</div>
<!-- 星星打分:再来店可能性 -->
<div class="note-rating-group">
<span class="note-rating-label">再来店可能性</span>
<div class="note-rating-right">
<div class="note-rating-row" data-score="0">
<span class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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>
</div>
<div class="note-rating-hints"><span>不可能</span><span></span><span>不好说</span><span></span><span>肯定能</span></div>
</div>
</div>
</div>
<textarea id="noteText" class="w-full h-32 p-4 bg-gray-50 rounded-xl resize-none text-sm text-gray-13 placeholder-gray-5 focus:outline-none focus:ring-2 focus:ring-primary/20 border border-gray-100" placeholder="请输入备注内容..."></textarea>
<button onclick="saveNote()" class="w-full h-12 bg-gradient-to-r from-primary to-blue-500 text-white font-medium rounded-xl mt-4 shadow-lg shadow-primary/30">保存</button>
</div>
@@ -358,5 +469,36 @@
<script src="../js/task-detail-notes.js"></script>
<script src="../js/task-detail-abandon.js"></script>
<script src="../js/ai-icons.js"></script>
<script>
function copySpeech(btn) {
var bubble = btn.closest('.speech-bubble');
var clone = bubble.cloneNode(true);
clone.querySelectorAll('.copy-btn, .ai-inline-icon').forEach(function(el) { el.remove(); });
var text = clone.textContent.trim();
if (navigator.clipboard) {
navigator.clipboard.writeText(text);
} else {
var ta = document.createElement('textarea');
ta.value = text;
ta.style.position = 'fixed';
ta.style.opacity = '0';
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
document.body.removeChild(ta);
}
btn.classList.add('copied');
btn.querySelector('span').textContent = '已复制';
var t = document.getElementById('toast');
t.textContent = '已复制到剪贴板';
t.classList.remove('hidden');
setTimeout(function() { t.classList.add('hidden'); }, 1500);
setTimeout(function() {
btn.classList.remove('copied');
btn.querySelector('span').textContent = '复制';
}, 2000);
}
</script>
</body>
</html>

View File

@@ -515,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"><span class="ai-inline-icon"></span>高流失风险,建议尽快联系</p>
<p class="text-sm text-gray-6 leading-relaxed task-desc"><span class="ai-inline-icon"><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>高流失风险,建议尽快联系</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"/>
@@ -533,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"><span class="ai-inline-icon"></span>VIP客户储值余额较多</p>
<p class="text-sm text-gray-6 leading-relaxed task-desc"><span class="ai-inline-icon"><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>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"/>
@@ -561,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"><span class="ai-inline-icon"></span>消费频率下降,需关注</p>
<p class="text-sm text-gray-6 leading-relaxed task-desc"><span class="ai-inline-icon"><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>消费频率下降,需关注</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"/>
@@ -579,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"><span class="ai-inline-icon"></span>偏好晚间时段,可推荐夜场套餐</p>
<p class="text-sm text-gray-6 leading-relaxed task-desc"><span class="ai-inline-icon"><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>偏好晚间时段,可推荐夜场套餐</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"/>
@@ -597,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"><span class="ai-inline-icon"></span>潜力客户,建议加强互动</p>
<p class="text-sm text-gray-6 leading-relaxed task-desc"><span class="ai-inline-icon"><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>潜力客户,建议加强互动</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"/>
@@ -678,6 +678,9 @@
</div>
</div>
<!-- AI 标识配色 -->
<script src="../js/ai-icons.js"></script>
<!-- 悬浮助手按钮 -->
<script src="../js/ai-float-btn.js"></script>

View 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="currentColor" opacity="0.12" stroke="currentColor" stroke-width="0.7"/><path d="M12 7V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="currentColor"/><circle cx="9" cy="11.5" r="1.8" fill="white" stroke="currentColor" stroke-width="0.6"/><circle cx="15" cy="11.5" r="1.8" fill="white" stroke="currentColor" stroke-width="0.6"/><circle cx="9.4" cy="11.2" r="0.7" fill="currentColor"/><circle cx="15.4" cy="11.2" r="0.7" fill="currentColor"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.5"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.5"/><rect x="3" y="10" width="2" height="4" rx="1" fill="currentColor" opacity="0.15" stroke="currentColor" stroke-width="0.5"/><rect x="19" y="10" width="2" height="4" rx="1" fill="currentColor" opacity="0.15" stroke="currentColor" stroke-width="0.5"/></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="currentColor" opacity="0.12" stroke="currentColor" stroke-width="0.7"/><path d="M12 7V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="currentColor"/><circle cx="9" cy="11.5" r="1.8" fill="white" stroke="currentColor" stroke-width="0.6"/><circle cx="15" cy="11.5" r="1.8" fill="white" stroke="currentColor" stroke-width="0.6"/><circle cx="9.4" cy="11.2" r="0.7" fill="currentColor"/><circle cx="15.4" cy="11.2" r="0.7" fill="currentColor"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.5"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.5"/><rect x="3" y="10" width="2" height="4" rx="1" fill="currentColor" opacity="0.15" stroke="currentColor" stroke-width="0.5"/><rect x="19" y="10" width="2" height="4" rx="1" fill="currentColor" opacity="0.15" stroke="currentColor" stroke-width="0.5"/></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="currentColor" opacity="0.12" stroke="currentColor" stroke-width="0.7"/><path d="M12 7V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="currentColor"/><circle cx="9" cy="11.5" r="1.8" fill="white" stroke="currentColor" stroke-width="0.6"/><circle cx="15" cy="11.5" r="1.8" fill="white" stroke="currentColor" stroke-width="0.6"/><circle cx="9.4" cy="11.2" r="0.7" fill="currentColor"/><circle cx="15.4" cy="11.2" r="0.7" fill="currentColor"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.5"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.5"/><rect x="3" y="10" width="2" height="4" rx="1" fill="currentColor" opacity="0.15" stroke="currentColor" stroke-width="0.5"/><rect x="19" y="10" width="2" height="4" rx="1" fill="currentColor" opacity="0.15" stroke="currentColor" stroke-width="0.5"/></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="currentColor" opacity="0.12" stroke="currentColor" stroke-width="0.7"/><path d="M12 7V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="currentColor"/><circle cx="9" cy="11.5" r="1.8" fill="white" stroke="currentColor" stroke-width="0.6"/><circle cx="15" cy="11.5" r="1.8" fill="white" stroke="currentColor" stroke-width="0.6"/><circle cx="9.4" cy="11.2" r="0.7" fill="currentColor"/><circle cx="15.4" cy="11.2" r="0.7" fill="currentColor"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.5"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.5"/><rect x="3" y="10" width="2" height="4" rx="1" fill="currentColor" opacity="0.15" stroke="currentColor" stroke-width="0.5"/><rect x="19" y="10" width="2" height="4" rx="1" fill="currentColor" opacity="0.15" stroke="currentColor" stroke-width="0.5"/></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="currentColor" opacity="0.12" stroke="currentColor" stroke-width="0.7"/><path d="M12 7V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="currentColor"/><circle cx="9" cy="11.5" r="1.8" fill="white" stroke="currentColor" stroke-width="0.6"/><circle cx="15" cy="11.5" r="1.8" fill="white" stroke="currentColor" stroke-width="0.6"/><circle cx="9.4" cy="11.2" r="0.7" fill="currentColor"/><circle cx="15.4" cy="11.2" r="0.7" fill="currentColor"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.5"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.5"/><rect x="3" y="10" width="2" height="4" rx="1" fill="currentColor" opacity="0.15" stroke="currentColor" stroke-width="0.5"/><rect x="19" y="10" width="2" height="4" rx="1" fill="currentColor" opacity="0.15" stroke="currentColor" stroke-width="0.5"/></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="currentColor" opacity="0.12" stroke="currentColor" stroke-width="0.7"/><path d="M12 7V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="3" r="1.5" fill="currentColor"/><circle cx="9" cy="11.5" r="1.8" fill="white" stroke="currentColor" stroke-width="0.6"/><circle cx="15" cy="11.5" r="1.8" fill="white" stroke="currentColor" stroke-width="0.6"/><circle cx="9.4" cy="11.2" r="0.7" fill="currentColor"/><circle cx="15.4" cy="11.2" r="0.7" fill="currentColor"/><path d="M9.5 15C10 16 14 16 14.5 15" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/><circle cx="7" cy="13.5" r="1" fill="#f5a0c0" opacity="0.5"/><circle cx="17" cy="13.5" r="1" fill="#f5a0c0" opacity="0.5"/><rect x="3" y="10" width="2" height="4" rx="1" fill="currentColor" opacity="0.15" stroke="currentColor" stroke-width="0.5"/><rect x="19" y="10" width="2" height="4" rx="1" fill="currentColor" opacity="0.15" stroke="currentColor" stroke-width="0.5"/></svg></span>AI智能洞察</span>
</div>
<p class="note">每个页面所有 Title AI 标识统一使用一种配色,刷新后随机分配</p>
</body>
</html>

View File

@@ -0,0 +1,124 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>账号申请 - 球房运营助手</title>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Noto Sans SC', -apple-system, BlinkMacSystemFont, sans-serif;
}
.bg-gradient-main {
background: linear-gradient(135deg, #e0f2fe 0%, #f0f9ff 50%, #ecfeff 100%);
}
.safe-area-top {
padding-top: env(safe-area-inset-top, 44px);
}
</style>
</head>
<body class="bg-gradient-main min-h-screen flex flex-col">
<!-- 顶部导航 -->
<div class="safe-area-top bg-white/80 backdrop-blur-lg sticky top-0 z-10">
<div class="h-11 flex items-center justify-center relative border-b border-gray-200/50">
<button onclick="history.back()" class="absolute left-4 p-1">
<svg class="w-5 h-5 text-gray-10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="15 18 9 12 15 6"/>
</svg>
</button>
<h1 class="text-base font-medium text-gray-13">申请访问权限</h1>
</div>
</div>
<!-- 主体内容 -->
<div class="flex-1 p-4">
<!-- 欢迎卡片 -->
<div class="bg-gradient-to-br from-primary to-blue-400 rounded-2xl p-5 mb-4 text-white shadow-lg shadow-primary/20">
<div class="flex items-center gap-4 mb-3">
<div class="w-12 h-12 bg-white/20 backdrop-blur-sm rounded-xl flex items-center justify-center">
<svg class="w-6 h-6 text-white" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
</svg>
</div>
<div>
<h2 class="text-lg font-semibold mb-1">欢迎加入球房运营助手</h2>
<p class="text-white/80 text-sm">请填写申请信息,等待管理员审核</p>
</div>
</div>
</div>
<!-- 说明文字 -->
<div class="bg-primary/5 border border-primary/10 rounded-xl p-4 mb-4">
<div class="flex items-start gap-3">
<svg class="w-5 h-5 text-primary flex-shrink-0 mt-0.5" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/>
</svg>
<div>
<p class="text-sm text-primary font-medium mb-1">申请说明</p>
<p class="text-xs text-gray-7 leading-relaxed">
请填写您的真实信息,包括姓名、岗位和所属门店等,以便管理员快速审核您的申请。
</p>
</div>
</div>
</div>
<!-- 表单区域 -->
<div class="bg-white rounded-2xl p-5 shadow-lg shadow-gray-200/50">
<div class="mb-3 flex items-center gap-1">
<span class="text-error text-sm">*</span>
<span class="text-sm font-medium text-gray-13">申请说明</span>
</div>
<textarea
id="applyReason"
class="w-full h-36 p-4 bg-gray-50 rounded-xl border border-gray-100 resize-none text-sm text-gray-13 placeholder-gray-5 focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary/30 transition-all"
placeholder="点击填写申请信息:&#10;1. 您的姓名&#10;2. 您的岗位(如:助教、店长等)&#10;3. 所属门店&#10;4. 其他说明(可选)"
></textarea>
<p id="errorTip" class="text-error text-xs mt-2 hidden">申请说明不能为空</p>
<div class="flex items-center justify-between mt-3">
<span class="text-xs text-gray-5">请认真填写,信息不完整可能导致审核不通过</span>
<span class="text-xs text-gray-6"><span id="charCount">0</span>/200</span>
</div>
</div>
<!-- 审核流程说明 -->
<div class="mt-4 bg-white rounded-2xl p-5 shadow-lg shadow-gray-200/50">
<h3 class="text-sm font-medium text-gray-13 mb-4">审核流程</h3>
<div class="flex items-center justify-between">
<div class="flex flex-col items-center">
<div class="w-8 h-8 bg-primary rounded-full flex items-center justify-center text-white text-xs font-medium mb-2">1</div>
<span class="text-xs text-gray-7">提交申请</span>
</div>
<div class="flex-1 h-0.5 bg-gray-200 mx-2"></div>
<div class="flex flex-col items-center">
<div class="w-8 h-8 bg-gray-200 rounded-full flex items-center justify-center text-gray-6 text-xs font-medium mb-2">2</div>
<span class="text-xs text-gray-6">等待审核</span>
</div>
<div class="flex-1 h-0.5 bg-gray-200 mx-2"></div>
<div class="flex flex-col items-center">
<div class="w-8 h-8 bg-gray-200 rounded-full flex items-center justify-center text-gray-6 text-xs font-medium mb-2">3</div>
<span class="text-xs text-gray-6">审核通过</span>
</div>
<div class="flex-1 h-0.5 bg-gray-200 mx-2"></div>
<div class="flex flex-col items-center">
<div class="w-8 h-8 bg-gray-200 rounded-full flex items-center justify-center text-gray-6 text-xs font-medium mb-2">4</div>
<span class="text-xs text-gray-6">开始使用</span>
</div>
</div>
</div>
</div>
<!-- 底部按钮 -->
<div class="p-4 pb-8 bg-white/80 backdrop-blur-lg border-t border-gray-100">
<button id="submitBtn" class="w-full py-4 bg-gradient-to-r from-primary to-blue-500 rounded-xl text-white font-medium text-base shadow-lg shadow-primary/30 active:scale-[0.98] transition-transform">
提交申请
</button>
<p class="text-xs text-gray-5 text-center mt-3">
审核通常需要 1-3 个工作日
</p>
</div>
</body>
</html>

View File

@@ -0,0 +1,812 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>看板-助教 - 球房运营助手</title>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Noto Sans SC', -apple-system, BlinkMacSystemFont, sans-serif;
padding-bottom: 80px;
}
.safe-area-top {
padding-top: env(safe-area-inset-top, 44px);
}
.tab-active {
color: #0052d9;
position: relative;
}
.tab-active::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 24px;
height: 3px;
background: linear-gradient(90deg, #0052d9, #5b9cf8);
border-radius: 2px;
}
.filter-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
}
.filter-overlay.show {
display: block;
}
.filter-dropdown {
display: none;
position: fixed;
left: 0;
right: 0;
background: white;
border-radius: 0 0 16px 16px;
box-shadow: 0 8px 24px rgba(0,0,0,0.15);
z-index: 1001;
max-height: 60vh;
overflow-y: auto;
}
.filter-dropdown.show {
display: block;
}
/* 二级筛选栏层级与动效 */
#filterBar {
overflow: hidden;
max-height: 120px;
transition: transform 220ms ease, opacity 220ms ease, max-height 220ms ease;
will-change: transform, opacity;
}
/* 仅用于「首次进入页面」的下滑出现(用 transition 触发一次) */
#filterBar.filter-bar-enter {
transform: translateY(-16px);
opacity: 0;
}
#filterBar.filter-bar-hidden {
opacity: 0;
transform: translateY(-110%);
pointer-events: none;
}
@keyframes filterBarDrop {
from { transform: translateY(-16px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
#filterBar { animation: none; transition: none; }
}
.coach-card {
transition: all 0.2s ease;
}
.dim-container { display: none; }
.dim-container.active { display: block; }
.coach-card:active {
transform: scale(0.98);
}
</style>
</head>
<body class="bg-gray-1 min-h-screen">
<!-- 顶部导航 -->
<div class="safe-area-top bg-white sticky top-0 z-20 shadow-sm">
<!-- 一级 Tab -->
<div class="flex border-b border-gray-2">
<a href="board-finance.html" class="flex-1 py-3 text-center text-sm font-medium text-gray-7">财务</a>
<a href="board-customer.html" class="flex-1 py-3 text-center text-sm font-medium text-gray-7">客户</a>
<a href="board-coach.html" class="flex-1 py-3 text-center text-sm font-medium tab-active">助教</a>
</div>
</div>
<!-- 筛选区域(二级)— 高度与财务客户看板一致,文字放大 -->
<div id="filterBar" class="bg-gray-1 sticky top-[44px] z-10 px-4 py-2 border-b border-gray-2">
<div class="bg-white rounded-lg p-1.5 flex gap-2 shadow-sm border border-gray-2">
<button onclick="toggleFilter('sort')" class="flex-[2] px-3 py-2 bg-gray-50 rounded-lg text-sm text-gray-10 flex items-center justify-center gap-1 border border-gray-100 whitespace-nowrap">
<span id="sortLabel" class="truncate font-medium">定档业绩最高</span>
<svg class="w-4 h-4 text-gray-5 flex-shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="6 9 12 15 18 9"/>
</svg>
</button>
<button onclick="toggleFilter('skill')" class="flex-1 px-3 py-2 bg-gray-50 rounded-lg text-sm text-gray-10 flex items-center justify-center gap-1 border border-gray-100 whitespace-nowrap">
<span id="skillLabel" class="truncate">不限</span>
<svg class="w-4 h-4 text-gray-5 flex-shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="6 9 12 15 18 9"/>
</svg>
</button>
<button onclick="toggleFilter('time')" class="flex-1 px-3 py-2 bg-gray-50 rounded-lg text-sm text-gray-10 flex items-center justify-center gap-1 border border-gray-100 whitespace-nowrap">
<span id="timeLabel" class="truncate">本月</span>
<svg class="w-4 h-4 text-gray-5 flex-shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="6 9 12 15 18 9"/>
</svg>
</button>
</div>
</div>
<!-- 遮罩层 -->
<div id="filterOverlay" class="filter-overlay" onclick="closeAllFilters()"></div>
<!-- 排序筛选弹窗 -->
<div id="sortDropdown" class="filter-dropdown">
<div class="p-4 space-y-2">
<div class="px-4 py-3 text-sm text-gray-10 hover:bg-primary/5 rounded-lg cursor-pointer" onclick="selectSort('定档业绩最高')">定档业绩最高</div>
<div class="px-4 py-3 text-sm text-gray-10 hover:bg-primary/5 rounded-lg cursor-pointer" onclick="selectSort('定档业绩最低')">定档业绩最低</div>
<div class="px-4 py-3 text-sm text-gray-10 hover:bg-primary/5 rounded-lg cursor-pointer" onclick="selectSort('工资最高')">工资最高</div>
<div class="px-4 py-3 text-sm text-gray-10 hover:bg-primary/5 rounded-lg cursor-pointer" onclick="selectSort('工资最低')">工资最低</div>
<div class="px-4 py-3 text-sm text-gray-10 hover:bg-primary/5 rounded-lg cursor-pointer" onclick="selectSort('客源储值最高')">客源储值最高</div>
<div class="px-4 py-3 text-sm text-gray-10 hover:bg-primary/5 rounded-lg cursor-pointer" onclick="selectSort('任务完成最多')">任务完成最多</div>
</div>
</div>
<!-- 擅长项目筛选弹窗 -->
<div id="skillDropdown" class="filter-dropdown">
<div class="p-4 space-y-2">
<div class="px-4 py-3 text-sm text-gray-10 hover:bg-primary/5 rounded-lg cursor-pointer" onclick="selectSkill('不限')">不限</div>
<div class="px-4 py-3 text-sm text-gray-10 hover:bg-primary/5 rounded-lg cursor-pointer" onclick="selectSkill('🎱')">🎱</div>
<div class="px-4 py-3 text-sm text-gray-10 hover:bg-primary/5 rounded-lg cursor-pointer" onclick="selectSkill('斯')"></div>
<div class="px-4 py-3 text-sm text-gray-10 hover:bg-primary/5 rounded-lg cursor-pointer" onclick="selectSkill('🀄')">🀄</div>
<div class="px-4 py-3 text-sm text-gray-10 hover:bg-primary/5 rounded-lg cursor-pointer" onclick="selectSkill('🎤')">🎤</div>
</div>
</div>
<!-- 时间筛选弹窗 -->
<div id="timeDropdown" class="filter-dropdown">
<div class="p-4 space-y-2">
<div class="px-4 py-3 text-sm text-gray-10 hover:bg-primary/5 rounded-lg cursor-pointer" onclick="selectTime('本月')">本月</div>
<div class="px-4 py-3 text-sm text-gray-10 hover:bg-primary/5 rounded-lg cursor-pointer" onclick="selectTime('本季度')">本季度</div>
<div class="px-4 py-3 text-sm text-gray-10 hover:bg-primary/5 rounded-lg cursor-pointer" onclick="selectTime('上月')">上月</div>
<div class="px-4 py-3 text-sm text-gray-10 hover:bg-primary/5 rounded-lg cursor-pointer" onclick="selectTime('前3个月')">前3个月不含本月</div>
<div class="px-4 py-3 text-sm text-gray-10 hover:bg-primary/5 rounded-lg cursor-pointer" onclick="selectTime('上季度')">上季度</div>
<div class="px-4 py-3 text-sm text-gray-10 hover:bg-primary/5 rounded-lg cursor-pointer" onclick="selectTime('最近6个月')">最近6个月不含本月不支持客源储值最高</div>
</div>
</div>
<!-- ====== 定档业绩最高/最低 ====== -->
<div id="dim-perf" class="dim-container active p-4 space-y-3">
<a href="coach-detail.html" class="block bg-white rounded-2xl p-4 shadow-sm coach-card">
<div class="flex items-center gap-3">
<div class="w-11 h-11 rounded-full bg-gradient-to-br from-blue-400 to-indigo-500 flex items-center justify-center flex-shrink-0">
<span class="text-white font-semibold text-base"></span>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-1.5 flex-wrap">
<span class="text-base font-semibold text-gray-13">小燕</span>
<span class="px-1.5 py-0.5 bg-gradient-to-r from-amber-400 to-orange-400 text-white text-xs rounded flex-shrink-0">星级</span>
<span class="px-1.5 py-0.5 bg-primary/10 text-primary text-xs rounded flex-shrink-0">🎱</span>
<span class="ml-auto flex items-center gap-2 text-xs flex-shrink-0">
<span>定档 <b class="text-primary text-sm">86.2h</b></span>
<span class="text-gray-5">折前 <span class="text-gray-7">92.0h</span></span>
</span>
</div>
<div class="flex items-center justify-between mt-1.5 text-xs">
<div class="flex items-center gap-2 text-gray-6 truncate">
<span>💖 王先生</span><span>💖 李女士</span><span>💛 赵总</span>
</div>
<span class="text-warning font-medium flex-shrink-0">距升档 13.8h</span>
</div>
</div>
</div>
</a>
<a href="coach-detail.html" class="block bg-white rounded-2xl p-4 shadow-sm coach-card">
<div class="flex items-center gap-3">
<div class="w-11 h-11 rounded-full bg-gradient-to-br from-green-400 to-emerald-500 flex items-center justify-center flex-shrink-0">
<span class="text-white font-semibold text-base"></span>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-1.5 flex-wrap">
<span class="text-base font-semibold text-gray-13">泡芙</span>
<span class="px-1.5 py-0.5 bg-gradient-to-r from-purple-400 to-violet-400 text-white text-xs rounded flex-shrink-0">高级</span>
<span class="px-1.5 py-0.5 bg-success/10 text-success text-xs rounded flex-shrink-0"></span>
<span class="ml-auto flex items-center gap-2 text-xs flex-shrink-0">
<span>定档 <b class="text-primary text-sm">72.5h</b></span>
<span class="text-gray-5">折前 <span class="text-gray-7">78.0h</span></span>
</span>
</div>
<div class="flex items-center justify-between mt-1.5 text-xs">
<div class="flex items-center gap-2 text-gray-6 truncate">
<span>💖 陈先生</span><span>💛 刘女士</span><span>💛 黄总</span>
</div>
<span class="text-warning font-medium flex-shrink-0">距升档 7.5h</span>
</div>
</div>
</div>
</a>
<a href="coach-detail.html" class="block bg-white rounded-2xl p-4 shadow-sm coach-card">
<div class="flex items-center gap-3">
<div class="w-11 h-11 rounded-full bg-gradient-to-br from-pink-400 to-rose-500 flex items-center justify-center flex-shrink-0">
<span class="text-white font-semibold text-base">A</span>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-1.5 flex-wrap">
<span class="text-base font-semibold text-gray-13">Amy</span>
<span class="px-1.5 py-0.5 bg-gradient-to-r from-amber-400 to-orange-400 text-white text-xs rounded flex-shrink-0">星级</span>
<span class="px-1.5 py-0.5 bg-primary/10 text-primary text-xs rounded flex-shrink-0">🎱</span>
<span class="px-1.5 py-0.5 bg-success/10 text-success text-xs rounded flex-shrink-0"></span>
<span class="ml-auto flex items-center gap-2 text-xs flex-shrink-0">
<span>定档 <b class="text-primary text-sm">68.0h</b></span>
<span class="text-gray-5">折前 <span class="text-gray-7">72.5h</span></span>
</span>
</div>
<div class="flex items-center justify-between mt-1.5 text-xs">
<div class="flex items-center gap-2 text-gray-6 truncate">
<span>💖 张先生</span><span>💛 周女士</span><span>💛 吴总</span>
</div>
<span class="text-warning font-medium flex-shrink-0">距升档 32.0h</span>
</div>
</div>
</div>
</a>
<a href="coach-detail.html" class="block bg-white rounded-2xl p-4 shadow-sm coach-card">
<div class="flex items-center gap-3">
<div class="w-11 h-11 rounded-full bg-gradient-to-br from-amber-400 to-orange-500 flex items-center justify-center flex-shrink-0">
<span class="text-white font-semibold text-base">M</span>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-1.5 flex-wrap">
<span class="text-base font-semibold text-gray-13">Mia</span>
<span class="px-1.5 py-0.5 bg-gradient-to-r from-blue-400 to-indigo-400 text-white text-xs rounded flex-shrink-0">中级</span>
<span class="px-1.5 py-0.5 bg-warning/10 text-warning text-xs rounded flex-shrink-0">🀄</span>
<span class="ml-auto flex items-center gap-2 text-xs flex-shrink-0">
<span>定档 <b class="text-primary text-sm">55.0h</b></span>
</span>
</div>
<div class="flex items-center justify-between mt-1.5 text-xs">
<div class="flex items-center gap-2 text-gray-6 truncate">
<span>💛 赵先生</span><span>💛 吴女士</span><span>💛 孙总</span>
</div>
<span class="text-warning font-medium flex-shrink-0">距升档 5.0h</span>
</div>
</div>
</div>
</a>
<a href="coach-detail.html" class="block bg-white rounded-2xl p-4 shadow-sm coach-card">
<div class="flex items-center gap-3">
<div class="w-11 h-11 rounded-full bg-gradient-to-br from-purple-400 to-violet-500 flex items-center justify-center flex-shrink-0">
<span class="text-white font-semibold text-base"></span>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-1.5 flex-wrap">
<span class="text-base font-semibold text-gray-13">糖糖</span>
<span class="px-1.5 py-0.5 bg-gradient-to-r from-gray-400 to-gray-500 text-white text-xs rounded flex-shrink-0">初级</span>
<span class="px-1.5 py-0.5 bg-primary/10 text-primary text-xs rounded flex-shrink-0">🎱</span>
<span class="ml-auto flex items-center gap-2 text-xs flex-shrink-0">
<span>定档 <b class="text-primary text-sm">42.0h</b></span>
<span class="text-gray-5">折前 <span class="text-gray-7">45.0h</span></span>
</span>
</div>
<div class="flex items-center justify-between mt-1.5 text-xs">
<div class="flex items-center gap-2 text-gray-6 truncate">
<span>💛 钱先生</span><span>💛 孙女士</span><span>💛 周总</span>
</div>
<span class="text-success font-medium flex-shrink-0">✅ 已达标</span>
</div>
</div>
</div>
</a>
<a href="coach-detail.html" class="block bg-white rounded-2xl p-4 shadow-sm coach-card">
<div class="flex items-center gap-3">
<div class="w-11 h-11 rounded-full bg-gradient-to-br from-cyan-400 to-teal-500 flex items-center justify-center flex-shrink-0">
<span class="text-white font-semibold text-base"></span>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-1.5 flex-wrap">
<span class="text-base font-semibold text-gray-13">露露</span>
<span class="px-1.5 py-0.5 bg-gradient-to-r from-blue-400 to-indigo-400 text-white text-xs rounded flex-shrink-0">中级</span>
<span class="px-1.5 py-0.5 bg-error/10 text-error text-xs rounded flex-shrink-0">🎤</span>
<span class="ml-auto flex items-center gap-2 text-xs flex-shrink-0">
<span>定档 <b class="text-primary text-sm">38.0h</b></span>
</span>
</div>
<div class="flex items-center justify-between mt-1.5 text-xs">
<div class="flex items-center gap-2 text-gray-6 truncate">
<span>💛 郑先生</span><span>💛 冯女士</span><span>💛 陈总</span>
</div>
<span class="text-warning font-medium flex-shrink-0">距升档 22.0h</span>
</div>
</div>
</div>
</a>
</div>
<!-- ====== 工资最高/最低 ====== -->
<div id="dim-salary" class="dim-container p-4 space-y-3">
<a href="coach-detail.html" class="block bg-white rounded-2xl p-4 shadow-sm coach-card">
<div class="flex items-center gap-3">
<div class="w-11 h-11 rounded-full bg-gradient-to-br from-blue-400 to-indigo-500 flex items-center justify-center flex-shrink-0">
<span class="text-white font-semibold text-base"></span>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-1.5 flex-wrap">
<span class="text-base font-semibold text-gray-13">小燕</span>
<span class="px-1.5 py-0.5 bg-gradient-to-r from-amber-400 to-orange-400 text-white text-xs rounded flex-shrink-0">星级</span>
<span class="px-1.5 py-0.5 bg-primary/10 text-primary text-xs rounded flex-shrink-0">🎱</span>
<span class="ml-auto flex items-center gap-1.5 flex-shrink-0">
<span class="px-1.5 py-0.5 bg-warning/10 text-warning text-xs rounded">预估</span>
<span class="text-lg font-bold text-gray-13">¥12,680</span>
</span>
</div>
<div class="flex items-center justify-between mt-1.5 text-xs">
<div class="flex items-center gap-2 text-gray-6 truncate">
<span>💖 王先生</span><span>💖 李女士</span><span>💛 赵总</span>
</div>
<div class="flex items-center gap-1.5 flex-shrink-0">
<span class="text-gray-10 font-semibold">定档 <b>86.2h</b></span>
<span class="text-gray-5">折前 <span class="text-gray-7">92.0h</span></span>
</div>
</div>
</div>
</div>
</a>
<a href="coach-detail.html" class="block bg-white rounded-2xl p-4 shadow-sm coach-card">
<div class="flex items-center gap-3">
<div class="w-11 h-11 rounded-full bg-gradient-to-br from-green-400 to-emerald-500 flex items-center justify-center flex-shrink-0">
<span class="text-white font-semibold text-base"></span>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-1.5 flex-wrap">
<span class="text-base font-semibold text-gray-13">泡芙</span>
<span class="px-1.5 py-0.5 bg-gradient-to-r from-purple-400 to-violet-400 text-white text-xs rounded flex-shrink-0">高级</span>
<span class="px-1.5 py-0.5 bg-success/10 text-success text-xs rounded flex-shrink-0"></span>
<span class="ml-auto flex items-center gap-1.5 flex-shrink-0">
<span class="px-1.5 py-0.5 bg-warning/10 text-warning text-xs rounded">预估</span>
<span class="text-lg font-bold text-gray-13">¥10,200</span>
</span>
</div>
<div class="flex items-center justify-between mt-1.5 text-xs">
<div class="flex items-center gap-2 text-gray-6 truncate">
<span>💖 陈先生</span><span>💛 刘女士</span><span>💛 黄总</span>
</div>
<div class="flex items-center gap-1.5 flex-shrink-0">
<span class="text-gray-10 font-semibold">定档 <b>72.5h</b></span>
<span class="text-gray-5">折前 <span class="text-gray-7">78.0h</span></span>
</div>
</div>
</div>
</div>
</a>
<a href="coach-detail.html" class="block bg-white rounded-2xl p-4 shadow-sm coach-card">
<div class="flex items-center gap-3">
<div class="w-11 h-11 rounded-full bg-gradient-to-br from-pink-400 to-rose-500 flex items-center justify-center flex-shrink-0">
<span class="text-white font-semibold text-base">A</span>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-1.5 flex-wrap">
<span class="text-base font-semibold text-gray-13">Amy</span>
<span class="px-1.5 py-0.5 bg-gradient-to-r from-amber-400 to-orange-400 text-white text-xs rounded flex-shrink-0">星级</span>
<span class="px-1.5 py-0.5 bg-primary/10 text-primary text-xs rounded flex-shrink-0">🎱</span>
<span class="px-1.5 py-0.5 bg-success/10 text-success text-xs rounded flex-shrink-0"></span>
<span class="ml-auto flex items-center gap-1.5 flex-shrink-0">
<span class="px-1.5 py-0.5 bg-warning/10 text-warning text-xs rounded">预估</span>
<span class="text-lg font-bold text-gray-13">¥9,800</span>
</span>
</div>
<div class="flex items-center justify-between mt-1.5 text-xs">
<div class="flex items-center gap-2 text-gray-6 truncate">
<span>💖 张先生</span><span>💛 周女士</span><span>💛 吴总</span>
</div>
<div class="flex items-center gap-1.5 flex-shrink-0">
<span class="text-gray-10 font-semibold">定档 <b>68.0h</b></span>
<span class="text-gray-5">折前 <span class="text-gray-7">72.5h</span></span>
</div>
</div>
</div>
</div>
</a>
<a href="coach-detail.html" class="block bg-white rounded-2xl p-4 shadow-sm coach-card">
<div class="flex items-center gap-3">
<div class="w-11 h-11 rounded-full bg-gradient-to-br from-amber-400 to-orange-500 flex items-center justify-center flex-shrink-0">
<span class="text-white font-semibold text-base">M</span>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-1.5 flex-wrap">
<span class="text-base font-semibold text-gray-13">Mia</span>
<span class="px-1.5 py-0.5 bg-gradient-to-r from-blue-400 to-indigo-400 text-white text-xs rounded flex-shrink-0">中级</span>
<span class="px-1.5 py-0.5 bg-warning/10 text-warning text-xs rounded flex-shrink-0">🀄</span>
<span class="ml-auto flex items-center gap-1.5 flex-shrink-0">
<span class="px-1.5 py-0.5 bg-warning/10 text-warning text-xs rounded">预估</span>
<span class="text-lg font-bold text-gray-13">¥7,500</span>
</span>
</div>
<div class="flex items-center justify-between mt-1.5 text-xs">
<div class="flex items-center gap-2 text-gray-6 truncate">
<span>💛 赵先生</span><span>💛 吴女士</span><span>💛 孙总</span>
</div>
<div class="flex items-center gap-1.5 flex-shrink-0">
<span class="text-gray-10 font-semibold">定档 <b>55.0h</b></span>
</div>
</div>
</div>
</div>
</a>
<a href="coach-detail.html" class="block bg-white rounded-2xl p-4 shadow-sm coach-card">
<div class="flex items-center gap-3">
<div class="w-11 h-11 rounded-full bg-gradient-to-br from-purple-400 to-violet-500 flex items-center justify-center flex-shrink-0">
<span class="text-white font-semibold text-base"></span>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-1.5 flex-wrap">
<span class="text-base font-semibold text-gray-13">糖糖</span>
<span class="px-1.5 py-0.5 bg-gradient-to-r from-gray-400 to-gray-500 text-white text-xs rounded flex-shrink-0">初级</span>
<span class="px-1.5 py-0.5 bg-primary/10 text-primary text-xs rounded flex-shrink-0">🎱</span>
<span class="ml-auto flex items-center gap-1.5 flex-shrink-0">
<span class="px-1.5 py-0.5 bg-warning/10 text-warning text-xs rounded">预估</span>
<span class="text-lg font-bold text-gray-13">¥6,200</span>
</span>
</div>
<div class="flex items-center justify-between mt-1.5 text-xs">
<div class="flex items-center gap-2 text-gray-6 truncate">
<span>💛 钱先生</span><span>💛 孙女士</span><span>💛 周总</span>
</div>
<div class="flex items-center gap-1.5 flex-shrink-0">
<span class="text-gray-10 font-semibold">定档 <b>42.0h</b></span>
<span class="text-gray-5">折前 <span class="text-gray-7">45.0h</span></span>
</div>
</div>
</div>
</div>
</a>
<a href="coach-detail.html" class="block bg-white rounded-2xl p-4 shadow-sm coach-card">
<div class="flex items-center gap-3">
<div class="w-11 h-11 rounded-full bg-gradient-to-br from-cyan-400 to-teal-500 flex items-center justify-center flex-shrink-0">
<span class="text-white font-semibold text-base"></span>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-1.5 flex-wrap">
<span class="text-base font-semibold text-gray-13">露露</span>
<span class="px-1.5 py-0.5 bg-gradient-to-r from-blue-400 to-indigo-400 text-white text-xs rounded flex-shrink-0">中级</span>
<span class="px-1.5 py-0.5 bg-error/10 text-error text-xs rounded flex-shrink-0">🎤</span>
<span class="ml-auto flex items-center gap-1.5 flex-shrink-0">
<span class="px-1.5 py-0.5 bg-warning/10 text-warning text-xs rounded">预估</span>
<span class="text-lg font-bold text-gray-13">¥5,100</span>
</span>
</div>
<div class="flex items-center justify-between mt-1.5 text-xs">
<div class="flex items-center gap-2 text-gray-6 truncate">
<span>💛 郑先生</span><span>💛 冯女士</span><span>💛 陈总</span>
</div>
<div class="flex items-center gap-1.5 flex-shrink-0">
<span class="text-gray-10 font-semibold">定档 <b>38.0h</b></span>
</div>
</div>
</div>
</div>
</a>
</div>
<!-- ====== 客源储值最高 ====== -->
<div id="dim-sv" class="dim-container p-4 space-y-3">
<a href="coach-detail.html" class="block bg-white rounded-2xl p-4 shadow-sm coach-card">
<div class="flex items-center gap-3">
<div class="w-11 h-11 rounded-full bg-gradient-to-br from-blue-400 to-indigo-500 flex items-center justify-center flex-shrink-0">
<span class="text-white font-semibold text-base"></span>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-1.5 flex-wrap">
<span class="text-base font-semibold text-gray-13">小燕</span>
<span class="px-1.5 py-0.5 bg-gradient-to-r from-amber-400 to-orange-400 text-white text-xs rounded flex-shrink-0">星级</span>
<span class="px-1.5 py-0.5 bg-primary/10 text-primary text-xs rounded flex-shrink-0">🎱</span>
<span class="ml-auto flex items-center gap-1.5 flex-shrink-0">
<span class="text-xs text-gray-6">储值</span>
<span class="text-lg font-bold text-gray-13">¥45,200</span>
</span>
</div>
<div class="flex items-center justify-between mt-1.5 text-xs">
<div class="flex items-center gap-2 text-gray-6 truncate">
<span>💖 王先生</span><span>💖 李女士</span>
</div>
<div class="flex items-center gap-1.5 flex-shrink-0">
<span class="text-gray-7">客户 <b class="text-gray-10">18</b></span>
<span class="text-gray-5">|</span>
<span class="text-gray-7">消耗 <b class="text-gray-10">¥8,600</b></span>
</div>
</div>
</div>
</div>
</a>
<a href="coach-detail.html" class="block bg-white rounded-2xl p-4 shadow-sm coach-card">
<div class="flex items-center gap-3">
<div class="w-11 h-11 rounded-full bg-gradient-to-br from-green-400 to-emerald-500 flex items-center justify-center flex-shrink-0">
<span class="text-white font-semibold text-base"></span>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-1.5 flex-wrap">
<span class="text-base font-semibold text-gray-13">泡芙</span>
<span class="px-1.5 py-0.5 bg-gradient-to-r from-purple-400 to-violet-400 text-white text-xs rounded flex-shrink-0">高级</span>
<span class="px-1.5 py-0.5 bg-success/10 text-success text-xs rounded flex-shrink-0"></span>
<span class="ml-auto flex items-center gap-1.5 flex-shrink-0">
<span class="text-xs text-gray-6">储值</span>
<span class="text-lg font-bold text-gray-13">¥38,600</span>
</span>
</div>
<div class="flex items-center justify-between mt-1.5 text-xs">
<div class="flex items-center gap-2 text-gray-6 truncate">
<span>💖 陈先生</span><span>💛 刘女士</span>
</div>
<div class="flex items-center gap-1.5 flex-shrink-0">
<span class="text-gray-7">客户 <b class="text-gray-10">15</b></span>
<span class="text-gray-5">|</span>
<span class="text-gray-7">消耗 <b class="text-gray-10">¥6,200</b></span>
</div>
</div>
</div>
</div>
</a>
<a href="coach-detail.html" class="block bg-white rounded-2xl p-4 shadow-sm coach-card">
<div class="flex items-center gap-3">
<div class="w-11 h-11 rounded-full bg-gradient-to-br from-pink-400 to-rose-500 flex items-center justify-center flex-shrink-0">
<span class="text-white font-semibold text-base">A</span>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-1.5 flex-wrap">
<span class="text-base font-semibold text-gray-13">Amy</span>
<span class="px-1.5 py-0.5 bg-gradient-to-r from-amber-400 to-orange-400 text-white text-xs rounded flex-shrink-0">星级</span>
<span class="px-1.5 py-0.5 bg-primary/10 text-primary text-xs rounded flex-shrink-0">🎱</span>
<span class="px-1.5 py-0.5 bg-success/10 text-success text-xs rounded flex-shrink-0"></span>
<span class="ml-auto flex items-center gap-1.5 flex-shrink-0">
<span class="text-xs text-gray-6">储值</span>
<span class="text-lg font-bold text-gray-13">¥32,100</span>
</span>
</div>
<div class="flex items-center justify-between mt-1.5 text-xs">
<div class="flex items-center gap-2 text-gray-6 truncate">
<span>💖 张先生</span><span>💛 周女士</span>
</div>
<div class="flex items-center gap-1.5 flex-shrink-0">
<span class="text-gray-7">客户 <b class="text-gray-10">14</b></span>
<span class="text-gray-5">|</span>
<span class="text-gray-7">消耗 <b class="text-gray-10">¥5,800</b></span>
</div>
</div>
</div>
</div>
</a>
<a href="coach-detail.html" class="block bg-white rounded-2xl p-4 shadow-sm coach-card">
<div class="flex items-center gap-3">
<div class="w-11 h-11 rounded-full bg-gradient-to-br from-amber-400 to-orange-500 flex items-center justify-center flex-shrink-0">
<span class="text-white font-semibold text-base">M</span>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-1.5 flex-wrap">
<span class="text-base font-semibold text-gray-13">Mia</span>
<span class="px-1.5 py-0.5 bg-gradient-to-r from-blue-400 to-indigo-400 text-white text-xs rounded flex-shrink-0">中级</span>
<span class="px-1.5 py-0.5 bg-warning/10 text-warning text-xs rounded flex-shrink-0">🀄</span>
<span class="ml-auto flex items-center gap-1.5 flex-shrink-0">
<span class="text-xs text-gray-6">储值</span>
<span class="text-lg font-bold text-gray-13">¥28,500</span>
</span>
</div>
<div class="flex items-center justify-between mt-1.5 text-xs">
<div class="flex items-center gap-2 text-gray-6 truncate">
<span>💛 赵先生</span><span>💛 吴女士</span>
</div>
<div class="flex items-center gap-1.5 flex-shrink-0">
<span class="text-gray-7">客户 <b class="text-gray-10">12</b></span>
<span class="text-gray-5">|</span>
<span class="text-gray-7">消耗 <b class="text-gray-10">¥4,100</b></span>
</div>
</div>
</div>
</div>
</a>
<a href="coach-detail.html" class="block bg-white rounded-2xl p-4 shadow-sm coach-card">
<div class="flex items-center gap-3">
<div class="w-11 h-11 rounded-full bg-gradient-to-br from-purple-400 to-violet-500 flex items-center justify-center flex-shrink-0">
<span class="text-white font-semibold text-base"></span>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-1.5 flex-wrap">
<span class="text-base font-semibold text-gray-13">糖糖</span>
<span class="px-1.5 py-0.5 bg-gradient-to-r from-gray-400 to-gray-500 text-white text-xs rounded flex-shrink-0">初级</span>
<span class="px-1.5 py-0.5 bg-primary/10 text-primary text-xs rounded flex-shrink-0">🎱</span>
<span class="ml-auto flex items-center gap-1.5 flex-shrink-0">
<span class="text-xs text-gray-6">储值</span>
<span class="text-lg font-bold text-gray-13">¥22,000</span>
</span>
</div>
<div class="flex items-center justify-between mt-1.5 text-xs">
<div class="flex items-center gap-2 text-gray-6 truncate">
<span>💛 钱先生</span><span>💛 孙女士</span>
</div>
<div class="flex items-center gap-1.5 flex-shrink-0">
<span class="text-gray-7">客户 <b class="text-gray-10">10</b></span>
<span class="text-gray-5">|</span>
<span class="text-gray-7">消耗 <b class="text-gray-10">¥3,500</b></span>
</div>
</div>
</div>
</div>
</a>
<a href="coach-detail.html" class="block bg-white rounded-2xl p-4 shadow-sm coach-card">
<div class="flex items-center gap-3">
<div class="w-11 h-11 rounded-full bg-gradient-to-br from-cyan-400 to-teal-500 flex items-center justify-center flex-shrink-0">
<span class="text-white font-semibold text-base"></span>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-1.5 flex-wrap">
<span class="text-base font-semibold text-gray-13">露露</span>
<span class="px-1.5 py-0.5 bg-gradient-to-r from-blue-400 to-indigo-400 text-white text-xs rounded flex-shrink-0">中级</span>
<span class="px-1.5 py-0.5 bg-error/10 text-error text-xs rounded flex-shrink-0">🎤</span>
<span class="ml-auto flex items-center gap-1.5 flex-shrink-0">
<span class="text-xs text-gray-6">储值</span>
<span class="text-lg font-bold text-gray-13">¥18,300</span>
</span>
</div>
<div class="flex items-center justify-between mt-1.5 text-xs">
<div class="flex items-center gap-2 text-gray-6 truncate">
<span>💛 郑先生</span><span>💛 冯女士</span>
</div>
<div class="flex items-center gap-1.5 flex-shrink-0">
<span class="text-gray-7">客户 <b class="text-gray-10">9</b></span>
<span class="text-gray-5">|</span>
<span class="text-gray-7">消耗 <b class="text-gray-10">¥2,800</b></span>
</div>
</div>
</div>
</div>
</a>
</div>
<!-- ====== 任务完成最多 ====== -->
<div id="dim-task" class="dim-container p-4 space-y-3">
<a href="coach-detail.html" class="block bg-white rounded-2xl p-4 shadow-sm coach-card">
<div class="flex items-center gap-3">
<div class="w-11 h-11 rounded-full bg-gradient-to-br from-blue-400 to-indigo-500 flex items-center justify-center flex-shrink-0">
<span class="text-white font-semibold text-base"></span>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-1.5 flex-wrap">
<span class="text-base font-semibold text-gray-13">小燕</span>
<span class="px-1.5 py-0.5 bg-gradient-to-r from-amber-400 to-orange-400 text-white text-xs rounded flex-shrink-0">星级</span>
<span class="px-1.5 py-0.5 bg-primary/10 text-primary text-xs rounded flex-shrink-0">🎱</span>
<span class="ml-auto text-sm text-gray-7 flex-shrink-0">召回 <b class="text-primary text-base">18</b></span>
</div>
<div class="flex items-center justify-between mt-1.5 text-xs">
<div class="flex items-center gap-2 text-gray-6 truncate">
<span>💖 王先生</span><span>💖 李女士</span><span>💛 赵总</span>
</div>
<span class="flex-shrink-0 text-gray-7">回访 <b class="text-gray-10">14</b></span>
</div>
</div>
</div>
</a>
<a href="coach-detail.html" class="block bg-white rounded-2xl p-4 shadow-sm coach-card">
<div class="flex items-center gap-3">
<div class="w-11 h-11 rounded-full bg-gradient-to-br from-green-400 to-emerald-500 flex items-center justify-center flex-shrink-0">
<span class="text-white font-semibold text-base"></span>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-1.5 flex-wrap">
<span class="text-base font-semibold text-gray-13">泡芙</span>
<span class="px-1.5 py-0.5 bg-gradient-to-r from-purple-400 to-violet-400 text-white text-xs rounded flex-shrink-0">高级</span>
<span class="px-1.5 py-0.5 bg-success/10 text-success text-xs rounded flex-shrink-0"></span>
<span class="ml-auto text-sm text-gray-7 flex-shrink-0">召回 <b class="text-primary text-base">15</b></span>
</div>
<div class="flex items-center justify-between mt-1.5 text-xs">
<div class="flex items-center gap-2 text-gray-6 truncate">
<span>💖 陈先生</span><span>💛 刘女士</span><span>💛 黄总</span>
</div>
<span class="flex-shrink-0 text-gray-7">回访 <b class="text-gray-10">13</b></span>
</div>
</div>
</div>
</a>
<a href="coach-detail.html" class="block bg-white rounded-2xl p-4 shadow-sm coach-card">
<div class="flex items-center gap-3">
<div class="w-11 h-11 rounded-full bg-gradient-to-br from-pink-400 to-rose-500 flex items-center justify-center flex-shrink-0">
<span class="text-white font-semibold text-base">A</span>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-1.5 flex-wrap">
<span class="text-base font-semibold text-gray-13">Amy</span>
<span class="px-1.5 py-0.5 bg-gradient-to-r from-amber-400 to-orange-400 text-white text-xs rounded flex-shrink-0">星级</span>
<span class="px-1.5 py-0.5 bg-primary/10 text-primary text-xs rounded flex-shrink-0">🎱</span>
<span class="px-1.5 py-0.5 bg-success/10 text-success text-xs rounded flex-shrink-0"></span>
<span class="ml-auto text-sm text-gray-7 flex-shrink-0">召回 <b class="text-primary text-base">12</b></span>
</div>
<div class="flex items-center justify-between mt-1.5 text-xs">
<div class="flex items-center gap-2 text-gray-6 truncate">
<span>💖 张先生</span><span>💛 周女士</span><span>💛 吴总</span>
</div>
<span class="flex-shrink-0 text-gray-7">回访 <b class="text-gray-10">13</b></span>
</div>
</div>
</div>
</a>
<a href="coach-detail.html" class="block bg-white rounded-2xl p-4 shadow-sm coach-card">
<div class="flex items-center gap-3">
<div class="w-11 h-11 rounded-full bg-gradient-to-br from-amber-400 to-orange-500 flex items-center justify-center flex-shrink-0">
<span class="text-white font-semibold text-base">M</span>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-1.5 flex-wrap">
<span class="text-base font-semibold text-gray-13">Mia</span>
<span class="px-1.5 py-0.5 bg-gradient-to-r from-blue-400 to-indigo-400 text-white text-xs rounded flex-shrink-0">中级</span>
<span class="px-1.5 py-0.5 bg-warning/10 text-warning text-xs rounded flex-shrink-0">🀄</span>
<span class="ml-auto text-sm text-gray-7 flex-shrink-0">召回 <b class="text-primary text-base">10</b></span>
</div>
<div class="flex items-center justify-between mt-1.5 text-xs">
<div class="flex items-center gap-2 text-gray-6 truncate">
<span>💛 赵先生</span><span>💛 吴女士</span><span>💛 孙总</span>
</div>
<span class="flex-shrink-0 text-gray-7">回访 <b class="text-gray-10">10</b></span>
</div>
</div>
</div>
</a>
<a href="coach-detail.html" class="block bg-white rounded-2xl p-4 shadow-sm coach-card">
<div class="flex items-center gap-3">
<div class="w-11 h-11 rounded-full bg-gradient-to-br from-purple-400 to-violet-500 flex items-center justify-center flex-shrink-0">
<span class="text-white font-semibold text-base"></span>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-1.5 flex-wrap">
<span class="text-base font-semibold text-gray-13">糖糖</span>
<span class="px-1.5 py-0.5 bg-gradient-to-r from-gray-400 to-gray-500 text-white text-xs rounded flex-shrink-0">初级</span>
<span class="px-1.5 py-0.5 bg-primary/10 text-primary text-xs rounded flex-shrink-0">🎱</span>
<span class="ml-auto text-sm text-gray-7 flex-shrink-0">召回 <b class="text-primary text-base">8</b></span>
</div>
<div class="flex items-center justify-between mt-1.5 text-xs">
<div class="flex items-center gap-2 text-gray-6 truncate">
<span>💛 钱先生</span><span>💛 孙女士</span><span>💛 周总</span>
</div>
<span class="flex-shrink-0 text-gray-7">回访 <b class="text-gray-10">10</b></span>
</div>
</div>
</div>
</a>
<a href="coach-detail.html" class="block bg-white rounded-2xl p-4 shadow-sm coach-card">
<div class="flex items-center gap-3">
<div class="w-11 h-11 rounded-full bg-gradient-to-br from-cyan-400 to-teal-500 flex items-center justify-center flex-shrink-0">
<span class="text-white font-semibold text-base"></span>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-1.5 flex-wrap">
<span class="text-base font-semibold text-gray-13">露露</span>
<span class="px-1.5 py-0.5 bg-gradient-to-r from-blue-400 to-indigo-400 text-white text-xs rounded flex-shrink-0">中级</span>
<span class="px-1.5 py-0.5 bg-error/10 text-error text-xs rounded flex-shrink-0">🎤</span>
<span class="ml-auto text-sm text-gray-7 flex-shrink-0">召回 <b class="text-primary text-base">6</b></span>
</div>
<div class="flex items-center justify-between mt-1.5 text-xs">
<div class="flex items-center gap-2 text-gray-6 truncate">
<span>💛 郑先生</span><span>💛 冯女士</span><span>💛 陈总</span>
</div>
<span class="flex-shrink-0 text-gray-7">回访 <b class="text-gray-10">9</b></span>
</div>
</div>
</div>
</a>
</div>
<!-- 悬浮助手按钮 -->
<!-- 通用底部导航 -->
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,167 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>助手对话记录 - 球房运营助手</title>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Noto Sans SC', -apple-system, BlinkMacSystemFont, sans-serif;
}
.safe-area-top {
padding-top: env(safe-area-inset-top, 44px);
}
.chat-item {
transition: all 0.2s ease;
}
.chat-item:active {
background: #f3f3f3;
}
</style>
</head>
<body class="bg-gray-1 min-h-screen">
<!-- 顶部导航 -->
<div class="safe-area-top bg-white sticky top-0 z-10">
<div class="h-11 flex items-center relative border-b border-gray-2 px-4">
<button onclick="history.back()" class="absolute left-4 p-1">
<svg class="w-5 h-5 text-gray-10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="15 18 9 12 15 6"/>
</svg>
</button>
<h1 class="flex-1 text-center text-base font-medium text-gray-13">助手对话记录</h1>
</div>
</div>
<!-- 对话列表 -->
<div class="divide-y divide-gray-100">
<!-- 对话记录 1 -->
<a href="chat.html" class="block bg-white px-4 py-4 chat-item">
<div class="flex items-start gap-3">
<div class="w-11 h-11 bg-gradient-to-br from-primary to-blue-400 rounded-xl flex items-center justify-center flex-shrink-0 shadow-sm">
<svg class="w-5 h-5 text-white" viewBox="0 0 24 24" fill="currentColor">
<path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/>
</svg>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between mb-1">
<h3 class="text-sm font-medium text-gray-13 truncate">如何提升王先生的到店频率?</h3>
<span class="text-xs text-gray-6 flex-shrink-0 ml-2">10分钟前</span>
</div>
<p class="text-xs text-gray-6">共 8 条消息</p>
</div>
</div>
</a>
<!-- 对话记录 2 -->
<a href="chat.html" class="block bg-white px-4 py-4 chat-item">
<div class="flex items-start gap-3">
<div class="w-11 h-11 bg-gradient-to-br from-success to-green-400 rounded-xl flex items-center justify-center flex-shrink-0 shadow-sm">
<svg class="w-5 h-5 text-white" viewBox="0 0 24 24" fill="currentColor">
<path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/>
</svg>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between mb-1">
<h3 class="text-sm font-medium text-gray-13 truncate">帮我分析本月财务数据</h3>
<span class="text-xs text-gray-6 flex-shrink-0 ml-2">2小时前</span>
</div>
<p class="text-xs text-gray-6">共 15 条消息</p>
</div>
</div>
</a>
<!-- 对话记录 3 -->
<a href="chat.html" class="block bg-white px-4 py-4 chat-item">
<div class="flex items-start gap-3">
<div class="w-11 h-11 bg-gradient-to-br from-warning to-orange-400 rounded-xl flex items-center justify-center flex-shrink-0 shadow-sm">
<svg class="w-5 h-5 text-white" viewBox="0 0 24 24" fill="currentColor">
<path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/>
</svg>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between mb-1">
<h3 class="text-sm font-medium text-gray-13 truncate">李女士的消费习惯分析</h3>
<span class="text-xs text-gray-6 flex-shrink-0 ml-2">昨天</span>
</div>
<p class="text-xs text-gray-6">共 12 条消息</p>
</div>
</div>
</a>
<!-- 对话记录 4 -->
<a href="chat.html" class="block bg-white px-4 py-4 chat-item">
<div class="flex items-start gap-3">
<div class="w-11 h-11 bg-gradient-to-br from-primary to-blue-400 rounded-xl flex items-center justify-center flex-shrink-0 shadow-sm">
<svg class="w-5 h-5 text-white" viewBox="0 0 24 24" fill="currentColor">
<path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/>
</svg>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between mb-1">
<h3 class="text-sm font-medium text-gray-13 truncate">如何提高客户满意度?</h3>
<span class="text-xs text-gray-6 flex-shrink-0 ml-2">昨天</span>
</div>
<p class="text-xs text-gray-6">共 20 条消息</p>
</div>
</div>
</a>
<!-- 对话记录 5 -->
<a href="chat.html" class="block bg-white px-4 py-4 chat-item">
<div class="flex items-start gap-3">
<div class="w-11 h-11 bg-gradient-to-br from-error to-red-400 rounded-xl flex items-center justify-center flex-shrink-0 shadow-sm">
<svg class="w-5 h-5 text-white" viewBox="0 0 24 24" fill="currentColor">
<path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/>
</svg>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between mb-1">
<h3 class="text-sm font-medium text-gray-13 truncate">小燕本月业绩如何?</h3>
<span class="text-xs text-gray-6 flex-shrink-0 ml-2">2天前</span>
</div>
<p class="text-xs text-gray-6">共 6 条消息</p>
</div>
</div>
</a>
<!-- 对话记录 6 -->
<a href="chat.html" class="block bg-white px-4 py-4 chat-item">
<div class="flex items-start gap-3">
<div class="w-11 h-11 bg-gradient-to-br from-success to-green-400 rounded-xl flex items-center justify-center flex-shrink-0 shadow-sm">
<svg class="w-5 h-5 text-white" viewBox="0 0 24 24" fill="currentColor">
<path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/>
</svg>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between mb-1">
<h3 class="text-sm font-medium text-gray-13 truncate">推荐一些促销活动方案</h3>
<span class="text-xs text-gray-6 flex-shrink-0 ml-2">3天前</span>
</div>
<p class="text-xs text-gray-6">共 25 条消息</p>
</div>
</div>
</a>
<!-- 对话记录 7 -->
<a href="chat.html" class="block bg-white px-4 py-4 chat-item">
<div class="flex items-start gap-3">
<div class="w-11 h-11 bg-gradient-to-br from-warning to-orange-400 rounded-xl flex items-center justify-center flex-shrink-0 shadow-sm">
<svg class="w-5 h-5 text-white" viewBox="0 0 24 24" fill="currentColor">
<path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/>
</svg>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between mb-1">
<h3 class="text-sm font-medium text-gray-13 truncate">分析高流失客户群体</h3>
<span class="text-xs text-gray-6 flex-shrink-0 ml-2">1周前</span>
</div>
<p class="text-xs text-gray-6">共 18 条消息</p>
</div>
</div>
</a>
</div>
</body>
</html>

View File

@@ -0,0 +1,155 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>助手对话 - 球房运营助手</title>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Noto Sans SC', -apple-system, BlinkMacSystemFont, sans-serif;
}
.safe-area-top {
padding-top: env(safe-area-inset-top, 44px);
}
.chat-container {
height: calc(100vh - 44px - 70px - env(safe-area-inset-top, 44px));
overflow-y: auto;
}
.message-bubble {
max-width: 80%;
}
.voice-btn:active {
background: #0052d9;
color: white;
}
</style>
</head>
<body class="bg-gray-1 min-h-screen flex flex-col">
<!-- 顶部导航 -->
<div class="safe-area-top bg-white sticky top-0 z-10">
<div class="h-11 flex items-center relative border-b border-gray-2 px-4">
<button onclick="history.back()" class="absolute left-4 p-1">
<svg class="w-5 h-5 text-gray-10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="15 18 9 12 15 6"/>
</svg>
</button>
<h1 class="flex-1 text-center text-base font-medium text-gray-13">智能助手</h1>
</div>
</div>
<!-- 对话内容区域 -->
<div class="chat-container flex-1 p-4 space-y-4">
<!-- 引用内容卡片 -->
<div class="bg-gray-2 rounded-lg p-3 text-sm">
<div class="flex items-center gap-2 text-gray-7 mb-1">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor">
<path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/>
</svg>
<span>来源:任务 - 高优先召回</span>
</div>
<p class="text-gray-9">王先生 · 最近到店15天前 · VIP客户</p>
</div>
<!-- 用户消息 -->
<div class="flex justify-end">
<div class="message-bubble bg-primary text-white rounded-2xl rounded-tr-sm px-4 py-3">
<p class="text-sm">如何提升这位客户的到店频率?</p>
</div>
</div>
<!-- 助手消息 -->
<div class="flex justify-start gap-2">
<div class="w-8 h-8 bg-gradient-to-br from-primary to-blue-400 rounded-full flex items-center justify-center flex-shrink-0">
<svg class="w-4 h-4 text-white" viewBox="0 0 24 24" fill="currentColor">
<path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z"/>
</svg>
</div>
<div class="message-bubble bg-white rounded-2xl rounded-tl-sm px-4 py-3 shadow-sm">
<p class="text-sm text-gray-13 leading-relaxed">
根据王先生的消费数据分析,我有以下建议:
</p>
<ol class="text-sm text-gray-13 leading-relaxed mt-2 list-decimal list-inside space-y-2">
<li><strong>个性化邀约</strong>王先生偏好晚间21:00后到店建议在这个时间段前发送邀约消息。</li>
<li><strong>活动推荐</strong>:他对斯诺克感兴趣,可以推荐即将举办的斯诺克比赛活动。</li>
<li><strong>储值提醒</strong>他的储值余额还有8600元可以提醒他来消费避免余额长期闲置。</li>
<li><strong>专属服务</strong>:安排他熟悉的助教张三接待,提升服务体验。</li>
</ol>
</div>
</div>
<!-- 用户消息 -->
<div class="flex justify-end">
<div class="message-bubble bg-primary text-white rounded-2xl rounded-tr-sm px-4 py-3">
<p class="text-sm">能帮我生成一条邀约话术吗?</p>
</div>
</div>
<!-- 助手消息 -->
<div class="flex justify-start gap-2">
<div class="w-8 h-8 bg-gradient-to-br from-primary to-blue-400 rounded-full flex items-center justify-center flex-shrink-0">
<svg class="w-4 h-4 text-white" viewBox="0 0 24 24" fill="currentColor">
<path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z"/>
</svg>
</div>
<div class="message-bubble bg-white rounded-2xl rounded-tl-sm px-4 py-3 shadow-sm">
<p class="text-sm text-gray-13 leading-relaxed">
好的,根据王先生的特点,为您生成以下邀约话术:
</p>
<div class="mt-3 p-3 bg-primary-light rounded-lg">
<p class="text-sm text-primary leading-relaxed">
"王哥您好!好久不见了,最近工作顺利吧?😊 我们店里最近新到了几张英国进口的斯诺克球桌,球感特别好,知道您是斯诺克爱好者,第一时间想到邀请您来体验一下。这周末晚上有空吗?我提前给您预留好包厢~"
</p>
</div>
<p class="text-xs text-gray-7 mt-2">💡 提示建议在晚间8点左右发送这是王先生活跃的时间段。</p>
</div>
</div>
<!-- 用户消息 -->
<div class="flex justify-end">
<div class="message-bubble bg-primary text-white rounded-2xl rounded-tr-sm px-4 py-3">
<p class="text-sm">太棒了,谢谢!</p>
</div>
</div>
<!-- 助手消息 -->
<div class="flex justify-start gap-2">
<div class="w-8 h-8 bg-gradient-to-br from-primary to-blue-400 rounded-full flex items-center justify-center flex-shrink-0">
<svg class="w-4 h-4 text-white" viewBox="0 0 24 24" fill="currentColor">
<path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z"/>
</svg>
</div>
<div class="message-bubble bg-white rounded-2xl rounded-tl-sm px-4 py-3 shadow-sm">
<p class="text-sm text-gray-13 leading-relaxed">
不客气!祝您沟通顺利!如果需要更多帮助,随时问我。😊
</p>
</div>
</div>
</div>
<!-- 输入区域 -->
<div class="bg-white border-t border-gray-2 p-3 pb-6">
<div class="flex items-end gap-2">
<div class="flex-1 bg-gray-1 rounded-2xl px-4 py-2.5 flex items-center">
<input type="text" placeholder="输入消息..." class="flex-1 bg-transparent text-sm text-gray-13 placeholder-gray-6 outline-none">
</div>
<button class="voice-btn w-10 h-10 bg-gray-1 rounded-full flex items-center justify-center text-gray-7 transition-colors">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 1a3 3 0 00-3 3v8a3 3 0 006 0V4a3 3 0 00-3-3z"/>
<path d="M19 10v2a7 7 0 01-14 0v-2"/>
<line x1="12" y1="19" x2="12" y2="23"/>
<line x1="8" y1="23" x2="16" y2="23"/>
</svg>
</button>
<button class="w-10 h-10 bg-primary rounded-full flex items-center justify-center text-white">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="currentColor">
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/>
</svg>
</button>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,487 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>助教详情 - 球房运营助手</title>
<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/coach-detail.css" rel="stylesheet">
</head>
<body class="bg-gray-1 min-h-screen">
<!-- Banner -->
<div class="banner-bg theme-coral texture-aurora relative text-white">
<div class="h-11 flex items-center relative px-4">
<button onclick="history.back()" class="absolute left-4 p-1">
<svg class="w-5 h-5 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="15 18 9 12 15 6"/></svg>
</button>
<h1 class="flex-1 text-center text-base font-medium">助教详情</h1>
</div>
<div class="px-5 pt-2 pb-5">
<div class="flex items-center gap-4">
<div class="w-14 h-14 rounded-2xl bg-white/20 backdrop-blur-sm flex items-center justify-center shadow-lg overflow-hidden flex-shrink-0">
<img src="../img/zjtx.png" class="w-full h-full object-cover" alt="助教头像">
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-2 mb-1">
<span class="text-lg font-semibold">小燕</span>
<span class="px-2 py-0.5 bg-amber-400/30 text-amber-100 rounded-full text-xs">星级</span>
</div>
<div class="flex items-center gap-2 text-white/70 text-xs">
<span class="px-2 py-0.5 bg-white/20 rounded">中🎱</span>
<span class="px-2 py-0.5 bg-white/20 rounded">🎯 斯诺克</span>
</div>
</div>
<div class="flex-shrink-0 text-right space-y-1.5">
<div class="text-white/70 text-xs">工龄 <span class="text-white font-bold text-base">3年</span></div>
<div class="text-white/70 text-xs">客户 <span class="text-white font-bold text-base">68人</span></div>
</div>
</div>
</div>
</div>
<!-- 主体内容 -->
<div class="p-4 space-y-4">
<!-- 绩效概览 -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<h2 class="st blue text-base font-semibold text-gray-13 mb-4">绩效概览</h2>
<div class="grid grid-cols-2 gap-3 mb-4">
<div class="bg-gradient-to-br from-blue-50 to-indigo-50 rounded-xl p-3 border border-blue-100/50">
<p class="text-xs text-gray-6 mb-1">本月定档业绩</p>
<p class="text-2xl font-bold text-primary pv">87.5<span class="text-xs font-normal text-gray-6">h</span></p>
<p class="text-xs text-gray-5 mt-0.5">折算前 89.0h</p>
</div>
<div class="bg-gradient-to-br from-green-50 to-emerald-50 rounded-xl p-3 border border-green-100/50">
<p class="text-xs text-gray-6 mb-1">本月工资(预估)</p>
<p class="text-2xl font-bold text-success pv">¥6,950</p>
<p class="text-xs text-warning mt-0.5">含预估部分</p>
</div>
<div class="bg-gradient-to-br from-orange-50 to-amber-50 rounded-xl p-3 border border-orange-100/50">
<p class="text-xs text-gray-6 mb-1">客源储值余额</p>
<p class="text-2xl font-bold text-warning pv">¥86,200</p>
<p class="text-xs text-gray-5 mt-0.5">68位客户合计</p>
</div>
<div class="bg-gradient-to-br from-purple-50 to-violet-50 rounded-xl p-3 border border-purple-100/50">
<p class="text-xs text-gray-6 mb-1">本月任务完成</p>
<p class="text-2xl font-bold text-purple-600 pv">38<span class="text-xs font-normal text-gray-6"></span></p>
<p class="text-xs text-gray-5 mt-0.5">覆盖 22 位客户</p>
</div>
</div>
<div class="bg-gray-50 rounded-xl p-3">
<div class="flex items-center justify-between mb-2">
<span class="text-xs text-gray-9 font-medium">绩效档位进度</span>
<span class="text-xs text-primary font-medium">距下一档还差 12.5h</span>
</div>
<div class="progress-sm">
<div class="fill bg-gradient-to-r from-primary to-blue-400" style="width:72%"></div>
</div>
<div class="flex justify-between mt-1.5 text-[10px] text-gray-5">
<span>当前 80h</span>
<span>目标 100h</span>
</div>
</div>
</div>
<!-- 收入明细 -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<div class="flex items-center justify-between mb-4">
<h2 class="st green text-base font-semibold text-gray-13">收入明细</h2>
<div class="flex items-center gap-1">
<span id="incomeTab_this" class="income-tab active" onclick="switchIncomeTab('this')">本月<span class="text-[10px] text-warning ml-0.5">预估</span></span>
<span id="incomeTab_last" class="income-tab" onclick="switchIncomeTab('last')">上月</span>
</div>
</div>
<!-- 本月 -->
<div id="incomeThisMonth" class="space-y-3">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2"><span class="w-2 h-2 rounded-full bg-primary"></span><span class="text-base text-gray-9">基础课时费</span></div>
<span class="text-base font-bold text-gray-13 pv">¥3,500</span>
</div>
<div class="flex items-center justify-between">
<div class="flex items-center gap-2"><span class="w-2 h-2 rounded-full bg-success"></span><span class="text-base text-gray-9">激励课时费</span></div>
<span class="text-base font-bold text-gray-13 pv">¥1,800</span>
</div>
<div class="flex items-center justify-between">
<div class="flex items-center gap-2"><span class="w-2 h-2 rounded-full bg-warning"></span><span class="text-base text-gray-9">充值提成</span></div>
<span class="text-base font-bold text-gray-13 pv">¥1,200</span>
</div>
<div class="flex items-center justify-between">
<div class="flex items-center gap-2"><span class="w-2 h-2 rounded-full bg-purple-500"></span><span class="text-base text-gray-9">酒水提成</span></div>
<span class="text-base font-bold text-gray-13 pv">¥450</span>
</div>
<div class="border-t border-gray-100 pt-2 flex items-center justify-between">
<span class="text-base font-semibold text-gray-9">合计(预估)</span>
<span class="text-base font-bold text-success pv">¥6,950</span>
</div>
</div>
<!-- 上月 -->
<div id="incomeLastMonth" class="space-y-3 hidden">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2"><span class="w-2 h-2 rounded-full bg-primary"></span><span class="text-base text-gray-9">基础课时费</span></div>
<span class="text-base font-bold text-gray-13 pv">¥3,800</span>
</div>
<div class="flex items-center justify-between">
<div class="flex items-center gap-2"><span class="w-2 h-2 rounded-full bg-success"></span><span class="text-base text-gray-9">激励课时费</span></div>
<span class="text-base font-bold text-gray-13 pv">¥1,900</span>
</div>
<div class="flex items-center justify-between">
<div class="flex items-center gap-2"><span class="w-2 h-2 rounded-full bg-warning"></span><span class="text-base text-gray-9">充值提成</span></div>
<span class="text-base font-bold text-gray-13 pv">¥1,100</span>
</div>
<div class="flex items-center justify-between">
<div class="flex items-center gap-2"><span class="w-2 h-2 rounded-full bg-purple-500"></span><span class="text-base text-gray-9">酒水提成</span></div>
<span class="text-base font-bold text-gray-13 pv">¥400</span>
</div>
<div class="border-t border-gray-100 pt-2 flex items-center justify-between">
<span class="text-base font-semibold text-gray-9">合计</span>
<span class="text-base font-bold text-success pv">¥7,200</span>
</div>
</div>
</div>
<!-- 任务执行 -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<div class="flex items-center justify-between mb-4">
<h2 class="st orange text-base font-semibold text-gray-13">任务执行</h2>
<div class="flex items-center gap-3 text-sm text-gray-7">
<span class="font-bold text-gray-10">完成</span>
<span class="text-primary font-bold">召回<span class="text-base">24</span></span>
<span class="text-success font-bold">回访<span class="text-base">14</span></span>
</div>
</div>
<!-- 前6项任务 -->
<div class="space-y-2">
<div class="flex items-center gap-2.5 p-2.5 bg-red-50/60 rounded-lg border border-red-100/60">
<span class="task-tag-text high-priority flex-shrink-0">高优先召回</span>
<span class="text-sm text-gray-13 flex-1 min-w-0 truncate">王先生</span>
<button onclick="showNotesPopup('王先生', [{pinned:true,text:'重点客户,每周必须联系',date:'2026-02-06'},{text:'上次来说最近出差多',date:'2026-02-01'}])" class="flex items-center gap-0.5 text-gray-8 flex-shrink-0"><svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/></svg><span class="text-xs font-medium">2</span></button>
<span class="text-sm flex-shrink-0">📌</span>
</div>
<div class="flex items-center gap-2.5 p-2.5 bg-red-50/60 rounded-lg border border-red-100/60">
<span class="task-tag-text high-priority flex-shrink-0">高优先召回</span>
<span class="text-sm text-gray-13 flex-1 min-w-0 truncate">李女士</span>
<span class="text-sm flex-shrink-0">📌</span>
</div>
<div class="flex items-center gap-2.5 p-2.5 bg-red-50/60 rounded-lg border border-red-100/60">
<span class="task-tag-text high-priority flex-shrink-0">高优先召回</span>
<span class="text-sm text-gray-13 flex-1 min-w-0 truncate">陈女士</span>
<button onclick="showNotesPopup('陈女士', [{text:'喜欢斯诺克,周末常来',date:'2026-01-28'}])" class="flex items-center gap-0.5 text-gray-8 flex-shrink-0"><svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/></svg><span class="text-xs font-medium">1</span></button>
<span class="text-sm flex-shrink-0">📌</span>
</div>
<div class="flex items-center gap-2.5 p-2.5 bg-orange-50/40 rounded-lg border border-orange-100/40">
<span class="task-tag-text priority flex-shrink-0">优先召回</span>
<span class="text-sm text-gray-13 flex-1 min-w-0 truncate">张先生</span>
</div>
<div class="flex items-center gap-2.5 p-2.5 bg-pink-50/40 rounded-lg border border-pink-100/40">
<span class="task-tag-text relationship flex-shrink-0">关系构建</span>
<span class="text-sm text-gray-13 flex-1 min-w-0 truncate">赵总</span>
<button onclick="showNotesPopup('赵总', [{pinned:true,text:'大客户,注意维护关系',date:'2026-02-03'},{text:'上次带了3个朋友来',date:'2026-01-25'},{text:'喜欢VIP包厢',date:'2026-01-15'}])" class="flex items-center gap-0.5 text-gray-8 flex-shrink-0"><svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/></svg><span class="text-xs font-medium">3</span></button>
</div>
<div class="flex items-center gap-2.5 p-2.5 bg-teal-50/40 rounded-lg border border-teal-100/40">
<span class="task-tag-text callback flex-shrink-0">客户回访</span>
<span class="text-sm text-gray-13 flex-1 min-w-0 truncate">周女士</span>
</div>
</div>
<!-- 隐藏的更多任务 -->
<div id="hiddenTasks" class="space-y-2 mt-2 hidden">
<div class="flex items-center gap-2.5 p-2.5 bg-orange-50/40 rounded-lg border border-orange-100/40">
<span class="task-tag-text priority flex-shrink-0">优先召回</span>
<span class="text-sm text-gray-13 flex-1 min-w-0 truncate">刘先生</span>
</div>
<div class="flex items-center gap-2.5 p-2.5 bg-teal-50/40 rounded-lg border border-teal-100/40">
<span class="task-tag-text callback flex-shrink-0">客户回访</span>
<span class="text-sm text-gray-13 flex-1 min-w-0 truncate">孙先生</span>
</div>
<div class="flex items-center gap-2.5 p-2.5 bg-pink-50/40 rounded-lg border border-pink-100/40">
<span class="task-tag-text relationship flex-shrink-0">关系构建</span>
<span class="text-sm text-gray-13 flex-1 min-w-0 truncate">吴女士</span>
</div>
<!-- 已放弃 -->
<div class="flex items-center gap-2.5 p-2.5 bg-gray-50 rounded-lg border border-gray-200 opacity-55">
<span class="text-sm text-gray-5 line-through flex-1 min-w-0 truncate">吴先生</span>
<span class="text-[10px] text-gray-5">客户拒绝</span>
</div>
<div class="flex items-center gap-2.5 p-2.5 bg-gray-50 rounded-lg border border-gray-200 opacity-55">
<span class="text-sm text-gray-5 line-through flex-1 min-w-0 truncate">郑女士</span>
<span class="text-[10px] text-gray-5">超时未响应</span>
</div>
</div>
<div class="mt-3 text-center">
<button id="toggleTasksBtn" onclick="toggleAllTasks()" class="text-sm text-primary font-medium">展开全部 ↓</button>
</div>
</div>
<!-- 客户关系 TOP5 -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<div class="flex items-center justify-between mb-4">
<h2 class="st pink text-base font-semibold text-gray-13">客户关系 TOP5</h2>
<span class="text-xs text-gray-6">近60天</span>
</div>
<div class="space-y-2.5">
<!-- 客户1 -->
<div class="flex items-center gap-3 p-3 bg-gradient-to-r from-pink-50/80 to-rose-50/40 rounded-xl border border-pink-100/40">
<div class="w-9 h-9 rounded-full bg-gradient-to-br from-pink-400 to-rose-500 flex items-center justify-center text-white text-xs font-medium flex-shrink-0"></div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-1.5">
<span class="text-sm font-semibold text-gray-13">王先生</span>
<span class="text-xs">❤️</span>
<span class="text-sm text-success font-bold pv">9.5</span>
</div>
<div class="flex items-center gap-4 mt-1 text-xs">
<div class="text-gray-6">服务 <span class="text-gray-11 font-semibold">25</span></div>
<div class="text-gray-6">储值 <span class="text-gray-11 font-semibold">¥8,600</span></div>
<div class="text-gray-6">消费 <span class="text-gray-11 font-semibold">¥12,800</span></div>
</div>
</div>
</div>
<!-- 客户2 -->
<div class="flex items-center gap-3 p-3 bg-gradient-to-r from-amber-50/80 to-yellow-50/40 rounded-xl border border-amber-100/40">
<div class="w-9 h-9 rounded-full bg-gradient-to-br from-amber-400 to-orange-500 flex items-center justify-center text-white text-xs font-medium flex-shrink-0"></div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-1.5">
<span class="text-sm font-semibold text-gray-13">李女士</span>
<span class="text-xs">❤️</span>
<span class="text-sm text-success font-bold pv">9.2</span>
</div>
<div class="flex items-center gap-4 mt-1 text-xs">
<div class="text-gray-6">服务 <span class="text-gray-11 font-semibold">22</span></div>
<div class="text-gray-6">储值 <span class="text-gray-11 font-semibold">¥6,200</span></div>
<div class="text-gray-6">消费 <span class="text-gray-11 font-semibold">¥9,500</span></div>
</div>
</div>
</div>
<!-- 客户3 -->
<div class="flex items-center gap-3 p-3 bg-gray-50 rounded-xl border border-gray-100">
<div class="w-9 h-9 rounded-full bg-gradient-to-br from-green-400 to-emerald-500 flex items-center justify-center text-white text-xs font-medium flex-shrink-0"></div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-1.5">
<span class="text-sm font-semibold text-gray-13">陈女士</span>
<span class="text-xs">❤️</span>
<span class="text-sm text-warning font-bold pv">8.5</span>
</div>
<div class="flex items-center gap-4 mt-1 text-xs">
<div class="text-gray-6">服务 <span class="text-gray-11 font-semibold">18</span></div>
<div class="text-gray-6">储值 <span class="text-gray-11 font-semibold">¥5,000</span></div>
<div class="text-gray-6">消费 <span class="text-gray-11 font-semibold">¥7,200</span></div>
</div>
</div>
</div>
<!-- 客户4 -->
<div class="flex items-center gap-3 p-3 bg-gray-50 rounded-xl border border-gray-100">
<div class="w-9 h-9 rounded-full bg-gradient-to-br from-blue-400 to-indigo-500 flex items-center justify-center text-white text-xs font-medium flex-shrink-0"></div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-1.5">
<span class="text-sm font-semibold text-gray-13">张先生</span>
<span class="text-xs">💛</span>
<span class="text-sm text-warning font-bold pv">7.8</span>
</div>
<div class="flex items-center gap-4 mt-1 text-xs">
<div class="text-gray-6">服务 <span class="text-gray-11 font-semibold">12</span></div>
<div class="text-gray-6">储值 <span class="text-gray-11 font-semibold">¥3,800</span></div>
<div class="text-gray-6">消费 <span class="text-gray-11 font-semibold">¥5,600</span></div>
</div>
</div>
</div>
<!-- 客户5 -->
<div class="flex items-center gap-3 p-3 bg-gray-50 rounded-xl border border-gray-100">
<div class="w-9 h-9 rounded-full bg-gradient-to-br from-purple-400 to-violet-500 flex items-center justify-center text-white text-xs font-medium flex-shrink-0"></div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-1.5">
<span class="text-sm font-semibold text-gray-13">赵先生</span>
<span class="text-xs">💛</span>
<span class="text-sm text-gray-7 font-bold pv">6.8</span>
</div>
<div class="flex items-center gap-4 mt-1 text-xs">
<div class="text-gray-6">服务 <span class="text-gray-11 font-semibold">8</span></div>
<div class="text-gray-6">储值 <span class="text-gray-11 font-semibold">¥2,000</span></div>
<div class="text-gray-6">消费 <span class="text-gray-11 font-semibold">¥3,200</span></div>
</div>
</div>
</div>
</div>
</div>
<!-- 近期服务明细 -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<h2 class="st purple text-base font-semibold text-gray-13 mb-4">近期服务明细</h2>
<div class="space-y-3">
<!-- 服务记录1 -->
<div class="service-card">
<div class="flex items-center justify-between mb-2">
<div class="flex items-center gap-2">
<span class="text-sm font-medium text-gray-13">王先生</span>
<span class="px-1.5 py-0.5 bg-primary/10 text-primary text-[10px] rounded">基础课</span>
</div>
<span class="text-[10px] text-gray-6">2026-02-07 21:30</span>
</div>
<div class="flex items-center justify-between">
<div class="flex items-center gap-3 text-xs text-gray-7">
<span class="px-2 py-0.5 bg-blue-50 text-primary rounded font-medium">A12号台</span>
<span>2.5h</span>
</div>
<span class="text-sm font-bold text-gray-13 pv">¥200</span>
</div>
</div>
<!-- 服务记录2 -->
<div class="service-card">
<div class="flex items-center justify-between mb-2">
<div class="flex items-center gap-2">
<span class="text-sm font-medium text-gray-13">李女士</span>
<span class="px-1.5 py-0.5 bg-success/10 text-success text-[10px] rounded">激励课</span>
</div>
<span class="text-[10px] text-gray-6">2026-02-07 19:00</span>
</div>
<div class="flex items-center justify-between">
<div class="flex items-center gap-3 text-xs text-gray-7">
<span class="px-2 py-0.5 bg-blue-50 text-primary rounded font-medium">VIP1号房</span>
<span>1.5h</span>
<span class="text-orange-500">定档绩效2h</span>
</div>
<span class="text-sm font-bold text-gray-13 pv">¥150</span>
</div>
</div>
<!-- 服务记录3 -->
<div class="service-card">
<div class="flex items-center justify-between mb-2">
<div class="flex items-center gap-2">
<span class="text-sm font-medium text-gray-13">陈女士</span>
<span class="px-1.5 py-0.5 bg-primary/10 text-primary text-[10px] rounded">基础课</span>
</div>
<span class="text-[10px] text-gray-6">2026-02-06 20:00</span>
</div>
<div class="flex items-center justify-between">
<div class="flex items-center gap-3 text-xs text-gray-7">
<span class="px-2 py-0.5 bg-blue-50 text-primary rounded font-medium">2号台</span>
<span>2h</span>
</div>
<span class="text-sm font-bold text-gray-13 pv">¥160</span>
</div>
</div>
<!-- 服务记录4 -->
<div class="service-card">
<div class="flex items-center justify-between mb-2">
<div class="flex items-center gap-2">
<span class="text-sm font-medium text-gray-13">张先生</span>
<span class="px-1.5 py-0.5 bg-success/10 text-success text-[10px] rounded">激励课</span>
</div>
<span class="text-[10px] text-gray-6">2026-02-05 14:00</span>
</div>
<div class="flex items-center justify-between">
<div class="flex items-center gap-3 text-xs text-gray-7">
<span class="px-2 py-0.5 bg-blue-50 text-primary rounded font-medium">5号台</span>
<span>1h</span>
</div>
<span class="text-sm font-bold text-gray-13 pv">¥80</span>
</div>
</div>
</div>
<div class="mt-3 text-center">
<button onclick="window.location.href='performance-records.html'" class="text-xs text-primary font-medium">查看更多服务记录 →</button>
</div>
</div>
<!-- 更多信息 -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<h2 class="st teal text-base font-semibold text-gray-13 mb-4">更多信息</h2>
<div class="flex items-center justify-between py-2 border-b border-gray-100 mb-4">
<span class="text-base text-gray-7">入职日期</span>
<span class="text-base text-gray-13">2023-03-15</span>
</div>
<div class="overflow-x-auto -mx-1">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-gray-200">
<th class="text-left py-2 px-2 text-gray-7 font-medium text-xs">月份</th>
<th class="text-right py-2 px-2 text-gray-7 font-medium text-xs">服务客户</th>
<th class="text-right py-2 px-2 text-gray-7 font-medium text-xs">业绩时长</th>
<th class="text-right py-2 px-2 text-gray-7 font-medium text-xs">工资</th>
</tr>
</thead>
<tbody>
<tr class="border-b border-gray-50 bg-blue-50/30">
<td class="py-2.5 px-2 text-gray-13 font-medium">本月<span class="text-[10px] text-warning ml-1">预估</span></td>
<td class="py-2.5 px-2 text-right text-gray-13 pv font-medium">22人</td>
<td class="py-2.5 px-2 text-right text-primary pv font-bold">87.5h</td>
<td class="py-2.5 px-2 text-right text-success pv font-bold">¥6,950</td>
</tr>
<tr class="border-b border-gray-50">
<td class="py-2.5 px-2 text-gray-13">上月</td>
<td class="py-2.5 px-2 text-right text-gray-13 pv">25人</td>
<td class="py-2.5 px-2 text-right text-gray-13 pv">92.0h</td>
<td class="py-2.5 px-2 text-right text-gray-13 pv">¥7,200</td>
</tr>
<tr class="border-b border-gray-50">
<td class="py-2.5 px-2 text-gray-13">4月</td>
<td class="py-2.5 px-2 text-right text-gray-13 pv">20人</td>
<td class="py-2.5 px-2 text-right text-gray-13 pv">85.0h</td>
<td class="py-2.5 px-2 text-right text-gray-13 pv">¥6,600</td>
</tr>
<tr class="border-b border-gray-50">
<td class="py-2.5 px-2 text-gray-13">3月</td>
<td class="py-2.5 px-2 text-right text-gray-13 pv">18人</td>
<td class="py-2.5 px-2 text-right text-gray-13 pv">78.5h</td>
<td class="py-2.5 px-2 text-right text-gray-13 pv">¥6,100</td>
</tr>
<tr>
<td class="py-2.5 px-2 text-gray-13">2月</td>
<td class="py-2.5 px-2 text-right text-gray-13 pv">15人</td>
<td class="py-2.5 px-2 text-right text-gray-13 pv">65.0h</td>
<td class="py-2.5 px-2 text-right text-gray-13 pv">¥5,200</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- 底部操作栏 -->
<div class="fixed bottom-0 left-0 right-0 h-16 bg-white/95 backdrop-blur-lg border-t border-gray-2 flex items-center gap-3 px-4 z-10">
<button onclick="window.location.href='chat.html'" class="flex-1 h-11 bg-gradient-to-r from-primary to-blue-500 text-white font-medium rounded-xl flex items-center justify-center gap-2 shadow-lg shadow-primary/30">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="currentColor"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/></svg>
问问助手
</button>
<button onclick="showNoteModal()" class="flex-1 h-11 bg-gray-100 text-gray-13 font-medium rounded-xl flex items-center justify-center gap-2">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
备注
</button>
</div>
<!-- 备注弹窗 -->
<div id="noteModal" class="fixed inset-0 bg-black/50 z-50 hidden items-end">
<div class="w-full bg-white rounded-t-3xl p-5 pb-8">
<div class="flex items-center justify-between mb-4">
<span class="text-base font-semibold text-gray-13">添加备注</span>
<button onclick="hideNoteModal()" class="w-8 h-8 flex items-center justify-center rounded-full bg-gray-100">
<svg class="w-4 h-4 text-gray-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
</button>
</div>
<textarea id="noteText" class="w-full h-32 p-4 bg-gray-50 rounded-xl resize-none text-sm text-gray-13 placeholder-gray-5 focus:outline-none focus:ring-2 focus:ring-primary/20 border border-gray-100" placeholder="请输入备注内容..."></textarea>
<button onclick="saveNote()" class="w-full h-12 bg-gradient-to-r from-primary to-blue-500 text-white font-medium rounded-xl mt-4 shadow-lg shadow-primary/30">保存</button>
</div>
</div>
<!-- Toast -->
<div id="toast" class="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-gray-13/80 text-white text-sm px-6 py-3 rounded-xl z-[100] hidden backdrop-blur-sm"></div>
<!-- 备注列表弹窗 -->
<div id="notesPopup" class="fixed inset-0 bg-black/50 z-50 hidden items-end" onclick="if(event.target===this)hideNotesPopup()">
<div class="w-full bg-white rounded-t-3xl p-5 pb-8 max-h-[70vh] overflow-y-auto">
<div class="flex items-center justify-between mb-4">
<span id="notesPopupTitle" class="text-base font-semibold text-gray-13">备注列表</span>
<button onclick="hideNotesPopup()" class="w-8 h-8 flex items-center justify-center rounded-full bg-gray-100">
<svg class="w-4 h-4 text-gray-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
</button>
</div>
<div id="notesPopupList" class="space-y-3"></div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,430 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>客户详情 - 球房运营助手</title>
<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">
<style>
body { font-family: 'Noto Sans SC', -apple-system, BlinkMacSystemFont, sans-serif; padding-bottom: 80px; }
.st { position: relative; padding-left: 12px; }
.st::before { content: ''; position: absolute; left: 0; top: 50%; transform: translateY(-50%); width: 4px; height: 16px; border-radius: 2px; }
.st.blue::before { background: linear-gradient(180deg, #0052d9, #5b9cf8); }
.st.green::before { background: linear-gradient(180deg, #00a870, #4cd964); }
.st.orange::before { background: linear-gradient(180deg, #ed7b2f, #ffc107); }
.st.pink::before { background: linear-gradient(180deg, #e851a4, #f5a0c0); }
.pv { font-variant-numeric: tabular-nums; }
.orig-price { text-decoration: line-through; color: #a6a6a6; font-size: 10px; }
</style>
</head>
<body class="bg-gray-1 min-h-screen">
<!-- Banner -->
<div class="banner-bg theme-dark-gold texture-aurora relative text-white">
<div class="h-11 flex items-center relative px-4">
<button onclick="history.back()" class="absolute left-4 p-1">
<svg class="w-5 h-5 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="15 18 9 12 15 6"/></svg>
</button>
<h1 class="flex-1 text-center text-base font-medium">客户详情</h1>
</div>
<div class="px-5 pt-2 pb-6">
<div class="flex items-center gap-4 mb-4">
<div class="w-16 h-16 rounded-2xl bg-white/20 backdrop-blur-sm flex items-center justify-center shadow-lg">
<span class="text-2xl font-bold text-white"></span>
</div>
<div class="flex-1">
<div class="flex items-center gap-2 mb-2">
<span class="text-xl font-semibold">王先生</span>
<span class="px-2 py-0.5 bg-gradient-to-r from-amber-500 to-yellow-400 text-black font-medium rounded-full text-xs">VIP</span>
</div>
<div class="flex items-center gap-4 text-white/70 text-sm">
<span>138****5678</span>
<span>VIP20231215</span>
</div>
</div>
</div>
<div class="bg-white/10 rounded-xl backdrop-blur-sm">
<div class="grid grid-cols-4">
<div class="text-center py-3 border-r border-white/10">
<p class="font-medium text-emerald-300 text-sm">¥8,600</p>
<p class="text-white/60 text-xs mt-1">储值余额</p>
</div>
<div class="text-center py-3 border-r border-white/10">
<p class="font-medium text-sm">¥2,800</p>
<p class="text-white/60 text-xs mt-1">60天消费</p>
</div>
<div class="text-center py-3 border-r border-white/10">
<p class="font-medium text-sm">7天</p>
<p class="text-white/60 text-xs mt-1">理想间隔</p>
</div>
<div class="text-center py-3">
<p class="font-medium text-amber-300 text-sm">12天</p>
<p class="text-white/60 text-xs mt-1">距今到店</p>
</div>
</div>
</div>
</div>
</div>
<div class="p-4 space-y-4">
<!-- AI 智能洞察(客户全貌总结) -->
<div class="rounded-2xl overflow-hidden shadow-sm">
<div class="bg-gradient-to-br from-[#667eea] to-[#764ba2] p-5 text-white">
<div class="flex items-center gap-2 mb-3">
<div class="w-6 h-6 bg-white/20 rounded-lg flex items-center justify-center">
<svg width="16" height="16" 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>
</div>
<span class="text-sm font-medium text-white/90">AI 智能洞察</span>
</div>
<!-- 客户画像总结 -->
<p class="text-sm text-white/90 leading-relaxed mb-3">高价值 VIP 客户,月均到店 4-5 次,偏好夜场中式台球,近期对斯诺克产生兴趣。社交属性强,常带固定球搭子,有拉新能力。储值余额充足,对促销活动响应积极。</p>
<!-- 营销策略建议 -->
<div class="bg-white/10 rounded-xl p-3 backdrop-blur-sm">
<p class="text-xs text-white/60 mb-2">📋 当前推荐策略</p>
<div class="space-y-1.5">
<div class="flex items-start gap-2">
<span class="shrink-0 w-1.5 h-1.5 bg-emerald-300 rounded-full mt-1.5"></span>
<p class="text-xs text-white/85 leading-relaxed">最后到店距今 12 天,超出理想间隔 7 天,建议尽快安排助教小燕主动联系召回</p>
</div>
<div class="flex items-start gap-2">
<span class="shrink-0 w-1.5 h-1.5 bg-amber-300 rounded-full mt-1.5"></span>
<p class="text-xs text-white/85 leading-relaxed">客户提到想练斯诺克走位,可推荐斯诺克专项课程包,结合储值优惠提升客单价</p>
</div>
<div class="flex items-start gap-2">
<span class="shrink-0 w-1.5 h-1.5 bg-pink-300 rounded-full mt-1.5"></span>
<p class="text-xs text-white/85 leading-relaxed">社交属性强,可邀请参加门店球友赛事活动,带动球搭子到店消费</p>
</div>
</div>
</div>
</div>
</div>
<!-- 维客线索 -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<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"><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-2.5">
<!-- 客户基础 -->
<div class="flex items-start gap-3 p-3 bg-gray-50 rounded-xl border border-gray-100">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-primary/10 text-primary text-[11px] font-medium rounded-sm leading-normal tracking-wide">客户<br>基础</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">🎂 生日 3月15日 · VIP会员 · 注册2年</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<!-- 消费习惯 -->
<div class="flex items-start gap-3 p-3 bg-gray-50 rounded-xl border border-gray-100">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-success/10 text-success text-[11px] font-medium rounded-sm leading-normal tracking-wide">消费<br>习惯</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">🌙 常来夜场 · 月均4-5次</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<div class="bg-gray-50 rounded-xl border border-gray-100 p-3">
<div class="flex items-start gap-3">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-success/10 text-success text-[11px] font-medium rounded-sm leading-normal tracking-wide">消费<br>习惯</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">💰 高客单价</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<p class="text-xs text-gray-7 mt-2 leading-relaxed">近60天场均消费 ¥420高于门店均值 ¥180偏好夜场时段酒水附加消费占比 35%</p>
</div>
<!-- 玩法偏好 -->
<div class="flex items-start gap-3 p-3 bg-gray-50 rounded-xl border border-gray-100">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-purple-500/10 text-purple-600 text-[11px] font-medium rounded-sm leading-normal tracking-wide">玩法<br>偏好</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">🎱 偏爱中式 · 斯诺克进阶中</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<!-- 促销接受 -->
<div class="bg-gray-50 rounded-xl border border-gray-100 p-3">
<div class="flex items-start gap-3">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-warning/10 text-warning text-[11px] font-medium rounded-sm leading-normal tracking-wide">促销<br>接受</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">🍷 爱点酒水套餐 · 对储值活动敏感</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<p class="text-xs text-gray-7 mt-2 leading-relaxed">最近3次到店均点了酒水套餐上次 ¥5000 储值活动当天即充值,对满赠类活动响应率高</p>
</div>
<!-- 社交关系 -->
<div class="bg-gray-50 rounded-xl border border-gray-100 p-3">
<div class="flex items-start gap-3">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-pink-500/10 text-pink-600 text-[11px] font-medium rounded-sm leading-normal tracking-wide">社交<br>关系</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">👥 常带朋友 · 固定球搭子2人</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<p class="text-xs text-gray-7 mt-2 leading-relaxed">近60天 80% 的到店为多人局常与「李哥」「阿杰」同行曾介绍2位新客办卡</p>
</div>
<!-- 重要反馈 -->
<div class="flex items-start gap-3 p-3 bg-gray-50 rounded-xl border border-gray-100">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-error/10 text-error text-[11px] font-medium rounded-sm leading-normal tracking-wide">重要<br>反馈</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">⚠️ 上次提到想练斯诺克走位对球桌维护质量比较在意建议优先安排VIP房</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:小燕</span>
</div>
</div>
</div>
</div>
<!-- 助教任务 -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<div class="flex items-center justify-between mb-4">
<h2 class="st blue text-sm font-semibold text-gray-13">助教任务</h2>
<span class="text-xs text-gray-6">当前进行中</span>
</div>
<div class="space-y-3">
<!-- 小燕 - 高优先召回 -->
<div class="p-3 bg-gradient-to-br from-red-50/80 to-rose-50/60 rounded-xl border border-red-100/60">
<div class="flex items-center justify-between mb-2">
<div class="flex items-center gap-2">
<span class="text-sm font-semibold text-gray-13">小燕</span>
<span class="px-2 py-0.5 bg-pink-100 text-pink-700 text-xs rounded-full font-medium">高级助教</span>
</div>
<span class="px-2 py-0.5 bg-red-100 text-red-700 text-xs rounded-full font-medium">高优先召回</span>
</div>
<div class="flex items-center justify-between text-xs text-gray-7 mb-2">
<span>上次服务02-20 21:30 · 2.5h</span>
</div>
<div class="grid grid-cols-3 gap-2">
<div class="bg-white/80 rounded-lg py-1.5 text-center"><p class="text-[10px] text-gray-6 mb-0.5">近60天次数</p><p class="text-sm font-bold text-primary">18次</p></div>
<div class="bg-white/80 rounded-lg py-1.5 text-center"><p class="text-[10px] text-gray-6 mb-0.5">总时长</p><p class="text-sm font-bold text-gray-13">17h</p></div>
<div class="bg-white/80 rounded-lg py-1.5 text-center"><p class="text-[10px] text-gray-6 mb-0.5">次均时长</p><p class="text-sm font-bold text-warning">0.9h</p></div>
</div>
</div>
<!-- 泡芙 - 关系构建 -->
<div class="p-3 bg-gradient-to-br from-pink-50/80 to-fuchsia-50/60 rounded-xl border border-pink-100/60">
<div class="flex items-center justify-between mb-2">
<div class="flex items-center gap-2">
<span class="text-sm font-semibold text-gray-13">泡芙</span>
<span class="px-2 py-0.5 bg-purple-100 text-purple-700 text-xs rounded-full font-medium">中级助教</span>
</div>
<span class="px-2 py-0.5 bg-pink-100 text-pink-700 text-xs rounded-full font-medium">关系构建</span>
</div>
<div class="flex items-center justify-between text-xs text-gray-7 mb-2">
<span>上次服务02-15 14:00 · 1.5h</span>
</div>
<div class="grid grid-cols-3 gap-2">
<div class="bg-white/80 rounded-lg py-1.5 text-center"><p class="text-[10px] text-gray-6 mb-0.5">近60天次数</p><p class="text-sm font-bold text-primary">12次</p></div>
<div class="bg-white/80 rounded-lg py-1.5 text-center"><p class="text-[10px] text-gray-6 mb-0.5">总时长</p><p class="text-sm font-bold text-gray-13">11h</p></div>
<div class="bg-white/80 rounded-lg py-1.5 text-center"><p class="text-[10px] text-gray-6 mb-0.5">次均时长</p><p class="text-sm font-bold text-warning">0.9h</p></div>
</div>
</div>
</div>
</div>
<!-- 最喜欢的助教 -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<div class="flex items-center justify-between mb-4">
<h2 class="st pink text-sm font-semibold text-gray-13">最喜欢的助教</h2>
<span class="text-xs text-gray-6">近60天</span>
</div>
<div class="space-y-3">
<div class="p-4 bg-gradient-to-br from-pink-50/80 to-rose-50/60 rounded-xl border border-pink-100/60">
<div class="flex items-center justify-between mb-2.5">
<div class="flex items-center gap-2.5"><span class="text-lg">❤️</span><span class="text-sm font-semibold text-gray-13">小燕</span></div>
<div class="flex items-center gap-1.5"><span class="text-xs text-gray-7">关系指数</span><span class="text-lg font-bold text-success">0.92</span></div>
</div>
<p class="text-xs text-gray-7 mb-1.5 pl-0.5">近60天</p>
<div class="grid grid-cols-4 gap-2">
<div class="bg-white/80 rounded-lg py-2 text-center"><p class="text-xs text-gray-7 mb-0.5">基础</p><p class="text-base font-bold text-primary">12h</p></div>
<div class="bg-white/80 rounded-lg py-2 text-center"><p class="text-xs text-gray-7 mb-0.5">激励</p><p class="text-base font-bold text-warning">5h</p></div>
<div class="bg-white/80 rounded-lg py-2 text-center"><p class="text-xs text-gray-7 mb-0.5">上课</p><p class="text-base font-bold text-gray-13">18次</p></div>
<div class="bg-white/80 rounded-lg py-2 text-center"><p class="text-xs text-gray-7 mb-0.5">充值</p><p class="text-base font-bold text-success">¥5,000</p></div>
</div>
</div>
<div class="p-4 bg-gradient-to-br from-amber-50/80 to-yellow-50/60 rounded-xl border border-amber-100/60">
<div class="flex items-center justify-between mb-2.5">
<div class="flex items-center gap-2.5"><span class="text-lg">💛</span><span class="text-sm font-semibold text-gray-13">泡芙</span></div>
<div class="flex items-center gap-1.5"><span class="text-xs text-gray-7">关系指数</span><span class="text-lg font-bold text-warning">0.78</span></div>
</div>
<p class="text-xs text-gray-7 mb-1.5 pl-0.5">近60天</p>
<div class="grid grid-cols-4 gap-2">
<div class="bg-white/80 rounded-lg py-2 text-center"><p class="text-xs text-gray-7 mb-0.5">基础</p><p class="text-base font-bold text-primary">8h</p></div>
<div class="bg-white/80 rounded-lg py-2 text-center"><p class="text-xs text-gray-7 mb-0.5">激励</p><p class="text-base font-bold text-warning">3h</p></div>
<div class="bg-white/80 rounded-lg py-2 text-center"><p class="text-xs text-gray-7 mb-0.5">上课</p><p class="text-base font-bold text-gray-13">12次</p></div>
<div class="bg-white/80 rounded-lg py-2 text-center"><p class="text-xs text-gray-7 mb-0.5">充值</p><p class="text-base font-bold text-success">¥3,000</p></div>
</div>
</div>
</div>
</div>
<!-- 消费记录(放在最下方) -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<h2 class="st orange text-sm font-semibold text-gray-13 mb-4">消费记录</h2>
<div class="space-y-4">
<!-- 台桌详情 Record 1有团购折扣2位助教有食品酒水 -->
<div class="rounded-xl border border-gray-200 overflow-hidden">
<div class="bg-gradient-to-r from-blue-50 to-indigo-50 px-4 py-2 flex items-center justify-between border-b border-blue-100/50">
<div class="flex items-center gap-2">
<span class="w-1.5 h-1.5 bg-primary rounded-full"></span>
<span class="text-sm font-semibold text-primary">A12号台</span>
</div>
<span class="text-sm text-gray-8">2026-02-05</span>
</div>
<!-- 时间信息:紧凑一行 -->
<div class="px-4 py-2.5 border-b border-gray-100 flex items-center justify-between">
<div class="flex items-center gap-3 text-base">
<span class="text-gray-13 font-medium pv">21:30</span>
<span class="text-gray-7"></span>
<span class="text-gray-13 font-medium pv">00:50</span>
<span class="px-2 py-0.5 bg-primary/10 text-primary text-xs rounded-full font-medium">3h20min</span>
</div>
<!-- 台费(有团购折扣) -->
<div class="text-right flex-shrink-0">
<span class="text-base font-bold text-gray-13 pv">¥180</span>
<span class="orig-price ml-1">¥240</span>
</div>
</div>
<!-- 助教列表一行2个卡片 -->
<div class="px-4 py-3">
<div class="grid grid-cols-2 gap-2 mb-3">
<!-- 助教卡片1小燕 -->
<div class="bg-gray-50 rounded-lg p-2.5">
<div class="flex items-center gap-1 mb-0.5">
<span class="text-sm font-medium text-gray-13">小燕</span>
<span class="px-1 py-px bg-pink-50 text-pink-600 text-[10px] rounded">高级</span>
</div>
<div class="text-xs text-gray-8">基础课 · 2.5h</div>
<div class="text-right mt-0.5">
<span class="text-sm font-bold text-gray-13 pv">¥200</span>
</div>
</div>
<!-- 助教卡片2Amy -->
<div class="bg-gray-50 rounded-lg p-2.5">
<div class="flex items-center gap-1 mb-0.5">
<span class="text-sm font-medium text-gray-13">Amy</span>
<span class="px-1 py-px bg-green-50 text-green-600 text-[10px] rounded">初级</span>
</div>
<div class="text-xs text-gray-8">激励课 · 0.5h</div>
<div class="flex items-center justify-between mt-0.5">
<span class="text-xs text-orange-500">定档绩效1h</span>
<span class="text-sm font-bold text-gray-13 pv">¥50</span>
</div>
</div>
</div>
<!-- 食品酒水 -->
<div class="flex items-center justify-between py-2 border-t border-gray-100">
<span class="text-sm text-gray-8">🍷 食品酒水</span>
<div class="text-right">
<span class="text-base font-medium text-warning pv">¥210</span>
<span class="orig-price ml-1">¥260</span>
</div>
</div>
<!-- 总金额 -->
<div class="flex items-center justify-between pt-2 border-t border-gray-200">
<span class="text-sm font-semibold text-gray-9">总金额</span>
<div class="text-right">
<span class="text-lg font-bold text-error pv">¥640</span>
<span class="orig-price ml-1">¥750</span>
</div>
</div>
</div>
</div>
<!-- 台桌详情 Record 2无食品酒水有定档绩效无折扣 -->
<div class="rounded-xl border border-gray-200 overflow-hidden">
<div class="bg-gradient-to-r from-blue-50 to-indigo-50 px-4 py-2 flex items-center justify-between border-b border-blue-100/50">
<div class="flex items-center gap-2">
<span class="w-1.5 h-1.5 bg-primary rounded-full"></span>
<span class="text-sm font-semibold text-primary">888号台</span>
</div>
<span class="text-sm text-gray-8">2026-02-01</span>
</div>
<div class="px-4 py-2.5 border-b border-gray-100 flex items-center justify-between">
<div class="flex items-center gap-3 text-base">
<span class="text-gray-13 font-medium pv">14:00</span>
<span class="text-gray-7"></span>
<span class="text-gray-13 font-medium pv">16:00</span>
<span class="px-2 py-0.5 bg-primary/10 text-primary text-xs rounded-full font-medium">2h00min</span>
</div>
<span class="text-base font-bold text-gray-13 pv">¥120</span>
</div>
<div class="px-4 py-3">
<div class="grid grid-cols-2 gap-2 mb-3">
<div class="bg-gray-50 rounded-lg p-2.5">
<div class="flex items-center gap-1 mb-0.5">
<span class="text-sm font-medium text-gray-13">泡芙</span>
<span class="px-1 py-px bg-purple-50 text-purple-600 text-[10px] rounded">中级</span>
</div>
<div class="text-xs text-gray-8">激励课 · 1.5h</div>
<div class="flex items-center justify-between mt-0.5">
<span class="text-xs text-orange-500">定档绩效2h</span>
<span class="text-sm font-bold text-gray-13 pv">¥100</span>
</div>
</div>
</div>
<!-- 总金额(无食品酒水,不展示该行) -->
<div class="flex items-center justify-between pt-2 border-t border-gray-200">
<span class="text-sm font-semibold text-gray-9">总金额</span>
<span class="text-lg font-bold text-error pv">¥220</span>
</div>
</div>
</div>
<!-- 商城订单 Record 3有食品酒水 -->
<div class="rounded-xl border border-gray-200 overflow-hidden">
<div class="bg-gradient-to-r from-emerald-50 to-green-50 px-4 py-2 flex items-center justify-between border-b border-green-100/50">
<div class="flex items-center gap-2">
<span class="w-1.5 h-1.5 bg-success rounded-full"></span>
<span class="text-sm font-semibold text-success">商城订单</span>
</div>
<span class="text-sm text-gray-8">2026-01-28</span>
</div>
<div class="px-4 py-3 space-y-2.5">
<!-- 助教卡片列表:同台桌详情风格 -->
<div class="grid grid-cols-2 gap-2">
<div class="bg-gray-50 rounded-lg p-2.5">
<div class="flex items-center gap-1 mb-0.5">
<span class="text-sm font-medium text-gray-13">小燕</span>
<span class="px-1 py-px bg-pink-50 text-pink-600 text-[10px] rounded">高级</span>
</div>
<div class="text-xs text-gray-8">基础课 · 1h</div>
<div class="text-right mt-0.5">
<span class="text-sm font-bold text-gray-13 pv">¥100</span>
</div>
</div>
</div>
<!-- 食品酒水 -->
<div class="flex items-center justify-between pt-2 border-t border-gray-100">
<span class="text-sm text-gray-8">🍷 食品酒水</span>
<span class="text-base font-medium text-warning pv">¥180</span>
</div>
<!-- 总金额 -->
<div class="flex items-center justify-between pt-2 border-t border-gray-200">
<span class="text-sm font-semibold text-gray-9">总金额</span>
<span class="text-lg font-bold text-error pv">¥280</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 底部操作栏 -->
<div class="fixed bottom-0 left-0 right-0 h-16 bg-white/95 backdrop-blur-lg border-t border-gray-2 flex items-center gap-3 px-4">
<button onclick="window.location.href='chat.html'" class="flex-1 h-11 bg-gradient-to-r from-primary to-blue-500 text-white font-medium rounded-xl flex items-center justify-center gap-2 shadow-lg shadow-primary/30">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="currentColor"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/></svg>
问问助手
</button>
<button class="flex-1 h-11 bg-gray-100 text-gray-13 font-medium rounded-xl flex items-center justify-center gap-2">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
备注
</button>
</div>
</body>
</html>

View File

@@ -0,0 +1,186 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>服务记录 - 球房运营助手</title>
<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/customer-service-records.css" rel="stylesheet">
</head>
<body class="bg-gray-1 min-h-screen pb-8">
<!-- Banner -->
<div class="banner-bg theme-coral texture-aurora relative text-white">
<div class="h-11 flex items-center relative px-4">
<button onclick="history.back()" class="absolute left-4 p-1">
<svg class="w-5 h-5 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="15 18 9 12 15 6"/>
</svg>
</button>
<h1 class="flex-1 text-center text-base font-medium">服务记录</h1>
</div>
<div class="px-5 pt-1 pb-5">
<div class="flex items-center gap-4">
<div class="w-12 h-12 rounded-2xl bg-white/20 backdrop-blur-sm flex items-center justify-center text-white text-lg font-semibold shadow-lg flex-shrink-0"></div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-2 mb-1">
<span class="text-lg font-semibold">王先生</span>
<span class="text-xs text-white/50">139****5678</span>
</div>
<div class="flex items-center gap-4 text-white/70 text-xs">
<span>累计服务 <span class="text-white font-bold text-sm">28</span></span>
<span>关系指数 <span class="text-white font-bold text-sm">0.85</span></span>
</div>
</div>
</div>
</div>
</div>
<!-- 月份切换 -->
<div class="bg-white px-4 py-3 border-b border-gray-2 flex items-center justify-center gap-6">
<button id="prevMonthBtn" onclick="switchMonth('prev')" class="p-1.5 rounded-full hover:bg-gray-100">
<svg class="w-4 h-4 text-gray-8" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="15 18 9 12 15 6"/>
</svg>
</button>
<span id="monthLabel" class="text-sm font-semibold text-gray-13">2026年2月</span>
<button id="nextMonthBtn" onclick="switchMonth('next')" class="p-1.5 rounded-full hover:bg-gray-100 opacity-30" disabled>
<svg class="w-4 h-4 text-gray-8" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="9 6 15 12 9 18"/>
</svg>
</button>
</div>
<!-- 月度统计概览 -->
<div class="px-4 py-3 bg-white border-b border-gray-2">
<div class="flex items-start justify-between">
<div class="text-center flex-1">
<p class="text-[10px] text-gray-6 mb-0.5">本月服务</p>
<p class="text-lg font-bold text-gray-13 tabnum" id="monthCount">6次</p>
</div>
<div class="w-px h-10 bg-gray-2 mt-0.5"></div>
<div class="text-center flex-1">
<p class="text-[10px] text-gray-6 mb-0.5">服务时长</p>
<p class="text-lg font-bold text-primary tabnum" id="monthHours">11.5h</p>
</div>
<div class="w-px h-10 bg-gray-2 mt-0.5"></div>
<div class="text-center flex-1">
<p class="text-[10px] text-gray-6 mb-0.5">关系指数</p>
<p class="text-lg font-bold text-warning tabnum" id="monthDrinks">0.85</p>
</div>
</div>
</div>
<!-- 记录列表 -->
<div class="p-4">
<div id="recordsList" class="space-y-3">
<!-- 2月7日 -->
<div class="record-card">
<div class="flex items-center justify-between mb-2">
<div class="flex items-center gap-2">
<span class="record-date">2月7日</span>
<span class="record-time">20:00 - 22:30</span>
</div>
<span class="record-duration">2.5h</span>
</div>
<div class="flex items-center gap-2 mb-2">
<span class="record-table">A12号台</span>
<span class="record-type type-basic">基础课</span>
<span class="record-income">¥200</span>
</div>
</div>
<!-- 2月5日 -->
<div class="record-card">
<div class="flex items-center justify-between mb-2">
<div class="flex items-center gap-2">
<span class="record-date">2月5日</span>
<span class="record-time">15:00 - 17:00</span>
</div>
<span class="record-duration">2.0h</span>
</div>
<div class="flex items-center gap-2 mb-2">
<span class="record-table">3号台</span>
<span class="record-type type-basic">基础课</span>
<span class="record-income">¥160</span>
</div>
</div>
<!-- 2月3日 -->
<div class="record-card">
<div class="flex items-center justify-between mb-2">
<div class="flex items-center gap-2">
<span class="record-date">2月3日</span>
<span class="record-time">19:30 - 21:30</span>
</div>
<span class="record-duration">2.0h</span>
</div>
<div class="flex items-center gap-2 mb-2">
<span class="record-table">VIP1号房</span>
<span class="record-type type-vip">包厢课</span>
<span class="record-income">¥190</span>
</div>
</div>
<!-- 2月1日 -->
<div class="record-card">
<div class="flex items-center justify-between mb-2">
<div class="flex items-center gap-2">
<span class="record-date">2月1日</span>
<span class="record-time">20:30 - 22:30</span>
</div>
<span class="record-duration">2.0h</span>
</div>
<div class="flex items-center gap-2 mb-2">
<span class="record-table">5号台</span>
<span class="record-type type-basic">基础课</span>
<span class="record-income">¥160</span>
</div>
</div>
<!-- 1月28日 -->
<div class="record-card">
<div class="flex items-center justify-between mb-2">
<div class="flex items-center gap-2">
<span class="record-date">1月28日</span>
<span class="record-time">19:00 - 21:00</span>
</div>
<span class="record-duration">2.0h</span>
</div>
<div class="flex items-center gap-2 mb-2">
<span class="record-table">VIP2号房</span>
<span class="record-type type-vip">包厢课</span>
<span class="record-income">¥190</span>
</div>
</div>
<!-- 1月22日 -->
<div class="record-card">
<div class="flex items-center justify-between mb-2">
<div class="flex items-center gap-2">
<span class="record-date">1月22日</span>
<span class="record-time">14:00 - 15:00</span>
</div>
<span class="record-duration">1.0h</span>
</div>
<div class="flex items-center gap-2 mb-2">
<span class="record-table">8号台</span>
<span class="record-type type-tip">打赏课</span>
<span class="record-income">¥80</span>
</div>
</div>
<div class="text-center py-3">
<p class="text-[10px] text-gray-5">— 已加载全部记录 —</p>
</div>
</div>
</div>
<!-- 悬浮助手按钮 -->
<!-- 月份切换交互 -->
</body>
</html>

View File

@@ -0,0 +1,104 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>首页设置 - 球房运营助手</title>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Noto Sans SC', -apple-system, BlinkMacSystemFont, sans-serif;
}
.safe-area-top {
padding-top: env(safe-area-inset-top, 44px);
}
.radio-checked {
border-color: #0052d9;
background: #0052d9;
}
.radio-checked::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 8px;
height: 8px;
background: white;
border-radius: 50%;
}
.option-card {
transition: all 0.2s ease;
}
.option-card.selected {
border-color: #0052d9;
background: linear-gradient(135deg, #ecf2fe 0%, #f8faff 100%);
}
</style>
</head>
<body class="bg-gray-1 min-h-screen">
<!-- 顶部导航 -->
<div class="safe-area-top bg-white sticky top-0 z-10">
<div class="h-11 flex items-center relative border-b border-gray-2 px-4">
<button onclick="history.back()" class="absolute left-4 p-1">
<svg class="w-5 h-5 text-gray-10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="15 18 9 12 15 6"/>
</svg>
</button>
<h1 class="flex-1 text-center text-base font-medium text-gray-13">首页设置</h1>
</div>
</div>
<!-- 说明文字 -->
<div class="px-4 py-4">
<p class="text-sm text-gray-7">选择登录后默认显示的首页</p>
</div>
<!-- 选项列表 -->
<div class="px-4 space-y-3">
<div id="option-task" class="option-card bg-white rounded-2xl p-4 border-2 border-transparent cursor-pointer selected" onclick="selectHome('task')">
<div class="flex items-center gap-4">
<div class="w-12 h-12 bg-gradient-to-br from-primary to-blue-400 rounded-xl flex items-center justify-center shadow-sm">
<svg class="w-6 h-6 text-white" viewBox="0 0 24 24" fill="currentColor">
<path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"/>
</svg>
</div>
<div class="flex-1">
<p class="text-base font-medium text-gray-13 mb-1">任务</p>
<p class="text-xs text-gray-6">查看待办任务和业绩概览</p>
</div>
<div id="radio-task" class="w-5 h-5 border-2 border-gray-4 rounded-full relative radio-checked"></div>
</div>
</div>
<div id="option-board" class="option-card bg-white rounded-2xl p-4 border-2 border-transparent cursor-pointer" onclick="selectHome('board')">
<div class="flex items-center gap-4">
<div class="w-12 h-12 bg-gradient-to-br from-success to-green-400 rounded-xl flex items-center justify-center shadow-sm">
<svg class="w-6 h-6 text-white" viewBox="0 0 24 24" fill="currentColor">
<path d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
</svg>
</div>
<div class="flex-1">
<p class="text-base font-medium text-gray-13 mb-1">看板</p>
<p class="text-xs text-gray-6">查看财务、客户、助教数据</p>
</div>
<div id="radio-board" class="w-5 h-5 border-2 border-gray-4 rounded-full relative"></div>
</div>
</div>
</div>
<!-- 提示信息 -->
<div class="px-4 py-6">
<div class="flex items-start gap-3 p-4 bg-primary/5 rounded-xl">
<svg class="w-5 h-5 text-primary flex-shrink-0 mt-0.5" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/>
</svg>
<p class="text-xs text-gray-7 leading-relaxed">设置会自动保存,切换选项后即刻生效。退出登录后重新登录仍会保持您的设置。</p>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,145 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>登录 - 球房运营助手</title>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Noto Sans SC', -apple-system, BlinkMacSystemFont, sans-serif;
}
.bg-gradient-main {
background: linear-gradient(135deg, #e0f2fe 0%, #f0f9ff 50%, #ecfeff 100%);
}
.checkbox-custom {
position: relative;
}
.checkbox-custom:checked {
background-color: #0052d9;
border-color: #0052d9;
}
.checkbox-custom:checked::after {
content: '';
position: absolute;
left: 4px;
top: 1px;
width: 5px;
height: 9px;
border: solid white;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
.btn-disabled {
background-color: #dcdcdc !important;
cursor: not-allowed;
}
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-8px); }
}
.float-animation {
animation: float 4s ease-in-out infinite;
}
@keyframes pulse-soft {
0%, 100% { opacity: 0.4; }
50% { opacity: 0.8; }
}
.pulse-soft {
animation: pulse-soft 3s ease-in-out infinite;
}
</style>
</head>
<body class="bg-gradient-main min-h-screen flex flex-col">
<!-- 装饰元素 -->
<div class="absolute top-20 left-8 w-16 h-16 bg-primary/10 rounded-full blur-xl pulse-soft"></div>
<div class="absolute top-40 right-6 w-20 h-20 bg-cyan-400/10 rounded-full blur-xl pulse-soft" style="animation-delay: 1s;"></div>
<div class="absolute bottom-40 left-12 w-12 h-12 bg-blue-400/10 rounded-full blur-xl pulse-soft" style="animation-delay: 0.5s;"></div>
<!-- 顶部区域 - Logo 和名称 -->
<div class="flex-1 flex flex-col items-center justify-center px-8 relative z-10">
<!-- Logo -->
<div class="relative mb-6 float-animation">
<div class="w-24 h-24 bg-gradient-to-br from-primary to-blue-400 rounded-3xl flex items-center justify-center shadow-xl shadow-primary/30">
<svg class="w-14 h-14 text-white" viewBox="0 0 24 24" fill="currentColor">
<circle cx="12" cy="12" r="10" fill="currentColor" opacity="0.3"/>
<circle cx="12" cy="12" r="6" fill="currentColor"/>
<circle cx="12" cy="12" r="2" fill="white"/>
</svg>
</div>
<!-- 装饰点 -->
<div class="absolute -top-1 -right-1 w-4 h-4 bg-cyan-400 rounded-full shadow-lg"></div>
<div class="absolute -bottom-2 -left-2 w-3 h-3 bg-blue-300 rounded-full shadow-lg"></div>
</div>
<!-- 应用名称 -->
<h1 class="text-2xl font-bold text-gray-13 mb-2">球房运营助手</h1>
<!-- 产品描述 -->
<p class="text-gray-7 text-sm text-center leading-relaxed mb-8">
为台球厅提升运营效率的内部管理工具
</p>
<!-- 功能亮点 -->
<div class="w-full max-w-sm bg-white/60 backdrop-blur-sm rounded-2xl p-5 mb-6">
<div class="grid grid-cols-3 gap-4 text-center">
<div>
<div class="w-10 h-10 bg-primary/10 rounded-xl flex items-center justify-center mx-auto mb-2">
<svg class="w-5 h-5 text-primary" viewBox="0 0 24 24" fill="currentColor">
<path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"/>
</svg>
</div>
<p class="text-xs text-gray-9 font-medium">任务管理</p>
</div>
<div>
<div class="w-10 h-10 bg-success/10 rounded-xl flex items-center justify-center mx-auto mb-2">
<svg class="w-5 h-5 text-success" viewBox="0 0 24 24" fill="currentColor">
<path d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
</svg>
</div>
<p class="text-xs text-gray-9 font-medium">数据看板</p>
</div>
<div>
<div class="w-10 h-10 bg-warning/10 rounded-xl flex items-center justify-center mx-auto mb-2">
<svg class="w-5 h-5 text-warning" viewBox="0 0 24 24" fill="currentColor">
<path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/>
</svg>
</div>
<p class="text-xs text-gray-9 font-medium">智能助手</p>
</div>
</div>
</div>
</div>
<!-- 底部区域 - 登录按钮和协议 -->
<div class="px-8 pb-10 relative z-10">
<!-- 微信登录按钮 -->
<button id="loginBtn" class="btn-disabled w-full py-4 rounded-xl text-white font-medium text-base flex items-center justify-center gap-2 transition-all duration-200 shadow-lg">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="currentColor">
<path d="M8.691 2.188C3.891 2.188 0 5.476 0 9.53c0 2.212 1.17 4.203 3.002 5.55a.59.59 0 0 1 .213.665l-.39 1.48c-.019.07-.048.141-.048.213 0 .163.13.295.29.295a.32.32 0 0 0 .165-.054l1.9-1.106a.96.96 0 0 1 .465-.116.94.94 0 0 1 .272.04 10.6 10.6 0 0 0 2.822.384c.136 0 .271-.002.405-.009a6.9 6.9 0 0 1-.315-2.053c0-3.694 3.614-6.69 8.076-6.69.233 0 .463.01.691.027C16.964 4.837 13.132 2.188 8.691 2.188zm-2.97 5.28a1.03 1.03 0 1 1 0-2.06 1.03 1.03 0 0 1 0 2.06zm5.96 0a1.03 1.03 0 1 1 0-2.06 1.03 1.03 0 0 1 0 2.06zM24 14.2c0-3.355-3.4-6.08-7.59-6.08s-7.59 2.725-7.59 6.08c0 3.356 3.4 6.08 7.59 6.08.772 0 1.515-.094 2.215-.268a.77.77 0 0 1 .224-.033.79.79 0 0 1 .382.095l1.565.912a.26.26 0 0 0 .135.044c.13 0 .238-.108.238-.242 0-.06-.024-.117-.04-.175l-.32-1.218a.48.48 0 0 1 .175-.547C22.95 17.89 24 16.165 24 14.2zm-10.14-.426a.85.85 0 1 1 0-1.7.85.85 0 0 1 0 1.7zm5.1 0a.85.85 0 1 1 0-1.7.85.85 0 0 1 0 1.7z"/>
</svg>
使用微信登录
</button>
<!-- 协议勾选 -->
<div class="flex items-start gap-3 mt-5">
<input type="checkbox" id="agreeCheckbox" class="checkbox-custom w-4 h-4 mt-0.5 border-2 border-gray-4 rounded appearance-none cursor-pointer transition-all duration-200 flex-shrink-0">
<label for="agreeCheckbox" class="text-xs text-gray-7 leading-relaxed cursor-pointer">
我已阅读并同意
<a href="#" class="text-primary font-medium">《用户协议》</a>
<a href="#" class="text-primary font-medium">《隐私政策》</a>
</label>
</div>
<!-- 底部说明 -->
<p class="text-xs text-gray-5 text-center mt-6">
仅限球房内部员工使用
</p>
</div>
</body>
</html>

View File

@@ -0,0 +1,113 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>我的 - 球房运营助手</title>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Noto Sans SC', -apple-system, BlinkMacSystemFont, sans-serif;
padding-bottom: 80px;
}
.safe-area-top {
padding-top: env(safe-area-inset-top, 44px);
}
</style>
</head>
<body class="bg-gray-1 min-h-screen">
<!-- 顶部导航 -->
<div class="safe-area-top bg-white">
<div class="h-11 flex items-center justify-center relative border-b border-gray-2">
<h1 class="text-base font-medium text-gray-13">我的</h1>
</div>
</div>
<!-- 用户信息区域 -->
<div class="bg-white p-6 flex items-center gap-4">
<div class="w-16 h-16 rounded-full overflow-hidden shadow-lg">
<img src="../img/zjtx.png" class="w-full h-full object-cover" alt="助教头像">
</div>
<div>
<div class="flex items-center gap-2 mb-1">
<span class="text-lg font-semibold text-gray-13">小燕</span>
<span class="px-2 py-0.5 bg-primary/10 text-primary text-xs rounded">助教</span>
</div>
<p class="text-sm text-gray-7">广州朗朗桌球</p>
</div>
</div>
<!-- 菜单列表 -->
<div class="mt-4">
<a href="notes.html" class="flex items-center justify-between bg-white px-4 py-4 border-b border-gray-1">
<div class="flex items-center gap-3">
<div class="w-8 h-8 bg-primary/10 rounded-lg flex items-center justify-center">
<svg class="w-5 h-5 text-primary" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/>
<path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/>
</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>
<a href="chat-history.html" class="flex items-center justify-between bg-white px-4 py-4 border-b border-gray-1">
<div class="flex items-center gap-3">
<div class="w-8 h-8 bg-success/10 rounded-lg flex items-center justify-center">
<svg class="w-5 h-5 text-success" viewBox="0 0 24 24" fill="currentColor">
<path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/>
</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">
<svg class="w-5 h-5 text-error" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4"/>
<polyline points="16 17 21 12 16 7"/>
<line x1="21" y1="12" x2="9" y2="12"/>
</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>
</button>
</div>
<!-- 退出确认弹窗 -->
<div id="logoutModal" class="fixed inset-0 bg-black/50 z-50 hidden items-center justify-center">
<div class="bg-white rounded-2xl w-72 overflow-hidden">
<div class="p-6 text-center">
<h3 class="text-base font-medium text-gray-13 mb-2">确认退出</h3>
<p class="text-sm text-gray-7">确认退出当前账号吗?</p>
</div>
<div class="flex border-t border-gray-2">
<button onclick="hideLogoutModal()" class="flex-1 py-3 text-sm text-gray-7 border-r border-gray-2">取消</button>
<button onclick="logout()" class="flex-1 py-3 text-sm text-error font-medium">退出</button>
</div>
</div>
</div>
<!-- 悬浮助手按钮 -->
<!-- 通用底部导航 -->
</body>
</html>

View File

@@ -0,0 +1,109 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>无权限 - 球房运营助手</title>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Noto Sans SC', -apple-system, BlinkMacSystemFont, sans-serif;
}
.bg-pattern {
background-color: #f8fafc;
background-image: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23e34d59' fill-opacity='0.03'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
}
@keyframes shake {
0%, 100% { transform: rotate(0deg); }
25% { transform: rotate(-5deg); }
75% { transform: rotate(5deg); }
}
.shake-animation {
animation: shake 0.5s ease-in-out;
}
</style>
</head>
<body class="bg-pattern min-h-screen flex flex-col">
<!-- 顶部装饰 -->
<div class="absolute top-0 left-0 right-0 h-64 bg-gradient-to-b from-error/10 to-transparent"></div>
<!-- 主体内容 -->
<div class="flex-1 flex flex-col items-center justify-center px-8 relative z-10">
<!-- 图标区域 -->
<div class="relative mb-8">
<!-- 背景光晕 -->
<div class="absolute inset-0 w-32 h-32 bg-error/20 rounded-full blur-xl"></div>
<!-- 主图标 -->
<div class="relative w-28 h-28 bg-gradient-to-br from-rose-400 to-red-500 rounded-3xl flex items-center justify-center shadow-xl shadow-error/30">
<svg class="w-14 h-14 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"/>
<line x1="4.93" y1="4.93" x2="19.07" y2="19.07"/>
</svg>
</div>
<!-- 装饰点 -->
<div class="absolute -top-2 -right-2 w-4 h-4 bg-error rounded-full opacity-60"></div>
<div class="absolute -bottom-1 -left-3 w-3 h-3 bg-rose-300 rounded-full opacity-60"></div>
</div>
<!-- 文字区域 -->
<div class="text-center mb-8">
<h1 class="text-2xl font-bold text-gray-10 mb-3">无访问权限</h1>
<p class="text-sm text-gray-7 leading-relaxed max-w-xs mx-auto">
很抱歉,您的访问申请未通过审核,或当前账号无访问权限
</p>
</div>
<!-- 原因说明卡片 -->
<div class="w-full max-w-sm bg-white rounded-2xl p-5 shadow-lg shadow-gray-200/50 mb-6">
<div class="flex items-start gap-4 mb-4">
<div class="w-10 h-10 bg-error/10 rounded-xl flex items-center justify-center flex-shrink-0">
<svg class="w-5 h-5 text-error" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/>
</svg>
</div>
<div class="flex-1">
<p class="text-sm font-medium text-gray-13 mb-1">可能的原因</p>
<ul class="text-xs text-gray-6 space-y-1">
<li>• 申请信息不完整或不符合要求</li>
<li>• 非本店授权员工账号</li>
<li>• 账号权限已被管理员收回</li>
</ul>
</div>
</div>
<div class="pt-4 border-t border-gray-100">
<div class="flex items-center justify-between">
<span class="text-xs text-gray-6">请联系管理员</span>
<span class="text-sm text-gray-13 font-medium">厉超</span>
</div>
</div>
</div>
<!-- 帮助提示 -->
<div class="flex items-center gap-2 text-gray-6 mb-8">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor">
<path d="M11 18h2v-2h-2v2zm1-16C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm0-14c-2.21 0-4 1.79-4 4h2c0-1.1.9-2 2-2s2 .9 2 2c0 2-3 1.75-3 5h2c0-2.25 3-2.5 3-5 0-2.21-1.79-4-4-4z"/>
</svg>
<span class="text-xs">如有疑问,请联系管理员重新申请</span>
</div>
</div>
<!-- 底部按钮 -->
<div class="px-8 pb-12">
<button onclick="switchAccount()" class="w-full py-3.5 bg-white border border-gray-200 rounded-xl text-gray-9 font-medium text-sm flex items-center justify-center gap-2 shadow-sm">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4"/>
<polyline points="16 17 21 12 16 7"/>
<line x1="21" y1="12" x2="9" y2="12"/>
</svg>
更换登录账号
</button>
</div>
</body>
</html>

View File

@@ -0,0 +1,139 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>备注 - 球房运营助手</title>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link href="../css/notes.css" rel="stylesheet">
</head>
<body class="bg-gray-1 min-h-screen">
<!-- 顶部导航 -->
<div class="safe-area-top bg-white sticky top-0 z-10">
<div class="h-11 flex items-center relative border-b border-gray-2 px-4">
<button onclick="history.back()" class="absolute left-4 p-1">
<svg class="w-5 h-5 text-gray-10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="15 18 9 12 15 6"/>
</svg>
</button>
<h1 class="flex-1 text-center text-base font-medium text-gray-13">备注</h1>
</div>
</div>
<!-- 备注列表(按时间由近及远) -->
<div class="p-4 space-y-3">
<div class="bg-white rounded-2xl p-4 shadow-sm">
<p class="text-sm text-gray-13 leading-relaxed mb-3">
小燕本月表现优秀课时完成率达到120%客户评价全部5星。建议下月提升课时费标准同时安排更多VIP客户给她。
</p>
<div class="flex items-center justify-between">
<span class="note-tag tag-coach">助教:小燕</span>
<span class="text-xs text-gray-6">2024-11-27 16:00</span>
</div>
</div>
<div class="bg-white rounded-2xl p-4 shadow-sm">
<p class="text-sm text-gray-13 leading-relaxed mb-3">
客户今天表示下周有朋友生日聚会想预约包厢。已告知包厢需要提前3天预约客户说会尽快确定时间再联系。
</p>
<div class="flex items-center justify-between">
<span class="note-tag tag-customer">客户:王先生</span>
<span class="text-xs text-gray-6">2024-11-27 15:30</span>
</div>
</div>
<div class="bg-white rounded-2xl p-4 shadow-sm">
<p class="text-sm text-gray-13 leading-relaxed mb-3">
完成高优先召回任务。客户反馈最近工作太忙这周末会来店里。已提醒客户储值卡还有2000元余额下月到期需要续费。
</p>
<div class="flex items-center justify-between">
<span class="note-tag tag-customer">客户:王先生</span>
<span class="text-xs text-gray-6">2024-11-26 18:45</span>
</div>
</div>
<div class="bg-white rounded-2xl p-4 shadow-sm">
<p class="text-sm text-gray-13 leading-relaxed mb-3">
Amy最近工作状态很好主动承担了培训新员工的任务。但需要注意她最近加班较多避免过度疲劳。建议适当调整排班。
</p>
<div class="flex items-center justify-between">
<span class="note-tag tag-coach">助教Amy</span>
<span class="text-xs text-gray-6">2024-11-26 11:30</span>
</div>
</div>
<div class="bg-white rounded-2xl p-4 shadow-sm">
<p class="text-sm text-gray-13 leading-relaxed mb-3">
泡芙本月表现优秀课时完成率达到120%客户评价全部5星。建议下月提升课时费标准。
</p>
<div class="flex items-center justify-between">
<span class="note-tag tag-customer">客户:陈女士</span>
<span class="text-xs text-gray-6">2024-11-25 10:20</span>
</div>
</div>
<div class="bg-white rounded-2xl p-4 shadow-sm">
<p class="text-sm text-gray-13 leading-relaxed mb-3">
泡芙的斯诺克教学水平有明显提升,最近几位客户反馈都很好。可以考虑让她带更多斯诺克方向的课程。
</p>
<div class="flex items-center justify-between">
<span class="note-tag tag-coach">助教:泡芙</span>
<span class="text-xs text-gray-6">2024-11-25 14:20</span>
</div>
</div>
<div class="bg-white rounded-2xl p-4 shadow-sm">
<p class="text-sm text-gray-13 leading-relaxed mb-3">
客户对今天的服务非常满意,特别提到小燕的教学很专业。客户表示会推荐朋友来店里体验。
</p>
<div class="flex items-center justify-between">
<span class="note-tag tag-customer">客户:李女士</span>
<span class="text-xs text-gray-6">2024-11-24 21:15</span>
</div>
</div>
<div class="bg-white rounded-2xl p-4 shadow-sm">
<p class="text-sm text-gray-13 leading-relaxed mb-3">
小燕反馈近期有几位客户希望增加晚间时段的课程建议协调排班增加21:00-23:00时段的助教配置。
</p>
<div class="flex items-center justify-between">
<span class="note-tag tag-coach">助教:小燕</span>
<span class="text-xs text-gray-6">2024-11-24 09:00</span>
</div>
</div>
<div class="bg-white rounded-2xl p-4 shadow-sm">
<p class="text-sm text-gray-13 leading-relaxed mb-3">
关系构建任务完成。与客户进行了30分钟的深入交流了解到客户是企业高管经常需要商务宴请场地。已记录客户需求后续可以推荐团建套餐。
</p>
<div class="flex items-center justify-between">
<span class="note-tag tag-customer">客户:张先生</span>
<span class="text-xs text-gray-6">2024-11-23 19:30</span>
</div>
</div>
<div class="bg-white rounded-2xl p-4 shadow-sm">
<p class="text-sm text-gray-13 leading-relaxed mb-3">
客户今天充值了5000元选择的是尊享套餐。客户提到喜欢安静的环境以后尽量安排包厢。
</p>
<div class="flex items-center justify-between">
<span class="note-tag tag-customer">客户:张先生</span>
<span class="text-xs text-gray-6">2024-11-22 14:00</span>
</div>
</div>
<div class="bg-white rounded-2xl p-4 shadow-sm">
<p class="text-sm text-gray-13 leading-relaxed mb-3">
Amy本周请假2天处理家事已安排泡芙和小燕分担她的客户。回来后需要跟进客户交接情况。
</p>
<div class="flex items-center justify-between">
<span class="note-tag tag-coach">助教Amy</span>
<span class="text-xs text-gray-6">2024-11-22 10:15</span>
</div>
</div>
<div class="bg-white rounded-2xl p-4 shadow-sm">
<p class="text-sm text-gray-13 leading-relaxed mb-3">
Amy最近工作状态很好主动承担了培训新员工的任务。但需要注意她最近加班较多避免过度疲劳。
</p>
<div class="flex items-center justify-between">
<span class="note-tag tag-customer">客户:李女士</span>
<span class="text-xs text-gray-6">2024-11-21 09:45</span>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,789 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>业绩明细 - 球房运营助手</title>
<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">
<style>
body {
font-family: 'Noto Sans SC', -apple-system, BlinkMacSystemFont, sans-serif;
}
.record-item {
transition: all 0.2s ease;
}
.record-item:active {
background: #f5f5f5;
}
.perf-value {
font-variant-numeric: tabular-nums;
}
.date-divider {
position: relative;
display: flex;
align-items: center;
gap: 8px;
padding: 14px 16px 4px;
}
.date-divider .dd-line {
flex: 1;
height: 1px;
background: #dcdcdc;
}
.date-divider .dd-date {
font-size: 11px;
color: #8b8b8b;
font-weight: 500;
white-space: nowrap;
}
.date-divider .dd-stats {
font-size: 11px;
color: #a6a6a6;
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
</style>
</head>
<body class="bg-gray-1 min-h-screen pb-8">
<!-- Tailwind 动态类预加载 -->
<div class="hidden">
<span class="rounded-lg from-blue-400 to-indigo-500 from-pink-400 to-rose-500 from-teal-400 to-emerald-500 from-green-400 to-teal-500 from-orange-400 to-amber-500 from-purple-400 to-violet-500 from-violet-400 to-purple-500 from-teal-300 to-emerald-400 from-amber-400 to-yellow-500 from-sky-400 to-blue-500 from-fuchsia-400 to-pink-500 from-rose-400 to-red-500 from-lime-400 to-green-500 from-slate-400 to-gray-500 from-indigo-300 to-violet-400 from-pink-300 to-rose-400 from-emerald-300 to-teal-400 from-cyan-400 to-sky-500 from-orange-300 to-red-400 from-yellow-400 to-orange-500 from-blue-300 to-purple-400 from-gray-300 to-gray-400"></span>
</div>
<!-- Banner -->
<div class="banner-bg theme-coral texture-aurora relative text-white">
<div class="h-11 flex items-center relative px-4">
<button onclick="history.back()" class="absolute left-4 p-1">
<svg class="w-5 h-5 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="15 18 9 12 15 6"/>
</svg>
</button>
<h1 class="flex-1 text-center text-base font-medium">业绩明细</h1>
</div>
<div class="px-5 pt-1 pb-5">
<div class="flex items-center gap-4">
<div class="w-14 h-14 rounded-2xl bg-white/20 backdrop-blur-sm flex items-center justify-center shadow-lg overflow-hidden flex-shrink-0">
<img src="../img/zjtx.png" class="w-full h-full object-cover" alt="助教头像">
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-2 mb-1">
<span class="text-lg font-semibold">小燕</span>
<span class="px-2 py-0.5 bg-amber-400/30 text-amber-100 rounded-full text-xs">星级</span>
</div>
<div class="flex items-center gap-2 text-white/70 text-xs">
<span>球会名称店</span>
</div>
</div>
</div>
</div>
</div>
<!-- 月份切换 -->
<div class="bg-white px-4 py-3 border-b border-gray-2 flex items-center justify-center gap-6">
<button id="prevMonthBtn" onclick="switchMonth('prev')" class="p-1.5 rounded-full hover:bg-gray-100">
<svg class="w-4 h-4 text-gray-8" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="15 18 9 12 15 6"/>
</svg>
</button>
<span id="monthLabel" class="text-sm font-semibold text-gray-13">2026年2月</span>
<button id="nextMonthBtn" onclick="switchMonth('next')" class="p-1.5 rounded-full hover:bg-gray-100 opacity-30" disabled>
<svg class="w-4 h-4 text-gray-8" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="9 6 15 12 9 18"/>
</svg>
</button>
</div>
<!-- 统计概览 -->
<div class="px-4 py-3 bg-white border-b border-gray-2">
<div class="flex items-start justify-between">
<div class="text-center flex-1">
<p class="text-[10px] text-gray-6 mb-0.5">总记录</p>
<p class="text-lg font-bold text-gray-13 perf-value" id="totalCount">32笔</p>
</div>
<div class="w-px h-10 bg-gray-2 mt-0.5"></div>
<div class="text-center flex-1">
<p class="text-[10px] text-gray-6 mb-0.5">总业绩时长</p>
<p class="text-lg font-bold text-primary perf-value" id="totalMinutes">59.0h</p>
<p class="text-[10px] text-gray-5">折算前 60.5h</p>
<p class="text-[10px] text-warning">预估</p>
</div>
<div class="w-px h-10 bg-gray-2 mt-0.5"></div>
<div class="text-center flex-1">
<p class="text-[10px] text-gray-6 mb-0.5">收入</p>
<p class="text-lg font-bold text-success perf-value" id="totalIncome">¥4,720</p>
<p class="text-[10px] text-warning">预估</p>
</div>
</div>
</div>
<!-- 记录列表 -->
<div class="p-4">
<div id="recordsList" class="bg-white rounded-2xl shadow-sm overflow-hidden">
<!-- 2月7日 -->
<div class="date-divider"><span class="dd-date">2月7日</span><div class="dd-line"></div><span class="dd-stats">时长 6.0h · 预估收入 ¥510</span></div>
<div class="record-item flex items-center gap-3 px-4 py-2.5">
<div class="w-[38px] h-[38px] rounded-lg bg-gradient-to-br from-blue-400 to-indigo-500 flex items-center justify-center text-white text-sm font-medium shadow-sm flex-shrink-0"></div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2 min-w-0">
<span class="text-sm font-medium text-gray-13 flex-shrink-0">王先生</span>
<span class="text-xs text-gray-6">20:00-22:00</span>
</div>
<div class="flex items-center gap-1 flex-shrink-0 ml-2">
<span class="text-sm font-bold text-emerald-600 perf-value">2.0h</span>
</div>
</div>
<div class="flex items-center justify-between mt-1">
<div class="flex items-center gap-1.5">
<span class="px-1.5 py-px bg-emerald-50 text-emerald-700 rounded text-[11px] font-medium flex-shrink-0">基础课</span>
<span class="text-xs text-gray-7">3号台</span>
</div>
<span class="text-[11px] text-gray-5 flex-shrink-0">我的预估收入 <span class="font-medium text-gray-9">¥160</span></span>
</div>
</div>
</div>
<div class="record-item flex items-center gap-3 px-4 py-2.5">
<div class="w-[38px] h-[38px] rounded-lg bg-gradient-to-br from-pink-400 to-rose-500 flex items-center justify-center text-white text-sm font-medium shadow-sm flex-shrink-0"></div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2 min-w-0">
<span class="text-sm font-medium text-gray-13 flex-shrink-0">李女士</span>
<span class="text-xs text-gray-6">16:00-18:00</span>
</div>
<div class="flex items-center gap-1 flex-shrink-0 ml-2">
<span class="text-sm font-bold text-emerald-600 perf-value">2.0h</span>
</div>
</div>
<div class="flex items-center justify-between mt-1">
<div class="flex items-center gap-1.5">
<span class="px-1.5 py-px bg-blue-50 text-blue-700 rounded text-[11px] font-medium flex-shrink-0">包厢课</span>
<span class="text-xs text-gray-7">VIP1号房</span>
</div>
<span class="text-[11px] text-gray-5 flex-shrink-0">我的预估收入 <span class="font-medium text-gray-9">¥190</span></span>
</div>
</div>
</div>
<div class="record-item flex items-center gap-3 px-4 py-2.5">
<div class="w-[38px] h-[38px] rounded-lg bg-gradient-to-br from-teal-400 to-emerald-500 flex items-center justify-center text-white text-sm font-medium shadow-sm flex-shrink-0"></div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2 min-w-0">
<span class="text-sm font-medium text-gray-13 flex-shrink-0">陈女士</span>
<span class="text-xs text-gray-6">10:00-12:00</span>
</div>
<div class="flex items-center gap-1 flex-shrink-0 ml-2">
<span class="text-sm font-bold text-emerald-600 perf-value">2.0h</span>
</div>
</div>
<div class="flex items-center justify-between mt-1">
<div class="flex items-center gap-1.5">
<span class="px-1.5 py-px bg-emerald-50 text-emerald-700 rounded text-[11px] font-medium flex-shrink-0">基础课</span>
<span class="text-xs text-gray-7">2号台</span>
</div>
<span class="text-[11px] text-gray-5 flex-shrink-0">我的预估收入 <span class="font-medium text-gray-9">¥160</span></span>
</div>
</div>
</div>
<!-- 2月6日 -->
<div class="date-divider"><span class="dd-date">2月6日</span><div class="dd-line"></div><span class="dd-stats">时长 3.5h · 预估收入 ¥280</span></div>
<div class="record-item flex items-center gap-3 px-4 py-2.5">
<div class="w-[38px] h-[38px] rounded-lg bg-gradient-to-br from-green-400 to-teal-500 flex items-center justify-center text-white text-sm font-medium shadow-sm flex-shrink-0"></div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2 min-w-0">
<span class="text-sm font-medium text-gray-13 flex-shrink-0">张先生</span>
<span class="text-xs text-gray-6">19:00-21:00</span>
</div>
<div class="flex items-center gap-1 flex-shrink-0 ml-2">
<span class="text-sm font-bold text-emerald-600 perf-value">2.0h</span>
</div>
</div>
<div class="flex items-center justify-between mt-1">
<div class="flex items-center gap-1.5">
<span class="px-1.5 py-px bg-emerald-50 text-emerald-700 rounded text-[11px] font-medium flex-shrink-0">基础课</span>
<span class="text-xs text-gray-7">5号台</span>
</div>
<span class="text-[11px] text-gray-5 flex-shrink-0">我的预估收入 <span class="font-medium text-gray-9">¥160</span></span>
</div>
</div>
</div>
<div class="record-item flex items-center gap-3 px-4 py-2.5">
<div class="w-[38px] h-[38px] rounded-lg bg-gradient-to-br from-orange-400 to-amber-500 flex items-center justify-center text-white text-sm font-medium shadow-sm flex-shrink-0"></div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2 min-w-0">
<span class="text-sm font-medium text-gray-13 flex-shrink-0">刘先生</span>
<span class="text-xs text-gray-6">15:30-17:00</span>
</div>
<div class="flex items-center gap-1 flex-shrink-0 ml-2">
<span class="text-sm font-bold text-amber-600 perf-value">1.5h</span>
</div>
</div>
<div class="flex items-center justify-between mt-1">
<div class="flex items-center gap-1.5">
<span class="px-1.5 py-px bg-amber-50 text-amber-700 rounded text-[11px] font-medium flex-shrink-0">打赏课</span>
<span class="text-xs text-gray-7">打赏</span>
</div>
<span class="text-[11px] text-gray-5 flex-shrink-0">我的预估收入 <span class="font-medium text-gray-9">¥120</span></span>
</div>
</div>
</div>
<!-- 2月5日 -->
<div class="date-divider"><span class="dd-date">2月5日</span><div class="dd-line"></div><span class="dd-stats">时长 4.0h · 预估收入 ¥320</span></div>
<div class="record-item flex items-center gap-3 px-4 py-2.5">
<div class="w-[38px] h-[38px] rounded-lg bg-gradient-to-br from-teal-400 to-emerald-500 flex items-center justify-center text-white text-sm font-medium shadow-sm flex-shrink-0"></div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2 min-w-0">
<span class="text-sm font-medium text-gray-13 flex-shrink-0">陈女士</span>
<span class="text-xs text-gray-6">20:00-22:00</span>
</div>
<div class="flex items-center gap-1 flex-shrink-0 ml-2">
<span class="text-sm font-bold text-emerald-600 perf-value">2.0h</span>
</div>
</div>
<div class="flex items-center justify-between mt-1">
<div class="flex items-center gap-1.5">
<span class="px-1.5 py-px bg-emerald-50 text-emerald-700 rounded text-[11px] font-medium flex-shrink-0">基础课</span>
<span class="text-xs text-gray-7">2号台</span>
</div>
<span class="text-[11px] text-gray-5 flex-shrink-0">我的预估收入 <span class="font-medium text-gray-9">¥160</span></span>
</div>
</div>
</div>
<div class="record-item flex items-center gap-3 px-4 py-2.5">
<div class="w-[38px] h-[38px] rounded-lg bg-gradient-to-br from-purple-400 to-violet-500 flex items-center justify-center text-white text-sm font-medium shadow-sm flex-shrink-0"></div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2 min-w-0">
<span class="text-sm font-medium text-gray-13 flex-shrink-0">赵先生</span>
<span class="text-xs text-gray-6">14:00-16:00</span>
</div>
<div class="flex items-center gap-1 flex-shrink-0 ml-2">
<span class="text-sm font-bold text-emerald-600 perf-value">2.0h</span> <span class="text-[10px] text-gray-5 font-normal">(折0.5h)</span>
</div>
</div>
<div class="flex items-center justify-between mt-1">
<div class="flex items-center gap-1.5">
<span class="px-1.5 py-px bg-emerald-50 text-emerald-700 rounded text-[11px] font-medium flex-shrink-0">基础课</span>
<span class="text-xs text-gray-7">7号台</span>
</div>
<span class="text-[11px] text-gray-5 flex-shrink-0">我的预估收入 <span class="font-medium text-gray-9">¥160</span></span>
</div>
</div>
</div>
<!-- 2月4日 -->
<div class="date-divider"><span class="dd-date">2月4日</span><div class="dd-line"></div><span class="dd-stats">时长 4.0h · 预估收入 ¥350</span></div>
<div class="record-item flex items-center gap-3 px-4 py-2.5">
<div class="w-[38px] h-[38px] rounded-lg bg-gradient-to-br from-violet-400 to-purple-500 flex items-center justify-center text-white text-sm font-medium shadow-sm flex-shrink-0"></div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2 min-w-0">
<span class="text-sm font-medium text-gray-13 flex-shrink-0">孙先生</span>
<span class="text-xs text-gray-6">19:00-21:00</span>
</div>
<div class="flex items-center gap-1 flex-shrink-0 ml-2">
<span class="text-sm font-bold text-emerald-600 perf-value">2.0h</span>
</div>
</div>
<div class="flex items-center justify-between mt-1">
<div class="flex items-center gap-1.5">
<span class="px-1.5 py-px bg-blue-50 text-blue-700 rounded text-[11px] font-medium flex-shrink-0">包厢课</span>
<span class="text-xs text-gray-7">VIP2号房</span>
</div>
<span class="text-[11px] text-gray-5 flex-shrink-0">我的预估收入 <span class="font-medium text-gray-9">¥190</span></span>
</div>
</div>
</div>
<div class="record-item flex items-center gap-3 px-4 py-2.5">
<div class="w-[38px] h-[38px] rounded-lg bg-gradient-to-br from-teal-300 to-emerald-400 flex items-center justify-center text-white text-sm font-medium shadow-sm flex-shrink-0"></div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2 min-w-0">
<span class="text-sm font-medium text-gray-13 flex-shrink-0">吴女士</span>
<span class="text-xs text-gray-6">15:00-17:00</span>
</div>
<div class="flex items-center gap-1 flex-shrink-0 ml-2">
<span class="text-sm font-bold text-emerald-600 perf-value">2.0h</span>
</div>
</div>
<div class="flex items-center justify-between mt-1">
<div class="flex items-center gap-1.5">
<span class="px-1.5 py-px bg-emerald-50 text-emerald-700 rounded text-[11px] font-medium flex-shrink-0">基础课</span>
<span class="text-xs text-gray-7">1号台</span>
</div>
<span class="text-[11px] text-gray-5 flex-shrink-0">我的预估收入 <span class="font-medium text-gray-9">¥160</span></span>
</div>
</div>
</div>
<!-- 2月3日 -->
<div class="date-divider"><span class="dd-date">2月3日</span><div class="dd-line"></div><span class="dd-stats">时长 3.5h · 预估收入 ¥280</span></div>
<div class="record-item flex items-center gap-3 px-4 py-2.5">
<div class="w-[38px] h-[38px] rounded-lg bg-gradient-to-br from-amber-400 to-yellow-500 flex items-center justify-center text-white text-sm font-medium shadow-sm flex-shrink-0"></div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2 min-w-0">
<span class="text-sm font-medium text-gray-13 flex-shrink-0">郑先生</span>
<span class="text-xs text-gray-6">20:00-22:00</span>
</div>
<div class="flex items-center gap-1 flex-shrink-0 ml-2">
<span class="text-sm font-bold text-emerald-600 perf-value">2.0h</span>
</div>
</div>
<div class="flex items-center justify-between mt-1">
<div class="flex items-center gap-1.5">
<span class="px-1.5 py-px bg-emerald-50 text-emerald-700 rounded text-[11px] font-medium flex-shrink-0">基础课</span>
<span class="text-xs text-gray-7">4号台</span>
</div>
<span class="text-[11px] text-gray-5 flex-shrink-0">我的预估收入 <span class="font-medium text-gray-9">¥160</span></span>
</div>
</div>
</div>
<div class="record-item flex items-center gap-3 px-4 py-2.5">
<div class="w-[38px] h-[38px] rounded-lg bg-gradient-to-br from-sky-400 to-blue-500 flex items-center justify-center text-white text-sm font-medium shadow-sm flex-shrink-0"></div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2 min-w-0">
<span class="text-sm font-medium text-gray-13 flex-shrink-0">黄女士</span>
<span class="text-xs text-gray-6">14:30-16:00</span>
</div>
<div class="flex items-center gap-1 flex-shrink-0 ml-2">
<span class="text-sm font-bold text-amber-600 perf-value">1.5h</span>
</div>
</div>
<div class="flex items-center justify-between mt-1">
<div class="flex items-center gap-1.5">
<span class="px-1.5 py-px bg-amber-50 text-amber-700 rounded text-[11px] font-medium flex-shrink-0">打赏课</span>
<span class="text-xs text-gray-7">打赏</span>
</div>
<span class="text-[11px] text-gray-5 flex-shrink-0">我的预估收入 <span class="font-medium text-gray-9">¥120</span></span>
</div>
</div>
</div>
<!-- 2月2日 -->
<div class="date-divider"><span class="dd-date">2月2日</span><div class="dd-line"></div><span class="dd-stats">时长 4.0h · 预估收入 ¥350</span></div>
<div class="record-item flex items-center gap-3 px-4 py-2.5">
<div class="w-[38px] h-[38px] rounded-lg bg-gradient-to-br from-fuchsia-400 to-pink-500 flex items-center justify-center text-white text-sm font-medium shadow-sm flex-shrink-0"></div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2 min-w-0">
<span class="text-sm font-medium text-gray-13 flex-shrink-0">林先生</span>
<span class="text-xs text-gray-6">19:00-21:00</span>
</div>
<div class="flex items-center gap-1 flex-shrink-0 ml-2">
<span class="text-sm font-bold text-emerald-600 perf-value">2.0h</span>
</div>
</div>
<div class="flex items-center justify-between mt-1">
<div class="flex items-center gap-1.5">
<span class="px-1.5 py-px bg-emerald-50 text-emerald-700 rounded text-[11px] font-medium flex-shrink-0">基础课</span>
<span class="text-xs text-gray-7">6号台</span>
</div>
<span class="text-[11px] text-gray-5 flex-shrink-0">我的预估收入 <span class="font-medium text-gray-9">¥160</span></span>
</div>
</div>
</div>
<div class="record-item flex items-center gap-3 px-4 py-2.5">
<div class="w-[38px] h-[38px] rounded-lg bg-gradient-to-br from-rose-400 to-red-500 flex items-center justify-center text-white text-sm font-medium shadow-sm flex-shrink-0"></div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2 min-w-0">
<span class="text-sm font-medium text-gray-13 flex-shrink-0">何女士</span>
<span class="text-xs text-gray-6">13:00-15:00</span>
</div>
<div class="flex items-center gap-1 flex-shrink-0 ml-2">
<span class="text-sm font-bold text-emerald-600 perf-value">2.0h</span>
</div>
</div>
<div class="flex items-center justify-between mt-1">
<div class="flex items-center gap-1.5">
<span class="px-1.5 py-px bg-blue-50 text-blue-700 rounded text-[11px] font-medium flex-shrink-0">包厢课</span>
<span class="text-xs text-gray-7">VIP3号房</span>
</div>
<span class="text-[11px] text-gray-5 flex-shrink-0">我的预估收入 <span class="font-medium text-gray-9">¥190</span></span>
</div>
</div>
</div>
<!-- 2月1日 -->
<div class="date-divider"><span class="dd-date">2月1日</span><div class="dd-line"></div><span class="dd-stats">时长 6.0h · 预估收入 ¥510</span></div>
<div class="record-item flex items-center gap-3 px-4 py-2.5">
<div class="w-[38px] h-[38px] rounded-lg bg-gradient-to-br from-blue-400 to-indigo-500 flex items-center justify-center text-white text-sm font-medium shadow-sm flex-shrink-0"></div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2 min-w-0">
<span class="text-sm font-medium text-gray-13 flex-shrink-0">王先生</span>
<span class="text-xs text-gray-6">20:30-22:30</span>
</div>
<div class="flex items-center gap-1 flex-shrink-0 ml-2">
<span class="text-sm font-bold text-emerald-600 perf-value">2.0h</span>
</div>
</div>
<div class="flex items-center justify-between mt-1">
<div class="flex items-center gap-1.5">
<span class="px-1.5 py-px bg-emerald-50 text-emerald-700 rounded text-[11px] font-medium flex-shrink-0">基础课</span>
<span class="text-xs text-gray-7">3号台</span>
</div>
<span class="text-[11px] text-gray-5 flex-shrink-0">我的预估收入 <span class="font-medium text-gray-9">¥160</span></span>
</div>
</div>
</div>
<div class="record-item flex items-center gap-3 px-4 py-2.5">
<div class="w-[38px] h-[38px] rounded-lg bg-gradient-to-br from-lime-400 to-green-500 flex items-center justify-center text-white text-sm font-medium shadow-sm flex-shrink-0"></div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2 min-w-0">
<span class="text-sm font-medium text-gray-13 flex-shrink-0">马先生</span>
<span class="text-xs text-gray-6">16:00-18:00</span>
</div>
<div class="flex items-center gap-1 flex-shrink-0 ml-2">
<span class="text-sm font-bold text-emerald-600 perf-value">2.0h</span>
</div>
</div>
<div class="flex items-center justify-between mt-1">
<div class="flex items-center gap-1.5">
<span class="px-1.5 py-px bg-emerald-50 text-emerald-700 rounded text-[11px] font-medium flex-shrink-0">基础课</span>
<span class="text-xs text-gray-7">8号台</span>
</div>
<span class="text-[11px] text-gray-5 flex-shrink-0">我的预估收入 <span class="font-medium text-gray-9">¥160</span></span>
</div>
</div>
</div>
<div class="record-item flex items-center gap-3 px-4 py-2.5">
<div class="w-[38px] h-[38px] rounded-lg bg-gradient-to-br from-slate-400 to-gray-500 flex items-center justify-center text-white text-sm font-medium shadow-sm flex-shrink-0"></div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2 min-w-0">
<span class="text-sm font-medium text-gray-13 flex-shrink-0">罗女士</span>
<span class="text-xs text-gray-6">12:30-14:30</span>
</div>
<div class="flex items-center gap-1 flex-shrink-0 ml-2">
<span class="text-sm font-bold text-emerald-600 perf-value">2.0h</span> <span class="text-[10px] text-gray-5 font-normal">(折0.5h)</span>
</div>
</div>
<div class="flex items-center justify-between mt-1">
<div class="flex items-center gap-1.5">
<span class="px-1.5 py-px bg-emerald-50 text-emerald-700 rounded text-[11px] font-medium flex-shrink-0">基础课</span>
<span class="text-xs text-gray-7">2号台</span>
</div>
<span class="text-[11px] text-gray-5 flex-shrink-0">我的预估收入 <span class="font-medium text-gray-9">¥160</span></span>
</div>
</div>
</div>
<div class="record-item flex items-center gap-3 px-4 py-2.5">
<div class="w-[38px] h-[38px] rounded-lg bg-gradient-to-br from-indigo-300 to-violet-400 flex items-center justify-center text-white text-sm font-medium shadow-sm flex-shrink-0"></div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2 min-w-0">
<span class="text-sm font-medium text-gray-13 flex-shrink-0">梁先生</span>
<span class="text-xs text-gray-6">10:00-12:00</span>
</div>
<div class="flex items-center gap-1 flex-shrink-0 ml-2">
<span class="text-sm font-bold text-emerald-600 perf-value">2.0h</span>
</div>
</div>
<div class="flex items-center justify-between mt-1">
<div class="flex items-center gap-1.5">
<span class="px-1.5 py-px bg-emerald-50 text-emerald-700 rounded text-[11px] font-medium flex-shrink-0">基础课</span>
<span class="text-xs text-gray-7">5号台</span>
</div>
<span class="text-[11px] text-gray-5 flex-shrink-0">我的预估收入 <span class="font-medium text-gray-9">¥160</span></span>
</div>
</div>
</div>
<div class="record-item flex items-center gap-3 px-4 py-2.5">
<div class="w-[38px] h-[38px] rounded-lg bg-gradient-to-br from-pink-300 to-rose-400 flex items-center justify-center text-white text-sm font-medium shadow-sm flex-shrink-0"></div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2 min-w-0">
<span class="text-sm font-medium text-gray-13 flex-shrink-0">宋女士</span>
<span class="text-xs text-gray-6">8:30-10:00</span>
</div>
<div class="flex items-center gap-1 flex-shrink-0 ml-2">
<span class="text-sm font-bold text-amber-600 perf-value">1.5h</span>
</div>
</div>
<div class="flex items-center justify-between mt-1">
<div class="flex items-center gap-1.5">
<span class="px-1.5 py-px bg-amber-50 text-amber-700 rounded text-[11px] font-medium flex-shrink-0">打赏课</span>
<span class="text-xs text-gray-7">打赏</span>
</div>
<span class="text-[11px] text-gray-5 flex-shrink-0">我的预估收入 <span class="font-medium text-gray-9">¥120</span></span>
</div>
</div>
</div>
<div class="record-item flex items-center gap-3 px-4 py-2.5">
<div class="w-[38px] h-[38px] rounded-lg bg-gradient-to-br from-emerald-300 to-teal-400 flex items-center justify-center text-white text-sm font-medium shadow-sm flex-shrink-0"></div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2 min-w-0">
<span class="text-sm font-medium text-gray-13 flex-shrink-0">谢先生</span>
<span class="text-xs text-gray-6">7:00-8:00</span>
</div>
<div class="flex items-center gap-1 flex-shrink-0 ml-2">
<span class="text-sm font-bold text-emerald-600 perf-value">1.0h</span>
</div>
</div>
<div class="flex items-center justify-between mt-1">
<div class="flex items-center gap-1.5">
<span class="px-1.5 py-px bg-emerald-50 text-emerald-700 rounded text-[11px] font-medium flex-shrink-0">基础课</span>
<span class="text-xs text-gray-7">1号台</span>
</div>
<span class="text-[11px] text-gray-5 flex-shrink-0">我的预估收入 <span class="font-medium text-gray-9">¥80</span></span>
</div>
</div>
</div>
<!-- 1月31日 -->
<div class="date-divider"><span class="dd-date">1月31日</span><div class="dd-line"></div><span class="dd-stats">时长 5.5h · 收入 ¥470</span></div>
<div class="record-item flex items-center gap-3 px-4 py-2.5">
<div class="w-[38px] h-[38px] rounded-lg bg-gradient-to-br from-cyan-400 to-sky-500 flex items-center justify-center text-white text-sm font-medium shadow-sm flex-shrink-0"></div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2 min-w-0">
<span class="text-sm font-medium text-gray-13 flex-shrink-0">韩女士</span>
<span class="text-xs text-gray-6">21:00-23:00</span>
</div>
<div class="flex items-center gap-1 flex-shrink-0 ml-2">
<span class="text-sm font-bold text-emerald-600 perf-value">2.0h</span>
</div>
</div>
<div class="flex items-center justify-between mt-1">
<div class="flex items-center gap-1.5">
<span class="px-1.5 py-px bg-emerald-50 text-emerald-700 rounded text-[11px] font-medium flex-shrink-0">基础课</span>
<span class="text-xs text-gray-7">4号台</span>
</div>
<span class="text-[11px] text-gray-5 flex-shrink-0">我的收入 <span class="font-medium text-gray-9">¥160</span></span>
</div>
</div>
</div>
<div class="record-item flex items-center gap-3 px-4 py-2.5">
<div class="w-[38px] h-[38px] rounded-lg bg-gradient-to-br from-orange-300 to-red-400 flex items-center justify-center text-white text-sm font-medium shadow-sm flex-shrink-0"></div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2 min-w-0">
<span class="text-sm font-medium text-gray-13 flex-shrink-0">唐先生</span>
<span class="text-xs text-gray-6">18:30-20:30</span>
</div>
<div class="flex items-center gap-1 flex-shrink-0 ml-2">
<span class="text-sm font-bold text-emerald-600 perf-value">2.0h</span> <span class="text-[10px] text-gray-5 font-normal">(折0.5h)</span>
</div>
</div>
<div class="flex items-center justify-between mt-1">
<div class="flex items-center gap-1.5">
<span class="px-1.5 py-px bg-blue-50 text-blue-700 rounded text-[11px] font-medium flex-shrink-0">包厢课</span>
<span class="text-xs text-gray-7">VIP2号房</span>
</div>
<span class="text-[11px] text-gray-5 flex-shrink-0">我的收入 <span class="font-medium text-gray-9">¥190</span></span>
</div>
</div>
</div>
<div class="record-item flex items-center gap-3 px-4 py-2.5">
<div class="w-[38px] h-[38px] rounded-lg bg-gradient-to-br from-yellow-400 to-orange-500 flex items-center justify-center text-white text-sm font-medium shadow-sm flex-shrink-0"></div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2 min-w-0">
<span class="text-sm font-medium text-gray-13 flex-shrink-0">冯女士</span>
<span class="text-xs text-gray-6">14:00-16:00</span>
</div>
<div class="flex items-center gap-1 flex-shrink-0 ml-2">
<span class="text-sm font-bold text-emerald-600 perf-value">2.0h</span>
</div>
</div>
<div class="flex items-center justify-between mt-1">
<div class="flex items-center gap-1.5">
<span class="px-1.5 py-px bg-emerald-50 text-emerald-700 rounded text-[11px] font-medium flex-shrink-0">基础课</span>
<span class="text-xs text-gray-7">6号台</span>
</div>
<span class="text-[11px] text-gray-5 flex-shrink-0">我的收入 <span class="font-medium text-gray-9">¥160</span></span>
</div>
</div>
</div>
<!-- 1月30日 -->
<div class="date-divider"><span class="dd-date">1月30日</span><div class="dd-line"></div><span class="dd-stats">时长 3.5h · 收入 ¥280</span></div>
<div class="record-item flex items-center gap-3 px-4 py-2.5">
<div class="w-[38px] h-[38px] rounded-lg bg-gradient-to-br from-green-400 to-teal-500 flex items-center justify-center text-white text-sm font-medium shadow-sm flex-shrink-0"></div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2 min-w-0">
<span class="text-sm font-medium text-gray-13 flex-shrink-0">张先生</span>
<span class="text-xs text-gray-6">19:30-21:30</span>
</div>
<div class="flex items-center gap-1 flex-shrink-0 ml-2">
<span class="text-sm font-bold text-emerald-600 perf-value">2.0h</span>
</div>
</div>
<div class="flex items-center justify-between mt-1">
<div class="flex items-center gap-1.5">
<span class="px-1.5 py-px bg-emerald-50 text-emerald-700 rounded text-[11px] font-medium flex-shrink-0">基础课</span>
<span class="text-xs text-gray-7">5号台</span>
</div>
<span class="text-[11px] text-gray-5 flex-shrink-0">我的收入 <span class="font-medium text-gray-9">¥160</span></span>
</div>
</div>
</div>
<div class="record-item flex items-center gap-3 px-4 py-2.5">
<div class="w-[38px] h-[38px] rounded-lg bg-gradient-to-br from-orange-400 to-amber-500 flex items-center justify-center text-white text-sm font-medium shadow-sm flex-shrink-0"></div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2 min-w-0">
<span class="text-sm font-medium text-gray-13 flex-shrink-0">刘先生</span>
<span class="text-xs text-gray-6">14:30-16:00</span>
</div>
<div class="flex items-center gap-1 flex-shrink-0 ml-2">
<span class="text-sm font-bold text-amber-600 perf-value">1.5h</span>
</div>
</div>
<div class="flex items-center justify-between mt-1">
<div class="flex items-center gap-1.5">
<span class="px-1.5 py-px bg-amber-50 text-amber-700 rounded text-[11px] font-medium flex-shrink-0">打赏课</span>
<span class="text-xs text-gray-7">打赏</span>
</div>
<span class="text-[11px] text-gray-5 flex-shrink-0">我的收入 <span class="font-medium text-gray-9">¥120</span></span>
</div>
</div>
</div>
<!-- 1月29日 -->
<div class="date-divider"><span class="dd-date">1月29日</span><div class="dd-line"></div><span class="dd-stats">时长 4.0h · 收入 ¥320</span></div>
<div class="record-item flex items-center gap-3 px-4 py-2.5">
<div class="w-[38px] h-[38px] rounded-lg bg-gradient-to-br from-teal-400 to-emerald-500 flex items-center justify-center text-white text-sm font-medium shadow-sm flex-shrink-0"></div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2 min-w-0">
<span class="text-sm font-medium text-gray-13 flex-shrink-0">陈女士</span>
<span class="text-xs text-gray-6">20:00-22:00</span>
</div>
<div class="flex items-center gap-1 flex-shrink-0 ml-2">
<span class="text-sm font-bold text-emerald-600 perf-value">2.0h</span>
</div>
</div>
<div class="flex items-center justify-between mt-1">
<div class="flex items-center gap-1.5">
<span class="px-1.5 py-px bg-emerald-50 text-emerald-700 rounded text-[11px] font-medium flex-shrink-0">基础课</span>
<span class="text-xs text-gray-7">2号台</span>
</div>
<span class="text-[11px] text-gray-5 flex-shrink-0">我的收入 <span class="font-medium text-gray-9">¥160</span></span>
</div>
</div>
</div>
<div class="record-item flex items-center gap-3 px-4 py-2.5">
<div class="w-[38px] h-[38px] rounded-lg bg-gradient-to-br from-pink-400 to-rose-500 flex items-center justify-center text-white text-sm font-medium shadow-sm flex-shrink-0"></div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2 min-w-0">
<span class="text-sm font-medium text-gray-13 flex-shrink-0">李女士</span>
<span class="text-xs text-gray-6">13:00-15:00</span>
</div>
<div class="flex items-center gap-1 flex-shrink-0 ml-2">
<span class="text-sm font-bold text-emerald-600 perf-value">2.0h</span>
</div>
</div>
<div class="flex items-center justify-between mt-1">
<div class="flex items-center gap-1.5">
<span class="px-1.5 py-px bg-blue-50 text-blue-700 rounded text-[11px] font-medium flex-shrink-0">包厢课</span>
<span class="text-xs text-gray-7">VIP1号房</span>
</div>
<span class="text-[11px] text-gray-5 flex-shrink-0">我的收入 <span class="font-medium text-gray-9">¥190</span></span>
</div>
</div>
</div>
<!-- 1月28日 -->
<div class="date-divider"><span class="dd-date">1月28日</span><div class="dd-line"></div><span class="dd-stats">时长 4.0h · 收入 ¥350</span></div>
<div class="record-item flex items-center gap-3 px-4 py-2.5">
<div class="w-[38px] h-[38px] rounded-lg bg-gradient-to-br from-purple-400 to-violet-500 flex items-center justify-center text-white text-sm font-medium shadow-sm flex-shrink-0"></div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2 min-w-0">
<span class="text-sm font-medium text-gray-13 flex-shrink-0">赵先生</span>
<span class="text-xs text-gray-6">19:00-21:00</span>
</div>
<div class="flex items-center gap-1 flex-shrink-0 ml-2">
<span class="text-sm font-bold text-emerald-600 perf-value">2.0h</span> <span class="text-[10px] text-gray-5 font-normal">(折0.5h)</span>
</div>
</div>
<div class="flex items-center justify-between mt-1">
<div class="flex items-center gap-1.5">
<span class="px-1.5 py-px bg-emerald-50 text-emerald-700 rounded text-[11px] font-medium flex-shrink-0">基础课</span>
<span class="text-xs text-gray-7">7号台</span>
</div>
<span class="text-[11px] text-gray-5 flex-shrink-0">我的收入 <span class="font-medium text-gray-9">¥160</span></span>
</div>
</div>
</div>
<div class="record-item flex items-center gap-3 px-4 py-2.5">
<div class="w-[38px] h-[38px] rounded-lg bg-gradient-to-br from-blue-300 to-purple-400 flex items-center justify-center text-white text-sm font-medium shadow-sm flex-shrink-0"></div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2 min-w-0">
<span class="text-sm font-medium text-gray-13 flex-shrink-0">董先生</span>
<span class="text-xs text-gray-6">15:00-17:00</span>
</div>
<div class="flex items-center gap-1 flex-shrink-0 ml-2">
<span class="text-sm font-bold text-emerald-600 perf-value">2.0h</span>
</div>
</div>
<div class="flex items-center justify-between mt-1">
<div class="flex items-center gap-1.5">
<span class="px-1.5 py-px bg-emerald-50 text-emerald-700 rounded text-[11px] font-medium flex-shrink-0">基础课</span>
<span class="text-xs text-gray-7">3号台</span>
</div>
<span class="text-[11px] text-gray-5 flex-shrink-0">我的收入 <span class="font-medium text-gray-9">¥160</span></span>
</div>
</div>
</div>
<!-- 1月27日 -->
<div class="date-divider"><span class="dd-date">1月27日</span><div class="dd-line"></div><span class="dd-stats">时长 4.0h · 收入 ¥350</span></div>
<div class="record-item flex items-center gap-3 px-4 py-2.5">
<div class="w-[38px] h-[38px] rounded-lg bg-gradient-to-br from-violet-400 to-purple-500 flex items-center justify-center text-white text-sm font-medium shadow-sm flex-shrink-0"></div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2 min-w-0">
<span class="text-sm font-medium text-gray-13 flex-shrink-0">孙先生</span>
<span class="text-xs text-gray-6">20:00-22:00</span>
</div>
<div class="flex items-center gap-1 flex-shrink-0 ml-2">
<span class="text-sm font-bold text-emerald-600 perf-value">2.0h</span>
</div>
</div>
<div class="flex items-center justify-between mt-1">
<div class="flex items-center gap-1.5">
<span class="px-1.5 py-px bg-blue-50 text-blue-700 rounded text-[11px] font-medium flex-shrink-0">包厢课</span>
<span class="text-xs text-gray-7">VIP2号房</span>
</div>
<span class="text-[11px] text-gray-5 flex-shrink-0">我的收入 <span class="font-medium text-gray-9">¥190</span></span>
</div>
</div>
</div>
<div class="record-item flex items-center gap-3 px-4 py-2.5">
<div class="w-[38px] h-[38px] rounded-lg bg-gradient-to-br from-sky-400 to-blue-500 flex items-center justify-center text-white text-sm font-medium shadow-sm flex-shrink-0"></div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2 min-w-0">
<span class="text-sm font-medium text-gray-13 flex-shrink-0">黄女士</span>
<span class="text-xs text-gray-6">14:30-16:30</span>
</div>
<div class="flex items-center gap-1 flex-shrink-0 ml-2">
<span class="text-sm font-bold text-amber-600 perf-value">1.5h</span>
</div>
</div>
<div class="flex items-center justify-between mt-1">
<div class="flex items-center gap-1.5">
<span class="px-1.5 py-px bg-amber-50 text-amber-700 rounded text-[11px] font-medium flex-shrink-0">打赏课</span>
<span class="text-xs text-gray-7">打赏</span>
</div>
<span class="text-[11px] text-gray-5 flex-shrink-0">我的收入 <span class="font-medium text-gray-9">¥120</span></span>
</div>
</div>
</div>
<div class="px-4 py-4 text-center"><p class="text-[10px] text-gray-5">— 已加载全部记录 —</p></div>
</div>
</div>
<!-- 悬浮助手按钮 -->
<!-- 月份切换交互 -->
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,129 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>审核中 - 球房运营助手</title>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Noto Sans SC', -apple-system, BlinkMacSystemFont, sans-serif;
}
.bg-pattern {
background-color: #f8fafc;
background-image: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%230052d9' fill-opacity='0.03'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
}
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
.float-animation {
animation: float 3s ease-in-out infinite;
}
@keyframes pulse-soft {
0%, 100% { opacity: 0.6; }
50% { opacity: 1; }
}
.pulse-soft {
animation: pulse-soft 2s ease-in-out infinite;
}
</style>
</head>
<body class="bg-pattern min-h-screen flex flex-col">
<!-- 顶部装饰 -->
<div class="absolute top-0 left-0 right-0 h-64 bg-gradient-to-b from-warning/10 to-transparent"></div>
<!-- 主体内容 -->
<div class="flex-1 flex flex-col items-center justify-center px-8 relative z-10">
<!-- 图标区域 -->
<div class="relative mb-8 float-animation">
<!-- 背景光晕 -->
<div class="absolute inset-0 w-32 h-32 bg-warning/20 rounded-full blur-xl"></div>
<!-- 主图标 -->
<div class="relative w-28 h-28 bg-gradient-to-br from-amber-400 to-orange-500 rounded-3xl flex items-center justify-center shadow-xl shadow-warning/30">
<svg class="w-14 h-14 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"/>
<polyline points="12 6 12 12 16 14"/>
</svg>
</div>
<!-- 装饰点 -->
<div class="absolute -top-2 -right-2 w-4 h-4 bg-warning rounded-full pulse-soft"></div>
<div class="absolute -bottom-1 -left-3 w-3 h-3 bg-amber-300 rounded-full pulse-soft" style="animation-delay: 0.5s;"></div>
</div>
<!-- 文字区域 -->
<div class="text-center mb-8">
<h1 class="text-2xl font-bold text-gray-10 mb-3">申请审核中</h1>
<p class="text-sm text-gray-7 leading-relaxed max-w-xs mx-auto">
您的访问申请已提交成功,正在等待管理员审核,请耐心等待
</p>
</div>
<!-- 进度提示卡片 -->
<div class="w-full max-w-sm bg-white rounded-2xl p-5 shadow-lg shadow-gray-200/50 mb-6">
<div class="flex items-center gap-4 mb-4">
<div class="w-10 h-10 bg-warning/10 rounded-xl flex items-center justify-center">
<svg class="w-5 h-5 text-warning" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/>
</svg>
</div>
<div class="flex-1">
<p class="text-sm font-medium text-gray-13">审核进度</p>
<p class="text-xs text-gray-6">通常需要 1-3 个工作日</p>
</div>
</div>
<div class="flex items-center gap-3">
<div class="flex items-center gap-2">
<div class="w-6 h-6 bg-success rounded-full flex items-center justify-center">
<svg class="w-4 h-4 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3">
<polyline points="20 6 9 17 4 12"/>
</svg>
</div>
<span class="text-xs text-gray-9">已提交</span>
</div>
<div class="flex-1 h-0.5 bg-gray-200">
<div class="h-full w-1/2 bg-warning rounded-full"></div>
</div>
<div class="flex items-center gap-2">
<div class="w-6 h-6 bg-warning/20 rounded-full flex items-center justify-center">
<div class="w-2 h-2 bg-warning rounded-full pulse-soft"></div>
</div>
<span class="text-xs text-gray-6">审核中</span>
</div>
<div class="flex-1 h-0.5 bg-gray-200"></div>
<div class="flex items-center gap-2">
<div class="w-6 h-6 bg-gray-100 rounded-full"></div>
<span class="text-xs text-gray-5">通过</span>
</div>
</div>
</div>
<!-- 联系提示 -->
<div class="flex items-center gap-2 text-gray-6 mb-8">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor">
<path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/>
</svg>
<span class="text-xs">如有疑问,请联系管理员</span>
</div>
</div>
<!-- 底部按钮 -->
<div class="px-8 pb-12">
<button onclick="switchAccount()" class="w-full py-3.5 bg-white border border-gray-200 rounded-xl text-gray-9 font-medium text-sm flex items-center justify-center gap-2 shadow-sm">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4"/>
<polyline points="16 17 21 12 16 7"/>
<line x1="21" y1="12" x2="9" y2="12"/>
</svg>
更换登录账号
</button>
</div>
</body>
</html>

View File

@@ -0,0 +1,356 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>任务详情 - 客户回访</title>
<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">
</head>
<body class="bg-gray-1 min-h-screen">
<!-- 通栏 Banner - 客户信息 -->
<div class="banner-bg theme-teal texture-aurora relative text-white">
<!-- 导航栏 -->
<div class="h-11 flex items-center relative px-4">
<button onclick="history.back()" class="absolute left-4 p-1">
<svg class="w-5 h-5 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="15 18 9 12 15 6"/>
</svg>
</button>
<h1 class="flex-1 text-center text-base font-medium">任务详情</h1>
</div>
<!-- 客户信息 -->
<div class="px-5 pt-2 pb-6">
<div class="flex items-center gap-4">
<div class="w-16 h-16 rounded-2xl bg-white/20 backdrop-blur-sm flex items-center justify-center shadow-lg">
<span class="text-2xl font-bold text-white"></span>
</div>
<div class="flex-1">
<div class="flex items-center gap-2 mb-2">
<span class="text-xl font-semibold">赵女士</span>
<span class="px-2 py-0.5 bg-white/25 backdrop-blur-sm text-white rounded-full text-xs font-medium">客户回访</span>
</div>
<div class="flex items-center gap-4 text-white/70 text-sm">
<span>135****6677</span>
<button onclick="this.previousElementSibling.textContent='13566776677';this.style.display='none'" class="px-2 py-0.5 bg-white/20 rounded text-white/90 text-xs">查看</button>
<span>💰 储值 <span style="background: linear-gradient(135deg, #d4af37 0%, #f4d03f 50%, #d4af37 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-weight: 600;">非常多</span></span>
</div>
</div>
</div>
</div>
</div>
<!-- 主体内容 -->
<div class="p-4 space-y-4">
<!-- 维客线索 -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<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"><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-2.5">
<!-- 客户基础 -->
<div class="flex items-start gap-3 p-3 bg-gray-50 rounded-xl border border-gray-100">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-primary/10 text-primary text-[11px] font-medium rounded-sm leading-normal tracking-wide">客户<br>基础</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">👩 女性 · VIP会员 · 入会1年半 · 忠实老客户</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<!-- 消费习惯 -->
<div class="flex items-start gap-3 p-3 bg-gray-50 rounded-xl border border-gray-100">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-success/10 text-success text-[11px] font-medium rounded-sm leading-normal tracking-wide">消费<br>习惯</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">☀️ 偏好周末下午 · 月均6-8次</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<div class="bg-gray-50 rounded-xl border border-gray-100 p-3">
<div class="flex items-start gap-3">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-success/10 text-success text-[11px] font-medium rounded-sm leading-normal tracking-wide">消费<br>习惯</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">💰 高客单价 · 爱点酒水</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<p class="text-xs text-gray-7 mt-2 leading-relaxed">场均消费 ¥420高于门店均值 ¥180酒水小食附加消费占比 40%偏好VIP包厢</p>
</div>
<!-- 玩法偏好 -->
<div class="bg-gray-50 rounded-xl border border-gray-100 p-3">
<div class="flex items-start gap-3">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-purple-500/10 text-purple-600 text-[11px] font-medium rounded-sm leading-normal tracking-wide">玩法<br>偏好</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">🎱 斯诺克爱好者 · 技术中上 · 喜欢研究杆法</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<p class="text-xs text-gray-7 mt-2 leading-relaxed">斯诺克占比 85%,偶尔玩中式八球;对高级杆法有浓厚兴趣,正在学习走位技巧</p>
</div>
<!-- 重要反馈 -->
<div class="bg-gray-50 rounded-xl border border-gray-100 p-3">
<div class="flex items-start gap-3">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-error/10 text-error text-[11px] font-medium rounded-sm leading-normal tracking-wide">重要<br>反馈</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">⭐ 上次服务好评,新球杆手感满意</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:小燕</span>
</div>
</div>
<p class="text-xs text-gray-7 mt-2 leading-relaxed">2月4日到店时表示新球杆手感很好希望下次能提前预留VIP包厢满意度持续保持高位</p>
</div>
</div>
</div>
<!-- 与我的关系 -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<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"><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>
</div>
<div class="flex-1">
<div class="h-3 bg-gray-100 rounded-full overflow-hidden">
<div class="h-full bg-gradient-to-r from-pink-400 to-rose-500 rounded-full" style="width: 88%"></div>
</div>
</div>
<span class="text-lg font-bold text-pink-500">0.88</span>
</div>
<p class="text-sm text-gray-7 leading-relaxed mb-4">
长期合作关系良好,共有 45 次服务记录。客户多次指定您服务,评价均为 5 星。是您的核心客户之一,需要持续维护。
</p>
<!-- 近期服务记录 -->
<div class="svc-section-bg">
<p class="text-sm font-semibold text-gray-13 mb-3">📋 近期服务记录</p>
<div>
<div class="svc-record">
<div class="flex items-center justify-between mb-1.5">
<div class="flex items-center gap-2">
<span class="svc-table">VIP2号房</span>
<span class="svc-type basic">基础课</span>
<span class="svc-duration">2.0h</span>
</div>
<span class="svc-income">¥190</span>
</div>
<div class="flex items-center justify-between">
<span class="svc-drinks">🍷 红牛x2 花生米x1</span>
<span class="svc-date">2026-02-04 15:00</span>
</div>
</div>
<div class="svc-record">
<div class="flex items-center justify-between mb-1.5">
<div class="flex items-center gap-2">
<span class="svc-table">8号台</span>
<span class="svc-type incentive">激励课</span>
<span class="svc-duration">1.5h</span>
</div>
<span class="svc-income">¥120</span>
</div>
<div class="flex items-center justify-between">
<span class="svc-drinks">🍷 可乐x2</span>
<span class="svc-date">2026-01-30 16:30</span>
</div>
</div>
<div class="svc-record">
<div class="flex items-center justify-between mb-1.5">
<div class="flex items-center gap-2">
<span class="svc-table">VIP2号房</span>
<span class="svc-type basic">基础课</span>
<span class="svc-duration">2.5h</span>
</div>
<span class="svc-income">¥200</span>
</div>
<div class="flex items-center justify-between">
<span class="svc-drinks">🍷 百威x3 薯条x1</span>
<span class="svc-date">2026-01-25 14:00</span>
</div>
</div>
</div>
<div class="mt-3 text-center">
<button onclick="window.location.href='customer-service-records.html'" class="text-xs text-primary font-medium">查看全部服务记录 →</button>
</div>
</div>
</div>
<!-- 任务建议 -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<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"><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 天前,关系良好,进行常规关怀回访:
</p>
<ul class="text-sm text-gray-9 space-y-1.5 list-disc list-inside">
<li>询问上次体验是否满意,是否有改进建议</li>
<li>告知近期新到的斯诺克相关设备或活动</li>
<li>提前预约下次到店时间,提供专属服务</li>
</ul>
</div>
<div class="mt-4 p-4 bg-gray-50 rounded-xl border border-gray-100">
<div class="flex items-center justify-between mb-2">
<span class="font-medium text-gray-13 text-sm">💬 话术参考</span>
<span class="ai-title-badge"><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>
<!-- 我给TA的备注 -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<div class="flex items-center justify-between mb-4">
<h2 class="section-title blue text-sm font-semibold text-gray-13">我给TA的备注</h2>
<span class="text-xs text-gray-6">2 条备注</span>
</div>
<div id="noteList" class="space-y-3">
<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="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>
</div>
</div>
<div id="noteEmpty" class="text-center py-6 hidden">
<svg class="w-10 h-10 text-gray-4 mx-auto mb-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/>
<path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/>
</svg>
<p class="text-sm text-gray-5">暂无备注</p>
</div>
</div>
</div>
<!-- 底部操作栏 -->
<div class="fixed bottom-0 left-0 right-0 h-16 bg-white/95 backdrop-blur-lg border-t border-gray-2 flex items-center gap-3 px-4">
<button onclick="window.location.href='chat.html'" class="flex-1 h-11 bg-gradient-to-r from-teal-500 to-cyan-500 text-white font-medium rounded-xl flex items-center justify-center gap-2 shadow-lg shadow-teal-500/30">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="currentColor">
<path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/>
</svg>
问问助手
</button>
<button onclick="showNoteModal()" class="flex-1 h-11 bg-gray-100 text-gray-13 font-medium rounded-xl flex items-center justify-center gap-2">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/>
<path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/>
</svg>
备注
</button>
</div>
<!-- 备注弹窗 -->
<div id="noteModal" class="fixed inset-0 bg-black/50 z-50 hidden items-end">
<div class="w-full bg-white rounded-t-3xl p-5 pb-8">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-2">
<span class="text-base font-semibold text-gray-13">添加备注</span>
<button id="noteExpandBtn" onclick="expandNoteRating()" class="note-expand-btn">展开评价 ▾</button>
</div>
<button onclick="hideNoteModal()" class="w-8 h-8 flex items-center justify-center rounded-full bg-gray-100">
<svg class="w-4 h-4 text-gray-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"/>
<line x1="6" y1="6" x2="18" y2="18"/>
</svg>
</button>
</div>
<div id="noteRatingSection" class="hidden">
<!-- 星星打分:再次服务意愿 -->
<div class="note-rating-group">
<span class="note-rating-label">再次服务此客户</span>
<div class="note-rating-right">
<div class="note-rating-row" data-score="0">
<span class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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>
</div>
<div class="note-rating-hints"><span>不想</span><span></span><span>一般</span><span></span><span>非常想</span></div>
</div>
</div>
<!-- 星星打分:再来店可能性 -->
<div class="note-rating-group">
<span class="note-rating-label">再来店可能性</span>
<div class="note-rating-right">
<div class="note-rating-row" data-score="0">
<span class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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>
</div>
<div class="note-rating-hints"><span>不可能</span><span></span><span>不好说</span><span></span><span>肯定能</span></div>
</div>
</div>
</div>
<textarea id="noteText" class="w-full h-32 p-4 bg-gray-50 rounded-xl resize-none text-sm text-gray-13 placeholder-gray-5 focus:outline-none focus:ring-2 focus:ring-primary/20 border border-gray-100" placeholder="请输入备注内容..."></textarea>
<button onclick="saveNote()" class="w-full h-12 bg-gradient-to-r from-teal-500 to-cyan-500 text-white font-medium rounded-xl mt-4 shadow-lg shadow-teal-500/30">保存</button>
</div>
</div>
<!-- 删除备注确认弹窗 -->
<div id="deleteNoteModal" class="fixed inset-0 bg-black/50 z-50 hidden items-center justify-center px-6">
<div class="w-full max-w-sm bg-white rounded-2xl p-5 shadow-xl">
<div class="flex items-center justify-between mb-3">
<span class="text-base font-semibold text-gray-13">删除备注</span>
<button onclick="hideDeleteNoteModal()" class="w-8 h-8 flex items-center justify-center rounded-full bg-gray-100">
<svg class="w-4 h-4 text-gray-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"/>
<line x1="6" y1="6" x2="18" y2="18"/>
</svg>
</button>
</div>
<p class="text-sm text-gray-7 mb-5">确定要删除这条备注吗?删除后无法恢复。</p>
<button onclick="deleteNote()" class="w-full h-12 bg-gradient-to-r from-error to-red-400 text-white font-medium rounded-xl shadow-lg shadow-error/30 text-sm">确认删除</button>
<button onclick="hideDeleteNoteModal()" class="w-full text-center text-sm text-gray-6 py-2 mt-2 bg-transparent border-0 cursor-pointer">取消</button>
</div>
</div>
<!-- Toast 提示 -->
<div id="toast" class="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-gray-13/80 text-white text-sm px-6 py-3 rounded-xl z-[100] hidden backdrop-blur-sm"></div>
</body>
</html>

View File

@@ -0,0 +1,356 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>任务详情 - 优先召回</title>
<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">
</head>
<body class="bg-gray-1 min-h-screen">
<!-- 通栏 Banner - 客户信息 -->
<div class="banner-bg theme-orange texture-aurora relative text-white">
<!-- 导航栏 -->
<div class="h-11 flex items-center relative px-4">
<button onclick="history.back()" class="absolute left-4 p-1">
<svg class="w-5 h-5 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="15 18 9 12 15 6"/>
</svg>
</button>
<h1 class="flex-1 text-center text-base font-medium">任务详情</h1>
</div>
<!-- 客户信息 -->
<div class="px-5 pt-2 pb-6">
<div class="flex items-center gap-4">
<div class="w-16 h-16 rounded-2xl bg-white/20 backdrop-blur-sm flex items-center justify-center shadow-lg">
<span class="text-2xl font-bold text-white"></span>
</div>
<div class="flex-1">
<div class="flex items-center gap-2 mb-2">
<span class="text-xl font-semibold">张先生</span>
<span class="px-2 py-0.5 bg-white/25 backdrop-blur-sm text-white rounded-full text-xs font-medium">优先召回</span>
</div>
<div class="flex items-center gap-4 text-white/70 text-sm">
<span>139****1234</span>
<button onclick="this.previousElementSibling.textContent='13912341234';this.style.display='none'" class="px-2 py-0.5 bg-white/20 rounded text-white/90 text-xs">查看</button>
<span>💰 储值 <span style="background: linear-gradient(135deg, #d4af37 0%, #f4d03f 50%, #d4af37 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-weight: 600;"></span></span>
</div>
</div>
</div>
</div>
</div>
<!-- 主体内容 -->
<div class="p-4 space-y-4">
<!-- 维客线索 -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<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"><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-2.5">
<!-- 客户基础 -->
<div class="flex items-start gap-3 p-3 bg-gray-50 rounded-xl border border-gray-100">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-primary/10 text-primary text-[11px] font-medium rounded-sm leading-normal tracking-wide">客户<br>基础</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">👤 普通会员 · 注册10个月 · 近期活跃度下降</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<!-- 消费习惯 -->
<div class="flex items-start gap-3 p-3 bg-gray-50 rounded-xl border border-gray-100">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-success/10 text-success text-[11px] font-medium rounded-sm leading-normal tracking-wide">消费<br>习惯</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">🌙 偏好夜场 20:00-23:00 · 之前月均3-4次</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<div class="bg-gray-50 rounded-xl border border-gray-100 p-3">
<div class="flex items-start gap-3">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-success/10 text-success text-[11px] font-medium rounded-sm leading-normal tracking-wide">消费<br>习惯</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">📉 频率下降 · 爱组局</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<p class="text-xs text-gray-7 mt-2 leading-relaxed">场均消费 ¥220近月到店仅 1 次(之前月均 3-4 次);喜欢和朋友组局,酒水消费占比 25%</p>
</div>
<!-- 玩法偏好 -->
<div class="bg-gray-50 rounded-xl border border-gray-100 p-3">
<div class="flex items-start gap-3">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-purple-500/10 text-purple-600 text-[11px] font-medium rounded-sm leading-normal tracking-wide">玩法<br>偏好</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">🎱 中式八球为主 · 喜欢组局对战 · 想练组合球</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<p class="text-xs text-gray-7 mt-2 leading-relaxed">中式八球占比 90%,技术水平中等;喜欢 3-4 人组局对战,社交属性强</p>
</div>
<!-- 重要反馈 -->
<div class="bg-gray-50 rounded-xl border border-gray-100 p-3">
<div class="flex items-start gap-3">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-error/10 text-error text-[11px] font-medium rounded-sm leading-normal tracking-wide">重要<br>反馈</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">⚠️ 换了工作,下班时间不固定</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:小燕</span>
</div>
</div>
<p class="text-xs text-gray-7 mt-2 leading-relaxed">2月3日沟通时提到最近换了工作下班时间不固定周末可能更方便建议调整联系时段为周末</p>
</div>
</div>
</div>
<!-- 与我的关系 -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<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"><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>
</div>
<div class="flex-1">
<div class="h-3 bg-gray-100 rounded-full overflow-hidden">
<div class="h-full bg-gradient-to-r from-amber-300 to-yellow-500 rounded-full" style="width: 55%"></div>
</div>
</div>
<span class="text-lg font-bold text-amber-500">0.55</span>
</div>
<p class="text-sm text-gray-7 leading-relaxed">
最近 2 个月互动较少,仅有 3 次服务记录。客户对您的印象中等,有提升空间。建议增加互动频次,建立更好的服务关系。
</p>
<!-- 最近服务记录 -->
<div class="svc-section-bg">
<p class="text-sm font-semibold text-gray-13 mb-3">📋 近期服务记录</p>
<div>
<div class="svc-record">
<div class="flex items-center justify-between mb-1.5">
<div class="flex items-center gap-2">
<span class="svc-table">5号台</span>
<span class="svc-type basic">基础课</span>
<span class="svc-duration">2.0h</span>
</div>
<span class="svc-income">¥160</span>
</div>
<div class="flex items-center justify-between">
<span class="svc-drinks">🍷 雪花x2 矿泉水x1</span>
<span class="svc-date">2026-02-06 19:00</span>
</div>
</div>
<div class="svc-record">
<div class="flex items-center justify-between mb-1.5">
<div class="flex items-center gap-2">
<span class="svc-table">A08号台</span>
<span class="svc-type incentive">激励课</span>
<span class="svc-duration">1.5h</span>
</div>
<span class="svc-income">¥150</span>
</div>
<div class="flex items-center justify-between">
<span class="svc-drinks">🍷 百威x1</span>
<span class="svc-date">2026-01-20 20:30</span>
</div>
</div>
<div class="svc-record">
<div class="flex items-center justify-between mb-1.5">
<div class="flex items-center gap-2">
<span class="svc-table">3号台</span>
<span class="svc-type basic">基础课</span>
<span class="svc-duration">2.0h</span>
</div>
<span class="svc-income">¥160</span>
</div>
<div class="flex items-center justify-between">
<span class="svc-drinks">🍷 可乐x2 红牛x1</span>
<span class="svc-date">2026-01-05 21:00</span>
</div>
</div>
</div>
<div class="mt-3 text-center">
<button onclick="window.location.href='customer-service-records.html'" class="text-xs text-primary font-medium">查看全部服务记录 →</button>
</div>
</div>
</div>
<!-- 任务建议 -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<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"><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 次,需要关注原因:
</p>
<ul class="text-sm text-gray-9 space-y-1.5 list-disc list-inside">
<li>了解是否工作变动或搬家导致不便</li>
<li>询问对门店服务是否有改进建议</li>
<li>推荐近期的会员优惠活动</li>
</ul>
</div>
<div class="mt-4 p-4 bg-gray-50 rounded-xl border border-gray-100">
<div class="flex items-center justify-between mb-2">
<span class="font-medium text-gray-13 text-sm">💬 话术参考</span>
<span class="ai-title-badge"><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>
<!-- 我给TA的备注 -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<div class="flex items-center justify-between mb-4">
<h2 class="section-title blue text-sm font-semibold text-gray-13">我给TA的备注</h2>
<span class="text-xs text-gray-6">2 条备注</span>
</div>
<div id="noteList" class="space-y-3">
<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="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>
</div>
</div>
<div id="noteEmpty" class="text-center py-6 hidden">
<svg class="w-10 h-10 text-gray-4 mx-auto mb-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/>
<path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/>
</svg>
<p class="text-sm text-gray-5">暂无备注</p>
</div>
</div>
</div>
<!-- 底部操作栏 -->
<div class="fixed bottom-0 left-0 right-0 h-16 bg-white/95 backdrop-blur-lg border-t border-gray-2 flex items-center gap-3 px-4">
<button onclick="window.location.href='chat.html'" class="flex-1 h-11 bg-gradient-to-r from-orange-500 to-amber-500 text-white font-medium rounded-xl flex items-center justify-center gap-2 shadow-lg shadow-orange-500/30">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="currentColor">
<path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/>
</svg>
问问助手
</button>
<button onclick="showNoteModal()" class="flex-1 h-11 bg-gray-100 text-gray-13 font-medium rounded-xl flex items-center justify-center gap-2">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/>
<path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/>
</svg>
备注
</button>
</div>
<!-- 备注弹窗 -->
<div id="noteModal" class="fixed inset-0 bg-black/50 z-50 hidden items-end">
<div class="w-full bg-white rounded-t-3xl p-5 pb-8">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-2">
<span class="text-base font-semibold text-gray-13">添加备注</span>
<button id="noteExpandBtn" onclick="expandNoteRating()" class="note-expand-btn">展开评价 ▾</button>
</div>
<button onclick="hideNoteModal()" class="w-8 h-8 flex items-center justify-center rounded-full bg-gray-100">
<svg class="w-4 h-4 text-gray-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"/>
<line x1="6" y1="6" x2="18" y2="18"/>
</svg>
</button>
</div>
<div id="noteRatingSection" class="hidden">
<!-- 星星打分:再次服务意愿 -->
<div class="note-rating-group">
<span class="note-rating-label">再次服务此客户</span>
<div class="note-rating-right">
<div class="note-rating-row" data-score="0">
<span class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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>
</div>
<div class="note-rating-hints"><span>不想</span><span></span><span>一般</span><span></span><span>非常想</span></div>
</div>
</div>
<!-- 星星打分:再来店可能性 -->
<div class="note-rating-group">
<span class="note-rating-label">再来店可能性</span>
<div class="note-rating-right">
<div class="note-rating-row" data-score="0">
<span class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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>
</div>
<div class="note-rating-hints"><span>不可能</span><span></span><span>不好说</span><span></span><span>肯定能</span></div>
</div>
</div>
</div>
<textarea id="noteText" class="w-full h-32 p-4 bg-gray-50 rounded-xl resize-none text-sm text-gray-13 placeholder-gray-5 focus:outline-none focus:ring-2 focus:ring-orange-500/20 border border-gray-100" placeholder="请输入备注内容..."></textarea>
<button onclick="saveNote()" class="w-full h-12 bg-gradient-to-r from-orange-500 to-amber-500 text-white font-medium rounded-xl mt-4 shadow-lg shadow-orange-500/30">保存</button>
</div>
</div>
<!-- 删除备注确认弹窗 -->
<div id="deleteNoteModal" class="fixed inset-0 bg-black/50 z-50 hidden items-center justify-center px-6">
<div class="w-full max-w-sm bg-white rounded-2xl p-5 shadow-xl">
<div class="flex items-center justify-between mb-3">
<span class="text-base font-semibold text-gray-13">删除备注</span>
<button onclick="hideDeleteNoteModal()" class="w-8 h-8 flex items-center justify-center rounded-full bg-gray-100">
<svg class="w-4 h-4 text-gray-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"/>
<line x1="6" y1="6" x2="18" y2="18"/>
</svg>
</button>
</div>
<p class="text-sm text-gray-7 mb-5">确定要删除这条备注吗?删除后无法恢复。</p>
<button onclick="deleteNote()" class="w-full h-12 bg-gradient-to-r from-error to-red-400 text-white font-medium rounded-xl shadow-lg shadow-error/30 text-sm">确认删除</button>
<button onclick="hideDeleteNoteModal()" class="w-full text-center text-sm text-gray-6 py-2 mt-2 bg-transparent border-0 cursor-pointer">取消</button>
</div>
</div>
<!-- Toast 提示 -->
<div id="toast" class="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-gray-13/80 text-white text-sm px-6 py-3 rounded-xl z-[100] hidden backdrop-blur-sm"></div>
</body>
</html>

View File

@@ -0,0 +1,335 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>任务详情 - 关系构建</title>
<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">
</head>
<body class="bg-gray-1 min-h-screen">
<!-- 通栏 Banner - 客户信息 -->
<div class="banner-bg theme-pink texture-aurora relative text-white">
<!-- 导航栏 -->
<div class="h-11 flex items-center relative px-4">
<button onclick="history.back()" class="absolute left-4 p-1">
<svg class="w-5 h-5 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="15 18 9 12 15 6"/>
</svg>
</button>
<h1 class="flex-1 text-center text-base font-medium">任务详情</h1>
</div>
<!-- 客户信息 -->
<div class="px-5 pt-2 pb-6">
<div class="flex items-center gap-4">
<div class="w-16 h-16 rounded-2xl bg-white/20 backdrop-blur-sm flex items-center justify-center shadow-lg">
<span class="text-2xl font-bold text-white"></span>
</div>
<div class="flex-1">
<div class="flex items-center gap-2 mb-2">
<span class="text-xl font-semibold">陈先生</span>
<span class="px-2 py-0.5 bg-white/25 backdrop-blur-sm text-white rounded-full text-xs font-medium">关系构建</span>
</div>
<div class="flex items-center gap-4 text-white/70 text-sm">
<span>137****8899</span>
<button onclick="this.previousElementSibling.textContent='13788998899';this.style.display='none'" class="px-2 py-0.5 bg-white/20 rounded text-white/90 text-xs">查看</button>
<span>💰 储值 <span style="background: linear-gradient(135deg, #d4af37 0%, #f4d03f 50%, #d4af37 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-weight: 600;">一般</span></span>
</div>
</div>
</div>
</div>
</div>
<!-- 主体内容 -->
<div class="p-4 space-y-4">
<!-- 维客线索 -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<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"><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-2.5">
<!-- 客户基础 -->
<div class="flex items-start gap-3 p-3 bg-gray-50 rounded-xl border border-gray-100">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-primary/10 text-primary text-[11px] font-medium rounded-sm leading-normal tracking-wide">客户<br>基础</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">🆕 新客户 · 入会2个月 · 消费潜力大</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<!-- 消费习惯 -->
<div class="flex items-start gap-3 p-3 bg-gray-50 rounded-xl border border-gray-100">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-success/10 text-success text-[11px] font-medium rounded-sm leading-normal tracking-wide">消费<br>习惯</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">☀️ 偏好下午 14:00-18:00 · 到店2次</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<div class="bg-gray-50 rounded-xl border border-gray-100 p-3">
<div class="flex items-start gap-3">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-success/10 text-success text-[11px] font-medium rounded-sm leading-normal tracking-wide">消费<br>习惯</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">💎 消费潜力大</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<p class="text-xs text-gray-7 mt-2 leading-relaxed">单次消费 ¥180高于新客均值 ¥120消费能力较强有提升空间适合推荐课程套餐</p>
</div>
<!-- 玩法偏好 -->
<div class="bg-gray-50 rounded-xl border border-gray-100 p-3">
<div class="flex items-start gap-3">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-purple-500/10 text-purple-600 text-[11px] font-medium rounded-sm leading-normal tracking-wide">玩法<br>偏好</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">🎱 初学者 · 对台球感兴趣 · 技术待提升</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<p class="text-xs text-gray-7 mt-2 leading-relaxed">尚未形成明确偏好,对各类玩法都有兴趣;技术水平初级,适合推荐入门课程和基础训练</p>
</div>
<!-- 重要反馈 -->
<div class="bg-gray-50 rounded-xl border border-gray-100 p-3">
<div class="flex items-start gap-3">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-error/10 text-error text-[11px] font-medium rounded-sm leading-normal tracking-wide">重要<br>反馈</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">💬 上次课后反馈良好,想继续学习</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:小燕</span>
</div>
</div>
<p class="text-xs text-gray-7 mt-2 leading-relaxed">2月5日基础课后表示很有收获希望能有更多练习机会建议安排球友交流活动帮助融入</p>
</div>
</div>
</div>
<!-- 与我的关系 -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<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"><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>
</div>
<div class="flex-1">
<div class="h-3 bg-gray-100 rounded-full overflow-hidden">
<div class="h-full bg-gradient-to-r from-blue-400 to-cyan-500 rounded-full" style="width: 45%"></div>
</div>
</div>
<span class="text-lg font-bold text-blue-500">0.45</span>
</div>
<p class="text-sm text-gray-7 leading-relaxed">
接触次数较少,仅有 2 次服务记录。客户对您还不太熟悉,但反馈良好。建议通过专业服务和关心,逐步建立信任关系。
</p>
<!-- 最近服务记录 -->
<div class="svc-section-bg">
<p class="text-sm font-semibold text-gray-13 mb-3">📋 近期服务记录</p>
<div>
<div class="svc-record">
<div class="flex items-center justify-between mb-1.5">
<div class="flex items-center gap-2">
<span class="svc-table">2号台</span>
<span class="svc-type basic">基础课</span>
<span class="svc-duration">1.5h</span>
</div>
<span class="svc-income">¥120</span>
</div>
<div class="flex items-center justify-between">
<span class="svc-drinks">🍷 矿泉水x2</span>
<span class="svc-date">2026-02-05 15:00</span>
</div>
</div>
<div class="svc-record">
<div class="flex items-center justify-between mb-1.5">
<div class="flex items-center gap-2">
<span class="svc-table">A03号台</span>
<span class="svc-type basic">基础课</span>
<span class="svc-duration">2.0h</span>
</div>
<span class="svc-income">¥160</span>
</div>
<div class="flex items-center justify-between">
<span class="svc-drinks">🍷 可乐x1 雪碧x1</span>
<span class="svc-date">2026-01-22 16:00</span>
</div>
</div>
<div class="svc-record">
<div class="flex items-center justify-between mb-1.5">
<div class="flex items-center gap-2">
<span class="svc-table">6号台</span>
<span class="svc-type incentive">激励课</span>
<span class="svc-duration">1.0h</span>
</div>
<span class="svc-income">¥100</span>
</div>
<div class="flex items-center justify-between">
<span class="svc-drinks">🍷 红牛x1</span>
<span class="svc-date">2026-01-10 14:30</span>
</div>
</div>
</div>
<div class="mt-3 text-center">
<button onclick="window.location.href='customer-service-records.html'" class="text-xs text-primary font-medium">查看全部服务记录 →</button>
</div>
</div>
</div>
<!-- 任务建议 -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<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"><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">
该客户消费潜力大但关系指数较低,建议重点培养:
</p>
<ul class="text-sm text-gray-9 space-y-1.5 list-disc list-inside">
<li>主动关心学习进度,提供技术指导</li>
<li>了解其兴趣爱好,建立共同话题</li>
<li>适时推荐适合初学者的课程套餐</li>
</ul>
</div>
<div class="mt-4 p-4 bg-gray-50 rounded-xl border border-gray-100">
<div class="flex items-center justify-between mb-2">
<span class="font-medium text-gray-13 text-sm">💬 话术参考</span>
<span class="ai-title-badge"><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>
<!-- 我给TA的备注 -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<div class="flex items-center justify-between mb-4">
<h2 class="section-title blue text-sm font-semibold text-gray-13">我给TA的备注</h2>
<span class="text-xs text-gray-6">暂无备注</span>
</div>
<div id="noteList" class="space-y-3 hidden"></div>
<div id="noteEmpty" class="text-center py-6">
<svg class="w-10 h-10 text-gray-4 mx-auto mb-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/>
<path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/>
</svg>
<p class="text-sm text-gray-5">快点击下方备注按钮,添加客人备注!</p>
</div>
</div>
</div>
<!-- 底部操作栏 -->
<div class="fixed bottom-0 left-0 right-0 h-16 bg-white/95 backdrop-blur-lg border-t border-gray-2 flex items-center gap-3 px-4">
<button onclick="window.location.href='chat.html'" class="flex-1 h-11 bg-gradient-to-r from-pink-500 to-rose-500 text-white font-medium rounded-xl flex items-center justify-center gap-2 shadow-lg shadow-pink-500/30">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="currentColor">
<path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/>
</svg>
问问助手
</button>
<button onclick="showNoteModal()" class="flex-1 h-11 bg-gray-100 text-gray-13 font-medium rounded-xl flex items-center justify-center gap-2">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/>
<path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/>
</svg>
备注
</button>
</div>
<!-- 备注弹窗 -->
<div id="noteModal" class="fixed inset-0 bg-black/50 z-50 hidden items-end">
<div class="w-full bg-white rounded-t-3xl p-5 pb-8">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-2">
<span class="text-base font-semibold text-gray-13">添加备注</span>
<button id="noteExpandBtn" onclick="expandNoteRating()" class="note-expand-btn">展开评价 ▾</button>
</div>
<button onclick="hideNoteModal()" class="w-8 h-8 flex items-center justify-center rounded-full bg-gray-100">
<svg class="w-4 h-4 text-gray-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"/>
<line x1="6" y1="6" x2="18" y2="18"/>
</svg>
</button>
</div>
<div id="noteRatingSection" class="hidden">
<!-- 星星打分:再次服务意愿 -->
<div class="note-rating-group">
<span class="note-rating-label">再次服务此客户</span>
<div class="note-rating-right">
<div class="note-rating-row" data-score="0">
<span class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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>
</div>
<div class="note-rating-hints"><span>不想</span><span></span><span>一般</span><span></span><span>非常想</span></div>
</div>
</div>
<!-- 星星打分:再来店可能性 -->
<div class="note-rating-group">
<span class="note-rating-label">再来店可能性</span>
<div class="note-rating-right">
<div class="note-rating-row" data-score="0">
<span class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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>
</div>
<div class="note-rating-hints"><span>不可能</span><span></span><span>不好说</span><span></span><span>肯定能</span></div>
</div>
</div>
</div>
<textarea id="noteText" class="w-full h-32 p-4 bg-gray-50 rounded-xl resize-none text-sm text-gray-13 placeholder-gray-5 focus:outline-none focus:ring-2 focus:ring-pink-500/20 border border-gray-100" placeholder="请输入备注内容..."></textarea>
<button onclick="saveNote()" class="w-full h-12 bg-gradient-to-r from-pink-500 to-rose-500 text-white font-medium rounded-xl mt-4 shadow-lg shadow-pink-500/30">保存</button>
</div>
</div>
<!-- 删除备注确认弹窗 -->
<div id="deleteNoteModal" class="fixed inset-0 bg-black/50 z-50 hidden items-center justify-center px-6">
<div class="w-full max-w-sm bg-white rounded-2xl p-5 shadow-xl">
<div class="flex items-center justify-between mb-3">
<span class="text-base font-semibold text-gray-13">删除备注</span>
<button onclick="hideDeleteNoteModal()" class="w-8 h-8 flex items-center justify-center rounded-full bg-gray-100">
<svg class="w-4 h-4 text-gray-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"/>
<line x1="6" y1="6" x2="18" y2="18"/>
</svg>
</button>
</div>
<p class="text-sm text-gray-7 mb-5">确定要删除这条备注吗?删除后无法恢复。</p>
<button onclick="deleteNote()" class="w-full h-12 bg-gradient-to-r from-error to-red-400 text-white font-medium rounded-xl shadow-lg shadow-error/30 text-sm">确认删除</button>
<button onclick="hideDeleteNoteModal()" class="w-full text-center text-sm text-gray-6 py-2 mt-2 bg-transparent border-0 cursor-pointer">取消</button>
</div>
</div>
<!-- Toast 提示 -->
<div id="toast" class="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-gray-13/80 text-white text-sm px-6 py-3 rounded-xl z-[100] hidden backdrop-blur-sm"></div>
</body>
</html>

View File

@@ -0,0 +1,445 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>任务详情 - 球房运营助手</title>
<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">
<style>
/* 话术气泡 */
.speech-bubble {
position: relative;
background: #f0f4ff;
border: 1px solid #c5c5c5;
border-radius: 12px;
padding: 12px 16px;
font-size: 14px;
line-height: 1.7;
color: #5e5e5e;
margin-bottom: 16px;
}
/* 尖角:旋转正方形,右下角伸出 */
.speech-bubble::after {
content: '';
position: absolute;
bottom: -8px;
right: 24px;
width: 14px;
height: 14px;
background: #f0f4ff;
border-right: 1px solid #c5c5c5;
border-bottom: 1px solid #c5c5c5;
transform: rotate(45deg);
}
/* 复制按钮 */
.copy-btn {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 0;
font-size: 12px;
color: #a6a6a6;
background: none;
border: none;
cursor: pointer;
transition: color 0.2s;
}
.copy-btn:active { color: #0052d9; }
.copy-btn svg { width: 14px; height: 14px; }
.copy-btn.copied { color: #00a870; }
</style>
</head>
<body class="bg-gray-1 min-h-screen">
<!-- 通栏 Banner - 客户信息 -->
<div class="banner-bg theme-red texture-aurora relative text-white">
<!-- 导航栏 -->
<div class="h-11 flex items-center relative px-4">
<button onclick="history.back()" class="absolute left-4 p-1">
<svg class="w-5 h-5 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="15 18 9 12 15 6"/>
</svg>
</button>
<h1 class="flex-1 text-center text-base font-medium">任务详情</h1>
<button onclick="showAbandonModal()" class="absolute right-4 text-white/50 text-sm">放弃</button>
</div>
<!-- 客户信息 -->
<div class="px-5 pt-2 pb-6">
<div class="flex items-center gap-4">
<div class="w-16 h-16 rounded-2xl bg-white/20 backdrop-blur-sm flex items-center justify-center shadow-lg">
<span class="text-2xl font-bold text-white"></span>
</div>
<div class="flex-1">
<div class="flex items-center gap-2 mb-2">
<span class="text-xl font-semibold">王先生</span>
<span class="px-2 py-0.5 bg-white/25 backdrop-blur-sm text-white rounded-full text-xs font-medium">高优先召回</span>
</div>
<div class="flex items-center gap-4 text-white/70 text-sm">
<span>138****5678</span>
<button onclick="this.previousElementSibling.textContent='13812345678';this.style.display='none'" class="px-2 py-0.5 bg-white/20 rounded text-white/90 text-xs">查看</button>
<span>💰 储值 <span style="background: linear-gradient(135deg, #d4af37 0%, #f4d03f 50%, #d4af37 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-weight: 600;">非常多</span></span>
</div>
</div>
</div>
</div>
</div>
<!-- 主体内容 -->
<div class="p-4 space-y-4">
<!-- 与我的关系(置顶) -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<div class="flex items-center justify-between mb-4">
<h2 class="section-title pink text-sm font-semibold text-gray-13">与我的关系</h2>
<span class="ai-title-badge"><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>
</div>
<div class="flex-1">
<div class="h-3 bg-gray-100 rounded-full overflow-hidden">
<div class="h-full bg-gradient-to-r from-pink-400 to-rose-500 rounded-full" style="width: 85%"></div>
</div>
</div>
<span class="text-lg font-bold text-pink-500">0.85</span>
</div>
<p class="text-sm text-gray-7 leading-relaxed">
最近 3 个月每周均有 1-2 次课程互动,客户反馈良好。上次服务评价 5 星,多次指定您为服务助教。
</p>
</div>
<!-- 任务建议 -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<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"><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 天未到店,存在流失风险。建议通过微信联系:
</p>
<ul class="text-sm text-gray-9 space-y-1.5 list-disc list-inside">
<li>询问近期是否有空,邀请体验新到的器材</li>
<li>告知本周末有会员专属活动</li>
<li>根据其偏好时段(晚间)推荐合适的时间</li>
</ul>
</div>
<div class="mt-4">
<div class="flex items-center justify-between mb-3">
<span class="font-medium text-gray-13 text-sm">💬 话术参考</span>
</div>
<div class="flex flex-col gap-5">
<div class="speech-bubble"><span class="ai-inline-icon"><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>王哥您好,好久不见!最近店里新到了几张国际标准的斯诺克球桌,知道您是斯诺克爱好者,想邀请您有空来体验一下~<div class="flex justify-end mt-0"><button class="copy-btn" onclick="copySpeech(this)"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg><span>复制</span></button></div></div>
<div class="speech-bubble"><span class="ai-inline-icon"><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>王哥,最近忙吗?这周末我们有个老客户专属的球友交流赛,奖品还挺丰富的,您要不要来参加?<div class="flex justify-end mt-0"><button class="copy-btn" onclick="copySpeech(this)"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg><span>复制</span></button></div></div>
<div class="speech-bubble"><span class="ai-inline-icon"><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>王哥好呀,上次您提到想练练斯诺克的走位,我最近研究了一些新的训练方法,下次来的时候可以一起试试~<div class="flex justify-end mt-0"><button class="copy-btn" onclick="copySpeech(this)"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg><span>复制</span></button></div></div>
<div class="speech-bubble"><span class="ai-inline-icon"><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>王哥,好久没见您了,您的老位置 A12 号台一直给您留着呢!最近晚上人不多,环境特别好,随时欢迎您来~<div class="flex justify-end mt-0"><button class="copy-btn" onclick="copySpeech(this)"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg><span>复制</span></button></div></div>
<div class="speech-bubble"><span class="ai-inline-icon"><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>王哥您好,我们这个月推出了储值会员专属的夜场优惠套餐,包含球台+酒水,性价比很高,给您留意着呢~<div class="flex justify-end mt-0"><button class="copy-btn" onclick="copySpeech(this)"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg><span>复制</span></button></div></div>
</div>
</div>
</div>
<!-- 维客线索 -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<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"><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-2.5">
<!-- 客户基础 -->
<div class="flex items-start gap-3 p-3 bg-gray-50 rounded-xl border border-gray-100">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-primary/10 text-primary text-[11px] font-medium rounded-sm leading-normal tracking-wide">客户<br>基础</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">🎂 生日 3月15日 · VIP会员 · 注册2年</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<!-- 消费习惯 -->
<div class="flex items-start gap-3 p-3 bg-gray-50 rounded-xl border border-gray-100">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-success/10 text-success text-[11px] font-medium rounded-sm leading-normal tracking-wide">消费<br>习惯</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">🌙 常来夜场 · 月均4-5次</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<div class="bg-gray-50 rounded-xl border border-gray-100 p-3">
<div class="flex items-start gap-3">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-success/10 text-success text-[11px] font-medium rounded-sm leading-normal tracking-wide">消费<br>习惯</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">💰 高客单价</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<p class="text-xs text-gray-7 mt-2 leading-relaxed">近60天场均消费 ¥420高于门店均值 ¥180偏好夜场时段酒水附加消费占比 35%</p>
</div>
<!-- 玩法偏好 -->
<div class="bg-gray-50 rounded-xl border border-gray-100 p-3">
<div class="flex items-start gap-3">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-purple-500/10 text-purple-600 text-[11px] font-medium rounded-sm leading-normal tracking-wide">玩法<br>偏好</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">🎱 偏爱中式八球 · 斯诺克进阶中 · 最近对花式九球也有兴趣</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:系统</span>
</div>
</div>
<p class="text-xs text-gray-7 mt-2 leading-relaxed">中式八球占比 60%,斯诺克 30%近2周开始尝试花式九球技术水平中等偏上</p>
</div>
<!-- 重要反馈 -->
<div class="bg-gray-50 rounded-xl border border-gray-100 p-3">
<div class="flex items-start gap-3">
<span class="shrink-0 w-10 h-10 flex items-center justify-center px-0.5 bg-error/10 text-error text-[11px] font-medium rounded-sm leading-normal tracking-wide">重要<br>反馈</span>
<div class="flex-1 relative min-h-[2.5rem]">
<p class="text-sm text-gray-13 leading-snug line-clamp-2">⚠️ 上次提到想练斯诺克走位</p>
<span class="absolute bottom-0 right-0 text-[11px] text-gray-6 whitespace-nowrap bg-gray-50 pl-1">By:小燕</span>
</div>
</div>
<p class="text-xs text-gray-7 mt-2 leading-relaxed">2月7日到店时主动提及希望有针对性的走位训练建议下次安排斯诺克专项课程</p>
</div>
</div>
</div>
<!-- 我给TA的备注 -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<div class="flex items-center justify-between mb-4">
<h2 class="section-title blue text-sm font-semibold text-gray-13">我给TA的备注</h2>
<span class="text-xs text-gray-6">3 条备注</span>
</div>
<div id="noteList" class="space-y-3">
<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>
<p class="text-sm text-gray-9 leading-relaxed">已通过微信联系王先生,表示对新到的斯诺克球桌感兴趣,周末可能来体验。</p>
</div>
<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>
<p class="text-sm text-gray-9 leading-relaxed">王先生最近出差较多,到店频率降低。建议等他回来后再约。</p>
</div>
<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>
<p class="text-sm text-gray-9 leading-relaxed">上次到店时推荐了会员续费活动,客户说考虑一下。</p>
</div>
</div>
<div id="noteEmpty" class="text-center py-6 hidden">
<svg class="w-10 h-10 text-gray-4 mx-auto mb-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/>
<path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/>
</svg>
<p class="text-sm text-gray-5">暂无备注</p>
</div>
</div>
<!-- 近期服务记录(置底) -->
<div class="bg-white rounded-2xl p-5 shadow-sm">
<div class="flex items-center justify-between mb-4">
<h2 class="section-title blue text-sm font-semibold text-gray-13">近期服务记录</h2>
<span class="text-xs text-gray-6">共 3 次</span>
</div>
<!-- 汇总统计 -->
<div class="flex items-center gap-3 mb-4">
<div class="flex-1 text-center py-2.5 bg-primary/5 rounded-xl">
<p class="text-lg font-bold text-primary">6.0<span class="text-xs font-normal text-gray-6 ml-0.5">h</span></p>
<p class="text-[11px] text-gray-6 mt-0.5">总时长</p>
</div>
<div class="flex-1 text-center py-2.5 bg-success/5 rounded-xl">
<p class="text-lg font-bold text-success">¥510</p>
<p class="text-[11px] text-gray-6 mt-0.5">总收入</p>
</div>
<div class="flex-1 text-center py-2.5 bg-warning/5 rounded-xl">
<p class="text-lg font-bold text-warning">¥170</p>
<p class="text-[11px] text-gray-6 mt-0.5">场均</p>
</div>
</div>
<!-- 记录列表 -->
<div class="space-y-2.5">
<div class="svc-record">
<div class="flex items-center justify-between mb-1.5">
<div class="flex items-center gap-2">
<span class="svc-table">A12号台</span>
<span class="svc-type basic">基础课</span>
<span class="svc-duration text-base">2.5h</span>
</div>
<span class="svc-income text-base">¥200</span>
</div>
<div class="flex items-center justify-between">
<span class="svc-drinks">🍷 百威x2 红牛x1</span>
<span class="svc-date text-xs">2026-02-07 21:30</span>
</div>
</div>
<div class="svc-record">
<div class="flex items-center justify-between mb-1.5">
<div class="flex items-center gap-2">
<span class="svc-table">3号台</span>
<span class="svc-type basic">基础课</span>
<span class="svc-duration text-base">2.0h</span>
</div>
<span class="svc-income text-base">¥160</span>
</div>
<div class="flex items-center justify-between">
<span class="svc-drinks">🍷 可乐x1</span>
<span class="svc-date text-xs">2026-02-01 20:30</span>
</div>
</div>
<div class="svc-record">
<div class="flex items-center justify-between mb-1.5">
<div class="flex items-center gap-2">
<span class="svc-table">VIP1号房</span>
<span class="svc-type incentive">激励课</span>
<span class="svc-duration text-base">1.5h</span>
</div>
<span class="svc-income text-base">¥150</span>
</div>
<div class="flex items-center justify-between">
<span class="svc-drinks">🍷 芝华士x1 矿泉水x2</span>
<span class="svc-date text-xs">2026-01-28 19:00</span>
</div>
</div>
</div>
<div class="mt-4 text-center">
<button onclick="window.location.href='customer-service-records.html'" class="text-xs text-primary font-medium">查看全部服务记录 →</button>
</div>
</div>
</div>
<!-- 底部操作栏 -->
<div class="fixed bottom-0 left-0 right-0 h-16 bg-white/95 backdrop-blur-lg border-t border-gray-2 flex items-center gap-3 px-4">
<button onclick="window.location.href='chat.html'" class="flex-1 h-11 bg-gradient-to-r from-primary to-blue-500 text-white font-medium rounded-xl flex items-center justify-center gap-2 shadow-lg shadow-primary/30">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="currentColor">
<path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/>
</svg>
问问助手
</button>
<button onclick="showNoteModal()" class="flex-1 h-11 bg-gray-100 text-gray-13 font-medium rounded-xl flex items-center justify-center gap-2">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/>
<path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/>
</svg>
备注
</button>
</div>
<!-- 备注弹窗 -->
<div id="noteModal" class="fixed inset-0 bg-black/50 z-50 hidden items-end">
<div class="w-full bg-white rounded-t-3xl p-5 pb-8">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-2">
<span class="text-base font-semibold text-gray-13">添加备注</span>
<button id="noteExpandBtn" onclick="expandNoteRating()" class="note-expand-btn">展开评价 ▾</button>
</div>
<button onclick="hideNoteModal()" class="w-8 h-8 flex items-center justify-center rounded-full bg-gray-100">
<svg class="w-4 h-4 text-gray-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"/>
<line x1="6" y1="6" x2="18" y2="18"/>
</svg>
</button>
</div>
<div id="noteRatingSection" class="hidden">
<!-- 星星打分:再次服务意愿 -->
<div class="note-rating-group">
<span class="note-rating-label">再次服务此客户</span>
<div class="note-rating-right">
<div class="note-rating-row" data-score="0">
<span class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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>
</div>
<div class="note-rating-hints"><span>不想</span><span></span><span>一般</span><span></span><span>非常想</span></div>
</div>
</div>
<!-- 星星打分:再来店可能性 -->
<div class="note-rating-group">
<span class="note-rating-label">再来店可能性</span>
<div class="note-rating-right">
<div class="note-rating-row" data-score="0">
<span class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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 class="nr-star"><svg class="nr-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><svg class="nr-filled" 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>
</div>
<div class="note-rating-hints"><span>不可能</span><span></span><span>不好说</span><span></span><span>肯定能</span></div>
</div>
</div>
</div>
<textarea id="noteText" class="w-full h-32 p-4 bg-gray-50 rounded-xl resize-none text-sm text-gray-13 placeholder-gray-5 focus:outline-none focus:ring-2 focus:ring-primary/20 border border-gray-100" placeholder="请输入备注内容..."></textarea>
<button onclick="saveNote()" class="w-full h-12 bg-gradient-to-r from-primary to-blue-500 text-white font-medium rounded-xl mt-4 shadow-lg shadow-primary/30">保存</button>
</div>
</div>
<!-- 删除备注确认弹窗 -->
<div id="deleteNoteModal" class="fixed inset-0 bg-black/50 z-50 hidden items-center justify-center px-6">
<div class="w-full max-w-sm bg-white rounded-2xl p-5 shadow-xl">
<div class="flex items-center justify-between mb-3">
<span class="text-base font-semibold text-gray-13">删除备注</span>
<button onclick="hideDeleteNoteModal()" class="w-8 h-8 flex items-center justify-center rounded-full bg-gray-100">
<svg class="w-4 h-4 text-gray-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"/>
<line x1="6" y1="6" x2="18" y2="18"/>
</svg>
</button>
</div>
<p class="text-sm text-gray-7 mb-5">确定要删除这条备注吗?删除后无法恢复。</p>
<button onclick="deleteNote()" class="w-full h-12 bg-gradient-to-r from-error to-red-400 text-white font-medium rounded-xl shadow-lg shadow-error/30 text-sm">确认删除</button>
<button onclick="hideDeleteNoteModal()" class="w-full text-center text-sm text-gray-6 py-2 mt-2 bg-transparent border-0 cursor-pointer">取消</button>
</div>
</div>
<!-- 放弃弹窗 -->
<div id="abandonModal" class="fixed inset-0 bg-black/50 z-50 hidden items-center justify-center px-6">
<div class="w-full max-w-sm bg-white rounded-2xl p-5 shadow-xl">
<div class="flex items-center justify-between mb-4">
<span class="text-base font-semibold text-gray-13">放弃维护</span>
<button onclick="hideAbandonModal()" class="w-8 h-8 flex items-center justify-center rounded-full bg-gray-100">
<svg class="w-4 h-4 text-gray-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"/>
<line x1="6" y1="6" x2="18" y2="18"/>
</svg>
</button>
</div>
<p class="text-sm text-gray-7 mb-3">请填写放弃原因(必填):</p>
<textarea id="abandonReason" class="w-full h-28 p-4 bg-gray-50 rounded-xl resize-none text-sm text-gray-13 placeholder-gray-5 focus:outline-none focus:ring-2 focus:ring-primary/20 border border-gray-100" placeholder="请输入放弃维护该客户的原因..."></textarea>
<p id="abandonError" class="text-xs text-error mt-1.5 hidden">请输入放弃原因后再提交</p>
<button onclick="submitAbandon()" class="w-full h-12 bg-gradient-to-r from-error to-red-400 text-white font-medium rounded-xl mt-4 shadow-lg shadow-error/30 text-sm">确认原因 放弃该客户的维护</button>
<button onclick="hideAbandonModal()" class="w-full text-center text-sm text-gray-6 py-2 mt-2 bg-transparent border-0 cursor-pointer">取消</button>
</div>
</div>
<!-- Toast 提示 -->
<div id="toast" class="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-gray-13/80 text-white text-sm px-6 py-3 rounded-xl z-[100] hidden backdrop-blur-sm"></div>
</body>
</html>

View File

@@ -0,0 +1,665 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>任务列表 - 球房运营助手</title>
<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">
<style>
body {
font-family: 'Noto Sans SC', -apple-system, BlinkMacSystemFont, sans-serif;
padding-bottom: 70px;
}
/* 任务卡片边框颜色 */
.task-card {
position: relative;
border-left: 4px solid transparent;
}
.task-card.high-priority {
border-left-color: #f43f5e;
}
.task-card.priority {
border-left-color: #f97316;
}
.task-card.relationship {
border-left-color: #ec4899;
}
.task-card.callback {
border-left-color: #14b8a6;
}
/* 标签颜色 - 圆角矩形 */
.tag-high-priority {
background: linear-gradient(135deg, #dc2626 0%, #ef4444 100%);
border-radius: 4px;
}
.tag-priority {
background: linear-gradient(135deg, #ea580c 0%, #f97316 100%);
border-radius: 4px;
}
.tag-relationship {
background: linear-gradient(135deg, #db2777 0%, #ec4899 100%);
border-radius: 4px;
}
.tag-callback {
background: linear-gradient(135deg, #0d9488 0%, #14b8a6 100%);
border-radius: 4px;
}
/* 进度条动画 */
.progress-bar {
background: linear-gradient(90deg, #f59e0b 0%, #fbbf24 50%, #fcd34d 100%);
transition: width 0.6s ease-out;
}
/* 6段档位进度条 */
.tier-progress {
display: flex;
gap: 2px;
height: 8px;
}
.tier-segment {
border-radius: 2px;
background: rgba(255,255,255,0.25);
position: relative;
overflow: hidden;
}
.tier-segment.completed {
background: linear-gradient(135deg, #34d399 0%, #10b981 100%);
}
.tier-segment.current {
background: linear-gradient(90deg, rgba(255,255,255,0.25) 0%, rgba(255,255,255,0.25) 50%, rgba(255,255,255,0.25) 100%);
overflow: hidden;
}
.tier-segment.current .tier-fill {
height: 100%;
background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%);
border-radius: 2px;
}
/* 红戳样式 - 透明印章风格 */
.red-stamp {
position: relative;
display: inline-flex;
flex-direction: column;
align-items: center;
}
.stamp-badge {
position: absolute;
top: -2px;
right: -28px;
transform: rotate(-12deg) scale(0);
width: 52px;
height: 52px;
border: 3px solid rgb(239, 68, 68);
border-radius: 50%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: transparent;
pointer-events: none;
opacity: 0;
box-shadow:
inset 0 0 0 2px rgba(255, 255, 255, 0.95),
inset 0 0 10px 2px rgba(255, 255, 255, 0.7),
inset 0 0 20px 4px rgba(255, 255, 255, 0.4);
}
/* 盖戳动画 */
@keyframes stampDown {
0% {
transform: rotate(-12deg) scale(3);
opacity: 0;
}
50% {
transform: rotate(-12deg) scale(0.9);
opacity: 0.9;
}
70% {
transform: rotate(-12deg) scale(1.05);
opacity: 0.85;
}
100% {
transform: rotate(-12deg) scale(1);
opacity: 0.8;
}
}
.stamp-badge.stamp-animate {
animation: stampDown 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}
.stamp-badge .thumb {
font-size: 18px;
line-height: 1;
}
.stamp-badge .stamp-text {
font-size: 11px;
color: rgb(220, 38, 38);
font-weight: bold;
margin-top: 1px;
text-shadow:
0 0 2px rgba(255,255,255,1),
0 0 4px rgba(255,255,255,0.9),
1px 1px 0 rgba(255,255,255,0.9),
-1px -1px 0 rgba(255,255,255,0.9),
1px -1px 0 rgba(255,255,255,0.9),
-1px 1px 0 rgba(255,255,255,0.9);
}
/* 进度条按比例宽度0-100(45.45%), 100-130(13.64%), 130-160(13.64%), 160-190(13.64%), 190-220(13.64%) */
.tier-segment-0 { flex: 100; }
.tier-segment-1 { flex: 30; }
.tier-segment-2 { flex: 30; }
.tier-segment-3 { flex: 30; }
.tier-segment-4 { flex: 30; }
/* 下降趋势样式 */
.trend-down {
color: rgba(255,255,255,0.5);
font-size: 12px;
}
/* 业绩卡片文字样式 */
.stat-value {
color: #ffffff;
text-shadow: 0 1px 3px rgba(0,0,0,0.25);
}
.stat-highlight {
color: #6ee7b7;
text-shadow: 0 1px 2px rgba(0,0,0,0.2);
}
.stat-label {
color: rgba(255,255,255,0.9);
text-shadow: 0 1px 2px rgba(0,0,0,0.15);
}
.stat-secondary {
color: rgba(255,255,255,0.7);
}
.stat-accent {
color: #fcd34d;
text-shadow: 0 1px 2px rgba(0,0,0,0.2);
}
/* 奖金金额突出样式 */
.bonus-amount {
text-shadow:
0 2px 4px rgba(0, 0, 0, 0.35),
0 0 12px rgba(251, 191, 36, 0.5);
}
/* === 任务分区 === */
.section-label {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 13px;
font-weight: 500;
padding: 3px 10px;
border-radius: 4px;
}
/* 置顶卡片微亮边框 */
.task-card.pinned {
box-shadow: 0 1px 4px rgba(245, 158, 11, 0.12), 0 0 0 1px rgba(245, 158, 11, 0.08);
}
/* 放弃任务 */
.task-card.abandoned {
border-left-color: #d1d5db !important;
opacity: 0.55;
}
.task-card.abandoned .task-name {
color: #9ca3af;
}
.task-card.abandoned .task-desc {
color: #c5c5c5;
}
.task-card.abandoned .task-tag-wrap > span:first-child {
background: #d1d5db !important;
}
/* 备注指示器 */
.note-indicator {
font-size: 12px;
margin-left: 2px;
}
/* === 长按上下文菜单 === */
.context-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.35);
z-index: 100;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s ease;
}
.context-overlay.active {
opacity: 1;
pointer-events: all;
}
.context-menu {
position: fixed;
z-index: 101;
background: #fff;
border-radius: 14px;
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.18);
min-width: 192px;
padding: 6px 0;
opacity: 0;
transform: scale(0.88);
pointer-events: none;
transition: opacity 0.15s ease, transform 0.15s ease;
transform-origin: top left;
}
.context-menu.active {
opacity: 1;
transform: scale(1);
pointer-events: all;
}
.ctx-item {
display: flex;
align-items: center;
gap: 10px;
padding: 13px 18px;
font-size: 14px;
color: #393939;
cursor: pointer;
transition: background 0.12s;
user-select: none;
}
.ctx-item:active {
background: #f3f3f3;
}
.ctx-item + .ctx-item {
border-top: 1px solid #f3f3f3;
}
/* === 备注 / 放弃弹窗 === */
.modal-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 200;
display: flex;
align-items: flex-start;
justify-content: center;
padding-top: 18vh;
padding-bottom: 40vh;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s ease;
}
.modal-overlay.active {
opacity: 1;
pointer-events: all;
}
.modal-card {
background: #fff;
border-radius: 16px;
width: 88%;
max-width: 360px;
padding: 24px 20px 20px;
transform: translateY(24px);
transition: transform 0.25s ease;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
}
.modal-overlay.active .modal-card {
transform: translateY(0);
}
.modal-card textarea {
width: 100%;
border: 1.5px solid #e7e7e7;
border-radius: 10px;
padding: 12px 14px;
font-size: 14px;
line-height: 1.6;
resize: none;
outline: none;
font-family: inherit;
color: #2c2c2c;
transition: border-color 0.2s;
box-sizing: border-box;
}
.modal-card textarea:focus {
border-color: #0052d9;
}
.modal-card textarea.error-border {
border-color: #e34d59;
}
.modal-error {
color: #e34d59;
font-size: 12px;
margin-top: 6px;
display: none;
}
.modal-error.show {
display: block;
}
.modal-submit-btn {
width: 100%;
padding: 12px;
border: none;
border-radius: 10px;
font-size: 15px;
font-weight: 500;
cursor: pointer;
margin-top: 14px;
transition: background 0.2s, opacity 0.2s;
font-family: inherit;
}
.modal-submit-btn.primary {
background: #0052d9;
color: #fff;
}
.modal-submit-btn.primary:active {
background: #003eb3;
}
.modal-submit-btn.danger {
background: #e34d59;
color: #fff;
}
.modal-submit-btn.danger:active {
background: #c9363f;
}
</style>
</head>
<body class="bg-gray-1 min-h-screen">
<!-- 顶部区域 - 用户信息和业绩卡片 -->
<div class="banner-bg theme-blue texture-aurora text-white pb-4">
<!-- 用户信息 -->
<div class="px-5 pt-10 pb-3">
<div class="flex items-center gap-4">
<div class="w-14 h-14 rounded-2xl bg-white/20 backdrop-blur-sm flex items-center justify-center shadow-lg overflow-hidden">
<img src="../img/zjtx.png" class="w-full h-full object-cover" alt="助教头像">
</div>
<div class="flex-1">
<div class="flex items-center gap-2 mb-1">
<span class="text-xl font-semibold">小燕</span>
<span class="px-2 py-0.5 bg-white/20 rounded-full text-xs">助教</span>
</div>
<p class="text-white/70 text-sm">广州朗朗桌球</p>
</div>
</div>
</div>
<!-- 业绩进度卡片 -->
<div class="mx-4">
<div class="bg-white/15 backdrop-blur-md rounded-2xl px-4 py-3 border border-white/20">
<!-- 第一层:标题行 -->
<div class="flex items-center justify-between mb-2">
<div class="flex items-baseline gap-2">
<span class="stat-label text-sm font-medium">距离100小时仅剩</span>
<span class="stat-accent text-xl font-bold">12.5小时</span>
</div>
<a href="performance.html" class="stat-secondary text-xs flex items-center gap-1 hover:text-white transition-colors">
查看详情
<svg class="w-3 h-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="9 18 15 12 9 6"/>
</svg>
</a>
</div>
<!-- 第二层5段档位进度条 + 边界小时数(按比例宽度) -->
<div class="relative mb-6">
<div class="tier-progress">
<div class="tier-segment tier-segment-0 completed" title="0档 <100h"></div>
<div class="tier-segment tier-segment-1 current" title="1档 100-130h">
<div class="tier-fill" style="width: 58%"></div>
</div>
<div class="tier-segment tier-segment-2" title="2档 130-160h"></div>
<div class="tier-segment tier-segment-3" title="3档 160-190h"></div>
<div class="tier-segment tier-segment-4" title="4档 190-220h"></div>
</div>
<!-- 档位边界小时数按比例定位0, 100/220≈45.45%, 130/220≈59.09%, 160/220≈72.73%, 190/220≈86.36%, 220/220=100% -->
<div class="absolute w-full top-full mt-1.5 flex text-[9px]" style="left: 0;">
<span class="text-white/60" style="position:absolute; left:0; transform:translateX(0);">0</span>
<span class="text-white/60" style="position:absolute; left:45.45%; transform:translateX(-50%);">100</span>
<span class="text-white/80 font-medium" style="position:absolute; left:59.09%; transform:translateX(-50%);">130</span>
<span class="text-white/60" style="position:absolute; left:72.73%; transform:translateX(-50%);">160</span>
<span class="text-white/60" style="position:absolute; left:86.36%; transform:translateX(-50%);">190</span>
<span class="text-white/60" style="position:absolute; right:0; transform:translateX(0);">220</span>
</div>
</div>
<!-- 第三层:核心数据 - 两列布局(左宽右窄) -->
<div class="flex items-stretch mb-2.5">
<!-- 左侧:课时数据 + 红戳占60% -->
<div class="pr-4 border-r border-white/25 flex justify-center items-center" style="flex: 3;">
<!-- 自动宽度容器:包含课时数据和红戳,居中显示 -->
<div class="red-stamp inline-block relative" style="padding-right: 35px;">
<div class="text-center">
<div class="flex items-baseline justify-center gap-1.5">
<span class="stat-highlight text-xl font-bold">77.5</span>
<span class="stat-secondary text-sm">|</span>
<span class="stat-accent text-xl font-bold">12</span>
<span class="stat-secondary text-sm">|</span>
<span class="stat-value text-xl font-bold">87.5</span>
</div>
<p class="stat-label text-xs mt-1.5">基础课 | 激励课 | 全部</p>
</div>
<!-- 红戳徽章 -->
<div class="stamp-badge" style="right: -5px; top: 5px;">
<span class="thumb">👍</span>
<span class="stamp-text">已完成</span>
</div>
</div>
</div>
<!-- 右侧奖金激励占40% -->
<div class="pl-3 text-center flex flex-col justify-center" style="flex: 2;">
<div class="bonus-amount">
<span class="text-3xl font-bold text-amber-300">800</span>
<span class="text-base text-amber-300/80"></span>
</div>
<p class="stat-label text-xs mt-1.5">达100h即得</p>
</div>
</div>
<!-- 第四层:预计收入 -->
<div class="flex items-center justify-between pt-2 border-t border-white/25">
<span class="stat-label text-xs">2月预计收入 | 比1月同期</span>
<a href="performance.html" class="flex items-center gap-1.5 group">
<span class="stat-value text-lg font-bold">¥6,206</span>
<span class="trend-down">↓368</span>
<svg class="w-3.5 h-3.5 stat-secondary group-hover:text-white transition-colors" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="9 18 15 12 9 6"/>
</svg>
</a>
</div>
</div>
</div>
</div>
<!-- 待办任务列表 -->
<div class="px-4 py-5">
<!-- 标题 -->
<div class="flex items-center justify-between mb-4">
<h2 class="text-base font-semibold text-gray-13">今日 客户维护</h2>
<span class="text-sm text-gray-6">共 7 项</span>
</div>
<!-- 📌 置顶区域 -->
<div class="mb-5">
<div class="flex items-center gap-1.5 mb-2.5">
<span class="section-label text-amber-700 bg-amber-50">📌 置顶</span>
<span class="text-sm text-gray-6">2项</span>
</div>
<div class="space-y-3">
<!-- 置顶: 王先生 -->
<div class="task-card high-priority pinned block bg-white rounded-xl p-4 shadow-sm cursor-pointer" data-task-id="1" data-task-name="王先生" data-task-type="high-priority">
<div class="flex items-start justify-between">
<div class="flex-1">
<div class="flex items-center gap-2 mb-1.5 task-tag-wrap">
<span class="tag-high-priority px-2 py-0.5 text-white text-xs font-medium">高优先召回</span>
<span class="text-base font-semibold text-gray-13 task-name">王先生</span>
<span class="text-sm">💖</span>
<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"><span class="ai-inline-icon"><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>高流失风险,建议尽快联系</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"/>
</svg>
</div>
</div>
<!-- 置顶: 李女士 -->
<div class="task-card high-priority pinned block bg-white rounded-xl p-4 shadow-sm cursor-pointer" data-task-id="2" data-task-name="李女士" data-task-type="high-priority">
<div class="flex items-start justify-between">
<div class="flex-1">
<div class="flex items-center gap-2 mb-1.5 task-tag-wrap">
<span class="tag-high-priority px-2 py-0.5 text-white text-xs font-medium">高优先召回</span>
<span class="text-base font-semibold text-gray-13 task-name">李女士</span>
<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"><span class="ai-inline-icon"><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>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"/>
</svg>
</div>
</div>
</div>
</div>
<!-- 一般任务区域 -->
<div class="mb-5">
<div class="flex items-center gap-1.5 mb-2.5">
<span class="section-label text-gray-9 bg-gray-2">一般任务</span>
<span class="text-sm text-gray-6">3项</span>
</div>
<div class="space-y-3">
<!-- 张先生 -->
<div class="task-card priority block bg-white rounded-xl p-4 shadow-sm cursor-pointer" data-task-id="3" data-task-name="张先生" data-task-type="priority">
<div class="flex items-start justify-between">
<div class="flex-1">
<div class="flex items-center gap-2 mb-1.5 task-tag-wrap">
<span class="tag-priority px-2 py-0.5 text-white text-xs font-medium">优先召回</span>
<span class="text-base font-semibold text-gray-13 task-name">张先生</span>
<span class="text-sm">💛</span>
<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"><span class="ai-inline-icon"><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>消费频率下降,需关注</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"/>
</svg>
</div>
</div>
<!-- 刘先生 -->
<div class="task-card priority block bg-white rounded-xl p-4 shadow-sm cursor-pointer" data-task-id="4" data-task-name="刘先生" data-task-type="priority">
<div class="flex items-start justify-between">
<div class="flex-1">
<div class="flex items-center gap-2 mb-1.5 task-tag-wrap">
<span class="tag-priority px-2 py-0.5 text-white text-xs font-medium">优先召回</span>
<span class="text-base font-semibold text-gray-13 task-name">刘先生</span>
<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"><span class="ai-inline-icon"><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>偏好晚间时段,可推荐夜场套餐</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"/>
</svg>
</div>
</div>
<!-- 陈先生 -->
<div class="task-card relationship block bg-white rounded-xl p-4 shadow-sm cursor-pointer" data-task-id="5" data-task-name="陈先生" data-task-type="relationship">
<div class="flex items-start justify-between">
<div class="flex-1">
<div class="flex items-center gap-2 mb-1.5 task-tag-wrap">
<span class="tag-relationship px-2 py-0.5 text-white text-xs font-medium">关系构建</span>
<span class="text-base font-semibold text-gray-13 task-name">陈先生</span>
<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"><span class="ai-inline-icon"><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>潜力客户,建议加强互动</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"/>
</svg>
</div>
</div>
</div>
</div>
<!-- ❌ 放弃的任务区域 -->
<div class="mb-2">
<div class="flex items-center gap-1.5 mb-2.5">
<span class="section-label text-gray-6 bg-gray-2">已放弃</span>
<span class="text-sm text-gray-6">2项</span>
</div>
<div class="space-y-3">
<!-- 放弃: 赵女士 -->
<div class="task-card callback abandoned block bg-white rounded-xl p-4 shadow-sm" data-task-id="6" data-task-name="赵女士" data-task-type="callback">
<div class="flex items-start justify-between">
<div class="flex-1">
<div class="flex items-center gap-2 mb-1.5 task-tag-wrap">
<span class="tag-callback px-2 py-0.5 text-white text-xs font-medium">客户回访</span>
<span class="text-base font-semibold text-gray-13 task-name">赵女士</span>
<span class="text-sm">🧡</span>
<span class="note-indicator" title="有备注">📝</span>
</div>
<p class="text-sm text-gray-7 leading-relaxed task-desc">最近到店3天前 · 余额:非常多</p>
<p class="text-sm text-gray-6 leading-relaxed task-desc">放弃原因:客户已转会至其他球房</p>
</div>
</div>
</div>
<!-- 放弃: 周先生 -->
<div class="task-card callback abandoned block bg-white rounded-xl p-4 shadow-sm" data-task-id="7" data-task-name="周先生" data-task-type="callback">
<div class="flex items-start justify-between">
<div class="flex-1">
<div class="flex items-center gap-2 mb-1.5 task-tag-wrap">
<span class="tag-callback px-2 py-0.5 text-white text-xs font-medium">客户回访</span>
<span class="text-base font-semibold text-gray-13 task-name">周先生</span>
<span class="text-sm">💛</span>
<span class="note-indicator" title="有备注">📝</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>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 长按上下文菜单 -->
<div class="context-overlay" id="contextOverlay"></div>
<div class="context-menu" id="contextMenu">
<div class="ctx-item" data-action="pin">
<span>📌</span><span>置顶任务</span>
</div>
<div class="ctx-item" data-action="abandon">
<span></span><span>放弃任务</span>
</div>
<div class="ctx-item" data-action="ai">
<span>🤖</span><span>问问AI助手</span>
</div>
<div class="ctx-item" data-action="remark">
<span>📝</span><span>备注</span>
</div>
</div>
<!-- 备注 / 放弃 弹窗(可复用组件) -->
<div class="modal-overlay" id="remarkModal">
<div class="modal-card">
<h3 class="text-base font-semibold text-gray-13 mb-1" id="modalTitle">添加备注</h3>
<p class="text-xs text-gray-6 mb-3" id="modalSubtitle">为该客户的维护任务添加备注</p>
<textarea id="remarkInput" rows="4" placeholder="请输入备注内容..."></textarea>
<div class="modal-error" id="remarkError">请填写放弃原因,不能为空</div>
<button class="modal-submit-btn primary" id="modalSubmitBtn">保存备注</button>
<button class="mt-2 w-full text-center text-sm text-gray-6 py-2 bg-transparent border-0 cursor-pointer" id="modalCancelBtn">取消</button>
</div>
</div>
<!-- AI 标识配色 -->
<!-- 悬浮助手按钮 -->
<!-- 通用底部导航 -->
<!-- 盖戳动画 -->
<!-- 长按菜单 + 备注弹窗 交互 -->
</body>

Binary file not shown.

After

Width:  |  Height:  |  Size: 601 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

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