2881 lines
124 KiB
Markdown
2881 lines
124 KiB
Markdown
# 企微IT智能服务台 — 系统架构设计文档
|
||
|
||
> **文档版本**: v0.11
|
||
> **创建日期**: 2025-07-11
|
||
> **最近更新**: 2026-06-07
|
||
> **架构师**: 高见远 (Gao)
|
||
> **状态**: 草稿(未上线,待评审)
|
||
> **说明**: 本文档已合并原 `ARCHITECTURE-v53-incremental.md` 内容(v5.3 坐席工作台增量架构),2026-06-07 更新:阶段一范围扩大,新增坐席自研工作台MVP(会话列表+聊天+快速回复),AI转人工链接从员工服务改为H5自建应用。
|
||
|
||
---
|
||
|
||
## 目录
|
||
|
||
1. [实现方案与框架选型](#1-实现方案与框架选型)
|
||
2. [文件列表](#2-文件列表)
|
||
3. [数据结构与接口(类图)](#3-数据结构与接口类图)
|
||
4. [程序调用流程(时序图)](#4-程序调用流程时序图)
|
||
5. [任务列表](#5-任务列表)
|
||
6. [依赖包列表](#6-依赖包列表)
|
||
7. [共享知识(跨文件约定)](#7-共享知识跨文件约定)
|
||
8. [待明确事项](#8-待明确事项)
|
||
9. [v5.3 坐席工作台增量架构](#9-v53-坐席工作台增量架构)
|
||
|
||
---
|
||
|
||
## 1. 实现方案与框架选型
|
||
|
||
### 1.1 整体架构
|
||
|
||
```
|
||
┌───────────────────────────────────────────────────────────────┐
|
||
│ Linux 服务器 Docker │
|
||
│ │
|
||
│ ┌──────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||
│ │ Nginx │ │ Frontend │ │ Frontend │ │
|
||
│ │ (反代) │──│ Agent │ │ H5 User │ │
|
||
│ │ :80/:443│ │ (Vue3+EP) │ │ (Vue3+Vant4)│ │
|
||
│ └────┬─────┘ └──────────────┘ └──────────────┘ │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||
│ │ FastAPI │ │ Redis │ │PostgreSQL│ │
|
||
│ │ Backend │──│ (缓存) │──│ (持久化) │ │
|
||
│ │ :8000 │ │ :6379 │ │ :5432 │ │
|
||
│ └────┬─────┘ └──────────┘ └──────────┘ │
|
||
│ │ │
|
||
└───────┼───────────────────────────────────────────────────────┘
|
||
│ HTTPS
|
||
▼
|
||
┌───────────────┐
|
||
│ 企微服务器 │
|
||
│ (消息回调API) │
|
||
└───────────────┘
|
||
```
|
||
|
||
### 1.2 核心技术挑战与解决方案
|
||
|
||
| # | 挑战 | 解决方案 | 为什么这样选 |
|
||
|---|------|---------|------------|
|
||
| 1 | 企微消息加解密 | 使用 `cryptography` 库实现 AES-CBC-256 加解密,兼容企微官方示例 | 企微要求所有回调消息必须加解密,不能用明文模式(生产环境);`cryptography` 是 Python 官方推荐库 |
|
||
| 2 | access_token 管理 | Redis 缓存 + 过期自动刷新(提前 300 秒刷新) | token 有效期 7200 秒,频繁调用获取接口会触发限流;Redis 的 TTL 机制天然适合 |
|
||
| 3 | 坐席端实时性 | 第一步用短轮询(3-5 秒),前端 `setInterval` + 后端轻量查询 | PRD 明确要求第一步不用 WebSocket;轮询实现简单,3-5 坐席量级下数据库压力可控 |
|
||
| 4 | H5 双栏布局 | CSS Flex 布局,左栏 60% 对话 + 右栏 40% AI 助手面板 | 企微 WebView 对 Flex 支持良好;60/40 比例在手机上对话区够宽,助手面板也能展示信息 |
|
||
| 5 | 会话标记评分 | 纯规则引擎(关键词匹配 + 计数器 + 公式计算) | 第一步不接 AI;规则引擎足够覆盖 VIP/举手/需介入/情绪 四种标记 |
|
||
| 6 | OAuth2 静默授权 | 企微 OAuth2 授权 + 后端换算用户身份 | H5 页面需要知道当前员工身份才能关联会话;企微支持静默授权,用户无感知 |
|
||
| 7 | 消息路由 | 所有消息先进路由层,按规则分发(第一步:新会话 → 坐席队列) | 核心架构决策:路由层是整个系统的"大脑",第二步接入 AI 只需修改路由逻辑 |
|
||
| 8 | 零基础开发者 | 每个文件详细注释(做什么 + 为什么),使用最简单的实现方式 | 开发者零基础,代码可读性 > 优雅性;避免过度抽象 |
|
||
| 9 | **员工端架构选型** | 主方案:H5 WebView;备选方案:企微原生1对1 + 外援群聊 | H5 有跨平台移植便利性和跨主体支持;原生1对1体验最优但绑定企微。详见下方 §1.2.1 |
|
||
|
||
### 1.2.1 员工端架构双方案设计
|
||
|
||
> **评估日期**: 2026-06-03 | **决策**: H5 为主方案,原生1对1为渐进升级/降级备用
|
||
|
||
本系统设计支持**两种员工端交互架构**,后端消息路由层已实现两套入口的完整支撑:
|
||
|
||
**方案A — H5 WebView(当前主方案)**:
|
||
|
||
```
|
||
员工 → 点击应用 → H5 页面(Vue3+Vant4) → 后端API → Dify AI / 坐席 → 应用消息推送 → 员工同一窗口
|
||
```
|
||
|
||
- **优势**:跨平台(钉钉/飞书/浏览器)、跨主体(非静默登录+其他认证)、丰富 UI(身份标识/评分/按钮)
|
||
- **代价**:需开发 H5 前端、需 OAuth2 鉴权、通知依赖 WebView 是否打开
|
||
- **对应代码**:`backend/app/api/h5.py` + `frontend-h5/`
|
||
|
||
**方案B — 企微原生1对1 + 外援群聊(备用/升级方案)**:
|
||
|
||
```
|
||
员工 → 企微与应用1对1聊天 → 企微回调 → 消息路由 → Dify AI / 坐席 → /message/send → 同一窗口
|
||
└─ 外援 → /appchat/create → 新群聊窗口
|
||
```
|
||
|
||
- **优势**:员工体验最优(原生聊天窗口)、零前端开发、通知必达、富媒体原生支持
|
||
- **代价**:绑定企微(无法跨平台/跨主体)、AI/人工区分需内容前缀、交互卡片需额外开发
|
||
- **对应代码**:`backend/app/api/wecom_callback.py` + `backend/app/services/message_router.py`(已在用)
|
||
- **切换成本**:核心链路已实现(回调接收+AI回复走 `/message/send`),**零代码改动即可切换**
|
||
|
||
**方案B 关键企微 API 清单**:
|
||
|
||
| API | 路径 | 用途 | 限制 |
|
||
|-----|------|------|------|
|
||
| 应用消息推送 | `/cgi-bin/message/send` | AI/坐席向员工1对1推送(主流程) | ≤账号上限×200人次/天 |
|
||
| 创建群聊 | `/cgi-bin/appchat/create` | 外援场景:创建多方协作群 | ≤1000群/天,仅自建应用 |
|
||
| 推送群消息 | `/cgi-bin/appchat/send` | 群内推送消息 | ≤2万人次/分 |
|
||
|
||
**决策建议**:
|
||
- 企微主体内员工服务 → **方案B 体验更优**(M2 阶段可优先启用)
|
||
- 需要跨平台/跨主体 → **方案A 不可替代**(保留 H5 扩展层)
|
||
- 应急降级 → **方案A 不可用时,方案B 零代码切换**(详见 `docs/01-项目总览与部署手册.md` §7.5)
|
||
|
||
### 1.2.1a 现有生产环境架构
|
||
|
||
> **记录日期**: 2026-06-07 | **用途**: 作为新系统演进的基线对照
|
||
|
||
公司已上线的 IT 咨询系统采用以下架构:
|
||
|
||
```
|
||
┌───────────────────────────────────────────────────────────────────┐
|
||
│ 现有生产环境架构 │
|
||
│ │
|
||
│ ┌──────────────────────┐ │
|
||
│ │ 企微AI机器人应用 │ ← 员工1对1对话入口 │
|
||
│ │ (自建应用 AgentId) │ │
|
||
│ └──────────┬───────────┘ │
|
||
│ │ 员工消息回调 │
|
||
│ ▼ │
|
||
│ ┌──────────────────────┐ ┌──────────────────┐ │
|
||
│ │ Dify (AI编排平台) │────→│ RAGFlow │ │
|
||
│ │ · 千问大模型 │←────│ · 知识库语义检索 │ │
|
||
│ │ · 对话上下文管理 │ │ · IT知识库 │ │
|
||
│ └──────────┬───────────┘ └──────────────────┘ │
|
||
│ │ AI回复 │
|
||
│ ▼ │
|
||
│ ┌──────────────────────┐ │
|
||
│ │ 企微 /message/send │ → 推送到员工1对1窗口 │
|
||
│ └──────────┬───────────┘ │
|
||
│ │ 关键字触发转人工 │
|
||
│ ▼ │
|
||
│ ┌──────────────────────┐ │
|
||
│ │ 企微-员工服务-桌面IT │ ← 独立窗口,人工坐席处理 │
|
||
│ │ · 排队 → 分配坐席 │ │
|
||
│ │ · 纯手动回复 │ │
|
||
│ │ · 无AI辅助/无知识管理 │ │
|
||
│ └──────────────────────┘ │
|
||
└───────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**现有系统 vs 新系统架构对比**:
|
||
|
||
| 维度 | 现有生产环境 | 新系统(方式四 H5) |
|
||
|------|------------|------------------|
|
||
| 员工入口 | 企微1对1与AI机器人对话 | 自建应用 H5 WebView |
|
||
| AI引擎 | RAGFlow + Dify + 千问(**复用**) | RAGFlow + Dify + 千问(**复用,不替换**) |
|
||
| AI→人工切换 | 关键字触发 → 推送员工服务链接 → 跳转新窗口 | H5 内同一对话流无缝切换 |
|
||
| 坐席工具 | 企微员工服务后台(纯手动) | 自研坐席工作台(AI Wingman 辅助) |
|
||
| 消息通道 | 企微 `/message/send`(AI回复)+ 员工服务(人工回复) | 企微 `/message/send`(通知必达)+ WebSocket(H5即时刷新) |
|
||
| 知识管理 | RAGFlow 知识库(人工维护) | RAGFlow + 坐席标注自动迭代 |
|
||
| 数据统计 | 无 | 会话/绩效/AI质量多维度看板 |
|
||
|
||
**关键决策:AI引擎复用,不替换**。新系统直接复用现有 RAGFlow + Dify + 千问基础设施,仅迁移员工入口(从1对1到H5)和坐席工具(从员工服务到自研工作台),AI能力零迁移成本。
|
||
|
||
**阶段一实施路径**:企微AI机器人 + Dify + RAGFlow + 千问**已在生产环境运行**,阶段一只做三件事:①员工端H5登录+身份识别 ②AI机器人转人工链接从"员工服务"改为H5自建应用 ③坐席自研工作台MVP(会话列表+聊天+快速回复)。AI引擎零改动,坐席端AI能力暂不接入(阶段三引入)。
|
||
|
||
### 1.2.1b H5 端 WebSocket 实时推送架构
|
||
|
||
> **设计日期**: 2026-06-07 | **状态**: 方案已确认,待开发
|
||
|
||
H5 员工端采用**双通道消息通知策略**,确保消息**既必达又即时**:
|
||
|
||
```
|
||
┌───────────────────────────────────────────────────────────────────┐
|
||
│ H5 双通道消息推送架构 │
|
||
│ │
|
||
│ 坐席发送消息 │
|
||
│ │ │
|
||
│ ├──── 通道1: 企微 /message/send ──────────────────┐ │
|
||
│ │ · 保证必达(系统级通知) │ │
|
||
│ │ · 员工未在H5页面时,收到企微通知弹窗/红点 │ │
|
||
│ │ · 员工点击通知 → 回到H5页面 → 拉取新消息 │ │
|
||
│ │ ▼ │
|
||
│ │ 员工企微客户端 │
|
||
│ │ │
|
||
│ └──── 通道2: WebSocket 推送 ────────────────────┐ │
|
||
│ · 保证即时(秒级刷新) │ │
|
||
│ · 员工在H5页面时,聊天区自动更新 │ │
|
||
│ · 页面未打开时,WS断连,降级为通道1 │ │
|
||
│ ▼ │
|
||
│ H5 WebView │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||
│ │ 后端 ws_manager.py 扩展 │ │
|
||
│ │ │ │
|
||
│ │ 原有:agent_id → WebSocket(坐席端) │ │
|
||
│ │ 新增:employee_id → WebSocket(H5员工端) │ │
|
||
│ │ │ │
|
||
│ │ 消息发送时: │ │
|
||
│ │ 1. 查找 employee 的 WS 连接 → 有 → 推送 new_message 事件 │ │
|
||
│ │ 2. 同时调用 /message/send → 企微系统级通知 │ │
|
||
│ │ 3. WS推送失败 → 不重试(通道1已兜底) │ │
|
||
│ └─────────────────────────────────────────────────────────────┘ │
|
||
└───────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**WebSocket 端点设计**:
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| 端点 | `GET /api/h5/ws?token={bearer_token}` |
|
||
| 鉴权 | Bearer Token(与 H5 REST API 共享 token 体系,Redis 查询 `employee:token:{token}`) |
|
||
| 连接管理 | `ws_manager.py` 扩展 `employee_connections: Dict[str, WebSocket]` |
|
||
| 消息格式 | `{"type": "new_message", "data": {"id": "uuid", "content": "...", "sender_type": "agent", "sender_name": "张三", "created_at": "..."}}` |
|
||
| 心跳 | 客户端每 30s 发 `{"type": "ping"}`,服务端回 `{"type": "pong"}`,超时 60s 断连 |
|
||
| 断连降级 | 前端检测 WS 断连 → 自动切换为轮询 `GET /h5/conversations/current/messages/poll`(3s间隔) |
|
||
| 重连策略 | 指数退避:1s → 2s → 4s → 8s → 16s → 30s(上限),重连成功后拉取断连期间消息 |
|
||
| 并发限制 | 同一员工只保留最新 WS 连接(与坐席端逻辑一致) |
|
||
|
||
**前端实现**:
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| 文件 | `frontend-h5/src/composables/useWebSocket.ts` |
|
||
| 状态管理 | `frontend-h5/src/stores/websocket.ts`(Pinia Store) |
|
||
| 事件监听 | `new_message` → 自动追加到聊天区消息列表 |
|
||
| 生命周期 | 页面 `onMounted` 连接 WS,`onUnmounted` 断连;`visibilitychange` 事件处理后台切换 |
|
||
|
||
**与现有代码的关系**:
|
||
|
||
| 现有代码 | 变更 |
|
||
|---------|------|
|
||
| `ws_manager.py` | 扩展 `ConnectionManager`,新增 `employee_connections` 字典 + `connect_employee` / `send_to_employee` 方法 |
|
||
| `messages.py` | 坐席发消息时,新增 `ws_manager.send_to_employee()` 调用(在现有 `wecom_service.send_text_message()` 之后) |
|
||
| `h5.py` | 新增 `GET /api/h5/ws` WebSocket 端点 |
|
||
| `frontend-h5/` | 新增 `useWebSocket.ts` + `websocket.ts` store |
|
||
|
||
#### 1.2.1b-1 WebSocket 技术评估结论(2026-06-11)
|
||
|
||
> **评估日期**: 2026-06-11 | **评估人**: 齐活林 (Qi) · 交付总监
|
||
|
||
基于当前项目实际代码(坐席端 `useWebSocket.ts` + 后端 `ws_manager.py`)的完整技术评估:
|
||
|
||
**性能评估 ⭐⭐⭐⭐⭐**:
|
||
|
||
| 指标 | WebSocket | HTTP 轮询(1s间隔) | SSE | 本项目适用性 |
|
||
|------|-----------|-------------------|-----|------------|
|
||
| 消息延迟 | 1-5ms | 200-1000ms | 3-10ms | 坐席需即时看到新消息 |
|
||
| 帧开销 | 2-14 字节/帧 | ~500 字节/次 | ~30 字节/帧 | 50 坐席量级影响不大 |
|
||
| 建立连接开销 | 1 次 HTTP 升级 | 每次请求 HTTP 头 | 1 次 HTTP 升级 | 长连接场景优势明显 |
|
||
| 服务端推送能力 | 全双工 | 半双工(只能拉) | 服务端→客户端单推 | typing 指示器等双向交互刚需 |
|
||
| CPU 占用 | 极低(事件驱动) | 高(频繁 HTTP 请求) | 低 | 单实例后端资源有限 |
|
||
|
||
**容量评估 ⭐⭐⭐⭐**:
|
||
|
||
| 维度 | 容量上限 | 本项目(~50 坐席) | 瓶颈判断 |
|
||
|------|---------|---------|----------|
|
||
| 同时连接数 | 单进程 1-5 万 | ~50 | 远未达上限 |
|
||
| 每连接内存 | ~10-50KB | 50×50KB=2.5MB | 可忽略 |
|
||
| 消息吞吐 | ~1 万条/秒 | 远低于此 | 无压力 |
|
||
| 水平扩展 | 当前单进程字典 | 暂不需要 | 量级增长时需改 |
|
||
|
||
**扩展路线**:
|
||
|
||
| 阶段 | 方案 | 改动量 |
|
||
|------|------|-------|
|
||
| 当前(<100 坐席) | 单进程内存字典 | ✅ 已实现 |
|
||
| 中期(100-1000 坐席) | Redis Pub/Sub 跨节点广播 | `ConnectionManager` 加 Redis 适配层 |
|
||
| 远期(>1000 坐席) | 专用 WS 集群(Centrifugo / Socket.IO + Redis adapter) | 架构重构 |
|
||
|
||
#### 1.2.1b-2 WebSocket 待办事项与风险清单
|
||
|
||
**🔴 高风险(必须处理)**:
|
||
|
||
| # | 风险 | 描述 | 当前代码现状 | 修复建议 | 对应阶段 |
|
||
|---|------|------|------------|---------|---------|
|
||
| WS-01 | **WS 认证缺失** | `/ws/{agent_id}` 无 token 验证,任何人可冒充坐席连接 | `ws.py:27` 仅取 `agent_id`,不验证身份 | 握手时从 query param 取 token → 查 Redis,不通过则 `close(code=4001)` | **阶段一 P0** |
|
||
| WS-02 | **Nginx 超时断连** | 默认 `proxy_read_timeout=60s`,60 秒无数据则断 | 前端 30s 心跳已覆盖 | 确认 Nginx 配置 `proxy_read_timeout` ≥ 90s(3 倍心跳) | **阶段一 P0** |
|
||
| WS-03 | **部署中断** | 后端重启时所有 WS 连接断开 | 前端指数退避重连已覆盖 | 部署脚本加"等待 5s 重连"延迟 | **阶段一 P1** |
|
||
|
||
**🟡 中风险(应处理)**:
|
||
|
||
| # | 风险 | 描述 | 当前代码现状 | 修复建议 | 对应阶段 |
|
||
|---|------|------|------------|---------|---------|
|
||
| WS-04 | **僵尸连接** | 客户端异常断开(合盖、断网),服务端无感知 | `send_to_agent` 失败自动清理 | 定时全量心跳检测(5 分钟 broadcast `health_check`) | 阶段二 2A |
|
||
| WS-05 | **消息丢失** | WS 断连期间推送的消息无法送达 | 断连后切轮询,但中间推送消息可能丢 | 重要事件(摇人邀请)用双通道:WS 即时 + 企微应用消息保底 | 阶段二 2A |
|
||
| WS-06 | **消息去重** | WS + 轮询双通道同时活跃时同一条消息被添加两次 | `handleNewMessage` 直接 push | 按 `message_id` 去重:`if (!messages.find(m => m.message_id === data.message_id))` | **阶段一 P0** |
|
||
| WS-07 | **服务端心跳超时** | 服务端不主动检测客户端是否存活 | 仅客户端→服务端 ping/pong | `await asyncio.wait_for(websocket.receive_json(), timeout=60)` | **阶段一 P1** |
|
||
|
||
**🟢 低风险(注意即可)**:
|
||
|
||
| # | 风险 | 描述 | 当前代码现状 |
|
||
|---|------|------|------------|
|
||
| WS-08 | 调试困难 | 浏览器 DevTools WS 面板不如 Network 直观 | 已有 console.log 日志 |
|
||
| WS-09 | CORS | WS 不受 CORS 限制,Nginx 代理可能限制 | 同源部署,无跨域问题 |
|
||
| WS-10 | 大消息 | 单帧过大可能被代理/浏览器截断 | 消息都是 JSON 小帧,无风险 |
|
||
|
||
### 1.2.2 坐席端 AI Wingman 智能辅助架构
|
||
|
||
> **设计日期**: 2026-06-04 | **状态**: 方案已确认
|
||
|
||
本系统在坐席端引入 AI Wingman(智能副驾驶),通过三层设计架构逐步赋能坐席:
|
||
|
||
```
|
||
┌───────────────────────────────────────────────────────────────────────┐
|
||
│ 坐席工作台(三栏布局) │
|
||
│ │
|
||
│ ┌──────────┐ ┌─────────────────────────┐ ┌──────────────────────┐ │
|
||
│ │ 会话列表 │ │ 对话区(中栏) │ │ AI Wingman(右栏) │ │
|
||
│ │ │ │ │ │ │ │
|
||
│ │ 排序+标签 │ │ ┌─────────────────┐ │ │ [AI草稿] 内嵌在对话流 │ │
|
||
│ │ │ │ │ [员工] 我的电脑.. │ │ │ │ │
|
||
│ │ │ │ │ ┌─────────────┐ │ │ │ ┌─ 会话自动摘要 ───┐ │ │
|
||
│ │ │ │ │ │AI建议回复 │ │ │ │ │ 问题/原因/方案 │ │ │
|
||
│ │ │ │ │ │[采纳][编辑][忽略]│ │ │ └────────────────┘ │ │
|
||
│ │ │ │ │ └─────────────┘ │ │ │ │ │
|
||
│ │ │ │ └─────────────────┘ │ │ ┌─ 自动标签 ───────┐ │ │
|
||
│ │ │ │ │ │ │ 账号问题/网络故障 │ │ │
|
||
│ │ │ │ ┌─────────────────┐ │ │ └────────────────┘ │ │
|
||
│ │ │ │ │ [坐席] 好的我来..│ │ │ │ │
|
||
│ │ │ │ └─────────────────┘ │ │ ┌─ 快捷回复库 ─────┐ │ │
|
||
│ │ │ │ │ │ │ 密码重置/VPN指引 │ │ │
|
||
│ │ │ └─────────────────────────┘ │ └────────────────┘ │ │
|
||
│ └──────────┘ └──────────────────────┘ │
|
||
└───────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**双区布局设计**:
|
||
|
||
| 区域 | 位置 | 功能 | 设计理由 |
|
||
|------|------|------|---------|
|
||
| 内嵌区 | 对话流中 | AI草稿回复(每条员工消息下方) | 与对话上下文紧密关联,坐席逐条审核 |
|
||
| 侧栏区 | 右侧面板 | 自动摘要、自动标签、知识推荐、快捷回复 | 参考性信息,按需查阅,不干扰实时对话 |
|
||
|
||
**三层功能架构**:
|
||
|
||
| 层 | 核心功能 | 目标指标 | 实施阶段 |
|
||
|----|---------|---------|---------|
|
||
| 效率层 | AI草稿回复 + 自动摘要 + 自动标签 | 打字量 -80%,处理时间 -60% | Phase 1 |
|
||
| 认知层 | 知识推荐 + SOP导航 + 相似工单 + 客户画像 | 认知负荷 -55%,新人上手 -50% | Phase 2 |
|
||
| 情感层 | 情绪识别 + 安抚话术 + 语气润色 + 疲劳检测 | 情绪耗竭 -45% | Phase 3 |
|
||
|
||
**底层 AI 架构**:
|
||
|
||
坐席端 AI Wingman 复用现有 Dify 基础设施,新增一个 `assistant` 类型的 Dify Agent:
|
||
|
||
| Agent | 用途 | system prompt 侧重 | 状态 |
|
||
|-------|------|-------------------|------|
|
||
| Agent 1 — 员工端 AI | 回答员工问题 | 友好、准确、引导自助 | 已实现 |
|
||
| Agent 2 — 坐席端 Wingman | 辅助坐席生成草稿/摘要/知识推荐 | 专业、结构化、可操作 | 待开发 |
|
||
|
||
两个 Agent **共用同一个知识库**(RAGFlow/Dify Knowledge Base),但对话上下文不同:
|
||
- Agent 1 接收:员工原始问题
|
||
- Agent 2 接收:完整对话历史 + 员工画像 + 坐席操作上下文
|
||
|
||
### 1.3 架构模式
|
||
|
||
**后端**: 分层架构(Layered Architecture)
|
||
```
|
||
API 层 (api/) → 服务层 (services/) → 数据访问层 (models/ + database.py)
|
||
```
|
||
- **为什么选分层而不是六边形/洋葱架构**: 零基础开发者更容易理解"请求进来 → 调用服务 → 操作数据库"的直线流程;第二步需要时再重构
|
||
|
||
**前端**: 组件化 + 状态管理
|
||
```
|
||
视图层 (views/) → 组件层 (components/) → 状态管理 (stores/) → API调用 (api/)
|
||
```
|
||
- **为什么选 Pinia 而不是 Vuex**: Pinia 是 Vue3 官方推荐,API 更简洁,TypeScript 支持更好
|
||
|
||
### 1.4 部署架构
|
||
|
||
```
|
||
Linux 服务器 Docker
|
||
├── nginx (反向代理 + HTTPS + 静态文件)
|
||
│ ├── / → 坐席工作台 (frontend-agent 构建产物)
|
||
│ ├── /h5 → 用户端 H5 (frontend-h5 构建产物)
|
||
│ └── /api → FastAPI 后端 (upstream)
|
||
├── backend (FastAPI 应用)
|
||
├── postgres (数据库)
|
||
└── redis (缓存)
|
||
```
|
||
|
||
- **为什么用 Nginx 做反代**: 统一入口、HTTPS 终止、静态文件服务、跨域处理;Docker 环境下 Nginx 配置简单
|
||
- **部署模式**: 预生产阶段 Docker Compose 单机部署(AI 系统独立主机,与数据平台通过域名+远程 IP 路由协作);正式环境迁移 K8s 集群
|
||
|
||
---
|
||
|
||
## 2. 文件列表
|
||
|
||
> 项目根目录: `C:\Users\simon\wecom_it_smart_desk`
|
||
|
||
### 2.1 后端 (backend/)
|
||
|
||
| # | 相对路径 | 说明 |
|
||
|---|---------|------|
|
||
| 1 | `backend/requirements.txt` | Python 依赖声明 |
|
||
| 2 | `backend/Dockerfile` | 后端 Docker 镜像构建 |
|
||
| 3 | `backend/alembic.ini` | Alembic 迁移配置 |
|
||
| 4 | `backend/alembic/env.py` | Alembic 迁移环境 |
|
||
| 5 | `backend/alembic/versions/.gitkeep` | 迁移版本目录占位 |
|
||
| 6 | `backend/app/__init__.py` | 应用包初始化 |
|
||
| 7 | `backend/app/main.py` | FastAPI 应用入口 |
|
||
| 8 | `backend/app/config.py` | 配置管理(读取环境变量) |
|
||
| 9 | `backend/app/database.py` | 数据库连接 + Session 管理 |
|
||
| 10 | `backend/app/models/__init__.py` | 模型包初始化(导出所有模型) |
|
||
| 11 | `backend/app/models/conversation.py` | 会话模型 |
|
||
| 12 | `backend/app/models/message.py` | 消息模型 |
|
||
| 13 | `backend/app/models/agent.py` | 坐席模型 |
|
||
| 14 | `backend/app/models/quick_reply_template.py` | 快速回复模板模型 |
|
||
| 15 | `backend/app/models/system_config.py` | 系统配置模型 |
|
||
| 16 | `backend/app/models/funny_phrase.py` | 趣味话术模型 |
|
||
| 17 | `backend/app/models/approval_link.py` | 审批流程链接模型 |
|
||
| 18 | `backend/app/models/software_download.py` | 软件下载入口模型 |
|
||
| 19 | `backend/app/models/agent_note.py` | 坐席备注模型 |
|
||
| 20 | `backend/app/schemas/__init__.py` | Schema 包初始化 |
|
||
| 21 | `backend/app/schemas/conversation.py` | 会话 Pydantic Schema |
|
||
| 22 | `backend/app/schemas/message.py` | 消息 Pydantic Schema |
|
||
| 23 | `backend/app/schemas/agent.py` | 坐席 Pydantic Schema |
|
||
| 24 | `backend/app/schemas/quick_reply.py` | 快速回复 Pydantic Schema |
|
||
| 25 | `backend/app/schemas/wecom.py` | 企微回调消息 Schema |
|
||
| 26 | `backend/app/schemas/h5.py` | H5 用户端 Schema |
|
||
| 27 | `backend/app/api/__init__.py` | API 包初始化 |
|
||
| 28 | `backend/app/api/router.py` | API 路由汇总 |
|
||
| 29 | `backend/app/api/wecom_callback.py` | 企微消息回调 API |
|
||
| 30 | `backend/app/api/conversations.py` | 会话管理 API |
|
||
| 31 | `backend/app/api/messages.py` | 消息管理 API |
|
||
| 32 | `backend/app/api/agents.py` | 坐席管理 API |
|
||
| 33 | `backend/app/api/quick_replies.py` | 快速回复模板 API |
|
||
| 34 | `backend/app/api/h5.py` | H5 用户端 API |
|
||
| 35 | `backend/app/services/__init__.py` | 服务包初始化 |
|
||
| 36 | `backend/app/services/wecom_service.py` | 企微 API 调用服务 |
|
||
| 37 | `backend/app/services/message_router.py` | 消息路由服务 |
|
||
| 38 | `backend/app/services/conversation_service.py` | 会话业务逻辑 |
|
||
| 39 | `backend/app/services/agent_service.py` | 坐席业务逻辑 |
|
||
| 40 | `backend/app/services/scoring_service.py` | 紧急度评分服务 |
|
||
| 41 | `backend/app/services/vip_service.py` | VIP 匹配服务 |
|
||
| 42 | `backend/app/utils/__init__.py` | 工具包初始化 |
|
||
| 43 | `backend/app/utils/wecom_crypto.py` | 企微消息加解密工具 |
|
||
| 44 | `backend/app/utils/token_manager.py` | access_token 管理器 |
|
||
| 45 | `backend/app/utils/response.py` | 统一响应格式工具 |
|
||
|
||
### 2.2 坐席工作台前端 (frontend-agent/)
|
||
|
||
| # | 相对路径 | 说明 |
|
||
|---|---------|------|
|
||
| 46 | `frontend-agent/package.json` | Node.js 依赖声明 |
|
||
| 47 | `frontend-agent/vite.config.ts` | Vite 构建配置 |
|
||
| 48 | `frontend-agent/tsconfig.json` | TypeScript 配置 |
|
||
| 49 | `frontend-agent/tsconfig.node.json` | TypeScript Node 配置 |
|
||
| 50 | `frontend-agent/index.html` | HTML 入口 |
|
||
| 51 | `frontend-agent/Dockerfile` | 前端 Docker 镜像构建 |
|
||
| 52 | `frontend-agent/env.d.ts` | 环境类型声明 |
|
||
| 53 | `frontend-agent/src/main.ts` | 应用入口 |
|
||
| 54 | `frontend-agent/src/App.vue` | 根组件 |
|
||
| 55 | `frontend-agent/src/router/index.ts` | 路由配置 |
|
||
| 56 | `frontend-agent/src/stores/conversation.ts` | 会话状态管理 |
|
||
| 57 | `frontend-agent/src/stores/agent.ts` | 坐席状态管理 |
|
||
| 58 | `frontend-agent/src/stores/quickReply.ts` | 快速回复状态管理 |
|
||
| 59 | `frontend-agent/src/api/index.ts` | Axios 实例 + 拦截器 |
|
||
| 60 | `frontend-agent/src/api/conversation.ts` | 会话 API 调用 |
|
||
| 61 | `frontend-agent/src/api/message.ts` | 消息 API 调用 |
|
||
| 62 | `frontend-agent/src/api/agent.ts` | 坐席 API 调用 |
|
||
| 63 | `frontend-agent/src/api/quickReply.ts` | 快速回复 API 调用 |
|
||
| 64 | `frontend-agent/src/views/Workspace.vue` | 坐席工作台主页面(三栏布局) |
|
||
| 65 | `frontend-agent/src/components/conversation/ConversationList.vue` | 会话列表组件 |
|
||
| 66 | `frontend-agent/src/components/conversation/ConversationItem.vue` | 会话列表项组件 |
|
||
| 67 | `frontend-agent/src/components/chat/ChatArea.vue` | 对话区组件 |
|
||
| 68 | `frontend-agent/src/components/chat/MessageBubble.vue` | 消息气泡组件 |
|
||
| 69 | `frontend-agent/src/components/chat/ReplyBox.vue` | 回复输入框组件 |
|
||
| 70 | `frontend-agent/src/components/assistant/AiAssistantPanel.vue` | AI 助手面板容器 |
|
||
| 71 | `frontend-agent/src/components/assistant/AiSuggestReply.vue` | AI 建议回复模块(mock) |
|
||
| 72 | `frontend-agent/src/components/assistant/QuickReplyPanel.vue` | 快速回复模板模块 |
|
||
| 73 | `frontend-agent/src/components/assistant/OperationSteps.vue` | 操作步骤模块(静态) |
|
||
| 74 | `frontend-agent/src/components/assistant/RiskAlert.vue` | 风险提示模块(预留接口) |
|
||
| 75 | `frontend-agent/src/components/assistant/UserInfoPanel.vue` | 用户信息面板 |
|
||
| 76 | `frontend-agent/src/styles/global.css` | 全局样式 |
|
||
|
||
### 2.3 用户端 H5 前端 (frontend-h5/)
|
||
|
||
| # | 相对路径 | 说明 |
|
||
|---|---------|------|
|
||
| 77 | `frontend-h5/package.json` | Node.js 依赖声明 |
|
||
| 78 | `frontend-h5/vite.config.ts` | Vite 构建配置 |
|
||
| 79 | `frontend-h5/tsconfig.json` | TypeScript 配置 |
|
||
| 80 | `frontend-h5/tsconfig.node.json` | TypeScript Node 配置 |
|
||
| 81 | `frontend-h5/index.html` | HTML 入口 |
|
||
| 82 | `frontend-h5/Dockerfile` | 前端 Docker 镜像构建 |
|
||
| 83 | `frontend-h5/env.d.ts` | 环境类型声明 |
|
||
| 84 | `frontend-h5/src/main.ts` | 应用入口 |
|
||
| 85 | `frontend-h5/src/App.vue` | 根组件 |
|
||
| 86 | `frontend-h5/src/router/index.ts` | 路由配置 |
|
||
| 87 | `frontend-h5/src/stores/conversation.ts` | 会话状态管理 |
|
||
| 88 | `frontend-h5/src/api/index.ts` | Axios 实例 + 拦截器 |
|
||
| 89 | `frontend-h5/src/api/conversation.ts` | 会话 API 调用 |
|
||
| 90 | `frontend-h5/src/views/ChatView.vue` | 聊天主页面(双栏布局) |
|
||
| 91 | `frontend-h5/src/components/chat/ChatPanel.vue` | 对话区面板 |
|
||
| 92 | `frontend-h5/src/components/chat/MessageBubble.vue` | 消息气泡组件 |
|
||
| 93 | `frontend-h5/src/components/chat/ShakeButton.vue` | 摇人按钮组件 |
|
||
| 94 | `frontend-h5/src/components/chat/InputBar.vue` | 输入栏组件 |
|
||
| 95 | `frontend-h5/src/components/assistant/AiHelperPanel.vue` | AI 助手面板容器 |
|
||
| 96 | `frontend-h5/src/components/assistant/ApprovalLinks.vue` | 审批流程链接模块 |
|
||
| 97 | `frontend-h5/src/components/assistant/SoftwareDownloads.vue` | 软件下载入口模块 |
|
||
| 98 | `frontend-h5/src/components/assistant/ComingSoon.vue` | "即将上线"占位组件 |
|
||
| 99 | `frontend-h5/src/styles/global.css` | 全局样式 |
|
||
|
||
### 2.4 基础设施
|
||
|
||
| # | 相对路径 | 说明 |
|
||
|---|---------|------|
|
||
| 100 | `docker-compose.yml` | Docker Compose 编排文件 |
|
||
| 101 | `nginx/nginx.conf` | Nginx 反向代理配置 |
|
||
| 102 | `.env.example` | 环境变量模板 |
|
||
|
||
---
|
||
|
||
## 3. 数据结构与接口(类图)
|
||
|
||
### 3.1 数据库表结构
|
||
|
||
#### 3.1.1 会话表 (conversations)
|
||
|
||
```sql
|
||
CREATE TABLE conversations (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
employee_id VARCHAR(64) NOT NULL, -- 企微员工UserID
|
||
employee_name VARCHAR(128) NOT NULL DEFAULT '', -- 员工姓名
|
||
department VARCHAR(256) NOT NULL DEFAULT '', -- 部门
|
||
position VARCHAR(128) NOT NULL DEFAULT '', -- 岗位
|
||
level VARCHAR(64) NOT NULL DEFAULT '', -- 等级
|
||
status VARCHAR(20) NOT NULL DEFAULT 'queued'
|
||
CHECK (status IN ('ai_handling','queued','serving','resolved')),
|
||
is_vip BOOLEAN NOT NULL DEFAULT FALSE, -- VIP标记
|
||
is_pinned BOOLEAN NOT NULL DEFAULT FALSE, -- 置顶标记
|
||
is_todo BOOLEAN NOT NULL DEFAULT FALSE, -- 代办标记
|
||
urgency_score INTEGER NOT NULL DEFAULT 1
|
||
CHECK (urgency_score BETWEEN 1 AND 5), -- 紧急度1-5
|
||
tags JSONB NOT NULL DEFAULT '{}', -- 标签集合,如 {"hand_raise":true,"emotion":"angry"}
|
||
assigned_agent_id VARCHAR(64), -- 分配的坐席ID
|
||
last_message_at TIMESTAMP WITH TIME ZONE, -- 最后消息时间(用于排序)
|
||
last_message_summary VARCHAR(256) NOT NULL DEFAULT '', -- 最后消息摘要
|
||
participants JSONB NOT NULL DEFAULT '[]', -- 会话参与者列表(邀请功能)
|
||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||
);
|
||
|
||
-- 索引
|
||
CREATE INDEX idx_conversations_status ON conversations(status);
|
||
CREATE INDEX idx_conversations_employee_id ON conversations(employee_id);
|
||
CREATE INDEX idx_conversations_assigned_agent ON conversations(assigned_agent_id);
|
||
CREATE INDEX idx_conversations_urgency_score ON conversations(urgency_score DESC);
|
||
CREATE INDEX idx_conversations_last_message_at ON conversations(last_message_at DESC);
|
||
CREATE INDEX idx_conversations_is_vip ON conversations(is_vip) WHERE is_vip = TRUE;
|
||
```
|
||
|
||
**tags JSONB 字段结构说明**:
|
||
```json
|
||
{
|
||
"hand_raise": true, // 举手标记
|
||
"need_intervene": true, // 需介入标记
|
||
"emotion": "angry", // 情绪标记: neutral/worried/angry/urgent
|
||
"emotion_keywords": ["急", "崩溃"], // 触发情绪标记的关键词
|
||
"repeat_count": 3 // 追问轮次计数
|
||
}
|
||
```
|
||
|
||
#### 3.1.2 消息表 (messages)
|
||
|
||
```sql
|
||
CREATE TABLE messages (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
conversation_id UUID NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
|
||
sender_type VARCHAR(20) NOT NULL
|
||
CHECK (sender_type IN ('employee','agent','ai','system')),
|
||
sender_id VARCHAR(64) NOT NULL, -- 发送者ID
|
||
sender_name VARCHAR(128) NOT NULL DEFAULT '', -- 发送者姓名(冗余,减少关联查询)
|
||
content TEXT NOT NULL DEFAULT '', -- 消息内容
|
||
msg_type VARCHAR(20) NOT NULL DEFAULT 'text'
|
||
CHECK (msg_type IN ('text','image','file','system')),
|
||
ai_suggestion BOOLEAN NOT NULL DEFAULT FALSE, -- 是否为AI建议(坐席端)
|
||
is_read BOOLEAN NOT NULL DEFAULT FALSE, -- 是否已读
|
||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||
);
|
||
|
||
-- 索引
|
||
CREATE INDEX idx_messages_conversation_id ON messages(conversation_id);
|
||
CREATE INDEX idx_messages_created_at ON messages(created_at);
|
||
CREATE INDEX idx_messages_conversation_created ON messages(conversation_id, created_at);
|
||
CREATE INDEX idx_messages_unread ON messages(conversation_id, is_read) WHERE is_read = FALSE;
|
||
```
|
||
|
||
#### 3.1.3 坐席表 (agents)
|
||
|
||
```sql
|
||
CREATE TABLE agents (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
user_id VARCHAR(64) NOT NULL UNIQUE, -- 企微用户ID(唯一)
|
||
name VARCHAR(128) NOT NULL, -- 坐席姓名
|
||
status VARCHAR(20) NOT NULL DEFAULT 'offline'
|
||
CHECK (status IN ('online','offline','busy')),
|
||
current_load INTEGER NOT NULL DEFAULT 0, -- 当前服务会话数
|
||
max_load INTEGER NOT NULL DEFAULT 5, -- 最大同时服务数
|
||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||
);
|
||
```
|
||
|
||
#### 3.1.4 快速回复模板表 (quick_reply_templates)
|
||
|
||
```sql
|
||
CREATE TABLE quick_reply_templates (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
category VARCHAR(64) NOT NULL DEFAULT '通用', -- 分类:账号/网络/软件/硬件/通用
|
||
title VARCHAR(128) NOT NULL, -- 模板标题
|
||
content TEXT NOT NULL, -- 模板内容,支持变量如 {employee_name}
|
||
variables JSONB NOT NULL DEFAULT '[]', -- 可用变量列表 ["employee_name","department"]
|
||
sort_order INTEGER NOT NULL DEFAULT 0, -- 排序权重
|
||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||
);
|
||
|
||
CREATE INDEX idx_qr_category ON quick_reply_templates(category);
|
||
```
|
||
|
||
#### 3.1.5 系统配置表 (system_configs)
|
||
|
||
```sql
|
||
CREATE TABLE system_configs (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
config_key VARCHAR(128) NOT NULL UNIQUE, -- 配置键
|
||
config_value TEXT NOT NULL, -- 配置值(JSON字符串或纯文本)
|
||
description VARCHAR(256) NOT NULL DEFAULT '', -- 配置说明
|
||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||
);
|
||
|
||
-- 预置配置数据
|
||
INSERT INTO system_configs (config_key, config_value, description) VALUES
|
||
('hand_raise_keywords', '["转人工","人工","人工服务","真人","客服"]', '举手触发关键词'),
|
||
('emotion_keywords_angry', '["崩溃","愤怒","投诉","差劲","垃圾"]', '愤怒情绪关键词'),
|
||
('emotion_keywords_urgent', '["急","紧急","马上","立刻","赶紧"]', '紧急情绪关键词'),
|
||
('emotion_keywords_worried', '["担心","害怕","出错","丢失","完蛋"]', '担忧情绪关键词'),
|
||
('intervene_round_threshold', '3', '需介入追问轮次阈值'),
|
||
('urgency_base_keyword_score', '1', '关键词匹配基础加分'),
|
||
('urgency_emotion_bonus', '1', '情绪标记加成分'),
|
||
('urgency_vip_bonus', '1', 'VIP加成分'),
|
||
('urgency_repeat_bonus', '1', '重复追问加成分'),
|
||
('funny_phrase_scene_shake', '大哥,俺这就去摇人,稍等...', '摇人按钮话术'),
|
||
('funny_phrase_scene_keyword', '收到!这就帮您摇位大神来', '关键词触发话术'),
|
||
('funny_phrase_scene_waiting', '人还在路上,别急别急~', '排队等待话术'),
|
||
('funny_phrase_scene_connected', '人摇来了!IT坐席为您服务', '坐席接入话术'),
|
||
('funny_phrase_scene_timeout', '坐席都在忙,不过AI还在呢,要不先聊聊?我再继续摇', '等待超时话术'),
|
||
('funny_phrase_scene_vip', '这就帮您安排专家,请稍候', 'VIP话术'),
|
||
('polling_interval_seconds', '3', '坐席轮询间隔(秒)'),
|
||
('access_token_buffer_seconds', '300', 'access_token提前刷新时间(秒)');
|
||
```
|
||
|
||
#### 3.1.6 趣味话术表 (funny_phrases)
|
||
|
||
```sql
|
||
CREATE TABLE funny_phrases (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
scene VARCHAR(64) NOT NULL, -- 触发场景: shake/keyword/waiting/connected/timeout/vip
|
||
content TEXT NOT NULL, -- 话术内容
|
||
tone VARCHAR(32) NOT NULL DEFAULT '亲切', -- 语气标签
|
||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||
is_active BOOLEAN NOT NULL DEFAULT TRUE, -- 是否启用
|
||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||
);
|
||
|
||
CREATE INDEX idx_fp_scene ON funny_phrases(scene);
|
||
```
|
||
|
||
#### 3.1.7 审批流程链接表 (approval_links)
|
||
|
||
```sql
|
||
CREATE TABLE approval_links (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
category VARCHAR(64) NOT NULL, -- 分类:IT/HR/行政/财务
|
||
title VARCHAR(128) NOT NULL, -- 审批名称
|
||
url TEXT NOT NULL, -- 审批链接
|
||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||
);
|
||
|
||
CREATE INDEX idx_al_category ON approval_links(category);
|
||
```
|
||
|
||
#### 3.1.8 软件下载入口表 (software_downloads)
|
||
|
||
```sql
|
||
CREATE TABLE software_downloads (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
category VARCHAR(64) NOT NULL, -- 分类:办公/开发/安全/工具
|
||
name VARCHAR(128) NOT NULL, -- 软件名称
|
||
version VARCHAR(32) NOT NULL DEFAULT '', -- 版本号
|
||
platform VARCHAR(32) NOT NULL DEFAULT '', -- 平台: Windows/Mac/Linux/全平台
|
||
download_url TEXT NOT NULL, -- 下载链接
|
||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||
);
|
||
|
||
CREATE INDEX idx_sd_category ON software_downloads(category);
|
||
```
|
||
|
||
#### 3.1.9 坐席备注表 (agent_notes)
|
||
|
||
```sql
|
||
CREATE TABLE agent_notes (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
conversation_id UUID NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
|
||
agent_id VARCHAR(64) NOT NULL, -- 坐席ID
|
||
content TEXT NOT NULL, -- 备注内容
|
||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||
);
|
||
|
||
CREATE INDEX idx_an_conversation ON agent_notes(conversation_id);
|
||
```
|
||
|
||
### 3.2 类图(Mermaid)
|
||
|
||
```mermaid
|
||
classDiagram
|
||
class Conversation {
|
||
+UUID id
|
||
+String employee_id
|
||
+String employee_name
|
||
+String department
|
||
+String position
|
||
+String level
|
||
+ConversationStatus status
|
||
+bool is_vip
|
||
+bool is_pinned
|
||
+bool is_todo
|
||
+int urgency_score
|
||
+JSON tags
|
||
+String assigned_agent_id
|
||
+DateTime last_message_at
|
||
+String last_message_summary
|
||
+DateTime created_at
|
||
+DateTime updated_at
|
||
}
|
||
|
||
class Message {
|
||
+UUID id
|
||
+UUID conversation_id
|
||
+SenderType sender_type
|
||
+String sender_id
|
||
+String sender_name
|
||
+String content
|
||
+MsgType msg_type
|
||
+bool ai_suggestion
|
||
+bool is_read
|
||
+DateTime created_at
|
||
}
|
||
|
||
class Agent {
|
||
+UUID id
|
||
+String user_id
|
||
+String name
|
||
+AgentStatus status
|
||
+int current_load
|
||
+int max_load
|
||
+DateTime created_at
|
||
+DateTime updated_at
|
||
}
|
||
|
||
class QuickReplyTemplate {
|
||
+UUID id
|
||
+String category
|
||
+String title
|
||
+String content
|
||
+JSON variables
|
||
+int sort_order
|
||
+DateTime created_at
|
||
+DateTime updated_at
|
||
}
|
||
|
||
class SystemConfig {
|
||
+UUID id
|
||
+String config_key
|
||
+String config_value
|
||
+String description
|
||
+DateTime updated_at
|
||
}
|
||
|
||
class FunnyPhrase {
|
||
+UUID id
|
||
+String scene
|
||
+String content
|
||
+String tone
|
||
+int sort_order
|
||
+bool is_active
|
||
+DateTime created_at
|
||
+DateTime updated_at
|
||
}
|
||
|
||
class ApprovalLink {
|
||
+UUID id
|
||
+String category
|
||
+String title
|
||
+String url
|
||
+int sort_order
|
||
+DateTime created_at
|
||
+DateTime updated_at
|
||
}
|
||
|
||
class SoftwareDownload {
|
||
+UUID id
|
||
+String category
|
||
+String name
|
||
+String version
|
||
+String platform
|
||
+String download_url
|
||
+int sort_order
|
||
+DateTime created_at
|
||
+DateTime updated_at
|
||
}
|
||
|
||
class AgentNote {
|
||
+UUID id
|
||
+UUID conversation_id
|
||
+String agent_id
|
||
+String content
|
||
+DateTime created_at
|
||
+DateTime updated_at
|
||
}
|
||
|
||
class WecomService {
|
||
-TokenManager token_manager
|
||
-httpx.AsyncClient client
|
||
+decrypt_message(xml_body, token, aes_key, corp_id) Dict
|
||
+encrypt_message(reply_dict, token, aes_key, corp_id) str
|
||
+send_text_message(user_id, content) Dict
|
||
+get_access_token() str
|
||
+get_user_info(user_id) Dict
|
||
+get_department_members(dept_id) List
|
||
}
|
||
|
||
class MessageRouter {
|
||
-WecomService wecom_service
|
||
-ConversationService conversation_service
|
||
-ScoringService scoring_service
|
||
-VipService vip_service
|
||
+route_message(msg: WecomInboundMessage) Conversation
|
||
-_find_or_create_conversation(msg) Conversation
|
||
-_update_tags(conv, msg) Conversation
|
||
-_calculate_urgency(conv) int
|
||
-_auto_assign_agent(conv) Agent
|
||
}
|
||
|
||
class ConversationService {
|
||
+get_conversations(filters) List~Conversation~
|
||
+get_conversation(id) Conversation
|
||
+update_conversation(id, data) Conversation
|
||
+update_status(id, status) Conversation
|
||
+toggle_pin(id) Conversation
|
||
+toggle_todo(id) Conversation
|
||
+get_messages(conversation_id, limit, offset) List~Message~
|
||
+send_message(conversation_id, sender_type, content) Message
|
||
+mark_messages_read(conversation_id, sender_type) None
|
||
}
|
||
|
||
class AgentService {
|
||
+get_agents() List~Agent~
|
||
+get_agent(id) Agent
|
||
+update_status(id, status) Agent
|
||
+login(user_id) Agent
|
||
+assign_conversation(agent_id, conversation_id) None
|
||
}
|
||
|
||
class ScoringService {
|
||
-SystemConfig config_repo
|
||
+calculate_urgency(conv, msg) int
|
||
-_keyword_score(msg) int
|
||
-_emotion_bonus(tags) int
|
||
-_vip_bonus(is_vip) int
|
||
-_repeat_bonus(tags) int
|
||
+detect_emotion(msg) str
|
||
+detect_hand_raise(msg) bool
|
||
+detect_need_intervene(conv) bool
|
||
}
|
||
|
||
class VipService {
|
||
-WecomService wecom_service
|
||
+is_vip(employee_id) bool
|
||
+get_employee_detail(employee_id) Dict
|
||
-_check_vip_rules(user_info) bool
|
||
}
|
||
|
||
class TokenManager {
|
||
-Redis redis
|
||
-String corp_id
|
||
-String corp_secret
|
||
+get_token() str
|
||
-_refresh_token() str
|
||
}
|
||
|
||
Conversation "1" --> "*" Message : has
|
||
Agent "1" --> "*" Conversation : serves
|
||
Conversation "1" --> "*" AgentNote : has
|
||
Agent "1" --> "*" AgentNote : writes
|
||
MessageRouter --> WecomService : uses
|
||
MessageRouter --> ConversationService : uses
|
||
MessageRouter --> ScoringService : uses
|
||
MessageRouter --> VipService : uses
|
||
WecomService --> TokenManager : uses
|
||
```
|
||
|
||
### 3.3 核心 API 接口定义
|
||
|
||
#### 3.3.1 统一响应格式
|
||
|
||
```python
|
||
# 所有 API 响应使用以下格式
|
||
{
|
||
"code": 0, # 0=成功, 非0=错误码
|
||
"data": {}, # 业务数据
|
||
"message": "success" # 消息说明
|
||
}
|
||
```
|
||
|
||
#### 3.3.2 企微回调 API
|
||
|
||
| 方法 | 路径 | 说明 | 请求 | 响应 |
|
||
|------|------|------|------|------|
|
||
| GET | `/api/wecom/callback` | 企微验证URL有效性 | `msg_signature, timestamp, nonce, echostr` | 解密后的 echostr 明文 |
|
||
| POST | `/api/wecom/callback` | 接收企微消息推送 | XML 加密消息体 | `"success"` 字符串 |
|
||
|
||
**企微回调 POST 请求体(加密后)**:
|
||
```xml
|
||
<xml>
|
||
<ToUserName><![CDATA[corp_id]]></ToUserName>
|
||
<AgentID>1000002</AgentID>
|
||
<Encrypt><![CDATA[加密内容]]></Encrypt>
|
||
</xml>
|
||
```
|
||
|
||
**企微回调 POST 响应**:
|
||
- 处理成功: 返回 HTTP 200 + `"success"`
|
||
- 需要被动回复: 返回加密后的 XML(第一步不使用被动回复,用主动发送)
|
||
|
||
#### 3.3.3 会话管理 API
|
||
|
||
| 方法 | 路径 | 说明 | 请求 | 响应 |
|
||
|------|------|------|------|------|
|
||
| GET | `/api/conversations` | 获取坐席会话列表 | Query: `status, page, page_size` | `{items: [Conversation], total: int}` |
|
||
| GET | `/api/conversations/{id}` | 获取会话详情 | - | `Conversation` |
|
||
| PUT | `/api/conversations/{id}` | 更新会话信息 | `ConversationUpdate` | `Conversation` |
|
||
| PUT | `/api/conversations/{id}/status` | 更新会话状态 | `{status: str}` | `Conversation` |
|
||
| PUT | `/api/conversations/{id}/pin` | 切换置顶 | - | `Conversation` |
|
||
| PUT | `/api/conversations/{id}/todo` | 切换代办 | - | `Conversation` |
|
||
| POST | `/api/conversations/{id}/assign` | 坐席接单 | `{agent_id: str}` | `Conversation` |
|
||
| POST | `/api/conversations/{id}/invite` | 邀请员工/部门加入会话 | `{user_ids: [], department_ids: [], history_shared: str}` | `ConversationInviteResponse` |
|
||
| POST | `/api/conversations/{id}/leave` | 参与者退出会话 | - | `Conversation` |
|
||
| DELETE | `/api/conversations/{id}/participants/{userid}` | 坐席移除参与者 | - | `Conversation` |
|
||
|
||
**Conversation 列表响应**:
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"data": {
|
||
"items": [
|
||
{
|
||
"id": "uuid",
|
||
"employee_id": "zhangsan",
|
||
"employee_name": "张三",
|
||
"department": "技术部",
|
||
"status": "serving",
|
||
"is_vip": true,
|
||
"is_pinned": false,
|
||
"is_todo": false,
|
||
"urgency_score": 4,
|
||
"tags": {"hand_raise": false, "emotion": "urgent", "need_intervene": true},
|
||
"assigned_agent_id": "agent001",
|
||
"last_message_at": "2025-07-11T10:30:00Z",
|
||
"last_message_summary": "我的电脑蓝屏了急急急",
|
||
"created_at": "2025-07-11T10:25:00Z",
|
||
"updated_at": "2025-07-11T10:30:00Z"
|
||
}
|
||
],
|
||
"total": 15
|
||
},
|
||
"message": "success"
|
||
}
|
||
```
|
||
|
||
#### 3.3.4 消息管理 API
|
||
|
||
| 方法 | 路径 | 说明 | 请求 | 响应 |
|
||
|------|------|------|------|------|
|
||
| GET | `/api/conversations/{id}/messages` | 获取会话消息列表 | Query: `limit=50, before=uuid` | `{items: [Message], has_more: bool}` |
|
||
| POST | `/api/conversations/{id}/messages` | 坐席发送消息 | `{content: str}` | `Message` |
|
||
|
||
**坐席发送消息请求**:
|
||
```json
|
||
{
|
||
"content": "您好,请问具体是什么报错信息呢?"
|
||
}
|
||
```
|
||
|
||
**消息响应**:
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"data": {
|
||
"id": "uuid",
|
||
"conversation_id": "uuid",
|
||
"sender_type": "agent",
|
||
"sender_id": "agent001",
|
||
"sender_name": "坐席小李",
|
||
"content": "您好,请问具体是什么报错信息呢?",
|
||
"msg_type": "text",
|
||
"ai_suggestion": false,
|
||
"is_read": true,
|
||
"created_at": "2025-07-11T10:31:00Z"
|
||
},
|
||
"message": "success"
|
||
}
|
||
```
|
||
|
||
#### 3.3.5 坐席管理 API
|
||
|
||
| 方法 | 路径 | 说明 | 请求 | 响应 |
|
||
|------|------|------|------|------|
|
||
| GET | `/api/agents` | 坐席列表 | - | `[Agent]` |
|
||
| GET | `/api/agents/me` | 当前坐席信息 | - | `Agent` |
|
||
| PUT | `/api/agents/{id}/status` | 更新坐席状态 | `{status: str}` | `Agent` |
|
||
| POST | `/api/agents/login` | 坐席登录 | `{user_id: str, name: str}` | `Agent` |
|
||
|
||
#### 3.3.6 快速回复模板 API
|
||
|
||
| 方法 | 路径 | 说明 | 请求 | 响应 |
|
||
|------|------|------|------|------|
|
||
| GET | `/api/quick-replies` | 获取模板列表 | Query: `category` | `[QuickReplyTemplate]` |
|
||
| POST | `/api/quick-replies` | 创建模板 | `QuickReplyCreate` | `QuickReplyTemplate` |
|
||
| PUT | `/api/quick-replies/{id}` | 更新模板 | `QuickReplyUpdate` | `QuickReplyTemplate` |
|
||
| DELETE | `/api/quick-replies/{id}` | 删除模板 | - | `{code: 0}` |
|
||
|
||
#### 3.3.7 H5 用户端 API
|
||
|
||
| 方法 | 路径 | 说明 | 请求 | 响应 |
|
||
|------|------|------|------|------|
|
||
| GET | `/api/h5/conversation` | 获取当前用户会话 | Header: `X-Employee-Id` | `Conversation` |
|
||
| POST | `/api/h5/conversation/shake` | 摇人请求 | `{employee_id, employee_name}` | `Conversation + 趣味话术` |
|
||
| GET | `/api/h5/conversation/messages` | 获取消息列表 | Query: `limit, before` | `[Message]` |
|
||
| GET | `/api/h5/approval-links` | 获取审批流程链接 | Query: `category` | `[ApprovalLink]` |
|
||
| GET | `/api/h5/software-downloads` | 获取软件下载入口 | Query: `category` | `[SoftwareDownload]` |
|
||
| POST | `/api/h5/oauth/callback` | OAuth2 回调 | `{code: str}` | `{employee_id, employee_name}` |
|
||
|
||
**摇人请求响应**:
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"data": {
|
||
"conversation": {
|
||
"id": "uuid",
|
||
"status": "queued",
|
||
"tags": {"hand_raise": true}
|
||
},
|
||
"funny_phrase": "大哥,俺这就去摇人,稍等..."
|
||
},
|
||
"message": "success"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 4. 程序调用流程(时序图)
|
||
|
||
### 4.1 员工发消息 → 坐席收到 → 坐席回复 → 员工收到
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant Emp as 员工(企微应用)
|
||
participant WX as 企微服务器
|
||
participant Nginx as Nginx
|
||
participant API as FastAPI
|
||
participant Router as MessageRouter
|
||
participant ConvSvc as ConversationService
|
||
participant Score as ScoringService
|
||
participant Vip as VipService
|
||
participant WXSvc as WecomService
|
||
participant DB as PostgreSQL
|
||
participant Redis as Redis
|
||
|
||
Emp->>WX: 发送消息
|
||
WX->>Nginx: POST /api/wecom/callback (加密XML)
|
||
Nginx->>API: 转发请求
|
||
API->>WXSvc: decrypt_message(xml)
|
||
WXSvc->>Redis: 获取 access_token (如过期则刷新)
|
||
Redis-->>WXSvc: token
|
||
WXSvc-->>API: 解密后的消息内容
|
||
API->>Router: route_message(msg)
|
||
Router->>ConvSvc: find_or_create_conversation(msg)
|
||
ConvSvc->>DB: 查询/创建 conversation 记录
|
||
DB-->>ConvSvc: conversation
|
||
Router->>Vip: is_vip(employee_id)
|
||
Vip->>WXSvc: get_user_info(employee_id)
|
||
WXSvc-->>Vip: 用户信息
|
||
Vip-->>Router: vip=True/False
|
||
Router->>Score: calculate_urgency(conv, msg)
|
||
Score->>Score: detect_emotion(msg) / detect_hand_raise(msg)
|
||
Score-->>Router: urgency_score + tags
|
||
Router->>ConvSvc: update_conversation(tags, urgency, vip)
|
||
ConvSvc->>DB: UPDATE conversations SET ...
|
||
Router->>ConvSvc: create_message(conv_id, msg)
|
||
ConvSvc->>DB: INSERT INTO messages ...
|
||
DB-->>ConvSvc: message
|
||
Router-->>API: conversation
|
||
API-->>Nginx: "success"
|
||
Nginx-->>WX: HTTP 200
|
||
|
||
Note over Emp: 坐席端轮询获取新消息
|
||
|
||
loop 每3秒轮询
|
||
Agent->>Nginx: GET /api/conversations
|
||
Nginx->>API: 转发请求
|
||
API->>ConvSvc: get_conversations(filters)
|
||
ConvSvc->>DB: SELECT * FROM conversations ORDER BY ...
|
||
DB-->>ConvSvc: conversations
|
||
ConvSvc-->>API: conversation 列表
|
||
API-->>Agent: 带标记的会话列表
|
||
end
|
||
|
||
Agent->>Nginx: GET /api/conversations/{id}/messages
|
||
Nginx->>API: 转发请求
|
||
API->>ConvSvc: get_messages(conv_id)
|
||
ConvSvc->>DB: SELECT * FROM messages WHERE conversation_id=...
|
||
DB-->>ConvSvc: messages
|
||
ConvSvc-->>API: messages
|
||
API-->>Agent: 消息列表
|
||
|
||
Agent->>Nginx: POST /api/conversations/{id}/messages {content}
|
||
Nginx->>API: 转发请求
|
||
API->>ConvSvc: send_message(conv_id, 'agent', content)
|
||
ConvSvc->>DB: INSERT INTO messages ...
|
||
ConvSvc->>WXSvc: send_text_message(employee_id, content)
|
||
WXSvc->>Redis: 获取 access_token
|
||
Redis-->>WXSvc: token
|
||
WXSvc->>WX: POST /cgi-bin/message/send
|
||
WX-->>WXSvc: 发送结果
|
||
WXSvc-->>ConvSvc: success
|
||
ConvSvc-->>API: message
|
||
API-->>Agent: 发送成功
|
||
WX->>Emp: 推送坐席回复消息
|
||
Emp->>Emp: 同一对话窗口显示消息
|
||
```
|
||
|
||
### 4.2 摇人按钮 → 举手标记 → 坐席接单
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant Emp as 员工(H5页面)
|
||
participant API as FastAPI
|
||
participant ConvSvc as ConversationService
|
||
participant Score as ScoringService
|
||
participant WXSvc as WecomService
|
||
participant Agent as 坐席工作台
|
||
participant DB as PostgreSQL
|
||
|
||
Emp->>API: POST /api/h5/conversation/shake
|
||
API->>ConvSvc: find_or_create_conversation(employee_id)
|
||
ConvSvc->>DB: 查询/创建 conversation
|
||
DB-->>ConvSvc: conversation
|
||
API->>Score: detect_hand_raise("摇人")
|
||
Score-->>API: hand_raise=True
|
||
API->>ConvSvc: update_conversation(tags={hand_raise:true})
|
||
ConvSvc->>DB: UPDATE conversations SET tags=...
|
||
API->>ConvSvc: get_funny_phrase(scene='shake')
|
||
ConvSvc->>DB: SELECT FROM funny_phrases WHERE scene='shake'
|
||
DB-->>ConvSvc: "大哥,俺这就去摇人,稍等..."
|
||
ConvSvc->>ConvSvc: send_message(conv_id, 'system', 趣味话术)
|
||
ConvSvc->>WXSvc: send_text_message(employee_id, 趣味话术)
|
||
API-->>Emp: {conversation, funny_phrase}
|
||
|
||
Note over Emp: H5显示摇人动画 + 趣味话术
|
||
|
||
loop 坐席轮询
|
||
Agent->>API: GET /api/conversations
|
||
API->>ConvSvc: get_conversations()
|
||
ConvSvc->>DB: SELECT ... ORDER BY urgency DESC
|
||
DB-->>ConvSvc: 列表(举手会话靠前)
|
||
API-->>Agent: 举手标记的会话(黄色标签)
|
||
end
|
||
|
||
Agent->>API: POST /api/conversations/{id}/assign {agent_id}
|
||
API->>ConvSvc: update_conversation(status='serving', assigned_agent_id)
|
||
ConvSvc->>DB: UPDATE conversations SET status='serving'
|
||
ConvSvc->>ConvSvc: send_message(conv_id, 'system', '人摇来了!IT坐席为您服务')
|
||
ConvSvc->>WXSvc: send_text_message(employee_id, 接入话术)
|
||
ConvSvc-->>API: updated conversation
|
||
API-->>Agent: 接单成功
|
||
|
||
WXSvc-->>Emp: 企微推送"人摇来了!IT坐席为您服务"
|
||
```
|
||
|
||
### 4.3 会话标记系统评分流程
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant WX as 企微消息
|
||
participant API as FastAPI
|
||
participant Router as MessageRouter
|
||
participant Score as ScoringService
|
||
participant Vip as VipService
|
||
participant DB as PostgreSQL
|
||
participant Redis as Redis
|
||
|
||
WX->>API: 员工消息回调
|
||
API->>Router: route_message(msg)
|
||
|
||
rect rgb(255, 240, 240)
|
||
Note over Router,Score: Step 1: VIP检测
|
||
Router->>Vip: is_vip(employee_id)
|
||
Vip->>Redis: GET vip_cache:{employee_id}
|
||
alt 缓存命中
|
||
Redis-->>Vip: is_vip=True/False
|
||
else 缓存未命中
|
||
Vip->>Vip: get_user_info(employee_id)
|
||
Vip->>Vip: _check_vip_rules(user_info)
|
||
Note over Vip: 规则: 总监及以上 或 关键部门
|
||
Vip->>Redis: SET vip_cache:{employee_id} EX 3600
|
||
end
|
||
Vip-->>Router: is_vip=True/False
|
||
end
|
||
|
||
rect rgb(255, 255, 220)
|
||
Note over Router,Score: Step 2: 情绪关键词检测
|
||
Router->>Score: detect_emotion(msg)
|
||
Score->>DB: SELECT FROM system_configs WHERE key LIKE 'emotion_keywords_%'
|
||
DB-->>Score: 关键词列表
|
||
Score->>Score: 遍历关键词匹配消息内容
|
||
Score-->>Router: emotion="urgent"
|
||
end
|
||
|
||
rect rgb(220, 255, 220)
|
||
Note over Router,Score: Step 3: 举手检测
|
||
Router->>Score: detect_hand_raise(msg)
|
||
Score->>DB: SELECT FROM system_configs WHERE key='hand_raise_keywords'
|
||
DB-->>Score: ["转人工","人工",...]
|
||
Score->>Score: 遍历关键词匹配
|
||
Score-->>Router: hand_raise=True
|
||
end
|
||
|
||
rect rgb(220, 220, 255)
|
||
Note over Router,Score: Step 4: 需介入检测
|
||
Router->>Score: detect_need_intervene(conv)
|
||
Score->>DB: SELECT COUNT FROM messages WHERE conversation_id=... AND sender_type='employee'
|
||
DB-->>Score: 员工消息数
|
||
Score->>DB: SELECT FROM system_configs WHERE key='intervene_round_threshold'
|
||
DB-->>Score: 3
|
||
Score->>Score: 员工连续追问 > 3轮?
|
||
Score-->>Router: need_intervene=True
|
||
end
|
||
|
||
rect rgb(255, 220, 255)
|
||
Note over Router,Score: Step 5: 紧急度计算
|
||
Router->>Score: calculate_urgency(conv, msg)
|
||
Score->>Score: base = keyword_score(1)
|
||
Score->>Score: + emotion_bonus(1)
|
||
Score->>Score: + vip_bonus(1)
|
||
Score->>Score: + repeat_bonus(1)
|
||
Score->>Score: total = min(5, base+emotion+vip+repeat)
|
||
Score-->>Router: urgency_score=4
|
||
end
|
||
|
||
Router->>DB: UPDATE conversations SET tags=..., urgency_score=4, is_vip=...
|
||
Router-->>API: 更新后的会话
|
||
```
|
||
|
||
### 4.4 坐席工作台轮询刷新流程
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant Browser as 坐席浏览器
|
||
participant App as Vue3 App
|
||
participant Store as Pinia Store
|
||
participant API as Backend API
|
||
participant DB as PostgreSQL
|
||
|
||
Note over Browser,App: 页面加载
|
||
|
||
Browser->>App: 挂载 Workspace.vue
|
||
App->>Store: 初始化 conversationStore
|
||
Store->>API: GET /api/conversations
|
||
API->>DB: SELECT * FROM conversations WHERE status IN ('queued','serving') ORDER BY ...
|
||
DB-->>API: 会话列表
|
||
API-->>Store: 会话数据
|
||
Store-->>App: 渲染会话列表
|
||
|
||
loop 每3秒 setInterval
|
||
App->>Store: pollConversations()
|
||
Store->>API: GET /api/conversations?page=1&page_size=50
|
||
API->>DB: SELECT ... (同上)
|
||
DB-->>API: 最新会话列表
|
||
API-->>Store: 最新数据
|
||
|
||
alt 数据有变化
|
||
Store->>Store: diff 比较,更新变化的会话
|
||
Store-->>App: 触发响应式更新
|
||
App->>App: 更新列表项标签/排序/未读数
|
||
else 数据无变化
|
||
Store-->>App: 无需更新
|
||
end
|
||
end
|
||
|
||
Note over App: 用户点击某个会话
|
||
|
||
App->>Store: selectConversation(id)
|
||
Store->>API: GET /api/conversations/{id}/messages?limit=50
|
||
API->>DB: SELECT * FROM messages WHERE conversation_id=... ORDER BY created_at
|
||
DB-->>API: 消息列表
|
||
API-->>Store: messages
|
||
Store-->>App: 渲染对话区
|
||
|
||
loop 选中会话的消息轮询
|
||
App->>Store: pollMessages(conv_id)
|
||
Store->>API: GET /api/conversations/{id}/messages?limit=20&before=latest_id
|
||
API->>DB: SELECT ... WHERE created_at > latest
|
||
DB-->>API: 新消息
|
||
API-->>Store: 新消息列表
|
||
alt 有新消息
|
||
Store->>Store: 追加消息到列表
|
||
Store-->>App: 滚动到底部,显示新消息
|
||
end
|
||
end
|
||
|
||
Note over App: 坐席发送回复
|
||
|
||
App->>Store: sendMessage(conv_id, content)
|
||
Store->>API: POST /api/conversations/{id}/messages {content}
|
||
API->>DB: INSERT INTO messages ...
|
||
API-->>Store: 发送的消息对象
|
||
Store->>Store: 追加到消息列表
|
||
Store-->>App: 显示在对话区
|
||
```
|
||
|
||
---
|
||
|
||
## 5. 任务列表
|
||
|
||
### T01: 项目基础设施
|
||
|
||
| 属性 | 值 |
|
||
|------|------|
|
||
| **任务编号** | T01 |
|
||
| **任务名称** | 项目基础设施搭建 |
|
||
| **预估工时** | 5 天 (D1-D5) |
|
||
| **依赖** | 无 |
|
||
| **优先级** | P0 |
|
||
| **验收标准** | `docker-compose up` 能启动所有容器;`alembic upgrade head` 能创建所有表;前后端 dev server 能启动 |
|
||
|
||
**涉及文件**:
|
||
|
||
| # | 文件路径 | 说明 |
|
||
|---|---------|------|
|
||
| 1 | `docker-compose.yml` | Docker Compose 编排(postgres + redis + backend + nginx) |
|
||
| 2 | `nginx/nginx.conf` | Nginx 反代 + 静态文件配置 |
|
||
| 3 | `.env.example` | 环境变量模板 |
|
||
| 4 | `backend/requirements.txt` | Python 依赖 |
|
||
| 5 | `backend/Dockerfile` | 后端镜像 |
|
||
| 6 | `backend/alembic.ini` | Alembic 配置 |
|
||
| 7 | `backend/alembic/env.py` | Alembic 环境 |
|
||
| 8 | `backend/alembic/versions/.gitkeep` | 迁移目录 |
|
||
| 9 | `backend/app/__init__.py` | 应用包 |
|
||
| 10 | `backend/app/main.py` | FastAPI 入口(CORS、路由挂载、健康检查) |
|
||
| 11 | `backend/app/config.py` | 配置管理(Pydantic Settings) |
|
||
| 12 | `backend/app/database.py` | 数据库连接 + Session 管理 |
|
||
| 13 | `backend/app/models/__init__.py` | 模型包初始化 |
|
||
| 14 | `backend/app/models/conversation.py` | 会话模型(全字段) |
|
||
| 15 | `backend/app/models/message.py` | 消息模型(全字段) |
|
||
| 16 | `backend/app/models/agent.py` | 坐席模型 |
|
||
| 17 | `backend/app/models/quick_reply_template.py` | 快速回复模板模型 |
|
||
| 18 | `backend/app/models/system_config.py` | 系统配置模型 |
|
||
| 19 | `backend/app/models/funny_phrase.py` | 趣味话术模型 |
|
||
| 20 | `backend/app/models/approval_link.py` | 审批链接模型 |
|
||
| 21 | `backend/app/models/software_download.py` | 软件下载模型 |
|
||
| 22 | `backend/app/models/agent_note.py` | 坐席备注模型 |
|
||
| 23 | `backend/app/schemas/__init__.py` | Schema 包 |
|
||
| 24 | `backend/app/schemas/conversation.py` | 会话 Schema(全字段含 validator) |
|
||
| 25 | `backend/app/schemas/message.py` | 消息 Schema |
|
||
| 26 | `backend/app/schemas/agent.py` | 坐席 Schema |
|
||
| 27 | `backend/app/schemas/quick_reply.py` | 快速回复 Schema |
|
||
| 28 | `backend/app/schemas/wecom.py` | 企微消息 Schema |
|
||
| 29 | `backend/app/schemas/h5.py` | H5 用户端 Schema |
|
||
| 30 | `backend/app/utils/__init__.py` | 工具包 |
|
||
| 31 | `backend/app/utils/response.py` | 统一响应工具 |
|
||
| 32 | `backend/app/api/__init__.py` | API 包 |
|
||
| 33 | `backend/app/api/router.py` | 路由汇总(空壳,后续填充) |
|
||
| 34 | `frontend-agent/package.json` | 坐席前端依赖 |
|
||
| 35 | `frontend-agent/vite.config.ts` | Vite 配置 |
|
||
| 36 | `frontend-agent/tsconfig.json` | TS 配置 |
|
||
| 37 | `frontend-agent/tsconfig.node.json` | TS Node 配置 |
|
||
| 38 | `frontend-agent/index.html` | HTML 入口 |
|
||
| 39 | `frontend-agent/Dockerfile` | 前端镜像 |
|
||
| 40 | `frontend-agent/env.d.ts` | 环境类型 |
|
||
| 41 | `frontend-agent/src/main.ts` | 应用入口(挂载 ElementPlus + Pinia + Router) |
|
||
| 42 | `frontend-agent/src/App.vue` | 根组件(空壳) |
|
||
| 43 | `frontend-agent/src/router/index.ts` | 路由配置(空壳) |
|
||
| 44 | `frontend-agent/src/api/index.ts` | Axios 实例 + 拦截器 |
|
||
| 45 | `frontend-agent/src/styles/global.css` | 全局样式 |
|
||
| 46 | `frontend-h5/package.json` | H5 前端依赖 |
|
||
| 47 | `frontend-h5/vite.config.ts` | Vite 配置 |
|
||
| 48 | `frontend-h5/tsconfig.json` | TS 配置 |
|
||
| 49 | `frontend-h5/tsconfig.node.json` | TS Node 配置 |
|
||
| 50 | `frontend-h5/index.html` | HTML 入口 |
|
||
| 51 | `frontend-h5/Dockerfile` | 前端镜像 |
|
||
| 52 | `frontend-h5/env.d.ts` | 环境类型 |
|
||
| 53 | `frontend-h5/src/main.ts` | 应用入口(挂载 Vant + Pinia + Router) |
|
||
| 54 | `frontend-h5/src/App.vue` | 根组件(空壳) |
|
||
| 55 | `frontend-h5/src/router/index.ts` | 路由配置(空壳) |
|
||
| 56 | `frontend-h5/src/api/index.ts` | Axios 实例 + 拦截器 |
|
||
| 57 | `frontend-h5/src/styles/global.css` | 全局样式 |
|
||
|
||
---
|
||
|
||
### T02: 后端核心服务
|
||
|
||
| 属性 | 值 |
|
||
|------|------|
|
||
| **任务编号** | T02 |
|
||
| **任务名称** | 后端核心服务(企微对接+消息路由+业务逻辑+API) |
|
||
| **预估工时** | 12 天 (D6-D17) |
|
||
| **依赖** | T01 |
|
||
| **优先级** | P0 |
|
||
| **验收标准** | 企微消息能收发、路由分发正确、标记评分正确、所有 API 可通过 Swagger 文档调用 |
|
||
|
||
**涉及文件**:
|
||
|
||
| # | 文件路径 | 说明 |
|
||
|---|---------|------|
|
||
| 1 | `backend/app/utils/wecom_crypto.py` | 企微消息 AES 加解密 |
|
||
| 2 | `backend/app/utils/token_manager.py` | access_token 缓存管理 |
|
||
| 3 | `backend/app/services/wecom_service.py` | 企微 API 封装(发消息、获取用户信息、通讯录) |
|
||
| 4 | `backend/app/services/vip_service.py` | VIP 规则匹配 |
|
||
| 5 | `backend/app/services/scoring_service.py` | 紧急度评分 + 标记检测 |
|
||
| 6 | `backend/app/services/conversation_service.py` | 会话 CRUD + 消息收发 |
|
||
| 7 | `backend/app/services/agent_service.py` | 坐席管理 + 分配逻辑 |
|
||
| 8 | `backend/app/services/message_router.py` | 消息路由层(核心编排) |
|
||
| 9 | `backend/app/api/wecom_callback.py` | 企微回调 API(GET验证 + POST接收) |
|
||
| 10 | `backend/app/api/conversations.py` | 会话管理 API |
|
||
| 11 | `backend/app/api/messages.py` | 消息管理 API |
|
||
| 12 | `backend/app/api/agents.py` | 坐席管理 API |
|
||
| 13 | `backend/app/api/quick_replies.py` | 快速回复模板 CRUD API |
|
||
| 14 | `backend/app/api/h5.py` | H5 用户端 API(摇人、审批链接、软件下载、OAuth) |
|
||
| 15 | `backend/app/api/router.py` | 路由汇总(更新,挂载所有子路由) |
|
||
| 16 | `backend/app/main.py` | 更新:添加启动事件(初始数据加载) |
|
||
|
||
**关键实现要点**:
|
||
|
||
1. **wecom_crypto.py**: 实现 `decrypt_message()` 和 `encrypt_message()` 两个核心方法。参考企微官方加解密库的逻辑,用 `cryptography` 重写。AES-CBC-256 模式,key = EncodingAESKey + "=",iv = key[:16]。
|
||
|
||
2. **token_manager.py**: 从 Redis 获取 token,如果不存在或即将过期(提前300秒),则调用企微 API 刷新并写入 Redis(TTL=7200秒)。
|
||
|
||
3. **message_router.py**: 这是核心编排类。`route_message()` 方法按顺序调用: find_or_create_conversation → vip检测 → 标记检测 → 紧急度计算 → 更新会话 → 创建消息记录。第一步逻辑简单: 所有新消息 → 坐席队列。
|
||
|
||
4. **scoring_service.py**: 从 system_configs 表读取关键词和阈值,实现 `calculate_urgency()` 公式。注意评分上限 clamp 到 5。
|
||
|
||
5. **conversation_service.py**: `send_message()` 需要同时写数据库和调用企微API发送消息,确保数据一致性。
|
||
|
||
---
|
||
|
||
### T03: 坐席工作台前端
|
||
|
||
| 属性 | 值 |
|
||
|------|------|
|
||
| **任务编号** | T03 |
|
||
| **任务名称** | 坐席工作台前端(三栏布局+会话管理+AI助手面板) |
|
||
| **预估工时** | 8 天 (D14-D21) |
|
||
| **依赖** | T01 |
|
||
| **优先级** | P0 |
|
||
| **验收标准** | 坐席能看到会话列表、点击查看对话、发送回复、查看AI助手面板各模块、标记操作(VIP/举手/置顶/代办)生效 |
|
||
|
||
**涉及文件**:
|
||
|
||
| # | 文件路径 | 说明 |
|
||
|---|---------|------|
|
||
| 1 | `frontend-agent/src/stores/conversation.ts` | 会话状态管理(轮询逻辑+数据缓存) |
|
||
| 2 | `frontend-agent/src/stores/agent.ts` | 坐席状态管理 |
|
||
| 3 | `frontend-agent/src/stores/quickReply.ts` | 快速回复状态管理 |
|
||
| 4 | `frontend-agent/src/api/conversation.ts` | 会话 API 调用 |
|
||
| 5 | `frontend-agent/src/api/message.ts` | 消息 API 调用 |
|
||
| 6 | `frontend-agent/src/api/agent.ts` | 坐席 API 调用 |
|
||
| 7 | `frontend-agent/src/api/quickReply.ts` | 快速回复 API 调用 |
|
||
| 8 | `frontend-agent/src/views/Workspace.vue` | 坐席主页面(三栏布局容器) |
|
||
| 9 | `frontend-agent/src/components/conversation/ConversationList.vue` | 会话列表(排序+标签+未读数) |
|
||
| 10 | `frontend-agent/src/components/conversation/ConversationItem.vue` | 会话列表项(姓名+标签+摘要+紧急度) |
|
||
| 11 | `frontend-agent/src/components/chat/ChatArea.vue` | 对话区(消息列表+自动滚动) |
|
||
| 12 | `frontend-agent/src/components/chat/MessageBubble.vue` | 消息气泡(区分employee/agent/ai/system) |
|
||
| 13 | `frontend-agent/src/components/chat/ReplyBox.vue` | 回复输入框(发送按钮+快捷键Enter) |
|
||
| 14 | `frontend-agent/src/components/assistant/AiAssistantPanel.vue` | AI助手面板容器(5模块Tab切换) |
|
||
| 15 | `frontend-agent/src/components/assistant/AiSuggestReply.vue` | AI建议回复(mock版,显示"即将启用") |
|
||
| 16 | `frontend-agent/src/components/assistant/QuickReplyPanel.vue` | 快速回复模板(CRUD + 变量替换) |
|
||
| 17 | `frontend-agent/src/components/assistant/OperationSteps.vue` | 操作步骤(静态配置) |
|
||
| 18 | `frontend-agent/src/components/assistant/RiskAlert.vue` | 风险提示(空故障库+预留接口) |
|
||
| 19 | `frontend-agent/src/components/assistant/UserInfoPanel.vue` | 用户信息面板(基本信息+历史+备注) |
|
||
| 20 | `frontend-agent/src/router/index.ts` | 更新路由配置 |
|
||
|
||
**关键实现要点**:
|
||
|
||
1. **ConversationList.vue**: 排序逻辑在 computed 中实现:紧急→举手→需介入→活跃→AI处理中→已结单,同级别按 last_message_at 倒序。使用 `setInterval` 每3秒调用 `pollConversations()`。
|
||
|
||
2. **MessageBubble.vue**: 根据 `sender_type` 使用不同颜色和位置。employee 消息靠左灰底,agent 消息靠右蓝底,ai 消息靠左绿底+AI标签,system 消息居中灰字。
|
||
|
||
3. **QuickReplyPanel.vue**: 完整 CRUD 实现。点击模板填充到 ReplyBox,支持 `{employee_name}` 等变量替换。使用 ElementPlus 的 `ElCollapse` 按分类折叠展示。
|
||
|
||
4. **AiSuggestReply.vue**: 第一步为 mock 版,显示静态占位文本"AI建议功能将在第二步启用",带一个"了解更多"按钮。
|
||
|
||
5. **RiskAlert.vue**: 从后端查询已知故障列表(第一步为空),预留接口。显示"暂无已知故障"占位。
|
||
|
||
6. **UserInfoPanel.vue**: 显示员工基本信息(从 conversation 的 department/position/level 字段)、坐席备注(从 agent_notes 读取,支持编辑保存)。
|
||
|
||
---
|
||
|
||
### T04: 用户端 H5 前端
|
||
|
||
| 属性 | 值 |
|
||
|------|------|
|
||
| **任务编号** | T04 |
|
||
| **任务名称** | 用户端H5前端(双栏布局+摇人+审批链接+软件下载) |
|
||
| **预估工时** | 5 天 (D23-D27) |
|
||
| **依赖** | T01, T02(需要后端 API 就绪) |
|
||
| **优先级** | P0 |
|
||
| **验收标准** | H5在企微WebView中正确渲染双栏;摇人按钮交互正常;审批链接/软件下载可点击;"即将上线"占位符正确显示 |
|
||
|
||
**涉及文件**:
|
||
|
||
| # | 文件路径 | 说明 |
|
||
|---|---------|------|
|
||
| 1 | `frontend-h5/src/stores/conversation.ts` | 会话状态管理(含摇人状态) |
|
||
| 2 | `frontend-h5/src/api/conversation.ts` | API 调用(摇人、消息、审批链接、软件下载) |
|
||
| 3 | `frontend-h5/src/views/ChatView.vue` | 聊天主页面(双栏布局容器) |
|
||
| 4 | `frontend-h5/src/components/chat/ChatPanel.vue` | 对话区面板(消息列表+摇人引导条) |
|
||
| 5 | `frontend-h5/src/components/chat/MessageBubble.vue` | 消息气泡 |
|
||
| 6 | `frontend-h5/src/components/chat/ShakeButton.vue` | 摇人按钮(橙色渐变+摇晃动画) |
|
||
| 7 | `frontend-h5/src/components/chat/InputBar.vue` | 输入栏(摇人按钮+文本输入+发送按钮) |
|
||
| 8 | `frontend-h5/src/components/assistant/AiHelperPanel.vue` | AI助手面板容器(4模块Tab) |
|
||
| 9 | `frontend-h5/src/components/assistant/ApprovalLinks.vue` | 审批流程链接(从API获取,按分类展示) |
|
||
| 10 | `frontend-h5/src/components/assistant/SoftwareDownloads.vue` | 软件下载入口(从API获取,按分类展示) |
|
||
| 11 | `frontend-h5/src/components/assistant/ComingSoon.vue` | "即将上线"占位组件 |
|
||
| 12 | `frontend-h5/src/router/index.ts` | 更新路由配置 |
|
||
|
||
**关键实现要点**:
|
||
|
||
1. **ChatView.vue**: 双栏布局使用 Flexbox,左栏 60%(ChatPanel)+ 右栏 40%(AiHelperPanel)。移动端窄屏时右栏可通过按钮展开/收起。
|
||
|
||
2. **ShakeButton.vue**: 使用 CSS `@keyframes` 实现 0.6 秒摇晃动画。橙色渐变 `#FF6B35→#FF8F5E`,44px 圆形,右上角红点。点击时触发 `POST /api/h5/conversation/shake`。
|
||
|
||
3. **InputBar.vue**: 布局为 `[摇人按钮] [文本输入框] [发送按钮]`。摇人按钮在最左侧,和微信语音按钮位置一致。
|
||
|
||
4. **ApprovalLinks.vue / SoftwareDownloads.vue**: 从后端 API 获取数据,按分类展示。使用 Vant4 的 `CellGroup` 和 `Cell` 组件。
|
||
|
||
5. **ComingSoon.vue**: 通用占位组件,接收 `title` prop,显示灰色图标 + "即将上线" 文字。用于"相似问题与做法"和"知识库搜索"两个模块。
|
||
|
||
6. **OAuth 集成**: 在 `main.ts` 或路由守卫中,检查 URL 是否包含 `code` 参数,如有则调用 `POST /api/h5/oauth/callback` 换取员工身份,存入 localStorage。
|
||
|
||
---
|
||
|
||
### T05: 集成联调与部署
|
||
|
||
| 属性 | 值 |
|
||
|------|------|
|
||
| **任务编号** | T05 |
|
||
| **任务名称** | 集成联调与部署验证 |
|
||
| **预估工时** | 3 天 (D28-D30) |
|
||
| **依赖** | T02, T03, T04 |
|
||
| **优先级** | P0 |
|
||
| **验收标准** | 完整链路打通:员工企微发消息→坐席网页收到→坐席回复→员工同一窗口收到;摇人全流程;Docker Compose 部署完成 |
|
||
|
||
**涉及文件**:
|
||
|
||
| # | 文件路径 | 说明 |
|
||
|---|---------|------|
|
||
| 1 | `docker-compose.yml` | 最终版(添加 init 数据脚本、健康检查) |
|
||
| 2 | `nginx/nginx.conf` | 最终版(添加 H5 路由、HTTPS 配置) |
|
||
| 3 | `backend/app/main.py` | 更新:添加初始数据填充逻辑(预置 system_configs、funny_phrases、quick_reply_templates) |
|
||
| 4 | `.env.example` | 更新:补充所有配置项说明 |
|
||
|
||
**关键联调检查点**:
|
||
|
||
1. **企微回调验证**: GET 请求能正确返回 echostr 明文
|
||
2. **消息收发链路**: 员工发消息 → 后端收到 → 坐席看到 → 坐席回复 → 员工收到
|
||
3. **摇人链路**: H5 点击摇人 → 举手标记出现 → 坐席接单 → 员工收到接入通知
|
||
4. **标记评分**: VIP 员工自动标记、关键词触发情绪/举手、追问轮次触发需介入、紧急度计算正确
|
||
5. **排序正确**: 会话列表按紧急→举手→需介入→活跃→已结单排序
|
||
6. **H5 OAuth**: 企微打开 H5 能静默获取员工身份
|
||
7. **Docker 部署**: `docker-compose up` 一键启停所有服务
|
||
|
||
---
|
||
|
||
### 任务依赖关系图
|
||
|
||
```mermaid
|
||
graph LR
|
||
T01[T01: 项目基础设施<br/>5天] --> T02[T02: 后端核心服务<br/>12天]
|
||
T01 --> T03[T03: 坐席工作台前端<br/>8天]
|
||
T01 --> T04[T04: 用户端H5前端<br/>5天]
|
||
T02 --> T05[T05: 集成联调与部署<br/>3天]
|
||
T03 --> T05
|
||
T04 --> T05
|
||
```
|
||
|
||
**时间线(串行,单开发者)**:
|
||
|
||
```
|
||
D1-D5 : T01 项目基础设施
|
||
D6-D17 : T02 后端核心服务
|
||
D18-D25 : T03 坐席工作台前端
|
||
D26-D30 : T04 用户端H5前端 + T05 集成联调
|
||
```
|
||
|
||
> **注**: T03 和 T02 可以部分并行——坐席前端的静态布局和组件可以在 T02 进行到一半时开始。T04 依赖 T02 的 H5 API 就绪,建议 T02 完成后再开始。T05 在 T02/T03/T04 基本完成后集中联调。
|
||
|
||
---
|
||
|
||
## 6. 依赖包列表
|
||
|
||
### 6.1 Python 后端依赖 (backend/requirements.txt)
|
||
|
||
```
|
||
# Web 框架
|
||
fastapi==0.111.0 # 高性能异步 Web 框架,自动生成 API 文档
|
||
uvicorn[standard]==0.30.1 # ASGI 服务器,支持热重载
|
||
python-multipart==0.0.9 # FastAPI 文件上传支持
|
||
|
||
# 数据库
|
||
sqlalchemy==2.0.31 # Python SQL 工具包和 ORM
|
||
psycopg2-binary==2.9.9 # PostgreSQL 数据库驱动
|
||
alembic==1.13.1 # 数据库迁移工具
|
||
|
||
# 缓存
|
||
redis==5.0.7 # Redis 客户端
|
||
|
||
# 数据验证
|
||
pydantic==2.7.4 # 数据验证和设置管理
|
||
pydantic-settings==2.3.4 # 从环境变量读取配置
|
||
|
||
# HTTP 客户端
|
||
httpx==0.27.0 # 异步 HTTP 客户端,用于调用企微 API
|
||
|
||
# 加密
|
||
cryptography==42.0.8 # 企微消息 AES 加解密
|
||
|
||
# 工具
|
||
python-dotenv==1.0.1 # 从 .env 文件加载环境变量
|
||
```
|
||
|
||
**选型说明**:
|
||
|
||
| 包 | 为什么选它 | 替代方案 |
|
||
|----|-----------|---------|
|
||
| FastAPI | 异步、自动文档、类型安全 | Flask(同步,无自动文档) |
|
||
| SQLAlchemy 2.0 | 支持 async session、声明式模型 | Tortoise ORM(生态较小) |
|
||
| httpx | 异步、API 类似 requests | requests(同步)、aiohttp(API 较底层) |
|
||
| cryptography | 官方推荐、功能全面 | pycryptodome(非官方维护) |
|
||
| pydantic-settings | 与 FastAPI 深度集成 | python-decouple(功能较弱) |
|
||
|
||
### 6.2 坐席工作台前端依赖 (frontend-agent/package.json)
|
||
|
||
```json
|
||
{
|
||
"dependencies": {
|
||
"vue": "^3.4.0",
|
||
"vue-router": "^4.3.0",
|
||
"pinia": "^2.1.0",
|
||
"element-plus": "^2.7.0",
|
||
"axios": "^1.7.0",
|
||
"@element-plus/icons-vue": "^2.3.0"
|
||
},
|
||
"devDependencies": {
|
||
"@vitejs/plugin-vue": "^5.0.0",
|
||
"vite": "^5.3.0",
|
||
"typescript": "^5.5.0",
|
||
"vue-tsc": "^2.0.0"
|
||
}
|
||
}
|
||
```
|
||
|
||
**选型说明**:
|
||
|
||
| 包 | 为什么选它 |
|
||
|----|-----------|
|
||
| ElementPlus | 企业级组件库,表格/表单/对话框开箱即用,适合坐席工作台 |
|
||
| Pinia | Vue3 官方推荐状态管理,API 简洁 |
|
||
| axios | 最流行的 HTTP 客户端,拦截器机制适合统一处理 |
|
||
|
||
### 6.3 用户端 H5 前端依赖 (frontend-h5/package.json)
|
||
|
||
```json
|
||
{
|
||
"dependencies": {
|
||
"vue": "^3.4.0",
|
||
"vue-router": "^4.3.0",
|
||
"pinia": "^2.1.0",
|
||
"vant": "^4.8.0",
|
||
"axios": "^1.7.0"
|
||
},
|
||
"devDependencies": {
|
||
"@vitejs/plugin-vue": "^5.0.0",
|
||
"@vant/auto-import-resolver": "^1.2.0",
|
||
"vite": "^5.3.0",
|
||
"typescript": "^5.5.0",
|
||
"vue-tsc": "^2.0.0",
|
||
"unplugin-vue-components": "^0.27.0"
|
||
}
|
||
}
|
||
```
|
||
|
||
**选型说明**:
|
||
|
||
| 包 | 为什么选它 |
|
||
|----|-----------|
|
||
| Vant4 | 移动端 UI 库,组件轻量、企微 WebView 兼容性好 |
|
||
| unplugin-vue-components | Vant 按需引入,减小打包体积 |
|
||
|
||
---
|
||
|
||
## 7. 共享知识(跨文件约定)
|
||
|
||
### 7.1 代码风格规范
|
||
|
||
| 规范 | 说明 |
|
||
|------|------|
|
||
| Python 代码风格 | 遵循 PEP 8,使用 4 空格缩进,行宽 120 |
|
||
| TypeScript 代码风格 | 使用 2 空格缩进,单引号,行宽 120 |
|
||
| 命名规范 - Python | 变量/函数: snake_case; 类: PascalCase; 常量: UPPER_SNAKE_CASE |
|
||
| 命名规范 - TypeScript | 变量/函数: camelCase; 组件/类/接口: PascalCase; 常量: UPPER_SNAKE_CASE |
|
||
| 命名规范 - 数据库 | 表名: snake_case 复数; 字段: snake_case |
|
||
| 命名规范 - API | URL 路径: kebab-case (如 `/quick-replies`); JSON 字段: snake_case |
|
||
| 注释规范 | 每个文件顶部加 `"""模块说明"""` 或 `// 模块说明`;每个函数加 docstring/JSDoc;关键逻辑加行内注释(做什么 + 为什么) |
|
||
|
||
### 7.2 错误处理模式
|
||
|
||
**后端统一错误响应**:
|
||
```python
|
||
# backend/app/utils/response.py
|
||
class AppException(Exception):
|
||
"""业务异常基类"""
|
||
def __init__(self, code: int, message: str, data: Any = None):
|
||
self.code = code
|
||
self.message = message
|
||
self.data = data
|
||
|
||
# 错误码规范:
|
||
# 0 = 成功
|
||
# 1000+ = 通用错误(参数错误、未授权等)
|
||
# 2000+ = 企微 API 错误
|
||
# 3000+ = 业务逻辑错误
|
||
|
||
ERR_PARAMS = AppException(1001, "参数错误")
|
||
ERR_UNAUTHORIZED = AppException(1002, "未授权")
|
||
ERR_NOT_FOUND = AppException(1003, "资源不存在")
|
||
ERR_WECOM_TOKEN = AppException(2001, "企微 access_token 获取失败")
|
||
ERR_WECOM_SEND = AppException(2002, "企微消息发送失败")
|
||
ERR_WECOM_DECRYPT = AppException(2003, "企微消息解密失败")
|
||
ERR_AGENT_OFFLINE = AppException(3001, "坐席不在线")
|
||
ERR_CONVERSATION_RESOLVED = AppException(3002, "会话已结单")
|
||
```
|
||
|
||
**前端统一错误处理**:
|
||
```typescript
|
||
// axios 拦截器中统一处理
|
||
// code !== 0 时,使用 ElementPlus 的 ElMessage.error() 或 Vant 的 showToast() 提示
|
||
// 网络错误统一提示"网络异常,请稍后重试"
|
||
```
|
||
|
||
### 7.3 日志规范
|
||
|
||
| 级别 | 使用场景 |
|
||
|------|---------|
|
||
| DEBUG | 详细调试信息(开发阶段开启,生产关闭) |
|
||
| INFO | 关键业务流程(消息接收、路由分发、坐席接单等) |
|
||
| WARNING | 非预期但可恢复的情况(企微 API 限流、缓存未命中等) |
|
||
| ERROR | 需要关注的错误(企微消息解密失败、数据库连接失败等) |
|
||
|
||
**日志格式**:
|
||
```
|
||
[2025-07-11 10:30:00] [INFO] [message_router] 收到员工消息: employee_id=zhangsan, content=我的电脑蓝屏了
|
||
[2025-07-11 10:30:01] [INFO] [message_router] 会话标记更新: conv_id=xxx, tags={"hand_raise":true,"emotion":"urgent"}, urgency=4
|
||
[2025-07-11 10:30:05] [ERROR] [wecom_service] 企微消息发送失败: employee_id=zhangsan, error=token过期
|
||
```
|
||
|
||
### 7.4 配置管理方式
|
||
|
||
| 配置类型 | 存储位置 | 说明 |
|
||
|---------|---------|------|
|
||
| 基础设施配置 | `.env` 文件 → 环境变量 | 数据库连接、Redis 地址、企微 corpid 等 |
|
||
| 业务规则配置 | `system_configs` 数据库表 | 关键词、阈值、话术等,支持动态修改 |
|
||
| 企微回调配置 | 企微管理后台 | 回调 URL、Token、EncodingAESKey |
|
||
|
||
**配置读取优先级**: 环境变量 > .env 文件 > 默认值
|
||
|
||
**关键环境变量**:
|
||
```env
|
||
# 企微配置
|
||
WECOM_CORP_ID=ww1234567890abcdef
|
||
WECOM_AGENT_ID=1000002
|
||
WECOM_SECRET=your-agent-secret
|
||
WECOM_TOKEN=your-callback-token
|
||
WECOM_ENCODING_AES_KEY=your-aes-key-43chars
|
||
|
||
# 数据库
|
||
DATABASE_URL=postgresql://user:pass@postgres:5432/wecom_it_desk
|
||
|
||
# Redis
|
||
REDIS_URL=redis://redis:6379/0
|
||
|
||
# 服务配置
|
||
BACKEND_HOST=0.0.0.0
|
||
BACKEND_PORT=8000
|
||
CORS_ORIGINS=http://localhost:5173,http://localhost:5174
|
||
```
|
||
|
||
### 7.5 API 版本与兼容约定
|
||
|
||
| 约定 | 说明 |
|
||
|------|------|
|
||
| API 前缀 | 所有 API 以 `/api/` 开头,第一步无版本号(后续如需版本化改为 `/api/v1/`) |
|
||
| 分页参数 | 统一使用 `page` (从1开始) + `page_size` (默认20,最大100) |
|
||
| 时间格式 | 所有时间使用 ISO 8601 UTC 格式: `2025-07-11T10:30:00Z` |
|
||
| UUID 格式 | 全部使用小写无破折号格式: `550e8400e29b41d4a716446655440000` |
|
||
| 空值处理 | JSON 响应中不传 null 字段,省略即可 |
|
||
|
||
### 7.6 数据库约定
|
||
|
||
| 约定 | 说明 |
|
||
|------|------|
|
||
| 主键 | 全部使用 UUID,数据库自动生成 `gen_random_uuid()` |
|
||
| 时间字段 | 全部使用 `TIMESTAMP WITH TIME ZONE`,默认 `NOW()` |
|
||
| 软删除 | 第一步不使用软删除,直接物理删除 |
|
||
| JSONB 字段 | 用于 tags、variables 等灵活结构,必须提供默认值 `{}` 或 `[]` |
|
||
| 迁移 | 所有表结构变更通过 Alembic 迁移脚本管理,不手动改表 |
|
||
|
||
---
|
||
|
||
## 8. 待明确事项
|
||
|
||
| # | 事项 | 影响范围 | 当前假设 | 建议确认时间 |
|
||
|---|------|---------|---------|------------|
|
||
| 1 | 企微自建应用的 `AgentId` 和回调 URL 是否已在企微管理后台创建? | 企微对接 | 假设已创建,开发者提供 corpid/secret/token/aes_key | T01 开始前 |
|
||
| 2 | HTTPS 证书如何获取?服务器是否有公网域名? | 部署 | 假设使用 Nginx 反向代理 + Let's Encrypt 或已有证书 | T01 开始前 |
|
||
| 3 | 企微 H5 页面是否必须在企微内打开?外部浏览器是否需要兼容? | H5 开发 | 假设只在企微 WebView 内使用,不兼容外部浏览器 | T04 开始前 |
|
||
| 4 | 坐席登录方式:是否使用企微扫码登录,还是简单的用户名密码? | 坐席管理 | 第一步假设简单用户名密码登录(坐席数量少,测试阶段) | T02 开始前 |
|
||
| 5 | 企微通讯录 API 权限是否已申请?(VIP 判断依赖此权限) | VIP 功能 | 假设已申请通讯录只读权限 | T02 开始前 |
|
||
| 6 | 第一步是否需要支持图片/文件消息? | 消息类型 | 假设第一步仅支持文本消息(PRD OQ-03) | T02 开始前 |
|
||
| 7 | 坐席同时服务会话数上限? | 分配逻辑 | 默认 5 个(agents.max_load 默认值) | T02 开发中 |
|
||
| 8 | 企微应用名称确认? | 用户体验 | 假设命名为"IT服务台"(PRD OQ-06) | T01 开始前 |
|
||
| 9 | 会话结单条件:坐席手动结单 or 员工长时间不回复自动结单? | 会话状态管理 | 第一步仅支持坐席手动结单 | T02 开始前 |
|
||
| 10 | H5 双栏在窄屏手机(<375px)上是否需要降级为单栏? | H5 布局 | 假设 <375px 时右栏改为底部弹出抽屉 | T04 开始前 |
|
||
| 11 | **WS 端点认证方案**:坐席端 `/ws/{agent_id}` 如何验证身份? | WebSocket 安全 | 阶段一暂用 query param 传 token,查 Redis 验证;阶段二迁移到企微 OAuth2 | 2A 开始前 |
|
||
| 12 | **Nginx WS 代理超时配置**:`proxy_read_timeout` 当前值是多少? | 部署稳定性 | 假设已配置 ≥ 90s,需在部署文档中明确 | T01 开始前 |
|
||
|
||
---
|
||
|
||
## 附录 A: 企微 API 对接要点速查
|
||
|
||
### A.1 消息回调验证(GET)
|
||
|
||
```
|
||
GET /api/wecom/callback?msg_signature=xxx×tamp=xxx&nonce=xxx&echostr=xxx
|
||
```
|
||
|
||
1. 将 token、timestamp、nonce 字典序排列拼接,SHA1 签名
|
||
2. 签名与 msg_signature 比对验证
|
||
3. 解密 echostr,返回明文
|
||
|
||
### A.2 消息接收(POST)
|
||
|
||
```
|
||
POST /api/wecom/callback
|
||
Content-Type: text/xml
|
||
```
|
||
|
||
1. 解析 XML 获取 Encrypt 字段
|
||
2. 验证签名(同上)
|
||
3. AES 解密获取消息明文 XML
|
||
4. 解析消息内容(Content、FromUserName、MsgType 等)
|
||
5. 路由消息到 MessageRouter
|
||
6. 返回 `"success"` 字符串
|
||
|
||
### A.3 消息发送
|
||
|
||
```
|
||
POST https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=TOKEN
|
||
```
|
||
|
||
```json
|
||
{
|
||
"touser": "UserID",
|
||
"msgtype": "text",
|
||
"agentid": 1000002,
|
||
"text": {
|
||
"content": "您好,IT坐席为您服务"
|
||
}
|
||
}
|
||
```
|
||
|
||
### A.4 access_token 获取
|
||
|
||
```
|
||
GET https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=ID&corpsecret=SECRET
|
||
```
|
||
|
||
- 返回: `{"access_token": "xxx", "expires_in": 7200}`
|
||
- 缓存到 Redis,TTL=7200,提前300秒刷新
|
||
|
||
### A.5 H5 OAuth2 静默授权
|
||
|
||
1. 前端跳转: `https://open.weixin.qq.com/connect/oauth2/authorize?appid=CORPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect`
|
||
2. 企微回调到 redirect_uri 并携带 code
|
||
3. 后端用 code 换取员工身份: `GET https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo?access_token=TOKEN&code=CODE`
|
||
4. 返回: `{"userid": "zhangsan", ...}`
|
||
|
||
---
|
||
|
||
## 附录 B: 数据库初始化数据
|
||
|
||
> 以下数据应在 `backend/app/main.py` 的 startup 事件中自动插入(仅当 system_configs 表为空时)
|
||
|
||
### B.1 预置快速回复模板
|
||
|
||
```sql
|
||
INSERT INTO quick_reply_templates (category, title, content, variables, sort_order) VALUES
|
||
('账号', '密码重置', '您好{employee_name},您的密码重置链接已发送至您的企业邮箱,请在30分钟内完成操作。', '["employee_name"]', 1),
|
||
('账号', '账号解锁', '您好,您的账号已解锁,请5分钟后重新尝试登录。如仍有问题请联系IT服务台。', '[]', 2),
|
||
('网络', 'VPN连接指引', '请按以下步骤操作:1.打开VPN客户端 2.选择"公司内网" 3.输入域账号密码 4.点击连接。详细图文教程请查看右侧"操作步骤"。', '[]', 3),
|
||
('网络', 'WiFi连接', '公司WiFi名称:Office-5G,密码请咨询前台或查看工位标签。', '[]', 4),
|
||
('软件', '软件安装申请', '您好,软件安装需要提交审批申请。请在右侧"审批流程"中点击"软件安装申请"链接提交。', '[]', 5),
|
||
('硬件', '设备报修', '您好,设备报修请提交工单。请在右侧"审批流程"中点击"设备报修"链接提交,IT会在24小时内联系您。', '[]', 6),
|
||
('通用', '会话结束', '您好,请问还有其他问题吗?如无其他问题,我将结束本次服务。祝您工作顺利!', '[]', 7),
|
||
('通用', '稍等回复', '收到,我正在为您查询,请稍等片刻。', '[]', 8);
|
||
```
|
||
|
||
### B.2 预置审批流程链接
|
||
|
||
```sql
|
||
INSERT INTO approval_links (category, title, url, sort_order) VALUES
|
||
('IT', '软件安装申请', 'https://审批系统地址/software-install', 1),
|
||
('IT', '设备报修工单', 'https://审批系统地址/device-repair', 2),
|
||
('IT', 'VPN开通申请', 'https://审批系统地址/vpn-apply', 3),
|
||
('IT', '权限申请', 'https://审批系统地址/permission-apply', 4),
|
||
('HR', '入职手续', 'https://审批系统地址/onboarding', 5),
|
||
('HR', '离职手续', 'https://审批系统地址/offboarding', 6),
|
||
('行政', '办公用品申领', 'https://审批系统地址/office-supplies', 7),
|
||
('财务', '报销申请', 'https://审批系统地址/reimbursement', 8);
|
||
```
|
||
|
||
### B.3 预置软件下载入口
|
||
|
||
```sql
|
||
INSERT INTO software_downloads (category, name, version, platform, download_url, sort_order) VALUES
|
||
('办公', '企业微信', '最新版', '全平台', 'https://work.weixin.qq.com/#download', 1),
|
||
('办公', 'WPS Office', '12.1', 'Windows/Mac', 'https://www.wps.cn/download', 2),
|
||
('办公', 'Microsoft Teams', '最新版', '全平台', 'https://www.microsoft.com/teams/download', 3),
|
||
('开发', 'VS Code', '1.90', 'Windows/Mac/Linux', 'https://code.visualstudio.com/download', 4),
|
||
('开发', 'Git', '2.45', 'Windows/Mac', 'https://git-scm.com/download', 5),
|
||
('安全', '公司VPN客户端', '3.2', 'Windows/Mac', 'https://内部下载地址/vpn-client', 6),
|
||
('工具', '7-Zip', '24.06', 'Windows', 'https://www.7-zip.org/download', 7),
|
||
('工具', 'PDF阅读器', '最新版', 'Windows/Mac', 'https://get.adobe.com/reader/', 8);
|
||
```
|
||
|
||
---
|
||
|
||
---
|
||
|
||
## 9. v5.3 坐席工作台增量架构
|
||
|
||
> 以下内容合并自 ARCHITECTURE-v53-incremental.md(2026-06-06),章节编号保持原样以便对照原文档。若需连续编号,可将 §1→§9.1、§2→§9.2 以此类推。
|
||
|
||
---
|
||
|
||
|
||
## 1. 实现方案与框架选型
|
||
|
||
### 1.1 核心技术挑战
|
||
|
||
| # | 挑战 | 难度 | 应对策略 |
|
||
|---|------|------|---------|
|
||
| 1 | CSS 变量驱动双主题系统,需确保所有现有硬编码色值迁移完成 | ⭐⭐ | 分层替换:先定义变量体系 → 替换 `global.css` → 逐组件迁移 inline style |
|
||
| 2 | 右栏 5-Tab → 上下两区重构,需保持快速回复键盘导航的焦点管理 | ⭐⭐⭐ | 使用 `useKeyboardShortcuts` composable 统一管理快捷键,避免各组件各自监听 |
|
||
| 3 | 中栏视图切换(聊天↔任务详情),需保持 WebSocket 连接和 Store 状态不丢失 | ⭐⭐ | 纯前端 `v-if`/`v-show` 切换,不销毁 Store;用 `workspaceView` 状态控制 |
|
||
| 4 | 排查步骤决策树 JSON 渲染,需支持判断节点 + 分支缩进 + 动画展开 | ⭐⭐⭐ | 递归组件 `FlowchartNode.vue`,`max-height` 过渡 + `overflow: hidden` |
|
||
| 5 | 会话列表 6 区 → 3 段折叠,数据映射需重新定义 computed 属性 | ⭐⭐ | 新增 `myConversations`/`colleagueConversations`/`historyConversations` 三个 computed |
|
||
|
||
### 1.2 框架选型(沿用 + 增量)
|
||
|
||
| 层 | 框架/库 | 版本 | 说明 |
|
||
|----|--------|------|------|
|
||
| 前端框架 | Vue 3 | ^3.4 | Composition API + `<script setup>` |
|
||
| UI 组件库 | Element Plus | ^2.7 | 沿用,少量自定义样式覆盖 |
|
||
| 状态管理 | Pinia | ^2.1 | 新增 `useTodoStore`、`useThemeStore` |
|
||
| 构建工具 | Vite | ^5.x | 沿用 |
|
||
| CSS 方案 | CSS Variables | 原生 | 双主题核心,不引入额外 CSS-in-JS |
|
||
| 后端框架 | FastAPI | ^0.111 | 沿用 |
|
||
| ORM | SQLAlchemy 2.0 | ^2.0 | 异步模式,新增模型 |
|
||
| 数据验证 | Pydantic v2 | ^2.7 | 沿用,新增 Schema |
|
||
|
||
> **决策**:不引入新 UI 框架或 CSS-in-JS 方案。双主题完全通过 CSS 变量 + `data-theme` 属性切换实现,与 Element Plus 主题变量共存。
|
||
|
||
### 1.3 架构模式
|
||
|
||
沿用现有 **MVVM + Composable** 模式:
|
||
|
||
```
|
||
View (Vue SFC)
|
||
↕ 双向绑定 / 事件
|
||
ViewModel (Pinia Store + Composables)
|
||
↕ API 调用
|
||
Model (TypeScript 接口 ↔ Pydantic Schema ↔ SQLAlchemy Model)
|
||
```
|
||
|
||
新增 Composable 层:
|
||
- `useTheme.ts` — 主题切换 + 持久化
|
||
- `useKeyboardShortcuts.ts` — 全局快捷键注册/卸载
|
||
|
||
---
|
||
|
||
## 2. 文件列表及相对路径
|
||
|
||
> 变更类型标记:🆕新增 / ✏️修改 / 🔄重写 / 🗑️废弃
|
||
|
||
### 2.1 前端文件
|
||
|
||
| # | 相对路径(基于 `frontend-agent/src/`) | 变更 | 说明 |
|
||
|---|--------------------------------------|------|------|
|
||
| 1 | `styles/global.css` | ✏️ | 新增深色主题 CSS 变量块 + 双主题色值体系;替换硬编码色为 `var()` |
|
||
| 2 | `composables/useTheme.ts` | 🆕 | 主题切换 composable(读取/写入 localStorage + 设置 `data-theme`) |
|
||
| 3 | `composables/useKeyboardShortcuts.ts` | 🆕 | 全局快捷键统一管理(Ctrl+1/2/3, Alt+1~5, ↑↓, Enter, /) |
|
||
| 4 | `stores/theme.ts` | 🆕 | 主题 Pinia Store(currentTheme 响应式 + toggle 方法) |
|
||
| 5 | `stores/todo.ts` | 🆕 | 待办事项 Pinia Store(todoList + fetch/更新状态) |
|
||
| 6 | `api/todo.ts` | 🆕 | 待办事项 API(GET 列表/详情, PUT 状态) |
|
||
| 7 | `api/troubleshooting.ts` | 🆕 | 排查模板 API(GET 列表/详情) |
|
||
| 8 | `api/conversation.ts` | ✏️ | Conversation 接口新增 `impact_scope`/`is_blocking`/`emotion_state` 字段 |
|
||
| 9 | `views/Workspace.vue` | ✏️ | 顶部栏抽离为 `TopBar.vue`;新增 `workspaceView` 状态控制视图切换 |
|
||
| 10 | `components/layout/TopBar.vue` | 🆕 | 独立顶栏组件(系统名称 FE-09 + 主题切换 + 坐席状态 + 应急模式) |
|
||
| 11 | `components/conversation/ConversationList.vue` | 🔄 | 三段折叠 + 搜索标签 + 底部待办面板挂载点 |
|
||
| 12 | `components/conversation/ConversationItem.vue` | ✏️ | 新增优先级图标(⛔👥⭐🔁);移除旧标签部分 |
|
||
| 13 | `components/conversation/TodoPanel.vue` | 🆕 | 左栏底部待办事项面板 |
|
||
| 14 | `components/chat/ChatArea.vue` | ✏️ | 顶部替换为 `UserInfoBar`;底部挂载 `TroubleshootBar`;新增视图切换逻辑 |
|
||
| 15 | `components/chat/UserInfoBar.vue` | 🆕 | 用户信息栏(chips + 展开详情 6 卡片) |
|
||
| 16 | `components/chat/ItLevelBadge.vue` | 🆕 | IT 等级徽标组件(7 级段位 + 渐变 + 王者发光) |
|
||
| 17 | `components/chat/AiRecommendInline.vue` | 🆕 | 聊天区内 AI 推荐回复(Ctrl+1/2/3) |
|
||
| 18 | `components/chat/TroubleshootBar.vue` | 🆕 | 排查步骤栏(路径视图 + 可展开流程图) |
|
||
| 19 | `components/chat/FlowchartNode.vue` | 🆕 | 决策树递归渲染节点 |
|
||
| 20 | `components/chat/TaskDetailView.vue` | 🆕 | 任务详情视图(工单/审批/设备三种子视图) |
|
||
| 21 | `components/assistant/AiAssistantPanel.vue` | 🔄 | 完全重写:移除 5 Tab,改为上下两区 |
|
||
| 22 | `components/assistant/AiSuggestReply.vue` | ✏️ | 适配右栏上方 AI 推荐区样式;增加置信度 + 快捷键提示 |
|
||
| 23 | `components/assistant/QuickReplyPanel.vue` | 🔄 | 重写:搜索置顶 + Alt 分类 + ↑↓ 导航 + Enter 确认 + 键盘指南 |
|
||
| 24 | `components/assistant/RiskAlert.vue` | 🗑️ | 废弃,不再引用 |
|
||
| 25 | `components/assistant/UserInfoPanel.vue` | 🗑️ | 废弃,功能并入 `UserInfoBar.vue` |
|
||
|
||
### 2.2 后端文件
|
||
|
||
| # | 相对路径(基于 `backend/app/`) | 变更 | 说明 |
|
||
|---|-------------------------------|------|------|
|
||
| 1 | `models/employee.py` | ✏️ | 新增 `it_level`/`it_level_source`/`notes` 字段 |
|
||
| 2 | `models/conversation.py` | ✏️ | 新增 `impact_scope`/`is_blocking`/`emotion_state` 字段 |
|
||
| 3 | `models/todo_item.py` | 🆕 | TodoItem 模型(id/type/title/priority/description/status/...) |
|
||
| 4 | `models/troubleshooting_template.py` | 🆕 | TroubleshootingTemplate 模型(id/name/category/path_steps/flowchart/...) |
|
||
| 5 | `schemas/employee.py` | ✏️ | EmployeeResponse 新增字段;新增 `ItLevelUpdateRequest` Schema |
|
||
| 6 | `schemas/conversation.py` | ✏️ | ConversationResponse/ConversationTags 新增字段 |
|
||
| 7 | `schemas/todo_item.py` | 🆕 | TodoItem CRUD Schema |
|
||
| 8 | `schemas/troubleshooting_template.py` | 🆕 | TroubleshootingTemplate CRUD Schema |
|
||
| 9 | `api/employees.py` | ✏️ | 新增 `PUT /api/employees/{id}/it-level` 端点 |
|
||
| 10 | `api/conversations.py` | ✏️ | 响应包含新字段(无需新端点) |
|
||
| 11 | `api/todo_items.py` | 🆕 | GET 列表/详情 + PUT 状态更新 |
|
||
| 12 | `api/troubleshooting_templates.py` | 🆕 | GET 列表/详情 + POST/PUT/DELETE(管理员) |
|
||
| 13 | `api/router.py` | ✏️ | 注册新路由 |
|
||
| 14 | `models/__init__.py` | ✏️ | 导入新模型(确保 SQLite 自动建表) |
|
||
|
||
### 2.3 文档与配置文件
|
||
|
||
| # | 相对路径 | 变更 | 说明 |
|
||
|---|---------|------|------|
|
||
| 1 | `docs/ARCHITECTURE-v53-incremental.md` | 🆕 | 本文档 |
|
||
| 2 | `docs/sequence-diagram.mermaid` | 🆕 | 时序图 |
|
||
| 3 | `docs/class-diagram.mermaid` | 🆕 | 类图 |
|
||
|
||
---
|
||
|
||
## 3. 数据结构与接口
|
||
|
||
### 3.1 类图
|
||
|
||
```mermaid
|
||
classDiagram
|
||
direction TB
|
||
|
||
class Employee {
|
||
+str id
|
||
+str corp_id
|
||
+str employee_id
|
||
+str name
|
||
+str department
|
||
+str position
|
||
+str mobile
|
||
+str email
|
||
+str avatar
|
||
+int status
|
||
+str it_level ★NEW
|
||
+str it_level_source ★NEW
|
||
+dict notes ★NEW
|
||
+datetime last_login_at
|
||
+datetime created_at
|
||
+datetime updated_at
|
||
}
|
||
|
||
class Conversation {
|
||
+str id
|
||
+str corp_id
|
||
+str employee_id
|
||
+str employee_name
|
||
+str department
|
||
+str status
|
||
+bool is_vip
|
||
+bool is_pinned
|
||
+bool is_todo
|
||
+int urgency_score
|
||
+dict tags
|
||
+str assigned_agent_id
|
||
+list collaborating_agent_ids
|
||
+int impact_scope ★NEW
|
||
+bool is_blocking ★NEW
|
||
+str emotion_state ★NEW
|
||
+datetime last_message_at
|
||
+str last_message_summary
|
||
+datetime created_at
|
||
+datetime updated_at
|
||
}
|
||
|
||
class TodoItem {
|
||
+str id
|
||
+str type
|
||
+str title
|
||
+str priority
|
||
+dict description
|
||
+str status
|
||
+str assigned_agent_id
|
||
+str corp_id
|
||
+datetime created_at
|
||
+datetime updated_at
|
||
}
|
||
|
||
class TroubleshootingTemplate {
|
||
+str id
|
||
+str name
|
||
+str category
|
||
+list path_steps
|
||
+dict flowchart
|
||
+bool is_active
|
||
+datetime created_at
|
||
+datetime updated_at
|
||
}
|
||
|
||
Employee "1" --> "*" Conversation : has
|
||
Conversation "1" --> "*" TodoItem : may generate
|
||
TroubleshootingTemplate "1" --> "0..1" Conversation : applied to
|
||
|
||
note for Employee "it_level: bronze|silver|gold|platinum|diamond|star|king\nit_level_source: system|manual"
|
||
note for Conversation "impact_scope: 受影响人数\nis_blocking: 是否阻断性\nemotion_state: normal|anxious|angry|urgent"
|
||
note for TodoItem "type: ticket|approval|device\npriority: urgent|high|normal\nstatus: pending|processing|resolved"
|
||
note for TroubleshootingTemplate "category: vpn|email|system|account\npath_steps: [{label, status}]\nflowchart: 递归树结构"
|
||
```
|
||
|
||
### 3.2 前端 TypeScript 接口新增
|
||
|
||
```typescript
|
||
// ---- api/conversation.ts 新增字段 ----
|
||
export interface Conversation {
|
||
// ... 现有字段 ...
|
||
impact_scope: number // ★NEW 影响范围(受影响人数)
|
||
is_blocking: boolean // ★NEW 阻断性标记
|
||
emotion_state: string // ★NEW 情绪状态: normal/anxious/angry/urgent
|
||
}
|
||
|
||
// ---- api/todo.ts ----
|
||
export interface TodoItem {
|
||
id: string
|
||
type: 'ticket' | 'approval' | 'device'
|
||
title: string
|
||
priority: 'urgent' | 'high' | 'normal'
|
||
description: Record<string, any>
|
||
status: 'pending' | 'processing' | 'resolved'
|
||
assigned_agent_id: string | null
|
||
corp_id: string
|
||
created_at: string
|
||
updated_at: string
|
||
}
|
||
|
||
// ---- api/troubleshooting.ts ----
|
||
export interface PathStep {
|
||
label: string
|
||
status: 'done' | 'current' | 'pending'
|
||
}
|
||
|
||
export interface FlowchartNode {
|
||
id: string
|
||
type: 'step' | 'decision'
|
||
label: string
|
||
status?: 'done' | 'current' | 'pending'
|
||
children?: FlowchartNode[]
|
||
yes_branch?: FlowchartNode
|
||
no_branch?: FlowchartNode
|
||
}
|
||
|
||
export interface TroubleshootingTemplate {
|
||
id: string
|
||
name: string
|
||
category: 'vpn' | 'email' | 'system' | 'account'
|
||
path_steps: PathStep[]
|
||
flowchart: FlowchartNode
|
||
is_active: boolean
|
||
created_at: string
|
||
updated_at: string
|
||
}
|
||
|
||
// ---- stores/theme.ts ----
|
||
export type ThemeMode = 'light' | 'dark'
|
||
|
||
// ---- stores/todo.ts ----
|
||
export interface TodoState {
|
||
todoList: TodoItem[]
|
||
loading: boolean
|
||
}
|
||
```
|
||
|
||
### 3.3 后端 Pydantic Schema 新增
|
||
|
||
```python
|
||
# ---- schemas/employee.py 新增 ----
|
||
class ItLevelUpdateRequest(BaseModel):
|
||
it_level: str = Field(..., pattern="^(bronze|silver|gold|platinum|diamond|star|king)$")
|
||
source: str = Field(default="manual", pattern="^(system|manual)$")
|
||
|
||
# ---- schemas/todo_item.py ----
|
||
class TodoItemResponse(BaseModel):
|
||
id: str
|
||
type: str # ticket/approval/device
|
||
title: str
|
||
priority: str # urgent/high/normal
|
||
description: Dict[str, Any]
|
||
status: str # pending/processing/resolved
|
||
assigned_agent_id: Optional[str] = None
|
||
corp_id: str
|
||
created_at: datetime
|
||
updated_at: datetime
|
||
model_config = {"from_attributes": True}
|
||
|
||
class TodoStatusUpdateRequest(BaseModel):
|
||
status: str = Field(..., pattern="^(pending|processing|resolved)$")
|
||
|
||
# ---- schemas/troubleshooting_template.py ----
|
||
class TroubleshootingTemplateResponse(BaseModel):
|
||
id: str
|
||
name: str
|
||
category: str
|
||
path_steps: List[Dict[str, Any]]
|
||
flowchart: Dict[str, Any]
|
||
is_active: bool
|
||
created_at: datetime
|
||
updated_at: datetime
|
||
model_config = {"from_attributes": True}
|
||
```
|
||
|
||
---
|
||
|
||
## 4. 程序调用流程
|
||
|
||
### 4.1 主题切换流程
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant U as 坐席
|
||
participant TB as TopBar.vue
|
||
participant TS as useThemeStore
|
||
participant UT as useTheme.ts
|
||
participant DOM as document.documentElement
|
||
participant LS as localStorage
|
||
|
||
U->>TB: 点击 ☀️/🌙 切换开关
|
||
TB->>TS: toggleTheme()
|
||
TS->>TS: currentTheme = currentTheme === 'light' ? 'dark' : 'light'
|
||
TS->>UT: applyTheme(currentTheme)
|
||
UT->>DOM: setAttribute('data-theme', theme)
|
||
UT->>LS: setItem('theme', theme)
|
||
DOM-->>DOM: CSS 变量自动切换(`:root` / `[data-theme="dark"]`)
|
||
DOM-->>U: 300ms 过渡动画,界面变色
|
||
|
||
Note over U,LS: 页面加载时
|
||
U->>UT: 首次进入页面
|
||
UT->>LS: getItem('theme')
|
||
LS-->>UT: 'dark' | 'light' | null
|
||
UT->>DOM: setAttribute('data-theme', theme || 'light')
|
||
```
|
||
|
||
### 4.2 待办事项点击 → 中间栏视图切换
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant U as 坐席
|
||
participant TP as TodoPanel.vue
|
||
participant TDS as useTodoStore
|
||
participant WS as Workspace.vue
|
||
participant TDV as TaskDetailView.vue
|
||
|
||
U->>TP: 点击待办条目(type=ticket)
|
||
TP->>TDS: selectTodoItem(item)
|
||
TDS->>TDS: currentTodoItem = item
|
||
TDS->>WS: conversationStore.workspaceView = 'task'
|
||
WS->>WS: v-if 判断 workspaceView
|
||
WS->>TDV: 渲染 TaskDetailView(type='ticket')
|
||
TDV->>TDV: 根据 type 渲染对应子视图
|
||
|
||
Note over U,TDV: 返回聊天视图
|
||
U->>TDV: 点击 "← 返回会话"
|
||
TDV->>WS: conversationStore.workspaceView = 'chat'
|
||
WS->>WS: 切回 ChatArea 渲染
|
||
```
|
||
|
||
### 4.3 排查步骤展开流程
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant U as 坐席
|
||
participant TSB as TroubleshootBar.vue
|
||
participant TS as troubleshooting API
|
||
participant FN as FlowchartNode.vue
|
||
|
||
Note over TSB: 默认显示路径方块(横向滚动)
|
||
TSB->>TS: GET /api/troubleshooting-templates?category=vpn
|
||
TS-->>TSB: 返回模板列表
|
||
TSB->>TSB: 渲染 path_steps 横向方块
|
||
|
||
U->>TSB: 点击 "▶ 展开全流程图"
|
||
TSB->>TSB: expanded = true
|
||
TSB->>FN: 递归渲染 flowchart 节点
|
||
FN->>FN: type='step' → 渲染步骤节点
|
||
FN->>FN: type='decision' → 渲染判断节点(❓黄底)
|
||
FN->>FN: 递归渲染 yes_branch / no_branch
|
||
|
||
Note over TSB: max-height 过渡 0.35s 展开
|
||
```
|
||
|
||
### 4.4 AI 推荐内联回复填入
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant U as 坐席
|
||
participant CA as ChatArea.vue
|
||
participant ARI as AiRecommendInline.vue
|
||
participant RB as ReplyBox.vue
|
||
participant CS as conversationStore
|
||
|
||
CA->>ARI: 坐席未回复 + 有用户消息 → 显示
|
||
ARI->>ARI: 渲染 1-3 条 AI 推荐卡片
|
||
|
||
alt 快捷键 Ctrl+1
|
||
U->>ARI: Ctrl+1 按下
|
||
ARI->>CS: pendingReplyText = recommendations[0].content
|
||
CS-->>RB: watch pendingReplyText → 填入输入框并聚焦
|
||
else 点击卡片
|
||
U->>ARI: 点击第 N 张卡片
|
||
ARI->>CS: pendingReplyText = recommendations[N].content
|
||
CS-->>RB: 填入输入框并聚焦
|
||
end
|
||
|
||
U->>RB: 发送回复
|
||
RB->>ARI: 坐席已回复 → 自动隐藏
|
||
```
|
||
|
||
### 4.5 右栏快速回复键盘导航
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant U as 坐席
|
||
participant KS as useKeyboardShortcuts
|
||
participant QRP as QuickReplyPanel.vue
|
||
participant CS as conversationStore
|
||
|
||
Note over KS: 全局注册(仅在输入框未聚焦时生效)
|
||
|
||
alt Alt+1 切换分类
|
||
U->>KS: Alt+1 按下
|
||
KS->>QRP: activeCategory = categories[0]
|
||
QRP->>QRP: 重新渲染当前分类回复列表
|
||
end
|
||
|
||
alt ↑↓ 导航条目
|
||
U->>KS: ↓ 按下
|
||
KS->>QRP: selectedIndex++
|
||
QRP->>QRP: 高亮下一条回复
|
||
end
|
||
|
||
alt Enter 确认填入
|
||
U->>KS: Enter 按下
|
||
KS->>QRP: 使用选中条目
|
||
QRP->>CS: pendingReplyText = selected.content
|
||
CS-->>QRP: ReplyBox 填入
|
||
end
|
||
|
||
alt / 聚焦搜索
|
||
U->>KS: / 按下
|
||
KS->>QRP: focusSearchInput()
|
||
end
|
||
```
|
||
|
||
---
|
||
|
||
## 5. 任务列表
|
||
|
||
### 5.1 所需依赖包
|
||
|
||
```yaml
|
||
# 前端(npm)— 无新增包
|
||
# CSS 变量 + 原生 DOM API 实现主题,无需额外依赖
|
||
|
||
# 后端(pip)— 无新增包
|
||
# SQLAlchemy 2.0 / Pydantic v2 / FastAPI 已包含所需功能
|
||
|
||
# 开发依赖(可选)
|
||
# - @vue/test-utils: 单元测试(如需)
|
||
```
|
||
|
||
> **说明**:本次增量无需新增任何 npm/pip 依赖。主题系统使用原生 CSS 变量,快捷键使用原生 `addEventListener`,决策树渲染使用 Vue 递归组件。
|
||
|
||
### 5.2 任务分解
|
||
|
||
---
|
||
|
||
#### T01: 项目基础设施 — 主题系统 + 后端模型扩展 + 入口改造
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| **优先级** | P0 |
|
||
| **依赖** | 无 |
|
||
| **预估文件数** | ~14 |
|
||
| **关键变更点** | |
|
||
|
||
**前端文件**:
|
||
- `styles/global.css` — 新增深色主题 CSS 变量块,替换硬编码色为 `var(--bg-primary)` 等
|
||
- `composables/useTheme.ts` — 主题切换逻辑
|
||
- `stores/theme.ts` — 主题 Pinia Store
|
||
- `views/Workspace.vue` — 应用 `data-theme`,移除顶部栏到 TopBar
|
||
- `components/layout/TopBar.vue` — 新建顶栏(系统名称 + 主题切换 + 坐席状态 + 应急模式)
|
||
|
||
**后端文件**:
|
||
- `models/employee.py` — 新增 `it_level`/`it_level_source`/`notes`
|
||
- `models/conversation.py` — 新增 `impact_scope`/`is_blocking`/`emotion_state`
|
||
- `models/todo_item.py` — 新建 TodoItem 模型
|
||
- `models/troubleshooting_template.py` — 新建 TroubleshootingTemplate 模型
|
||
- `models/__init__.py` — 导入新模型
|
||
- `schemas/employee.py` — 新增 `ItLevelUpdateRequest`
|
||
- `schemas/conversation.py` — ConversationResponse 新增字段
|
||
- `schemas/todo_item.py` — 新建
|
||
- `schemas/troubleshooting_template.py` — 新建
|
||
|
||
**验收标准**:
|
||
1. `document.documentElement.setAttribute('data-theme', 'dark')` 后全局色值切换
|
||
2. 刷新页面后主题持久化
|
||
3. 后端新增字段在 API 响应中返回
|
||
4. SQLite 自动建表包含 `todo_items` 和 `troubleshooting_templates` 表
|
||
|
||
---
|
||
|
||
#### T02: 左栏改造 — 三段折叠 + 优先级图标 + 待办面板
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| **优先级** | P0 |
|
||
| **依赖** | T01 |
|
||
| **预估文件数** | ~7 |
|
||
| **关键变更点** | |
|
||
|
||
**前端文件**:
|
||
- `components/conversation/ConversationList.vue` — 重构为 3 段折叠 + 搜索标签
|
||
- `components/conversation/ConversationItem.vue` — 新增优先级图标渲染
|
||
- `components/conversation/TodoPanel.vue` — 新建待办事项面板
|
||
- `stores/todo.ts` — 新建待办 Pinia Store
|
||
- `api/todo.ts` — 新建待办 API
|
||
- `api/conversation.ts` — Conversation 接口新增 `impact_scope`/`is_blocking`/`emotion_state`
|
||
|
||
**后端文件**:
|
||
- `api/todo_items.py` — 新建待办 CRUD API(含 Mock 数据)
|
||
- `api/router.py` — 注册待办路由
|
||
|
||
**数据映射规则**(6 区 → 3 段):
|
||
|
||
| 原 6 区 | 新 3 段 | 映射逻辑 |
|
||
|---------|---------|---------|
|
||
| 待接单 (queued) | 📌 我的会话 | `is_mine || status === 'queued'` |
|
||
| 我的会话 (serving + is_mine) | 📌 我的会话 | `status === 'serving' && is_mine` |
|
||
| 协作会话 | 📌 我的会话 | `is_collaborator` |
|
||
| 其他坐席会话 | 👥 同事会话 | `!is_mine && status === 'serving'` |
|
||
| AI 处理区 | 👥 同事会话 | `status === 'ai_handling'` |
|
||
| 已结单 | 🕐 历史会话 | `status === 'resolved'` |
|
||
|
||
**验收标准**:
|
||
1. 三段折叠:📌我的默认展开,👥同事/🕐历史默认折叠
|
||
2. 优先级图标:⛔ 阻断性红底、👥 影响范围黄底、⭐ 角色等级紫底、🔁 重复问题橙底
|
||
3. 搜索标签:全部/待处理/进行中/已完成 药丸筛选
|
||
4. 待办面板 max-height: 220px,内部滚动
|
||
5. 点击待办条目 → `workspaceView = 'task'`
|
||
|
||
---
|
||
|
||
#### T03: 中栏改造 — 用户信息栏 + AI 推荐内联 + 排查步骤栏
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| **优先级** | P0 |
|
||
| **依赖** | T01 |
|
||
| **预估文件数** | ~8 |
|
||
| **关键变更点** | |
|
||
|
||
**前端文件**:
|
||
- `components/chat/ChatArea.vue` — 替换顶部栏为 `UserInfoBar`;底部挂载 `TroubleshootBar`;消息中插入 `AiRecommendInline`
|
||
- `components/chat/UserInfoBar.vue` — 新建(chips + 展开详情 6 卡片)
|
||
- `components/chat/ItLevelBadge.vue` — 新建(7 级段位徽标)
|
||
- `components/chat/AiRecommendInline.vue` — 新建(内联 AI 推荐)
|
||
- `components/chat/TroubleshootBar.vue` — 新建(路径视图 + 流程图)
|
||
- `components/chat/FlowchartNode.vue` — 新建(递归决策树节点)
|
||
- `composables/useKeyboardShortcuts.ts` — 新建(Ctrl+1/2/3 注册)
|
||
- `api/troubleshooting.ts` — 新建排查模板 API
|
||
|
||
**后端文件**:
|
||
- `api/troubleshooting_templates.py` — 新建排查模板 CRUD API(含 Mock 数据 5-8 套模板)
|
||
- `api/router.py` — 注册排查模板路由
|
||
- `api/employees.py` — 新增 `PUT /api/employees/{id}/it-level` 端点
|
||
|
||
**验收标准**:
|
||
1. UserInfoBar 常驻显示 chips(情绪+时长+轮次+重复+备注+IT等级)
|
||
2. 点击展开 6 卡片 3 列 grid,max-height 动画 0.35s
|
||
3. AI 推荐仅在坐席未回复时显示,Ctrl+1/2/3 快捷填入
|
||
4. TroubleshootBar 始终可见,默认路径方块;展开流程图含判断节点
|
||
5. 快捷键仅在输入框未聚焦时生效
|
||
|
||
---
|
||
|
||
#### T04: 右栏改造 + 任务详情视图
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| **优先级** | P0 |
|
||
| **依赖** | T01, T02 |
|
||
| **预估文件数** | ~5 |
|
||
| **关键变更点** | |
|
||
|
||
**前端文件**:
|
||
- `components/assistant/AiAssistantPanel.vue` — 完全重写(上下两区)
|
||
- `components/assistant/AiSuggestReply.vue` — 适配新布局 + 置信度显示
|
||
- `components/assistant/QuickReplyPanel.vue` — 重写(搜索置顶 + Alt 分类 + ↑↓ 导航 + Enter + 键盘指南)
|
||
- `components/chat/TaskDetailView.vue` — 新建(工单/审批/设备 3 种子视图)
|
||
|
||
**废弃文件**:
|
||
- `components/assistant/RiskAlert.vue` — 移除引用
|
||
- `components/assistant/UserInfoPanel.vue` — 移除引用
|
||
|
||
**验收标准**:
|
||
1. 右栏上方 ~1/3 AI 推荐区,下方 ~2/3 快速回复区
|
||
2. Alt+1~5 切换分类,↑↓ 导航条目,Enter 确认填入,/ 聚焦搜索
|
||
3. 底部常驻键盘指南条
|
||
4. TaskDetailView 支持 3 种子视图(工单/审批/设备),"← 返回会话" 切回聊天
|
||
5. RiskAlert.vue、UserInfoPanel.vue 不再被任何组件引用
|
||
|
||
---
|
||
|
||
#### T05: 集成联调 + 快捷键完善 + 样式打磨
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| **优先级** | P1 |
|
||
| **依赖** | T01, T02, T03, T04 |
|
||
| **预估文件数** | ~3 |
|
||
| **关键变更点** | |
|
||
|
||
**前端文件**:
|
||
- `composables/useKeyboardShortcuts.ts` — 完善全局快捷键注册/卸载(与 T03 初版合并后打磨)
|
||
- `styles/global.css` — 主题过渡动画完善 + 深色模式边缘 case 修复
|
||
- `views/Workspace.vue` — 最终集成调整(视图切换状态管理 + 双主题适配验证)
|
||
|
||
**验收标准**:
|
||
1. 主题切换过渡 ≤ 300ms,所有组件双主题无色值遗漏
|
||
2. 快捷键全局生效:Ctrl+1/2/3(AI 推荐)、Alt+1~5(快速回复分类)、↑↓ Enter /(快速回复操作)
|
||
3. 快捷键与输入框不冲突(输入框聚焦时快捷键不触发)
|
||
4. 视图切换(聊天↔任务)状态不丢失,WebSocket 保持连接
|
||
5. 王者徽标 `king-glow` 发光动画正常
|
||
|
||
---
|
||
|
||
### 5.3 任务依赖图
|
||
|
||
```mermaid
|
||
graph LR
|
||
T01[T01: 基础设施<br/>主题+模型+TopBar] --> T02[T02: 左栏改造<br/>三段折叠+待办面板]
|
||
T01 --> T03[T03: 中栏改造<br/>用户信息栏+AI推荐+排查]
|
||
T01 --> T04[T04: 右栏+任务视图<br/>AI助手重构+TaskDetail]
|
||
T02 --> T04
|
||
T01 --> T05[T05: 集成联调<br/>快捷键+样式打磨]
|
||
T02 --> T05
|
||
T03 --> T05
|
||
T04 --> T05
|
||
```
|
||
|
||
---
|
||
|
||
## 6. 共享知识
|
||
|
||
### 6.1 CSS 变量命名规范
|
||
|
||
```css
|
||
:root {
|
||
/* 背景 */
|
||
--bg-primary: #f5f7fa; /* 主背景 */
|
||
--bg-secondary: #ffffff; /* 次背景(卡片/面板) */
|
||
--bg-tertiary: #f0f2f5; /* 三级背景(段头/分区) */
|
||
--bg-hover: #e8eaed; /* 悬停背景 */
|
||
--bg-active: #d9dce0; /* 激活/选中背景 */
|
||
--bg-accent-soft: #ecf5ff; /* 强调色浅底 */
|
||
|
||
/* 文字 */
|
||
--text-primary: #303133; /* 主文字 */
|
||
--text-secondary: #606266; /* 次文字 */
|
||
--text-tertiary: #909399; /* 辅助文字 */
|
||
--text-placeholder: #c0c4cc;/* 占位文字 */
|
||
|
||
/* 边框 */
|
||
--border-color: #e4e7ed;
|
||
--border-light: #ebeef5;
|
||
|
||
/* 强调色 */
|
||
--accent: #409eff;
|
||
--accent-hover: #66b1ff;
|
||
--accent-soft: #ecf5ff;
|
||
|
||
/* 语义色 */
|
||
--color-success: #67c23a;
|
||
--color-warning: #e6a23c;
|
||
--color-danger: #f56c6c;
|
||
--color-info: #909399;
|
||
|
||
/* 深色主题覆盖 */
|
||
}
|
||
|
||
[data-theme="dark"] {
|
||
--bg-primary: #0f1923;
|
||
--bg-secondary: #151f2b;
|
||
--bg-tertiary: #1a2736;
|
||
--bg-hover: #1e3044;
|
||
--bg-active: #243b52;
|
||
--bg-accent-soft: rgba(77, 166, 255, 0.12);
|
||
|
||
--text-primary: #e8edf2;
|
||
--text-secondary: #8ba1b7;
|
||
--text-tertiary: #5c7a94;
|
||
--text-placeholder: #3d5568;
|
||
|
||
--border-color: #243b52;
|
||
--border-light: #1e3044;
|
||
|
||
--accent: #4da6ff;
|
||
--accent-hover: #73b9ff;
|
||
--accent-soft: rgba(77, 166, 255, 0.12);
|
||
|
||
--color-success: #52c41a;
|
||
--color-warning: #faad14;
|
||
--color-danger: #ff4d4f;
|
||
--color-info: #8ba1b7;
|
||
}
|
||
```
|
||
|
||
**使用规则**:
|
||
- 所有新增组件**必须**使用 CSS 变量,禁止硬编码色值
|
||
- 现有组件在修改时逐步替换硬编码为变量
|
||
- Element Plus 组件的主题覆盖通过 `:deep()` 选择器 + CSS 变量实现
|
||
|
||
### 6.2 组件通信模式
|
||
|
||
| 场景 | 模式 | 示例 |
|
||
|------|------|------|
|
||
| 跨组件快捷键填入 | `conversationStore.pendingReplyText` | AI推荐/快速回复 → ReplyBox |
|
||
| 视图切换 | `conversationStore.workspaceView` | TodoPanel → Workspace → ChatArea/TaskDetailView |
|
||
| 主题切换 | `useThemeStore.currentTheme` + `data-theme` 属性 | TopBar → 全局 DOM |
|
||
| 待办事项选中 | `useTodoStore.currentTodoItem` | TodoPanel → TaskDetailView |
|
||
| 排查模板选中 | `local ref` in TroubleshootBar | 不需跨组件,局部状态 |
|
||
|
||
### 6.3 Store 状态结构约定
|
||
|
||
```typescript
|
||
// ---- conversationStore 扩展 ----
|
||
{
|
||
// ... 现有状态 ...
|
||
workspaceView: 'chat' | 'task' // ★NEW 中间栏当前视图
|
||
}
|
||
|
||
// ---- themeStore ----
|
||
{
|
||
currentTheme: 'light' | 'dark'
|
||
toggleTheme(): void
|
||
}
|
||
|
||
// ---- todoStore ----
|
||
{
|
||
todoList: TodoItem[]
|
||
currentTodoItem: TodoItem | null
|
||
loading: boolean
|
||
fetchTodoList(): Promise<void>
|
||
selectTodoItem(item: TodoItem): void
|
||
updateTodoStatus(id: string, status: string): Promise<void>
|
||
}
|
||
```
|
||
|
||
### 6.4 API 响应格式约定
|
||
|
||
沿用现有 `{code, data, message}` 格式:
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"data": { ... },
|
||
"message": "success"
|
||
}
|
||
```
|
||
|
||
### 6.5 IT 等级枚举映射
|
||
|
||
```typescript
|
||
const IT_LEVELS = [
|
||
{ key: 'bronze', label: '青铜', level: 1, cssClass: 'bronze' },
|
||
{ key: 'silver', label: '白银', level: 2, cssClass: 'silver' },
|
||
{ key: 'gold', label: '黄金', level: 3, cssClass: 'gold' },
|
||
{ key: 'platinum', label: '铂金', level: 4, cssClass: 'platinum' },
|
||
{ key: 'diamond', label: '钻石', level: 5, cssClass: 'diamond' },
|
||
{ key: 'star', label: '星耀', level: 6, cssClass: 'star' },
|
||
{ key: 'king', label: '王者', level: 7, cssClass: 'king' },
|
||
] as const
|
||
```
|
||
|
||
### 6.6 优先级图标映射
|
||
|
||
```typescript
|
||
const PRIORITY_ICONS = [
|
||
{ key: 'is_blocking', icon: '⛔', cssClass: 'pi-blocked', bg: '#f56c6c' },
|
||
{ key: 'impact_scope', icon: '👥', cssClass: 'pi-impact', bg: '#e6a23c', highThreshold: 5 },
|
||
{ key: 'role_level', icon: '⭐', cssClass: 'pi-role', bg: '#9b59b6' },
|
||
{ key: 'is_repeat', icon: '🔁', cssClass: 'pi-repeat', bg: '#f59e0b' },
|
||
] as const
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 待明确事项
|
||
|
||
| # | 事项 | 影响范围 | 建议默认值 | 优先级 |
|
||
|---|------|---------|-----------|--------|
|
||
| 1 | **情绪状态识别方式**:AI 自动分析 vs 坐席手动标记? | BE-02, FE-04 | P0 先坐席手动标记,P1 加 AI 辅助 | P0 |
|
||
| 2 | **影响范围数据来源**:坐席标记 vs 系统自动判断? | BE-02, FE-02 | P0 坐席手动标记,`impact_scope` 默认 0 | P0 |
|
||
| 3 | **原 6 区→3 段映射**:协作会话归入"我的会话"还是"同事会话"? | FE-02 | 协作会话归入"📌我的会话"(因为坐席仍在参与) | P0 |
|
||
| 4 | **排查模板匹配方式**:自动匹配 vs 坐席手动选择? | BE-04, FE-06 | P0 坐席手动选择,P2 做自动匹配 | P0 |
|
||
| 5 | **同事会话范围**:全部坐席 vs 同组坐席? | FE-02 | P0 全部坐席(前端过滤可后续加) | P1 |
|
||
| 6 | **排查步骤栏最小/最大高度** | FE-06 | 最小 44px(路径方块),最大 300px(展开流程图) | P1 |
|
||
| 7 | **AI 推荐回复触发时机**:每条用户消息后都触发? | FE-05 | 仅坐席未回复时显示(已确认) | P0 |
|
||
| 8 | **IT 等级在用户端显示方式**:完整徽标 vs 简化文本? | FE-04 | 需与用户端产品确认,坐席端先用完整徽标 | P2 |
|
||
| 9 | **Element Plus 深色主题适配**:EP 组件(ElInput/ElTag 等)在深色模式下的样式覆盖策略 | 全局 | 通过 `:deep()` + CSS 变量覆盖 EP 的 `--el-*` 变量 | P1 |
|
||
| 10 | **排查模板 Mock 数据结构**:flowchart JSON 的具体递归结构定义 | BE-04 | 需定义标准节点结构(见 §3.2 FlowchartNode) | P0 |
|
||
|
||
---
|
||
|
||
## 附录 A:关键组件 Props/Emits 定义
|
||
|
||
### TopBar.vue
|
||
|
||
```typescript
|
||
// Props
|
||
interface TopBarProps {
|
||
agentName: string
|
||
agentStatus: 'online' | 'busy' | 'offline'
|
||
emergencyMode: boolean
|
||
}
|
||
|
||
// Emits
|
||
interface TopBarEmits {
|
||
toggleTheme: []
|
||
changeStatus: [status: string]
|
||
toggleEmergency: [enabled: boolean]
|
||
logout: []
|
||
}
|
||
```
|
||
|
||
### UserInfoBar.vue
|
||
|
||
```typescript
|
||
// Props
|
||
interface UserInfoBarProps {
|
||
employeeName: string
|
||
department: string
|
||
position: string
|
||
itLevel: string
|
||
emotionState: string
|
||
waitDuration: number // 秒
|
||
conversationCount: number
|
||
isRepeat: boolean
|
||
hasNotes: boolean
|
||
}
|
||
|
||
// Emits
|
||
interface UserInfoBarEmits {
|
||
adjustItLevel: [newLevel: string]
|
||
}
|
||
```
|
||
|
||
### TroubleshootBar.vue
|
||
|
||
```typescript
|
||
// Props
|
||
interface TroubleshootBarProps {
|
||
category?: 'vpn' | 'email' | 'system' | 'account'
|
||
}
|
||
|
||
// 无 Emits — 坐席交互仅影响组件内部状态
|
||
```
|
||
|
||
### TaskDetailView.vue
|
||
|
||
```typescript
|
||
// Props
|
||
interface TaskDetailViewProps {
|
||
todoItem: TodoItem
|
||
}
|
||
|
||
// Emits
|
||
interface TaskDetailViewEmits {
|
||
back: [] // 返回聊天视图
|
||
statusChanged: [id: string, newStatus: string]
|
||
}
|
||
```
|
||
|
||
### AiRecommendInline.vue
|
||
|
||
```typescript
|
||
// Props
|
||
interface AiRecommendInlineProps {
|
||
recommendations: Array<{
|
||
id: string
|
||
content: string
|
||
confidence: number
|
||
}>
|
||
visible: boolean // 仅坐席未回复时 true
|
||
}
|
||
|
||
// Emits
|
||
interface AiRecommendInlineEmits {
|
||
fillReply: [content: string]
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 附录 B:数据库迁移注意事项
|
||
|
||
### SQLite(本地开发)
|
||
|
||
SQLite 使用 `Base.metadata.create_all` 自动建表,新增模型只需在 `models/__init__.py` 中导入即可。新增字段需注意:
|
||
|
||
- SQLite 不支持 `ALTER TABLE ADD COLUMN` 添加带 `NOT NULL` 且无默认值的列
|
||
- 所有新增字段**必须**设置 `default` 值(已在模型定义中保证)
|
||
|
||
### PostgreSQL(生产)
|
||
|
||
使用 Alembic 迁移:
|
||
|
||
```bash
|
||
alembic revision --autogenerate -m "v53_add_it_level_impact_scope_todo_troubleshooting"
|
||
alembic upgrade head
|
||
```
|
||
|
||
新增表:`todo_items`、`troubleshooting_templates`
|
||
新增列:`employees.it_level`、`employees.it_level_source`、`employees.notes`、`conversations.impact_scope`、`conversations.is_blocking`、`conversations.emotion_state`
|
||
|
||
---
|
||
|
||
> **文档结束** — 本架构设计文档涵盖企微IT智能服务台第一步(消息接管+极简坐席)的完整技术方案,作为工程师编写代码的基准文档。
|