front/docs/在线客服模块设计方案.md
zzh abfca7a21a feat: add H5 customer center, marketing admin, and employee profile UX
Ship customer portal and marketing pages, user edit drawer with employee archive, IDE employee numbers, channel config refactor, and related nginx/route updates.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-26 23:44:02 +08:00

680 lines
27 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 在线客服模块设计方案
> **文档状态**:产品 + 架构 + 表结构 + 接口(**先评审,再开发**)
> **编写日期**:2026-07-26
> **关联页面**:H5「我的 / 联系客服」、AI 顾问(`/assistant`、`/h5/assistant`)、坐席工作台(新建)
> **关联能力**:专属顾问(`Customer.CustomerService` → `/customer/service-advisor`)、AI 对话(`/xhs/ai/chat`)、站内信(`/system/notice/*`)、微信注册绑定
> **关联文档**:`docs/H5客户中心设计方案.md`、`docs/前端页面设计规范.md`、`docs/rbac系统设计-060801.md`、`docs/数据权限设计.md`
> **目标读者**:产品、前端、后端、运营、测试
---
## 0 阅读指引
| 角色 | 优先阅读 |
| --- | --- |
| 产品 / 运营 | §1~§3、§5、§11 |
| 前端 | §4~§6、§8、§10 |
| 后端 / DBA | §4、§7~§9、§10 |
| 测试 | §5、§11、§12 |
**分期对照(务必先对齐)**
| 阶段 | 名称 | 是否改实时通道 | 交付物 |
| --- | --- | --- | --- |
| **P0** | AI + 微信兜底 | 否 | 入口打通、顾问二维码、复制单号 |
| **P1** | 站内轻量 IM(主推) | 是(SignalR) | 会话 / 消息 / 坐席工作台 / 转人工 |
| **P2** | 触达与渠道扩展 | 可选 | 离线通知、公众号模板、企微等 |
> 本文 **默认按 P0 → P1 落地**;P2 仅列边界,不进首版开发范围。
---
## 1 背景与目标
### 1.1 为什么要做
客户高频问题(多少钱、到哪了、出问题找谁)已在 H5 客户中心与 AI 顾问中部分覆盖,但「找人」仍停在:
| 现状 | 缺口 |
| --- | --- |
| AI 顾问 HTTP 一问一答(`/xhs/ai/chat`) | 无法转人工、无会话留痕给坐席 |
| `GET /customer/service-advisor` 展示微信二维码 | 系统外沟通,订单上下文靠客户口述 |
| H5「联系客服」规划为入口 | 尚未形成可运营的客服闭环 |
| 问题件站内信 / 邮件通知 | 非双向即时对话 |
物流售后场景需要:**带着运单上下文、可留痕、可考核、可回退到微信** 的客服能力,而不是官网浮窗式售前聊天。
### 1.2 建设目标
1. **三层服务**:AI 一线 → 站内人工 → 微信兜底,按能力成熟度渐进上线。
2. **上下文优先**:会话可挂 `OrderNo` / 订单状态摘要,减少「请发单号」。
3. **归属清晰**:优先路由到客户已绑定的 `CustomerService` 专属客服。
4. **双端一致**:客户侧(H5 / PC 顾问壳)与坐席侧(中后台)共用同一会话模型。
5. **可运营**:会话状态、未读、转接基础能力可统计;首版不做大屏 SLA。
### 1.3 设计原则
1. **不推翻现有顾问模型**:`Customer.CustomerService` + 顾问二维码继续作为兜底与身份来源。
2. **AI 与人工同壳**:复用 `AiAdvisorShell` / H5 AI Tab,增加「转人工」而非另起一套聊天 UI(客户侧)。
3. **实时用 SignalR**:与 .NET 8 栈一致;多实例用 Redis backplane(已有 FreeRedis)。
4. **身份隔离**:客户会话强制 `CustomerId` 隔离;坐席受 RBAC + 数据权限约束。
5. **先业务后推送**:P1 先保证在线双方实时;离线触达(站内信 / 公众号)可后置。
### 1.4 非目标(P0 / P1 不做)
- 语音 / 视频通话
- 全渠道聚合(企微、抖音、美洽等)作为主通道
- 机器人知识库编排、意图平台
- 坐席大屏、复杂 SLA / 质检评分体系
- 客户侧管理端 H5(坐席仅 PC 中后台)
- 改变订单状态机、计费、现有 AI Provider 配置模型
### 1.5 方案选型结论
| 方案 | 结论 |
| --- | --- |
| 仅 AI + 微信二维码 | **P0 必做**,成本最低 |
| 自研 SignalR 轻量 IM | **P1 主路径**,订单上下文与归属可深嵌 OMS |
| 第三方在线客服(美洽 / 环信等) | **不作为主路径**;仅适合官网匿名售前且不绑 OMS 时另议 |
---
## 2 角色与场景
### 2.1 角色
| 角色 | 登录形态 | 核心诉求 |
| --- | --- | --- |
| **客户**(含子客户) | `loginType=1` | 带着单号问人、尽快得到答复 |
| **专属客服** | `loginType=0`,对应 `Customer.CustomerService` | 只看自己客户、处理跟单问题 |
| **客服主管 / 值班** | `loginType=0` + 权限 | 认领公共队列、转接、查看范围更大(受数据权限) |
| **AI 顾问** | 系统机器人 | 一线解答;无法解决时引导转人工 |
### 2.2 高频场景(按紧迫度)
```text
1. 订单异常 / 理赔 → 要人立刻接
2. 查价 / 渠道选择搞不清 → 先问 AI,再转人工
3. 「货到哪了」→ AI / 自助轨迹优先;仍不懂再人工
4. 充值、账号问题 → 人工或微信
5. 坐席不在线 → 微信二维码 + 复制单号(绝不让客户卡死)
```
### 2.3 成功标准(客户感受)
| 场景 | 成功标准 |
| --- | --- |
| 联系客服 | 30 秒内看到「AI / 在线客服 / 微信」之一可用 |
| 转人工 | 单号与最近提问自动带入,无需重述 |
| 坐席侧 | 打开会话即见客户卡片 + 最近订单摘要 |
| 无人在线 | 明确提示并给出顾问微信,不出现空白等待 |
---
## 3 产品分期
### 3.1 P0 — AI + 微信兜底(约 1~2 周)
**目标**:H5 / AI 壳「找得到人」。
| 项 | 说明 |
| --- | --- |
| 入口 | H5「我的 → 联系客服」;订单异常「问顾问 / 联系客服」 |
| 能力 | 拉 `service-advisor`;展示姓名 / 微信号 / 二维码;一键复制单号 |
| 可选 | AI 页底部固定「联系专属客服」抽屉(复用现有 Advisor 抽屉) |
| 不做 | 会话表、SignalR、坐席工作台 |
### 3.2 P1 — 站内轻量 IM(主交付)
**目标**:客户 ↔ 坐席站内实时对话 + AI 转人工。
| 项 | 说明 |
| --- | --- |
| 客户侧 | 会话列表 + 聊天窗(嵌入 AI 壳旁或独立页);文本 + 图片 |
| 坐席侧 | 收件箱(待接入 / 进行中 / 已结束)+ 聊天 + 右侧客户/订单上下文 |
| 实时 | SignalR Hub;断线可 HTTP 补拉历史 |
| 分配 | 优先专属客服;离线进公共队列认领 |
| 兜底 | 无坐席在线 → 降级 P0 微信卡片 |
| 通知 | 未读角标;可选写站内信(`noticeType=message`) |
### 3.3 P2 — 触达与渠道(延后)
- 坐席离线时公众号 / 订阅消息提醒客户「有新回复」
- 客户离线时站内信 / 邮件提醒坐席(或反向)
- 企微 / 微信客服作为外部渠道,会话仍落本库
- 快捷回复库、会话评价、简易报表
- 转接多人、技能组、自动分配负载均衡
---
## 4 总体架构
### 4.1 逻辑架构
```text
┌─────────────┐ ┌──────────────┐ ┌────────────────────┐
│ H5 / PC 客户 │ │ 中后台坐席台 │ │ AI(现有 XhsAi) │
│ 聊天 + 入口 │ │ Inbox + 上下文│ │ HTTP /xhs/ai/chat │
└──────┬──────┘ └──────┬───────┘ └─────────┬──────────┘
│ REST + SignalR │ REST + SignalR │ 转人工时写入消息
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ SimpleAdmin.Api │
│ CsConversationService(HTTP) + CsChatHub(SignalR) │
└─────────────┬───────────────────────────────┬───────────────┘
│ │
▼ ▼
MySQL(会话/消息) Redis(在线状态 + SignalR backplane)
│
▼(可选)
RabbitMQ / Notice(离线提醒,P1 末或 P2)
```
### 4.2 与现有模块关系
| 现有能力 | 关系 |
| --- | --- |
| `Customer.CustomerService` | 默认 Assignee 解析源 |
| `/customer/service-advisor` | P0 展示;P1 无人在线时降级 |
| `AiAdvisorShell` / `/xhs/ai/chat` | 一线;「转人工」创建会话并可选灌入最近 AI 对话 |
| `OmsFileUploadHelper` | 聊天图片上传(新 folder:`cs`) |
| `NoticeService` | 可选:新消息站内信 |
| 数据权限 | 坐席列表/认领范围按客服数据权限过滤 |
| nginx | P1 必须补 WebSocket Upgrade |
### 4.3 技术选型
| 点 | 选型 | 说明 |
| --- | --- | --- |
| 实时 | **ASP.NET Core SignalR** | 官方、易鉴权、易水平扩展 |
| 前端 SDK | `@microsoft/signalr` | 勿复用 hiprint 的 `socket.io-client` |
| Backplane | **Redis** | 多 Api 实例必开 |
| 落库 | SqlSugar + MySQL | 与 OMS 一致 |
| 在线状态 | Redis KV | `cs:agent:{userId}`,心跳续期 |
| 附件 | 本地 `wwwroot/uploads/cs/{yyyyMM}/` | 图片 ≤ 5MB,与现规则一致 |
### 4.4 服务端部署注意
1. `AddSignalR().AddStackExchangeRedis(...)` 或与现有 FreeRedis 兼容的 backplane 方案(实现阶段选定一种,避免双 Redis 客户端混乱)。
2. nginx 为 Hub 增加独立 location(建议 `/hubs/`),配置:
```nginx
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
```
3. 前端 Docker / `nginx-docker.conf`、`nginx-default.conf` 同步修改;新坐席路由若与 API 前缀冲突,按规范 §十五做 SPA `try_files`。
---
## 5 核心流程
### 5.1 P0:联系专属客服
```text
客户点「联系客服」
→ GET /customer/service-advisor
→ 有微信:展示二维码 + 复制微信号
→ 无配置:提示「暂未分配专属客服,请联系管理员」
→ 若从订单进入:提供「复制单号」
```
### 5.2 P1:客户发起 / 继续会话
```text
客户进入客服页
→ 若有进行中会话:直接进入
→ 否则创建会话(可选带 orderNo、source、首条留言)
→ 分配:
1) 解析 Customer.CustomerService → 目标坐席
2) 坐席在线:状态=Open,推送给该坐席
3) 坐席离线:状态=Queued,进入公共队列
4) 无人可服务且队列过久(可配置,默认立即):展示微信降级卡片
→ 双方通过 Hub 收发;每条消息落库
```
### 5.3 AI → 转人工
```text
客户在 AI 顾问对话中点「转人工」
→ 创建(或复用进行中)会话
→ 将最近 N 条(建议 10)AI 对话写入 CsMessage
SenderType = Bot / Customer
→ 系统消息:「客户从 AI 顾问转入,关联运单:XXX」
→ 进入 §5.2 分配逻辑
→ UI 切换到人工会话视图(同一壳内 Tab 或抽屉)
```
### 5.4 坐席接单与结束
```text
坐席打开 Inbox
→ 我的进行中 + 公共待接入(有权限)
→ 认领 Queued 会话 → Assignee=自己,Status=Open
→ 回复文本/图片;可查看右侧订单摘要
→ 结束会话 → Status=Closed(客户可再开新会话)
→ 转接(P1 可做简化版):指定同事 → 改 Assignee + 系统消息
```
### 5.5 断线与补拉
```text
SignalR 断线 → 客户端自动重连
→ 重连成功后 HTTP 拉取 cursor 之后的消息(按 MessageId / CreateTime)
→ 不依赖「推送一定到达」作为唯一真相;数据库为真相源
```
---
## 6 页面、路由与权限
### 6.1 菜单与路由(坐席 / 管理端)
```text
客户服务(新建顶级或挂在「客户」下,评审定)
└── 在线客服 /customerService/inbox
```
前端:`src/router/routes/newModules/customerService.ts`
| path | name | component | permission |
| --- | --- | --- | --- |
| `inbox` | `CsInbox` | `views/customerService/inbox/index.vue` | `CustomerService.Inbox.Query` |
布局:左会话列表 + 中聊天区 + 右上下文(非表格主列表页;工具按钮仍遵循 32px / 主色变量)。列表类配置页(快捷回复等)P2 再按 `table-toolbar` 规范做。
### 6.2 客户侧入口
| 入口 | 路由 / 位置 | P0 | P1 |
| --- | --- | --- | --- |
| H5 我的 → 联系客服 | `/h5/me` 列表项 → 抽屉或 `/h5/cs` | 微信卡片 | 会话页 |
| H5 / PC AI 顾问 | `AiAdvisorShell` | 顾问微信抽屉 | 「转人工」 |
| 订单异常 | 订单卡片操作 | 复制单号 + 联系客服 | 带 `orderNo` 建会话 |
| PC 顶栏(可选) | 未读角标进 Inbox(仅坐席) | — | 有 |
建议客户会话页:
| path | 说明 |
| --- | --- |
| `/h5/cs` | H5 客服会话(P1) |
| `/assistant` 内嵌人工面板 | PC 与 AI 同壳切换(P1) |
### 6.3 RBAC 权限码
格式:`{Module}.{Resource}.{Action}`。
| 权限码 | 说明 |
| --- | --- |
| `CustomerService.Inbox` | 在线客服模块 |
| `CustomerService.Inbox.Query` | 查看收件箱 / 会话详情 / 历史消息 |
| `CustomerService.Inbox.Reply` | 发送消息、上传附件 |
| `CustomerService.Inbox.Claim` | 认领公共队列 |
| `CustomerService.Inbox.Close` | 结束会话 |
| `CustomerService.Inbox.Transfer` | 转接(P1 简化或 P1.1) |
| `CustomerService.Inbox.ViewAll` | 跨专属查看(主管;仍受数据权限) |
客户门户接口:**无上述 Permission**;仅 `loginType=1` + 当前 `CustomerId` 隔离。
### 6.4 后端 Service 与前缀
| 类 | 路由前缀 | 说明 |
| --- | --- | --- |
| `CsConversationService` | `/cs/conversation/*` | 会话 CRUD / 列表 / 认领 / 关闭 |
| `CsMessageService` | `/cs/message/*` | 历史分页、(可选)HTTP 发消息兜底 |
| `CsAgentService` | `/cs/agent/*` | 坐席心跳、在线状态 |
| `CsChatHub` | `/hubs/cs` | SignalR |
上传:`POST /cs/upload/image`(内部走 `OmsFileUploadHelper`,folder=`cs`)。
---
## 7 数据模型
> 表前缀建议 `t_cs_`;字段 PascalCase;审计字段对齐 OMS 公共规范(`Id`、`CreateTime`、`UpdateTime`、`IsDeleted` 等,以项目现有基类为准)。
### 7.1 `t_cs_conversation` 会话
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| Id | bigint PK | |
| CustomerId | int | 主客户 Id(子客户发起也归主客户,或记 SubUserId,见下) |
| SubUserId | int? | 子客户 RBAC 用户 Id(可空) |
| AssigneeUserId | int? | 当前坐席用户 Id |
| Status | varchar(20) | `Queued` / `Open` / `Closed` |
| Source | varchar(32) | `h5` / `assistant` / `order` / `ai_transfer` … |
| OrderNo | varchar(64)? | 关联运单号(展示用,不强绑订单表 FK) |
| Subject | varchar(200)? | 首条摘要 / 标题 |
| LastMessageAt | datetime? | 排序用 |
| LastMessagePreview | varchar(200)? | 列表预览 |
| CustomerUnread | int | 客户未读数 |
| AgentUnread | int | 坐席未读数 |
| ClosedAt | datetime? | |
| CloseReason | varchar(64)? | `agent` / `customer` / `system` |
| ExtJson | text? | 扩展(AI 会话 Id 等) |
索引建议:
- `(CustomerId, Status, LastMessageAt)`
- `(AssigneeUserId, Status, LastMessageAt)`
- `(Status, LastMessageAt)` — 公共队列
### 7.2 `t_cs_message` 消息
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| Id | bigint PK | |
| ConversationId | bigint | |
| SenderType | varchar(16) | `Customer` / `Agent` / `Bot` / `System` |
| SenderId | int? | 客户用户或坐席用户 Id |
| MsgType | varchar(16) | `text` / `image` / `system` |
| Content | text | 文本或图片 URL |
| PayloadJson | text? | 结构化扩展 |
| ClientMsgId | varchar(64)? | 客户端去重 |
| CreateTime | datetime | |
索引:`(ConversationId, Id)`、`(ConversationId, CreateTime)`;`ClientMsgId` 唯一(可按会话维度)。
### 7.3 坐席在线(可不建表)
Redis:
```text
cs:agent:online:{userId} = 1 TTL 60s(心跳 20s 续)
cs:agent:conn:{connectionId} → userId
```
### 7.4 快捷回复(P2)
`t_cs_quick_reply`:Title、Content、Sort、Enabled — 本期不建。
### 7.5 状态机
```text
创建
│
▼
Queued ──认领/专属在线自动接入──► Open ──结束──► Closed
│ │
└──────── 客户取消/超时 ────────┴──► Closed
```
规则:
- 同一 `CustomerId` **同时最多 1 个** `Queued|Open` 会话(再进线则复用)。
- `Closed` 后新开会话。
- 转接只改 `AssigneeUserId`,不改 `CustomerId`。
---
## 8 接口与 Hub 协议
### 8.1 HTTP — 客户侧(需客户登录)
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET | `/cs/conversation/my-active` | 当前进行中会话,无则 null |
| POST | `/cs/conversation/open` | 创建或返回进行中;body: `orderNo?`, `source`, `firstMessage?` |
| GET | `/cs/conversation/my-list` | 历史会话分页 |
| GET | `/cs/message/list` | `conversationId` + `beforeId?` + `pageSize` |
| POST | `/cs/message/send` | HTTP 兜底发消息(与 Hub 等价落库) |
| POST | `/cs/upload/image` | 上传图片 |
| POST | `/cs/conversation/close` | 客户结束(可选) |
`open` 响应需带:`conversation`、`assigneeOnline`、`fallbackAdvisor`(无人在线时附带原 `service-advisor` 结构)。
### 8.2 HTTP — 坐席侧(需内部登录 + Permission)
| 方法 | 路径 | 权限 |
| --- | --- | --- |
| GET | `/cs/conversation/inbox` | Query;query: `tab=mine\|queue\|closed` |
| GET | `/cs/conversation/detail` | Query;含客户摘要 |
| POST | `/cs/conversation/claim` | Claim |
| POST | `/cs/conversation/close` | Close |
| POST | `/cs/conversation/transfer` | Transfer |
| GET | `/cs/message/list` | Query |
| POST | `/cs/message/send` | Reply |
| POST | `/cs/upload/image` | Reply |
| POST | `/cs/agent/heartbeat` | Query(或连上 Hub 即心跳) |
| GET | `/cs/conversation/context` | Query;`customerId` + 最近订单摘要 |
**Inbox 数据范围**:
1. `mine`:`AssigneeUserId == 当前用户`
2. `queue`:`Status=Queued` 且当前用户数据权限覆盖该客户(或 `ViewAll`)
3. 无 `ViewAll` 时,默认还可看到 `Customer.CustomerService` 映射到自己的会话
### 8.3 上下文接口(右侧栏)
`GET /cs/conversation/context?customerId=&orderNo?` 返回:
```json
{
"customer": { "id": 0, "name": "", "code": "", "group": "", "balance": 0 },
"advisorKey": "",
"recentOrders": [
{ "orderNo": "", "status": "", "statusName": "", "updatedAt": "" }
],
"focusOrder": { "orderNo": "", "status": "", "trackSummary": "" }
}
```
复用现有订单查询能力,**只读摘要**,不在客服模块复制订单逻辑。
### 8.4 SignalR Hub `/hubs/cs`
**鉴权**:JWT(query `access_token` 或 Header,按现有前端封装统一)。
**客户端 → 服务端**
| 方法 | 参数 | 说明 |
| --- | --- | --- |
| `JoinConversation` | `conversationId` | 加入组 `cs:conv:{id}`;校验参与权 |
| `LeaveConversation` | `conversationId` | |
| `SendMessage` | `{ conversationId, msgType, content, clientMsgId }` | 落库并广播 |
| `Typing` | `conversationId` | 可选,P1.1 |
| `AgentHeartbeat` | — | 坐席续在线 |
**服务端 → 客户端**
| 事件 | 载荷 | 说明 |
| --- | --- | --- |
| `message` | 消息 DTO | 新消息 |
| `conversationUpdated` | 会话摘要 | 列表刷新 |
| `assigned` | 会话 Id + 坐席信息 | 客户侧提示已接入 |
| `closed` | 会话 Id | |
| `fallbackAdvisor` | 顾问 DTO | 无人在线降级 |
| `error` | `{ code, message }` | 业务错误 |
**组设计**:
- `cs:conv:{conversationId}` — 会话内消息
- `cs:agent:{userId}` — 定向推给坐席
- `cs:customer:{customerId}` — 定向推给该客户所有连接
- `cs:queue` — 有 `Claim` 权限的坐席收「新排队」事件(实现可用权限过滤代替大组广播)
### 8.5 消息发送统一语义
无论 HTTP 还是 Hub:
1. 校验会话状态为 `Open`(或 `Queued` 时允许客户继续留言)
2. `ClientMsgId` 去重
3. 写入 `t_cs_message`
4. 更新会话预览、未读、时间
5. 通过 Hub 推送;对方离线则累加未读,P1 可写一条站内信(开关配置)
---
## 9 分配与权限细节
### 9.1 专属客服解析
复用 `CustomerServiceAdvisorResolver` 思路:
1. 读 `t_customer.CustomerService`(工号 / 用户名 / 业务键,与现网一致)
2. 映射到 RBAC `UserId`
3. 若映射失败:会话进 `Queued`,不阻塞客户留言
### 9.2 在线判定
- 坐席页打开并成功连接 Hub 后开始心跳
- TTL 过期视为离线
- 创建会话时若专属坐席离线 → `Queued` + 返回 `fallbackAdvisor`
### 9.3 子客户
- 会话 `CustomerId` = 主客户 Id
- `SubUserId` = 发起人
- 主客户下其他子账号是否可见:P1 默认 **仅发起人 + 主账号** 可见(实现时用当前用户与主从关系判断);避免子账号互相窥私聊
### 9.4 数据权限
坐席 `queue` / `ViewAll` 必须走现有数据权限过滤器(`docs/数据权限设计.md`),禁止仅靠前端藏按钮。
---
## 10 前端交互要点
### 10.1 坐席 Inbox(PC)
```text
┌──────────────┬─────────────────────┬──────────────────┐
│ Tab:我的/ │ 聊天区 │ 客户卡片 │
│ 排队/已结束 │ 气泡 + 输入框 │ 最近订单 │
│ 会话列表 │ 系统消息/AI 记录 │ 聚焦运单摘要 │
│ 未读角标 │ 结束 / 转接 │ 打开订单详情链 │
└──────────────┴─────────────────────┴──────────────────┘
```
- 主色使用 `var(--app-primary-color)`
- 按钮高度 32px;危险操作(结束)二次确认
- 权限:`hasPermission('CustomerService.Inbox.Reply')` 等
### 10.2 客户侧(H5)
- P0:底部抽屉展示顾问二维码(与 AI 壳现抽屉一致)
- P1:独立 `/h5/cs` 全屏聊天气泡;顶栏显示接入状态(排队中 / 某某客服已接入 / 请添加微信)
- 与 AI Tab 关系:AI 页提供「转人工」;人工页提供「回 AI 顾问」
### 10.3 AI 同壳
- 保留现有 AI 消息流
- 转人工后:上方可折叠「AI 对话记录」,下方进入人工输入
- 或整页切换,但 **必须带上 order 上下文条**(与 `useAskAdvisor` 一致)
### 10.4 前端工程目录(建议)
```text
src/api/cs/
src/views/customerService/inbox/
src/views/h5/customer-center/cs/
src/components/CsChat/ # 气泡、输入框、连接状态(客户/坐席复用)
src/composables/useCsHub.ts
```
---
## 11 配置项与开关
| 配置键 | 默认 | 说明 |
| --- | --- | --- |
| `Cs:Enabled` | false | 总开关;false 时入口仅 P0 微信 |
| `Cs:SignalREnabled` | true | 可降级为纯 HTTP 轮询(应急) |
| `Cs:QueueFallbackSeconds` | 0 | 0=创建即若专属离线展示微信;>0 为等待秒数 |
| `Cs:AiTransferHistoryCount` | 10 | 转入时灌入 AI 消息条数 |
| `Cs:OfflineNoticeEnabled` | false | 新消息写站内信 |
| `Cs:MaxImageSizeMb` | 5 | 附件上限 |
配置可落 `t_system_other_config` 或 `appsettings`,实现阶段二选一,保持与现网配置风格一致。
---
## 12 验收标准
### 12.1 P0
- [ ] H5「联系客服」可展示专属顾问姓名 / 微信 / 二维码
- [ ] 从异常订单可复制单号
- [ ] 未分配顾问时有明确空态,不白屏
- [ ] AI 壳仍可打开顾问抽屉
### 12.2 P1
- [ ] 客户可创建会话、发文本/图片,刷新后历史仍在
- [ ] 专属客服在线时自动进入「我的」列表并实时收到消息
- [ ] 专属离线时进排队;有权限坐席可认领
- [ ] 无人可服务时返回微信降级卡片
- [ ] AI「转人工」带最近对话与 orderNo
- [ ] 坐席右侧能看到客户与最近订单摘要
- [ ] 结束会话后客户再进线为新会话
- [ ] 双实例部署下消息不丢(Redis backplane)
- [ ] nginx WebSocket 升级验证通过
- [ ] 权限码控制认领 / 回复 / 结束;客户无法调坐席接口
### 12.3 非功能
- [ ] 消息 `ClientMsgId` 重复提交不双写
- [ ] 客户只能读写本客户会话
- [ ] 图片上传超限有错误提示
- [ ] Hub 断线重连后消息可补齐
---
## 13 实施顺序(建议)
| 序号 | 任务 | 阶段 |
| --- | --- | --- |
| 1 | P0 入口与顾问卡片打通(H5 + 异常订单) | P0 |
| 2 | DDL:`t_cs_conversation` / `t_cs_message` + 仓储 | P1 |
| 3 | HTTP 会话/消息/上传 API + 权限码种子 | P1 |
| 4 | 坐席 Inbox 静态联调(可先轮询) | P1 |
| 5 | SignalR Hub + 前端 `useCsHub` + nginx | P1 |
| 6 | 分配逻辑 + 在线心跳 + 微信降级 | P1 |
| 7 | AI 转人工 + 上下文侧栏 | P1 |
| 8 | 未读角标 +(可选)站内信 | P1 |
| 9 | 转接 / 快捷回复 / 公众号触达 | P2 |
**DDL 脚本路径(落地时)**:`oms0414-back/sql/migrate_cs_module.sql`
**权限种子**:随 RBAC 菜单 SQL 或现有权限导入方式一并提交。
---
## 14 风险与对策
| 风险 | 对策 |
| --- | --- |
| 坐席不用系统、仍只回微信 | P0 微信永远保留;P1 强调「转人工带单号」提升系统内处理价值 |
| SignalR 经 nginx 失败 | 独立 `/hubs/` location + 验收清单;可临时 `SignalREnabled=false` HTTP 轮询 |
| 多实例串消息 | 强制 Redis backplane,禁止单机假设 |
| 客户把客服当即时 IM 闲聊 | 入口文案强调「物流咨询」;系统消息提示工作时间(可配置) |
| AI 灌入消息过长 | 限制条数与单条长度;仅转人工时写入 |
| `CustomerService` 键与 UserId 映射不稳定 | 收敛解析规则并在客户编辑页校验「可映射」 |
---
## 15 开放问题(评审需拍板)
1. **菜单挂载**:独立「客户服务」还是挂在现有「客户」菜单下?
2. **子客户会话可见性**:是否允许主账号查看所有子账号会话?
3. **工作时间**:是否首版就做「非工作时间仅微信」?
4. **P1 是否包含转接**:还是 P1 只做认领 + 结束?
5. **站内信提醒**:P1 默认开还是关?
6. **客户 PC 端**:是否必须与 H5 同步上线人工会话,还是 H5 优先?
---
## 16 附录:与 H5 设计方案的衔接
| H5 文档点 | 本文落点 |
| --- | --- |
| 我的 → 联系客服 | §6.2 / P0~P1 |
| 异常 → 问 AI / 联系客服 | §5.1、§5.3 |
| 复制单号给人工客服 | P0 保留;P1 升级为自动带 `orderNo` |
| AI 顾问为随身客服 | 仍为一线;人工为升级路径,不替换 AI Tab |
---
**文档维护**:评审结论请回写 §15;开发启动后将「文档状态」改为「开发中」,并补充实际 DDL 文件名与权限种子脚本路径。