# 企微智能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&timestamp=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支持服务台第一步（消息接管+极简坐席）的完整技术方案，作为工程师编写代码的基准文档。
