Files
wecom_it_smart_desk/docs/02-技术文档/技术架构/技术核查-坐席会话条目操作菜单-v1.0.md
T

550 lines
46 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
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.
# 技术核查 — 坐席端会话条目操作菜单(左栏三点菜单 / 右键菜单)
> **文档编号**: 技术核查-坐席会话条目操作菜单-v1.0
> **版本**: v1.0
> **状态**: ✅ 核查完成 — 供产品设计原型 v1.7 与后续开发决策使用
> **日期**: 2026-08-09
> **作者**: 高见远(架构师)
> **需求来源**: 宋献(IT 支持组组长)口述需求 4 条(见 §0)
> **关联**: PRD-REQ-坐席-011-统一工作队列重构-v0.1、REQ-坐席-004、REQ-坐席-009、REQ-坐席-010
> **核查范围**: 仅 `src/frontend-agent/`(活跃代码)与 `src/backend/`。根目录 `frontend-agent/` 为 2026-07-13 monorepo 重组前遗留副本,**线上无挂载、不在核查范围**
---
## 0. 需求原文与核查任务映射
| # | 需求原文 | 本文对应章节 |
|---|---|---|
| 1 | 确认现有坐席架构,控件能否在「选中对象-右键调出菜单」模式实现 | §2 全章 + §4 |
| 2 | 左栏每个会话表单右边增加菜单按钮(三个点),点击弹出接单/置顶/代办/转接 | §2.2、§2.3、§4 |
| 3 | 上述完成后,去除中栏上方 4 个按钮 | §5.1(**本文提出重大异议**) |
| 4 | 先基于最新原型(V1.6)更新细致原型图 | §1.4(**本文提出前置警告**) |
| — | 前置认知:「V1.0–V1.6 原型中 4 个按钮丢失了」 | §1.4 **结论:认知需修正** |
---
## 1. A. 现状核查(代码实证)
### 1.1 四个操作的完整实现链路
**结论:四个操作全部为真实闭环,非 Mock。**
| 操作 | UI 组件 | 事件处理 | Store Action | API 函数 | 后端端点 | 闭环 |
|---|---|---|---|---|---|---|
| **接单** | `UserInfoBar.vue:116-124` `<el-button @click="$emit('assign')">` | `ChatArea.vue:431-442` `handleAssign()` | `stores/conversation.ts:498-505` `assignConv()` | `api/conversation.ts:176-181` `assignConversation()` | `POST /conversations/{id}/assign`<br>`backend/app/api/conversations.py:265-271` | ✅ 真实 |
| **置顶** | `UserInfoBar.vue:127-133` | `ChatArea.vue:507-514` `handleTogglePin()` | `stores/conversation.ts:535-542` `togglePinConv()` | `api/conversation.ts:215-218` `togglePin()` | `POST /conversations/{id}/pin`<br>`conversations.py:373-378` | ✅ 真实 |
| **代办** | `UserInfoBar.vue:136-142` | `ChatArea.vue:519-526` `handleToggleTodo()` | `stores/conversation.ts:549-556` `toggleTodoConv()` | `api/conversation.ts:227-230` `toggleTodo()` | `POST /conversations/{id}/todo`<br>`conversations.py:466-471` | ✅ 真实 |
| **转接** | `UserInfoBar.vue:145-164` `<el-dropdown>` + 坐席列表 | `ChatArea.vue:531-547` `handleTransfer(targetAgentId)`(含 `ElMessageBox.confirm` 二次确认) | `stores/conversation.ts:564-571` `transferConv()` | `api/conversation.ts:239-244` `transferConversation()` | `POST /conversations/{id}/transfer`<br>`conversations.py:493-499` | ✅ 真实 |
**四个端点均带权限装饰器**
- `assign` / `transfer` / `grab``@require_permission("conversation", "update", "all")`
- `resolve` / `pin` / `todo``@require_permission("conversation", "update", "own")`
> ⚠️ **权限差异对菜单设计的直接影响**`pin` / `todo` 是 `own` 权限,意味着后端**只允许对自己名下的会话**置顶/代办。若三点菜单在「同事会话」「历史会话」条目上无差别地展示这两项,用户点击后将收到 403,属于必须在前端拦截的可见性约束(详见 §1.2 与 §4.4)。
**对比参照 — PRD-011 §3.1 提到的 Mock 问题现状**
`TaskDetailView.vue``handleAction()` **已被修复**,不再是 PRD-011 所记录的无条件 `ElMessage.success`
```js
// src/frontend-agent/src/components/chat/TaskDetailView.vue:127-133(当前实际代码)
function handleAction(action: string): void {
if (props.todoItem.type === 'approval') {
console.info('[TaskDetailView] 审批动作已跳转企微审批原系统:', action)
return
}
ElMessage.info('该操作需在原系统中完成')
}
```
**结论**:会话侧 4 个操作是**真闭环**,任务侧(审批/工单)是**降级跳转**。二者性质根本不同——这一差异是 §5 组件抽象设计的核心依据。**建议同步更新 PRD-011 §3.1 的行号与描述,该条 P0 阻塞项的表述已过期。**
---
### 1.2 中栏 UserInfoBar 按钮完整清单与可见性条件
`UserInfoBar.vue:114-194``.user-info-bar__actions` 区共 **6 个操作控件 + 1 个模式 chip**PM 设计菜单项状态时须逐条对齐:
| # | 按钮 | 代码位置 | 显示条件(`v-if` | 禁用条件(`:disabled`) | 文案动态切换 | 本次是否移除 |
|---|---|---|---|---|---|---|
| 1 | **接单** | `:116-124` | 常驻显示 | `status !== 'queued'` | `queued`→「接单」;其他→「已接单」 | ✅ 用户要求移除 |
| 2 | **置顶** | `:127-133` | 常驻显示 | 无 | `is_pinned`→「取消置顶」;否则→「📌 置顶」 | ✅ 用户要求移除 |
| 3 | **代办** | `:136-142` | 常驻显示 | 无 | `is_todo`→「取消代办」;否则→「📋 代办」 | ✅ 用户要求移除 |
| 4 | **转接** | `:145-164` | 常驻显示 | 无(但列表可为空,空时显示 disabled 项「暂无可用坐席」`:159-161`) | 固定「转接」,二级为在线坐席列表 `姓名 (当前负载/最大负载)` | ✅ 用户要求移除 |
| 5 | **摇人** | `:167-174` | `canInviteCollaborator`<br>= `status === 'serving' && (is_mine \|\| is_collaborator)`<br>`ChatArea.vue:325-329`) | 无 | 固定「🤝 摇人」 | ❌ **保留** |
| 6 | **结单 / 等待确认** | `:177-193` | `status === 'serving'` → 结单(danger<br>`status === 'pending_close'` → 「⏳ 等待员工确认」(warning, disabled | `pending_close` 态恒禁用 | 二态互斥 | ❌ **保留** |
| — | 历史会话开关 chip | `:100-110` | 常驻(在 chips 区,非 actions 区) | `historyLoading``is-loading` | `historyMode`→「🕐 返回当前」;否则→「🕐 历史会话」 | ❌ 保留 |
**其他相关状态源**
- `conversation.status` 五态:`ai_handling` / `queued` / `serving` / `pending_close` / `resolved``api/conversation.ts:46`
- `is_mine``is_collaborator``can_grab` 三个归属判别字段(`api/conversation.ts:69, 72, 79`
- 转接目标坐席列表来源:`agentStore.availableAgents`,由 `stores/agent.ts:226-243` `loadAvailableAgents()` 拉取 `getAgents('online')` 并**排除自己**;DEV 环境失败时降级到 mock 数据
**⚠️ 关键设计缺口**:现有 4 个按钮**几乎没有基于会话归属的可见性控制**——置顶/代办按钮在任何状态下都常驻且不禁用。这在中栏是可接受的(中栏只显示当前打开的会话,多为自己的),但**一旦下沉到左栏,左栏同时呈现「我的会话 / 同事会话 / 历史会话」三个分区(`ConversationList.vue:44-77`)**,菜单项必须新增归属维度的可见性规则。这是本次改造**真正新增的产品设计工作量**,而非单纯的控件搬家。
---
### 1.3 左栏 ConversationItem 现状:DOM 结构、右侧空间占用、事件绑定
#### 1.3.1 DOM 结构(`ConversationItem.vue:11-149`
```
.conversation-item (display:flex; align-items:center; gap:8px; padding:8px 10px; margin:1px 6px; position:relative)
│ ← global.css:473-484@click="$emit('click')" 绑在根节点(:21
├── .conv-avatar-wrap (48×48 头像 + .new-msg-dot 新消息圆点) :23-43
├── .conversation-info (flex:1; min-width:0; overflow:hidden) :46-138
│ ├── .conversation-name 第一行 :48-96
│ │ ├── 📌 置顶图标 (v-if is_pinned) :50
│ │ ├── 📋 代办图标 (v-if is_todo) :52
│ │ ├── 姓名 .text-ellipsis :54
│ │ ├── VIP / 招手 / 需介入 / 情绪 / 坐席名 / 待确认 共 6 类 tag-badge :56-82
│ │ └── .priority-icons (margin-left:auto) 最多 4 个 16×16 图标 :84-95
│ ├── .conversation-summary-row 第二行:摘要(flex:1) + 时间(flex-shrink:0) :99-102
│ └── .conversation-meta 第三行:5 星紧急度 + 「接手」/「退出」按钮 :105-137
└── .conv-target-avatar 右侧 处理对象缩略头像(v-if showTargetAvatar :141-148
```
#### 1.3.2 右侧空间占用盘点(**这是三点按钮方案最大的物理约束**)
左栏总宽 `--sidebar-width: 260px``global.css:98``min-width: 200px`,可拖拽调整 `global.css:270-271`)。
条目内部可用宽度 ≈ 260 − 6×2margin)− 10×2padding= **228px**
| 区域 | 宽度占用 | 可否让位 |
|---|---|---|
| 头像 `.conv-avatar-wrap` | 48px + gap 8px = **56px** | ❌ 不可 |
| `.conv-target-avatar`(右侧缩略头像) | 约 28-32px + gap 8px ≈ **36-40px**`section !== 'history'` 时**恒显示**`:316-321` | ⚠️ 可争议 |
| `.conversation-info` 剩余 | ≈ **132-136px** | — |
**结论:`.conversation-item` 的最右侧已被 `.conv-target-avatar` 占用**(除历史会话外恒显示)。三点按钮**没有现成的空槽位**,PM 必须在以下三条中做出明确取舍,不能回避:
- **方案 a**:三点按钮 hover 时**覆盖**absolute 定位)在 `.conv-target-avatar` 之上 —— 保留信息密度,但 hover 时缩略头像被遮挡;
- **方案 b**:三点按钮**替换** `.conv-target-avatar`(hover 时缩略头像淡出、三点淡入)—— 视觉最干净,推荐;
- **方案 c**:三点按钮插到 `.conv-target-avatar` 右侧 —— 再吃掉 ~24px`.conversation-info` 压到 ~108px,第一行的 6 类 tag-badge + 4 个优先级图标将严重挤压,**不推荐**。
> 补充约束:第三行 `.conversation-meta` 已存在「接手」`grab-btn``:116-125``v-if="showGrab && can_grab"`)与「退出」`leave-btn``:127-136``v-if="showLeave"`)两个 `margin-left:auto` 的 link 按钮。三点菜单里若也放「接手」,会与之重复,需在 §4.4 的菜单项矩阵中统一收口。
#### 1.3.3 hover 态与事件绑定现状
| 项 | 现状 | 代码位置 |
|---|---|---|
| hover 背景 | `.conversation-item:hover { background: var(--bg-hover) }` | `global.css:486-488` |
| active 态 | `.conversation-item.active { background: accent-soft; border-color: accent }` | `global.css:490-493` |
| **hover 显隐控件** | **当前完全没有**。所有子元素都是常显或 `v-if` 数据驱动 | — |
| 根节点点击 | `@click="$emit('click')"``ConversationList.vue:52``conversationStore.selectConversation(conv.id)` | `ConversationItem.vue:21` |
| 内部按钮防冒泡 | 已有先例:`@click.stop="$emit('grab')"` / `@click.stop="$emit('leave')"` | `:122, :133` |
| **右键事件** | **左栏当前无任何 `@contextmenu` 绑定** | 全项目仅 `MessageItem.vue:23` 一处 |
| **键盘可达性** | **零**。根节点是裸 `<div>`,无 `tabindex`、无 `role`、无 `@keydown`。列表不可 Tab 遍历、不可方向键导航 | `ConversationItem.vue:12-21` |
| 虚拟滚动 | **未启用**。全项目 grep `virtual` / `RecycleScroller` **零命中**`ConversationList.vue:44-77` 为三段 `v-for` 全量渲染 | — |
#### 1.3.4 🔴 关键约束:父容器 overflow 会裁剪菜单
```css
/* global.css:269-279 */
.workspace-sidebar { width: var(--sidebar-width); overflow: hidden; position: relative; }
/* global.css:356-360 */
.conversation-list-scroll { flex: 1; overflow-y: auto; overflow-x: hidden; }
```
**双重裁剪**:滚动容器 `overflow-x: hidden` + 侧栏 `overflow: hidden`
**推论(必须写入技术约束)**:任何以 `position: absolute` 挂在 `.conversation-item``position: relative`)内部的菜单,
1. **横向必被裁掉** —— 260px 窄栏内放不下一个 120-160px 的菜单还要向右展开;
2. **纵向在列表首尾会被滚动容器裁掉**
**菜单 DOM 必须脱离左栏容器**,只有两条合规路径:`position: fixed` + 视口坐标(`MessageItem.vue` 现有做法),或 Teleport 到 bodyElement Plus Popper 默认行为)。**这一条直接决定了 §4 的技术选型。**
---
### 1.4 「原型丢失 vs 代码丢失」——确定结论
**结论:4 个按钮在真实运行代码中从未丢失,只在原型 HTML 的 v1.4 及之后版本中不存在。用户的前置认知需要修正两处:一是丢失发生在原型侧不是代码侧;二是起始版本是 v1.4 不是 v1.5。**
#### 逐版本关键词计数实证
| 原型版本 | 行数 | 接单 | 置顶 | 代办 | 转接 | 结单 | 判定 |
|---|---|---|---|---|---|---|---|
| v1.0 | 2447 | 11 | 3 | 1 | 2 | 1 | ✅ 有(`:1559-1567` |
| v1.1 | 2448 | 11 | 3 | 1 | 2 | 1 | ✅ 有 |
| v1.2 | 2544 | 12 | 3 | 1 | 2 | 1 | ✅ 有 |
| v1.3 | 2707 | 12 | 3 | 1 | 2 | 1 | ✅ 有 |
| **v1.4**.archive | **961** | **0** | **0** | **0** | **0** | **0** | ❌ **首次消失** |
| v1.5 | 1058 | 0 | 0 | 0 | 0 | 0 | ❌ 无 |
| v1.6 | 1088 | 0 | 0 | 0 | 0 | 0 | ❌ 无 |
v1.0 原文(`原型-...-v1.0.html:1559-1567`):
```html
<!-- 2026-07-24 优化:接单按钮(用户信息栏右侧操作区) -->
<div style="display:flex;align-items:center;gap:6px;margin-left:auto;">
<button class="btn-action primary" ...>📥 接单</button>
<button class="btn-action warning" ...>📌 置顶</button>
<button class="btn-action default" ...>📋 代办</button>
<button class="btn-action default" ...>🔄 转接</button>
</div>
```
#### 根因:v1.4 起原型换了血统,不是同一条演进线
| 版本 | `<title>` | 性质 |
|---|---|---|
| v1.0v1.3 | (完整工作台原型) | **全量高保真原型**2400-2700 行 |
| v1.4 | `坐席工作台 v1.4(B方案分栏 + 排查步骤全屏流程图)` | **专题示意原型**961 行 |
| v1.5 | `v1.5(B方案分栏 + 全屏流程图 + 智能推荐竖排 + 工具栏重组)` | 专题示意原型 |
| v1.6 | `v1.6(B方案分栏 + 全屏流程图 + 智能推荐竖排 + 工具栏重组 + 工具栏图标统一)` | 专题示意原型 |
v1.4 是为论证「B 方案三栏分栏比例」和「排查步骤全屏流程图」而新画的**布局示意图**,不是 v1.3 的增量演进。证据:v1.6 通篇带 `col-badge` 标注(`260px 固定` `中栏 ≈ 60%` `右栏 ≈ 40%`),中栏 `.user-info-bar``:539-546`)只有头像+姓名+工号+在线状态**三个元素,无任何操作按钮区**;左栏 `.conv-item``:498-533`)只有 头像/姓名/时间/摘要/未读数 五个字段。
**对照真实代码的保真度落差**(这是给 PM 的核心警告):
| 元素 | 真实代码(`ConversationItem.vue` | v1.6 原型(`.conv-item` |
|---|---|---|
| 置顶/代办图标 | ✅ 📌📋 | ❌ 无 |
| tag-badge | ✅ VIP/招手/需介入/情绪/坐席名/待确认 共 6 类 | ❌ 无 |
| 优先级图标 | ✅ ⛔👥⭐🔁 最多 4 个 | ❌ 无 |
| 紧急度星级 | ✅ 5 星 | ❌ 无 |
| 接手/退出按钮 | ✅ 有 | ❌ 无 |
| 处理对象缩略头像 | ✅ 有 | ❌ 无 |
| 中栏操作按钮 | ✅ 6 个 | ❌ 0 个 |
> 🔴 **给 PM 的前置警告**:需求 4「基于 V1.6 更新细致原型图」若直接在 v1.6 上加三点菜单,产出的原型将**继续偏离真实实现两代**——按 v1.6 那个五字段的简版条目去设计菜单按钮位置,会得出「右侧有大片空槽」的错误结论,而真实条目右侧已被缩略头像占满(§1.3.2)。
>
> **建议**v1.7 的左栏必须**回填真实字段**(可参照 v1.3 的高保真度 + `ConversationItem.vue` 的当前实现),否则本次评审通过的原型无法作为开发依据。这是一个需要 PM 明确接受的额外工作量,不宜静默跳过。
---
## 2. B. 交互形态可行性评估
### 2.1 技术底座盘点
| 能力 | 现状 | 结论 |
|---|---|---|
| Element Plus 版本 | `element-plus@^2.7.0``package.json` | 2.x`el-dropdown` / `el-popover` 均可用 |
| **EP 有无原生右键菜单组件** | **没有**。Element Plus 2.x **不提供** ContextMenu 组件(对比 Ant Design 的 `Dropdown trigger=['contextMenu']`、Naive UI 的 `n-dropdown :x :y` | ⚠️ 右键方案需自行拼装 |
| `el-dropdown` 现有用法 | `UserInfoBar.vue:145`(转接)、`TopBar.vue:37`(在线状态) | ✅ 团队已熟练 |
| `el-popover` 现有用法 | `AiReplyModeSwitch.vue:24-82`(🤖 三态开关,含 `trigger="click"` `placement="bottom-start"` `:hide-after="0"` `popper-class` | ✅ **最佳复用模板** |
| 自研右键菜单 | `MessageItem.vue:23, 135-155, 326-336` 一处 | ⚠️ 实现较粗糙,见 §2.2 |
| `@vueuse/core` | `^14.0.0` | ✅ 有 `onClickOutside` / `useElementBounding`,可用于自研定位 |
| z-index 约定 | **无统一约定**。散落 `100/999/1000/1200/3000/9999/999999` | 🔴 风险,见 §6 R-3 |
### 2.2 精读 `MessageItem.vue` 现有右键实现(复用成本评估)
```js
// MessageItem.vue:326-336
function showContextMenu(event: MouseEvent): void {
contextMenuStyle.value = { left: `${event.clientX}px`, top: `${event.clientY}px` }
contextMenuVisible.value = true
}
function closeContextMenu(): void { contextMenuVisible.value = false }
```
```html
<!-- :23 --> <div class="message-row" @contextmenu.prevent="showContextMenu">
<!-- :135 --> <div v-if="contextMenuVisible" class="context-menu" :style="contextMenuStyle">
<!-- :155 --> <div v-if="contextMenuVisible" class="context-menu__overlay" @click="closeContextMenu"></div>
```
```css
/* :631-669 */
.context-menu { position: fixed; z-index: 1000; ... }
.context-menu__overlay { position: fixed; inset: 0; z-index: 999; }
```
**逐项评估**
| 技术点 | 现有实现 | 评估 |
|---|---|---|
| 定位算法 | `left/top = clientX/clientY``position: fixed` | ✅ **`fixed` 是正确选择**,天然规避 §1.3.4 的 overflow 裁剪。这是本实现最有价值的部分 |
| **边界处理** | **完全没有**。菜单在视口右缘/下缘会溢出屏幕外 | 🔴 **缺陷**。中栏消息区较宽、菜单项少(最多 3 项 ≈ 120px 高),问题不明显;**左栏条目密集,列表底部条目右键必然溢出视口下缘**,不修复不可用 |
| 关闭时机 | 全屏 overlay `@click` 关闭 | ⚠️ 仅覆盖鼠标左键点击。**未处理**:Esc 键、滚轮滚动、窗口 resize、在别处再次右键(会叠出第二个菜单)、路由/会话切换 |
| z-index | 菜单 1000 / overlay 999 | 🔴 **低于 Element Plus popper 默认起始值 2000**。EP 的 `el-dropdown`/`el-popover`/`el-message-box` 若同时在场会盖住自研菜单 |
| 无障碍 | 无 `role="menu"` / `role="menuitem"`,无焦点管理,Esc 不关闭,方向键不导航 | 🔴 不满足基本 a11y |
| 多实例互斥 | 无。每个 `MessageItem` 各持一份 `contextMenuVisible`,理论上可同时弹出多个 | ⚠️ 靠 overlay 侥幸规避 |
**复用成本量化**
- **直接复制**:0.2 人日,但把上述 5 项缺陷一并复制到左栏 —— **不建议**
- **提取 + 加固为通用 composable**`useContextMenu`fixed 定位 + 视口翻转 + Esc/scroll/resize 关闭 + 全局单例互斥 + z-index token):**1.0-1.5 人日**,同时可反向修复 `MessageItem.vue` 的现有缺陷;
- **改用 `el-popover` 虚拟触发**`virtual-triggering` + `virtual-ref`):EP 自带 Popper.js 翻转/贴边/Teleport/z-index 自动管理,**0.5 人日**,但需接受 EP popper 的默认动画与样式覆写成本。
### 2.3 三选项对比
#### 选项 1:纯右键上下文菜单(选中对象 → 右键调出)
| 维度 | 评估 |
|---|---|
| **可行性** | ✅ 技术上完全可行。`@contextmenu.prevent` 绑到 `.conversation-item` 根节点即可 |
| **改造量** | 中。1.0-1.5 人日(含 composable 加固) |
| **优势** | 零视觉侵入 —— 不占用 §1.3.2 中已经紧张的右侧 24px;对熟练坐席效率最高;天然适配未来 PRD-011 的长列表 |
| **风险** | 🔴 **发现性为零**。无任何视觉提示,新坐席不可能自己发现。IT 支持组虽是技术人群,但坐席工作台是**多人轮岗**场景,不能假设全员知晓<br>🔴 需覆写浏览器原生右键菜单,部分用户会感到被剥夺<br>⚠️ 触摸屏/远程桌面(VDI)环境下右键可能不可靠 —— 需确认坐席实际办公环境 |
| **适用性** | ❌ **不适合作为唯一入口**。作为快捷方式是加分项 |
#### 选项 2:三点按钮 + Popover(用户倾向方案)
| 维度 | 评估 |
|---|---|
| **可行性** | ✅ 可行,且有现成模板 `AiReplyModeSwitch.vue` |
| **改造量** | 中。`el-popover` + `MoreFilled` 图标(`@element-plus/icons-vue@^2.3.0` 已装)+ 菜单项可见性矩阵,**1.5-2.0 人日**(大头在菜单项状态矩阵与归属权限判定,不在控件本身) |
| **优势** | ✅ 发现性明确,零学习成本,与微信/钉钉/Slack 一致<br>✅ `el-popover` **默认 Teleport 到 body**,自动绕开 §1.3.4 的 overflow 裁剪<br>✅ Popper.js 自动处理 `placement` 翻转与贴边,260px 窄栏用 `placement="bottom-end"` 即可稳定<br>✅ z-index 由 EP 统一管理(2000+ 自增),不会与 `MessageItem` 的 1000 打架 |
| **风险** | ⚠️ **占位冲突**(§1.3.2)—— 必须与 `.conv-target-avatar` 二选一或叠放,PM 须明确<br>⚠️ **冒泡冲突** —— 三点按钮必须 `@click.stop`,否则点开菜单会连带切换会话。项目内已有正确先例(`ConversationItem.vue:122/133``@click.stop`),风险可控<br>⚠️ hover 显隐会引入「鼠标移出条目→按钮消失→菜单是否跟着关」的边界判定,需明确规则(建议:菜单打开期间按钮**强制常显**并给条目加 `.is-menu-open` 态)<br>⚠️ 每个条目挂一个 `el-popover` 实例,50+ 条目时有轻微渲染开销(EP popover 内容 lazy 渲染,实测可接受;若上虚拟滚动则必须改为单例菜单) |
| **适用性** | ✅ **适合作为主入口** |
#### 选项 3:三点按钮为主 + 右键为快捷方式(并存)
| 维度 | 评估 |
|---|---|
| **可行性** | ✅ 可行,**且是成本最低的并存实现** —— 前提是菜单内容抽成一个独立组件,两个触发源共用同一份菜单项定义与 handler |
| **改造量** | 选项 2 基础上 **+0.3-0.5 人日**(仅新增 `@contextmenu.prevent` 触发 + `el-popover``virtual-triggering` 坐标注入) |
| **优势** | ✅ 覆盖新手(三点可见)与熟手(右键高效)两类用户<br>✅ 与 IM 类产品心智完全一致(微信 PC 会话列表:右键有菜单;企微:右键有菜单)<br>✅ **对宋献本人的原始诉求(需求 1 问右键、需求 2 要三点)是唯一的「全都要」解**,且不是妥协——二者本就是同一菜单的两个触发器 |
| **风险** | ⚠️ 需保证两个入口的菜单项与状态**永远一致**(用同一 `computed` 生成菜单项数组即可根治)<br>⚠️ 互斥:右键打开 A 条目菜单时若又点 B 条目三点,须先关 A(EP popover 需手动做单例控制,或用全局 `activeMenuId` |
| **适用性** | ✅ **推荐** |
### 2.4 必答技术点逐条结论
| # | 技术点 | 结论 |
|---|---|---|
| 1 | **Element Plus 是否有可复用组件** | 部分有。`el-popover`(✅ 推荐,模板见 `AiReplyModeSwitch.vue:24-82`)、`el-dropdown`(可用但 `trigger` 不支持 contextmenu,且默认渲染一个触发容器,用于条目内略重)。**EP 2.x 无原生 ContextMenu 组件**,右键需用 `el-popover``virtual-triggering` + `virtual-ref` 拼装,或自研 |
| 2 | **复用 MessageItem 右键实现的成本** | 直接复制 0.2 人日但继承 5 项缺陷(无边界翻转 / z-index 1000 偏低 / 关闭时机不全 / 无 a11y / 无单例互斥);提取加固为通用 composable 1.0-1.5 人日;**改用 el-popover 虚拟触发 0.5 人日且缺陷全免 —— 推荐后者** |
| 3 | **260px 窄栏下的弹出方向与溢出裁剪** | 🔴 **必须 Teleport 或 fixed**`.workspace-sidebar{overflow:hidden}``global.css:277`+ `.conversation-list-scroll{overflow-x:hidden}``global.css:359`)双重裁剪,absolute 定位的菜单**必被裁**。用 `el-popover` 默认 teleport 到 body 即可根除。弹出方向建议 `placement="bottom-end"`(右对齐向下),Popper 在视口下缘不足时自动翻转为 `top-end`。菜单宽度建议 132-148px(4 项中文两字 + 图标),不要超过侧栏宽度 |
| 4 | **三点按钮与条目点击的冒泡冲突** | ⚠️ 真实存在但**已有解**。`@click="$emit('click')"` 绑在根节点(`ConversationItem.vue:21`),三点按钮须 `@click.stop`;项目内 `grab-btn`/`leave-btn``:122/:133`)已是此写法。**额外注意**`el-popover` 的 popper 内容 teleport 到 body 后**不在条目 DOM 子树内,不会冒泡到条目**——菜单项点击天然安全;但 `#reference` 插槽内的按钮仍在子树内,必须 `.stop`。右键同理需 `@contextmenu.prevent.stop` |
| 5 | **虚拟滚动(PRD-011 U-5)下菜单跟随** | 当前**未启用虚拟滚动**(全项目零命中)。但 PRD-011 U-5 已挂起该项。🔴 **前瞻性约束**:虚拟滚动会在滚动时**回收并复用条目 DOM**,若菜单实例挂在条目内部,滚动时 reference 元素被销毁 → 菜单错位或残留。**唯一稳妥的架构是「单例菜单」**:整个左栏只渲染一个菜单组件(挂在 `ConversationList` 层级或全局),条目只负责 `emit('open-menu', { item, x, y })`。**建议本次就按单例架构实现**,成本相同,可免除未来上虚拟滚动时的返工 |
| 6 | **键盘可达性** | 🔴 现状为零:`.conversation-item` 是裸 `<div>`,无 `tabindex`/`role`/`keydown``ConversationItem.vue:12-21`)。本次改造**不会让它变差,但也不会自动变好**。最低要求:三点按钮用原生 `<button>``el-button`(可 Tab 聚焦、Enter/Space 激活);菜单容器加 `role="menu"`,项加 `role="menuitem"`,支持 ↑↓ 导航与 Esc 关闭 —— `el-popover` 不自带这套,需手写约 0.3 人日。**建议列为 P2 单独排期,不阻塞本次**。项目已有 `useKeyboardShortcuts.ts`(含 Escape/Tab/方向键处理框架,`:110/:124-125/:151`),可挂接 |
---
## 3. C. 与 PRD-011 的兼容性设计
### 3.1 耦合点识别
PRD-011 Phase 2 计划:「定义统一 `ListItem` 类型(含 `kind` 判别式),条目组件支持会话态与任务态两种渲染」(PRD-011 §6 Phase 2)。
本次要在左栏会话条目上加操作菜单 —— **这正是那个「条目组件」**。若本次把菜单硬编码进 `ConversationItem.vue`,Phase 2 落地时必然重写。**这是三个月后二次返工的确切来源。**
### 3.2 kind 分派设计
```mermaid
graph TD
A["ListItem (PRD-011 统一类型)"] --> B{"kind 判别式"}
B -->|conversation| C["会话菜单<br/>接单 / 置顶 / 代办 / 转接<br/>+ 接手 / 退出协作)"]
B -->|approval| D["审批菜单<br/>在企微审批中打开<br/>U-1 已定:降级跳转)"]
B -->|ticket| E["工单菜单<br/>在 ITSM 中打开<br/>U-1.2 阻塞:读链路即断)"]
C --> F["WorkItemActionMenu<br/>(统一渲染 + 定位 + 关闭 + a11y"]
D --> F
E --> F
F --> G["emit('action', { kind, id, action, payload })"]
G --> H["ConversationList / WorkQueueList<br/>按 kind 路由到对应 store action"]
```
**三类菜单的本质差异(必须在架构上体现,不能靠 if-else 混写)**
| kind | 操作性质 | 执行路径 | 反馈模型 | 依据 |
|---|---|---|---|---|
| `conversation` | **服务端真闭环** | store → API → 后端端点 → `fetchConversations()` 刷新 | 同步成功/失败 | §1.1 实证 |
| `approval` | **降级跳转** | `<a target="_blank">` 跳企微 → `sys_approval_change` 回调回写 → WS 推送 | 异步最终一致 | PRD-011 §6 Phase 0、U-1 |
| `ticket` | **当前无对象** | `ITSMService.get_todo_list()` 无条件 `return []``itsm_service.py:113-130` | 无 | PRD-011 U-1.2 |
> 🔴 **对宋献的直接回答**:「未来待办条目进入左栏后,同一个三点菜单是否要按 kind 分派出不同菜单项?」
> **必须分派,而且不只是菜单项文案不同 —— 是执行语义与反馈模型的根本不同。**
> 会话操作点完即生效;审批操作点完只是跳走,真正的状态变更在企微侧异步回写。**若共用一套「点击 → toast 成功」的反馈模型,就会重演 PRD-011 §3.1 定性为 P0 的那个错误**(绿色成功提示 + 外部系统实际未变更)。菜单项定义必须携带 `execMode: 'direct' | 'redirect' | 'disabled'` 字段,由容器按 mode 分派反馈策略。
### 3.3 建议的组件抽象边界
**结论:应该抽 `WorkItemActionMenu`,但要抽在正确的层次上——抽「菜单壳」,不抽「业务动作」。**
```
components/common/WorkItemActionMenu.vue ← 【本次新建】纯 UI 壳,零业务逻辑
职责:单例渲染 / fixed 或 teleport 定位 / 视口翻转 / Esc·scroll·resize·outside 关闭
/ role=menu + ↑↓ 键盘导航 / z-index token / 分组与分隔线 / danger 项样式
Props: items: MenuItem[] visible: boolean anchor: {x,y} | HTMLElement
Emits: select(action: string) close()
MenuItem: { key, label, icon?, danger?, disabled?, hidden?,
execMode: 'direct'|'redirect'|'disabled', tooltip? }
⛔ 不认识 conversation / approval / ticket,不 import 任何 store 与 api
composables/useConversationMenuItems.ts ← 【本次新建】会话菜单项工厂
入参:conversation + currentAgentId + section('my'|'colleague'|'history')
出参:MenuItem[](已按 §4.4 矩阵算好 hidden/disabled/label
✅ 承载全部可见性规则,可单测,PRD-011 Phase 2 时原样复用
composables/useTodoMenuItems.ts ← 【PRD-011 Phase 2 新建】任务菜单项工厂
components/conversation/ConversationList.vue ← 【本次改造】持有单例菜单实例 + 路由 action 到 store
components/conversation/ConversationItem.vue ← 【本次改造】仅新增三点按钮与 @contextmenu
只 emit('open-menu', {id, x, y}),不含菜单 DOM
```
**为什么这样切**
1. **单例菜单挂在 List 层** → 直接解决 §2.4-5 的虚拟滚动难题,且 Phase 2 换成 `WorkQueueList` 时菜单壳零改动;
2. **菜单项工厂按 kind 分文件** → 完全对齐 PRD-011 的 `kind` 判别式,新增 kind 只加一个 composable,不动壳;
3. **壳不认识业务** → 中栏 `MessageItem.vue` 的右键菜单**也可以迁过来复用**,顺带修掉 §2.2 列出的 5 项缺陷(可作为 P2 收益)。
**与 PRD-011 Phase 2 的衔接检查表**
| PRD-011 Phase 2 任务 | 本次设计是否兼容 |
|---|---|
| 定义统一 `ListItem`(含 `kind` | ✅ `MenuItem[]` 由 kind 专属工厂生成,`WorkItemActionMenu` 不感知 kind |
| 条目组件支持会话态/任务态两种渲染 | ✅ 条目只 `emit('open-menu')`,两态共用同一菜单壳 |
| 左栏 Tab 分层(会话/待办/全部) | ✅ 「全部」Tab 混排时,容器按 `item.kind` 选工厂即可 |
| U-5 虚拟滚动 | ✅ 单例架构天然兼容 |
| U-6 待办未读/变更标识 | ➖ 与菜单无关 |
---
## 4. D. 明确推荐
### 4.1 推荐方案
> **推荐选项 3:三点按钮为主入口 + 右键为快捷方式,菜单壳单例化并抽象为 `WorkItemActionMenu`。**
**我同意宋献的方案 2 方向(三点按钮),但认为不应止步于此,理由如下。**
1. **需求 1 与需求 2 不是二选一,而是同一件事的两个触发器。** 宋献先问「右键能不能做」,再提「加三个点」——从提问顺序看,右键是他真正想要的效率形态,三点是他为了保险给出的可实现形态。二者共用同一份菜单定义时,**并存的边际成本只有 0.3-0.5 人日**(见 §2.3 选项 3)。为省这半天而只做一个,是错误的取舍。
2. **纯右键(选项 1)不可单独采用。** 左栏会话列表是坐席**每天使用频率最高**的区域,且服务台存在轮岗与新人。零发现性的交互在这个位置是产品事故,不是效率优化。
3. **单例菜单架构不是过度设计,是防返工的最小投入。** PRD-011 U-5 已把虚拟滚动挂起、Phase 2 已明确要做统一容器。若本次把 `el-popover` 逐条挂在 `ConversationItem` 内,虚拟滚动落地时条目 DOM 回收会直接导致菜单错位——**那时的重写成本 ≥ 现在多花的 0.5 人日**。这正是宋献要求「避免三个月后第二次重做」的具体着力点。
**推荐实施顺序**
| 阶段 | 内容 | 估时 |
|---|---|---|
| S1 | `WorkItemActionMenu.vue`(单例壳,fixed + 翻转 + 关闭时机 + z-index token | 1.0 人日 |
| S2 | `useConversationMenuItems.ts`(§4.4 可见性矩阵)+ 单测 | 0.5 人日 |
| S3 | `ConversationItem` 三点按钮(hover 显隐 + `@click.stop`+ `@contextmenu.prevent.stop` | 0.5 人日 |
| S4 | `ConversationList` 单例接线 + action 路由到 store | 0.5 人日 |
| S5 | 中栏按钮调整(**见 §5.1,不建议全删**) | 0.3 人日 |
| S6 | 键盘导航与 a11yrole/↑↓/Esc | 0.3 人日(可 P2 |
| — | **合计** | **2.8-3.1 人日** |
### 4.2 与用户倾向的差异点(明确说明,不附和)
| 项 | 宋献倾向 | 我的建议 | 论据 |
|---|---|---|---|
| 交互形态 | 三点按钮(方案 2) | 三点按钮 **+ 右键并存** | 并存边际成本仅 0.3-0.5 人日;需求 1 本就在问右键 |
| 组件位置 | (未明确) | 菜单**单例挂 List 层**,不挂条目内 | PRD-011 U-5 虚拟滚动 + Phase 2 统一容器 |
| 中栏按钮 | **全删 4 个** | **仅删 3 个,转接保留**(详见 §5.1) | 操作路径退化,见 §5.1 论证 |
| 原型基线 | 基于 v1.6 | v1.6 **需先回填左栏真实字段**再改 | v1.6 是专题示意图,左栏保真度落后真实代码两代(§1.4) |
### 4.3 三点按钮的落位建议(供 PM 直接采用)
- **位置**`.conversation-item` 最右侧,**替换** `.conv-target-avatar`hover 时缩略头像 `opacity:0` 淡出、三点 `opacity:1` 淡入,同一栅格位,不产生布局抖动)——即 §1.3.2 的方案 b;
- **尺寸**:24×24px 命中区,图标 `MoreFilled``@element-plus/icons-vue@^2.3.0` 已装)14-16px
- **显隐**:默认隐藏,`.conversation-item:hover``.is-menu-open``:focus-visible` 时显示(保证键盘可达);
- **菜单**`placement="bottom-end"`,宽 140px,视口下缘不足时自动翻转 `top-end`
- **历史会话分区**`section === 'history'` 时本无缩略头像(`ConversationItem.vue:318`),三点按钮直接占该位。
### 4.4 菜单项可见性矩阵(PM 设计原型的直接输入)
按会话 `status` × 归属(`is_mine` / `is_collaborator` / `can_grab`)× 所属分区(`section`)分派。**这是 §1.2 指出的「真正新增的产品设计工作量」的收口。**
| 菜单项 | 显示条件 | 禁用条件 | 动态文案 | 后端权限 |
|---|---|---|---|---|
| **接单** | `status === 'queued'` | — | 「接单」 | `update:all` |
| **置顶 / 取消置顶** | `is_mine \|\| is_collaborator` | — | `is_pinned` → 「取消置顶」 | `update:own` ⚠️ 非本人会话会 403,**必须靠显示条件拦住** |
| **代办 / 取消代办** | `is_mine \|\| is_collaborator` | — | `is_todo` → 「取消代办」 | `update:own` ⚠️ 同上 |
| **转接 ▸** | `status === 'serving' && is_mine` | `availableAgents.length === 0` → 禁用并提示「暂无可用坐席」 | 「转接」,二级为在线坐席 `姓名 (负载/上限)` | `update:all` |
| **接手** | `can_grab && !is_mine && status === 'serving'` | — | 「接手」 | `update:all` |
| **退出协作** | `is_collaborator && status === 'serving'` | — | 「退出协作」(danger | — |
**待 PM 决策的三个问题**
1. **转接的二级菜单**:三点菜单里做二级展开(`el-popover` 嵌套,260px 窄栏下体验待验证),还是点「转接」后弹独立 Dialog 选坐席?**架构建议后者**——窄栏内嵌套二级菜单在视口边缘的翻转组合过于脆弱,且坐席列表可能较长需要滚动与搜索。
2. **接手/退出协作**是否收进三点菜单?收进则第三行的 `grab-btn`/`leave-btn``ConversationItem.vue:116-136`)应同步移除,避免同一动作两个入口;不收则菜单只有 4 项、更聚焦。**架构倾向:本次不动,保持第三行按钮**,减少变更面。
3. **结单**是否也进左栏菜单?用户未提,但它是与置顶/代办同等级的会话动作。**架构建议不进**——结单需走摘要确认 Dialog`ChatArea.vue:448-502`),在左栏触发一个模态流程属于路径错位。
---
## 5. E. 风险与待明确事项
### 5.1 🔴 核心风险:移除中栏按钮导致的操作路径退化(正面回答)
**判断:会构成退化,且退化程度按操作分为两档。必须区别对待,不建议 4 个一刀切全删。**
#### 场景推演
坐席正在中栏与员工对话,需要执行某操作:
| 操作 | 当前路径 | 全删后路径 | 退化判定 |
|---|---|---|---|
| **接单** | 中栏点「接单」 | 左栏找条目 → hover → 点三点 → 点接单 | ⚪ **不退化**。接单发生在**打开会话之前/之时**,左栏本就是接单的自然场所(Zendesk/ServiceNow 皆如此)。且中栏按钮在 `status !== 'queued'` 时恒为 disabled 的「已接单」,长期占位却不可点,**删除反而是净收益** |
| **置顶** | 中栏点「置顶」 | 左栏找条目 → 三点 → 置顶 | 🟡 **轻微退化,可接受**。置顶是**列表组织行为**,其效果(条目上浮)也只在左栏可见。操作与反馈同处一栏,语义上反而更内聚 |
| **代办** | 中栏点「代办」 | 左栏找条目 → 三点 → 代办 | 🟡 同置顶 |
| **转接** | 中栏点「转接」→ 下拉选坐席 → 确认 | 左栏找条目 → hover → 三点 → 转接 → 选坐席 → 确认 | 🔴 **显著退化** |
#### 为什么转接是显著退化
1. **触发时机不同**。置顶/代办是**列表管理动作**,坐席在扫视队列时执行;转接是**对话过程动作**——坐席读完员工描述、判断"这不归我管"的那一刻,注意力和鼠标都在中栏。此时被要求"回左栏,找到当前这条,hover 出三点,点开菜单,选转接",是**注意力焦点的强制迁移**。
2. **交互步数从 3 步涨到 6 步**
- 现状:`点转接 → 选坐席 → 确认`
- 全删后:`视线移左栏 → 定位当前条目 → hover → 点三点 → 点转接 → 选坐席 → 确认`
- 且第 2 步「定位当前条目」本身有成本——左栏三分区、可滚动、条目会因置顶/紧急度**动态重排**(`ConversationList.vue:44-77`),当前会话未必在视口内。
3. **中栏本就要留按钮,删了转接反而制造不一致**。用户明确只删 4 个,**「摇人」(`UserInfoBar.vue:167-174`)与「结单」(`:177-193`)保留在中栏**。结果是:中栏留着"摇人/结单",转接却要去左栏——**同为对话过程中的协作类动作,入口被劈成两处**。这比"全部在中栏"或"全部在左栏"都差。
#### 缓解建议(按优先级)
| 优先级 | 建议 | 说明 |
|---|---|---|
| **P0** | **中栏保留「转接」,删除「接单/置顶/代办」3 个** | 与「摇人/结单」构成语义一致的"对话过程动作组"(转接/摇人/结单 = 把这件事交出去/拉人进来/做完了)。左栏三点菜单**同时也提供转接**,两个入口共用同一 handler,符合"高频动作允许多入口"原则 |
| P1 | 若宋献坚持 4 个全删 → **必须补快捷键** | 项目已有 `useKeyboardShortcuts.ts` 框架(含 `ctrl+shift+a` 等组合的注册范式,`:141-151`)。为转接注册全局快捷键(如 `Ctrl+Shift+T`)作用于当前会话,可把 6 步压回 2 步 |
| P1 | 若 4 个全删 → **中栏 UserInfoBar 收一个「⋯」总菜单** | 把接单/置顶/代办/转接收进中栏自己的三点菜单。视觉上达成用户"去掉 4 个按钮"的诉求(栏内只剩摇人/结单/⋯),功能上零退化。**这可能是最贴近宋献真实意图的解**——他要的多半是"顶栏太挤、按钮太多",而不是"这些功能不该在中栏" |
| P2 | 灰度与回滚 | 中栏按钮删除做成配置开关,上线后收集坐席反馈,2 周内可回滚 |
> **给宋献的一句话建议**:如果去掉中栏 4 个按钮的动机是「顶栏视觉太拥挤」,那么**把它们收进中栏自己的「⋯」菜单**比**赶到左栏**更优——既减少了视觉元素,又不迁移操作焦点。建议在原型 v1.7 里把这两种形态都画出来做 A/B 对比,再定稿。
### 5.2 风险登记
| 编号 | 风险 | 等级 | 缓解 |
|---|---|---|---|
| **R-1** | 转接操作路径退化,坐席转接效率下降 | 🔴 高 | §5.1:中栏保留转接,或补快捷键,或改中栏「⋯」总菜单 |
| **R-2** | 原型 v1.7 基于低保真的 v1.6,左栏条目字段缺失导致菜单落位设计与实现对不上 | 🔴 高 | §1.4:v1.7 左栏必须回填 `ConversationItem.vue` 的真实字段后再设计;否则本次评审产出不可作为开发依据 |
| **R-3** | z-index 层级冲突。项目无统一约定(`100/999/1000/1200/3000/9999/999999` 混用),EP popper 默认 2000+ | 🟡 中 | 引入 z-index token(建议:`--z-dropdown: 2000` 对齐 EP`--z-modal: 3000``--z-toast: 9000`);优先用 EP popover 交由其管理 |
| **R-4** | 菜单项可见性与后端权限不一致 → 点击后 403。`pin`/`todo``update:own` 权限(`conversations.py:374, 467`) | 🟡 中 | §4.4 矩阵在前端严格拦截;后端 403 时前端给明确文案而非静默(现状 `stores/conversation.ts:539-541``console.error`,**用户无任何感知**——这是既有缺陷,建议一并修) |
| **R-5** | 复制 `MessageItem.vue` 右键实现,把无边界翻转 / 无 Esc 关闭 / 无单例互斥等缺陷带入左栏 | 🟡 中 | §2.2:改用 `el-popover` 虚拟触发,或提取加固为通用 composable |
| **R-6** | 未来虚拟滚动(PRD-011 U-5)导致条目内菜单实例随 DOM 回收而错位 | 🟡 中 | §3.3:本次即采用单例菜单架构 |
| **R-7** | 三点按钮与 `.conv-target-avatar` 争位,导致 hover 时信息遮挡或布局抖动 | 🟢 低 | §4.3:同栅格位淡入淡出替换,不改变布局盒模型 |
| **R-8** | PRD-011 §3.1 的 P0 描述已过期(`TaskDetailView.vue` 的无条件 `ElMessage.success` 已修复) | 🟢 低 | 建议 PM 更新 PRD-011 §3.1 行号与结论,避免后续评审基于过期事实 |
### 5.3 待明确事项
| 编号 | 事项 | 需谁决策 | 阻塞级别 |
|---|---|---|---|
| **Q-1** | 中栏 4 个按钮是全删、删 3 留转接、还是改收进中栏「⋯」菜单? | 宋献 | 🔴 阻塞原型 v1.7 定稿 |
| **Q-2** | 三点按钮与 `.conv-target-avatar` 的占位取舍(覆盖 / 替换 / 并列) | 宋献 + PM | 🔴 阻塞原型 |
| **Q-3** | 转接的坐席选择用二级菜单还是独立 Dialog? | PM(架构建议 Dialog | 🟡 阻塞开发 |
| **Q-4** | 「接手」「退出协作」是否收进三点菜单(收则移除第三行按钮)? | PM(架构建议不动) | 🟡 阻塞开发 |
| **Q-5** | 「结单」是否进左栏菜单? | PM(架构建议不进) | 🟢 |
| **Q-6** | 是否同时提供右键快捷方式? | 宋献(架构强烈建议:是) | 🟡 |
| **Q-7** | 坐席实际办公环境是否有 VDI/远程桌面/触摸屏?影响右键可靠性 | 宋献 | 🟡 影响 Q-6 |
| **Q-8** | 原型 v1.7 是否接受"先回填左栏真实字段"的额外工作量? | 宋献 | 🔴 阻塞原型 |
| **Q-9** | 键盘导航(↑↓/Esc/Tab)是本次做还是 P2 单排? | 宋献 | 🟢 |
---
## 6. 附录:核查方法与证据清单
| 类别 | 文件 | 用途 |
|---|---|---|
| 中栏按钮 | `src/frontend-agent/src/components/chat/UserInfoBar.vue:114-194` | 6 个按钮清单与可见性条件 |
| 事件处理 | `src/frontend-agent/src/components/chat/ChatArea.vue:325-336, 431-547` | 4 个 handler + 摇人可见性 computed |
| Store | `src/frontend-agent/src/stores/conversation.ts:498-587` | assignConv/togglePinConv/toggleTodoConv/transferConv/grabConv |
| API | `src/frontend-agent/src/api/conversation.ts:176-256` | 5 个 API 函数 |
| 后端 | `src/backend/app/api/conversations.py:265, 327, 373, 466, 493, 525` | 6 个端点与权限装饰器 |
| 左栏条目 | `src/frontend-agent/src/components/conversation/ConversationItem.vue`(全文 550 行) | DOM 结构 / 事件 / 计算属性 |
| 左栏列表 | `src/frontend-agent/src/components/conversation/ConversationList.vue:44-77` | 三分区渲染与 section 传参 |
| 全局样式 | `src/frontend-agent/src/styles/global.css:98, 269-279, 356-360, 473-501` | 侧栏宽度 / overflow 裁剪 / 条目样式 |
| 右键先例 | `src/frontend-agent/src/components/chat/MessageItem.vue:23, 135-155, 326-336, 631-669` | 定位 / 关闭 / z-index |
| EP popover 先例 | `src/frontend-agent/src/components/chat/ai-assist/AiReplyModeSwitch.vue:24-82` | 最佳复用模板 |
| 任务侧 Mock | `src/frontend-agent/src/components/chat/TaskDetailView.vue:127-133` | 证明 PRD-011 §3.1 已过期 |
| 依赖版本 | `src/frontend-agent/package.json` | element-plus ^2.7.0 / @element-plus/icons-vue ^2.3.0 / @vueuse/core ^14.0.0 |
| 快捷键框架 | `src/frontend-agent/src/composables/useKeyboardShortcuts.ts:102-234` | 可挂接的快捷键注册范式 |
| 原型 | `docs/01-产品文档/04-坐席工作台/原型-...-v1.0v1.6.html` | 逐版本关键词计数(§1.4) |
| PRD | `docs/01-产品文档/04-坐席工作台/PRD-REQ-坐席-011-统一工作队列重构-v0.1.md` | kind 判别式 / U-5 / §3.1 |
**核查声明**:本文所有结论均基于 2026-08-09 时点 `src/` 目录下的实际代码,未依赖任何历史文档描述或记忆。核查过程未修改任何现有文件,未编写任何业务代码。
---
## 7. 变更记录
| 日期 | 版本 | 变更内容 | 变更人 |
|---|---|---|---|
| 2026-08-09 | v1.0 | 创建。完成现状核查(A)、三形态可行性评估(B)、PRD-011 兼容性设计(C)、方案推荐(D)、风险与待明确事项(E) | 高见远(架构师) |
</content>
</invoke>