wip: 2026-08-11 工作树快照(docs/memory/h5.py/scripts 等 447 项未评审改动,安全提交到 feat 分支)

This commit is contained in:
Simon
2026-08-11 09:59:44 +08:00
parent 6be361fb63
commit f2fd4fa012
447 changed files with 273482 additions and 1386 deletions
@@ -1,12 +1,12 @@
# PRD — 坐席端统一工作队列重构
> **REQ编号**: REQ-坐席-011
> **版本**: v0.1(方案草案 / 沟通确认阶段)
> **版本**: v0.1(方案草案 / 沟通确认阶段C-8 决策见 §6.4
> **状态**: 🟡 待评审 — 含未决项,不得据此排期开发
> **优先级**: P1(其中 Phase 0 为 P0
> **日期**: 2026-08-08
> **作者**: 宋献
> **关联**: REQ-坐席-004(任务详情视图切换)、REQ-坐席-009(会话状态Tab筛选)、任务说明书 #132
> **关联**: REQ-坐席-004(任务详情视图切换)、REQ-坐席-009(会话状态Tab筛选)、任务说明书 #132、原型 `原型-REQ-坐席-000-坐席工作台-v1.8.html`v1.8 演进)
---
@@ -66,22 +66,61 @@ D-3 不是筛选条件的调整,而是新增一条状态机路径。当前 `to
---
## 3. 阻塞项(P0
## 3. 阻塞项(原 P02026-08-09 修订为 P1
### 3.1 任务详情操作按钮全部为 Mock
### 3.1 任务详情操作按钮无服务端闭环(原「全部为 Mock」,已部分修复)
```js
// src/frontend-agent/src/components/chat/TaskDetailView.vue:117
> **📝 2026-08-09 修订**:本节原记录 `TaskDetailView.vue:117` 为无条件 `ElMessage.success` 的 Mock 实现。
> 经架构师代码核查(`技术核查-坐席会话条目操作菜单-v1.0.md` §1.1),**该无条件成功提示已被修复**,行号亦已变更。
> 原描述已过期,现更新如下,避免后续评审基于过期事实。
#### 3.1.1 当前实际代码
```ts
// src/frontend-agent/src/components/chat/TaskDetailView.vue:129-1352026-08-09 时点)
function handleAction(action: string): void {
ElMessage.success(`操作成功:${action}`) // 仅 toast,不调用任何接口
if (props.todoItem.type === 'approval') {
// 企微不支持服务端代审批,跳转由 ApprovalDetail 的 <a target="_blank"> 原生完成
console.info('[TaskDetailView] 审批动作已跳转企微审批原系统:', action)
return
}
ElMessage.info('该操作需在原系统中完成')
}
```
REQ-坐席-004 §2.4 定义的全部操作 —— 工单(接单 / 开始处理 / 结单 / 转派)、审批(通过 / 拒绝 / 转交)—— **均不生效**
**修复内容**:无条件 `ElMessage.success` 已移除,改为按 `type` 分支——
- `type === 'approval'` → 静默返回(跳转由 `ApprovalDetail``<a target="_blank">` 原生完成),不再弹成功提示;
- 其他类型(工单等) → `ElMessage.info('该操作需在原系统中完成')`,中性提示,不谎报成功。
**风险定级:P0,阻塞布局合并。**
#### 3.1.2 修订后的结论
理由:当前待办面板位于右栏底部 260px 区域,坐席误操作的暴露面有限。一旦将待办提升至左栏主队列首屏,等同于把不可用功能放置于最高可见度位置。坐席点击「审批通过」后收到绿色成功提示,而企业微信侧该单仍处于挂起状态 —— 属于会造成真实业务后果的错误反馈。
| 项 | 原结论(v0.1 初稿) | 现结论(2026-08-09 核查后) |
|---|---|---|
| 是否谎报成功 | 是(无条件绿色成功 toast) | ❌ **否,已修复**。审批静默跳转,其他类型为中性 info |
| 是否服务端闭环 | 否 | ❌ **仍否**。审批为降级跳转 + 回调回写(U-1 已定),工单受 U-1.1 / U-1.2 外部阻塞 |
| 风险定级 | 🔴 P0,阻塞布局合并 | 🟡 **降级为 P1**,不再是"错误反馈"型 P0,但**服务端闭环缺失依然阻塞 Phase 2 的工单/审批混排**(Phase 0 的降级跳转 + 回写仍须先落地) |
**剩余阻塞点**(未因本次修复而消解):
- 审批:`approval.py:902``sys_approval_change` 回调状态回写尚未补全 → 跳转后前端无法感知外部状态变更(Phase 0 待办项);
- 工单:`ITSMService.get_todo_list()` 无条件 `return []`(U-1.2),读链路即断,写接口更无从谈起(U-1.1 / T05)。
#### 3.1.3 🔴 与会话侧操作的性质差异(Phase 2 组件抽象的核心依据)
| 维度 | 会话侧 4 操作(接单/置顶/代办/转接) | 任务侧操作(审批/工单) |
|---|---|---|
| 闭环性质 | **服务端真闭环**`UserInfoBar.vue:116-164``ChatArea.vue` handler → `stores/conversation.ts:498-571``api/conversation.ts:176-244` → 后端 `conversations.py` 六端点,**全链路实证可用** | **降级跳转**(审批)/ **当前无对象**(工单) |
| 执行路径 | store → API → 后端 → `fetchConversations()` 刷新 | `<a target="_blank">` 跳外部系统 → 回调回写 → WS 推送 |
| 反馈模型 | **同步**成功/失败 | **异步最终一致** |
| 后端权限 | `assign`/`transfer`/`grab` = `update:all``resolve`/`pin`/`todo` = `update:own` | 不适用 |
> **这一差异是 Phase 2 组件抽象不可回避的约束**:左栏统一容器上的操作菜单**必须按 `kind` 分派反馈策略**,菜单项定义需携带 `execMode: 'direct' | 'redirect' | 'disabled'` 字段。
> **若三类工作项共用一套「点击 → toast 成功」的反馈模型,就会以另一种形式重演本节原先定性为 P0 的那个错误。**
> 具体设计见 `PRD-REQ-坐席-012-会话条目操作菜单-v1.0.md` §7.2。
**风险定级:🟡 P1(由 P0 降级)。**
降级理由:谎报成功的错误反馈已消除,坐席不再会因绿色提示误判外部系统已变更。
**但仍阻塞 Phase 2**:待办提升至左栏主队列首屏后,若操作仍只是"提示到原系统操作",等同于把无实际能力的入口放在最高可见度位置——Phase 0 的降级跳转 + 状态回写仍是 Phase 2 的硬前置(§6 D-2 顺序不变)。
---
@@ -123,7 +162,7 @@ REQ-坐席-004 §2.4 定义的全部操作 —— 工单(接单 / 开始处理
|---|---|---|
| 审批操作 → 降级跳转 + 回写 | 通过 / 拒绝 / 转交 → 点击「在企微审批中打开」跳转原系统,由 `approval_webhook.py` 接收 `sys_approval_change` 回调 → 补全 `approval.py:902` 状态回写 → WS 推送 | Level 0 立即可做;Level 1 需补回调回写 |
| 工单操作 → 降级跳转(暂) | 接单 / 开始处理 / 结单 / 转派 → 点击「在 ITSM 中打开」跳转原系统;ITSM 写接口到位前不承诺服务端闭环(受 T05 外部阻塞)。**⚠️ 注意:该跳转依赖工单可见,而当前 ITSM 读列表亦未实现(见 U-1.2),故本行在读链路打通前无实际可操作对象** | Level 0(暂,受 U-1.2 前置) |
| 失败态处理 | 移除无条件 `ElMessage.success`改为「跳转成功提示 + 状态回写后刷新」按三态分派;仍禁止以 toast 作为验收依据 | — |
| 失败态处理 | ~~移除无条件 `ElMessage.success`~~**已完成**2026-08-09 核查确认,见 §3.1.1)。**剩余部分**:改为「跳转成功提示 + 状态回写后刷新」按三态分派(当前仅做到中性 info,尚无回写后刷新);仍禁止以 toast 作为验收依据 | — |
**验收标准**:操作后外部系统(ITSM / 企微审批)**状态真实变更**,且前端反馈与外部状态**最终一致**(通过回调 / webhook 回写达成)。禁止以 toast 成功作为验收依据。审批 / 工单在降级跳转模式下,以「跳转成功 + 原系统状态通过回调回写并刷新」作为验收闭环,不要求前端内嵌直接操作。
@@ -158,6 +197,36 @@ REQ-坐席-004 §2.4 定义的全部操作 —— 工单(接单 / 开始处理
**观测指标**:会话首响时长(是否因混排而劣化)、待办平均处理时长、坐席 Tab 切换频次、认领冲突率。
### Phase 0.5 — TaskDetailView 操作区精简(C-8 决策,2026-08-10
**背景**:原 `TaskDetailView` 操作区固定渲染 4 按钮(接单 / 开始处理 / 结单 / 转派),垂直占 body 区 50%+ 空间,导致「处理进度 / SLA」等关键状态信息需滚动才看完。
**设计变更**:状态机驱动 + 二级收纳
| Task Type | 主操作按钮(按状态) | ⋯ 次要动作 |
|---|---|---|
| 工单 queued | 📥 接单 | 转派 / 挂起 |
| 工单 serving | ✅ 结单 | 转派 / 挂起 |
| 审批 pending | ✅ 审批通过 | 拒绝审批 / 转交审批 / 加签 |
| 设备异常 | ✅ 标记恢复 | 一键开单 / 派工 / 加入巡检计划 |
**收益**
- 操作区按钮数 4 → 1~2(-50%)
- body 区可用垂直高度 +50%↑
- 状态信息一眼可见,无需滚动
**状态机原则**:开始处理被吸收到状态推进逻辑(点「接单」后接单 + 开始处理隐式完成,不显式拆分)。
**实现细节**
- 前端:组件 `useConversationMenuItems.ts` 状态机新增 `claim / recover / approve` reducer
- 后端:**不变**(仅前端状态机调整 + ⋯ 菜单 UI)
- 视觉密度补偿:主按钮 padding 从 `8×18``8×22`
**影响范围**
- ✅ 不影响:左栏三点菜单(v1.7 §4.4 矩阵)、中栏顶栏 UserInfoBarv1.7 D-2 已删 3 留 1
- ⚠️ 涉及文件:`TaskDetailView.vue:128-135``useConversationMenuItems.ts`
- 📐 原型版本:v1.7 → v1.8(已落,决策依据详见原型变更说明段)
---
## 7. 未决项
@@ -203,3 +272,7 @@ REQ-坐席-004 §2.4 定义的全部操作 —— 工单(接单 / 开始处理
| 2026-08-08 | v0.1 | 创建方案草案;固化 D-1/D-2/D-3 三项决策;完成原始论据核查与现状事实核实 | 宋献 |
| 2026-08-08 | v0.1 | 据 U-1 技术验证结论(架构师高见远)修订 §6 Phase 0 与 §7 U-1:审批明确为降级跳转 + 回写(U-1 已验证);新增 U-1.1 ITSM 写接口外部阻塞项;同步下调 R-5 风险表述 | 宋献 |
| 2026-08-08 | v0.1 | 主理人复核补录 **U-1.2**:代码实证 `ITSMService.get_todo_list()` 无条件返回空列表,ITSM 工单在坐席端读链路即断、当前待办全为企微审批单。该项前置于 U-1.1,一并标注于 §6 Phase 0 工单行 | 齐活林(交付总监) |
| 2026-08-09 | v0.1 | **修正 §3.1(原描述已过期)**`TaskDetailView.vue` 的无条件 `ElMessage.success` 已被修复,行号由 `:117` 更新为 `:129-135`,现为按 `type` 分支(approval 静默跳企微 / 其他 `ElMessage.info('该操作需在原系统中完成')`)。风险由 🔴 P0 降级为 🟡 P1(谎报成功已消除,服务端闭环缺失仍阻塞 Phase 2)。新增 §3.1.3 明确「**会话侧 4 操作是真闭环,任务侧审批/工单是降级跳转,二者性质根本不同**」,并将其确立为 Phase 2 按 `kind` 分派反馈策略(`execMode` 字段)的核心依据,指向 PRD-REQ-坐席-012 §7.2。依据:`技术核查-坐席会话条目操作菜单-v1.0.md` §1.1 | 许清楚(产品经理) |
| 2026-08-09 | v0.1 | **Phase 0 审批线落地**:T01(前端降级跳转)+ T02(后端 `/approval/callback` + `approval_webhook` 回调回写)已实现并通过 QA 独立回归(51 例全绿,前端 type-check 改动文件干净,无源码缺陷)。代码已 commit `9292f41` 于分支 `feat/agent-approval-degrade-jump`,并已推送 Gitea(远端 SHA 与本地一致,`main` 未被改写)。PR 入口:<http://192.168.3.200:8418/simon/wecom_it_smart_desk/pulls/new/feat/agent-approval-degrade-jump>。**工单线(T03+)仍未启动**,受 U-1.2ITSM 读链路断裂)+ U-1.1(写接口缺失)外部阻塞 | 寇豆码(工程师)→ 严过关(QA)→ 齐活林(主理人编排) |
| 2026-08-10 | v0.1 | **C-8 决策**TaskDetailView 操作区由原版固定 4 按钮(接单/开始处理/结单/转派)重构为「状态驱动主操作按钮 + ⋯ 次要动作收纳」。依据:操作区占 body 区底部 50%+ 垂直空间,导致「处理进度 / SLA」等状态信息需滚动才看完。详见新增 §6.4。原型 v1.7 → v1.8,代码待 `useConversationMenuItems.ts` 状态机 reducer 改造。**不影响**:左栏三点菜单(v1.7 §4.4 矩阵)、中栏顶栏 UserInfoBar、后端 API | 宋献 |
| 2026-08-10 | v0.1 | **PR #5 已合并**commit `af87f1de` redis_client 修复(approval.py + byod.py)经 Gitea UI 合并为双亲 merge `d8e7dbe`main 现位于 `5db3079d`。本地 main 已同步。审批回调 `POST /approval/callback` 生产实测 HTTP 200 `{errcode:0}` | 齐活林 → 宋献(合并)|
@@ -0,0 +1,507 @@
# PRD — 坐席端会话条目操作菜单(左栏三点菜单 + 右键菜单)
> **REQ编号**: REQ-坐席-012
> **版本**: v1.0
> **状态**: 🟡 待评审 — 交互形态与中栏取舍已由需求方拍板,菜单矩阵与组件边界待技术评审
> **优先级**: P1
> **日期**: 2026-08-09
> **作者**: 许清楚(产品经理)
> **需求来源**: 宋献(IT 支持组组长)口述需求 4 条
> **技术依据**: `docs/02-技术文档/技术架构/技术核查-坐席会话条目操作菜单-v1.0.md`(高见远,2026-08-09
> **配套原型**: `原型-REQ-坐席-000-坐席工作台-v1.7.html`
> **关联**: PRD-REQ-坐席-011(统一工作队列重构)、REQ-坐席-004、REQ-坐席-009、REQ-坐席-010
> **性质**: 增量 PRD。仅描述本次新增/变更,不复述既有功能。
---
## 1. 背景
### 1.1 需求原文与映射
| # | 宋献原始需求 | 本 PRD 对应 |
|---|---|---|
| 1 | 确认现有坐席架构,控件能否在「选中对象-右键调出菜单」模式实现 | §3.1 D-3、§4.3 |
| 2 | 左栏每个会话表单右边增加菜单按钮(三个点),点击弹出接单/置顶/代办/转接 | §4.1、§4.2、§5 |
| 3 | 上述完成后,去除中栏上方「接单/置顶/代办/转接」4 个按钮 | §3.1 D-2、§6 |
| 4 | 先基于最新原型更新细致原型图,与用户确认后再改代码 | §3.1 D-1、§10 |
### 1.2 🔴 前置事实修正(必须在评审开场说明)
需求提出时的前置认知是「V1.0–V1.6 原型中这 4 个按钮丢失了」。经架构师代码核查,**该认知需要修正两处**:
| 认知 | 事实 |
|---|---|
| 「按钮丢失了」 | ❌ **4 个按钮在真实运行代码中从未丢失**。接单 `UserInfoBar.vue:116-124`、置顶 `:127-133`、代办 `:136-142`、转接 `:145-164`,且四者均为**服务端真闭环**(store → API → 后端端点,非 Mock) |
| 「从 v1.5 开始丢失」 | ❌ 丢失只发生在**原型 HTML 侧**,且起始版本是 **v1.4**。v1.0–v1.3 均含 4 按钮(关键词计数实证:接单 11-12 次 / 置顶 3 次 / 代办 1 次 / 转接 2 次),v1.4 起归零 |
**根因**v1.4 起原型**换了血统**。v1.0v1.3 是 24002700 行的全量高保真原型;v1.4 是为论证「B 方案三栏分栏比例」与「排查步骤全屏流程图」而**新画的专题示意图**(961 行),并非 v1.3 的增量演进。v1.5/v1.6 沿此专题线继续演进。
**后果(这是本次必须先做字段回填的原因)**v1.6 的左栏 `.conv-item` 只有「头像/姓名/时间/摘要/未读数」5 个字段,而真实代码 `ConversationItem.vue` 的条目含:
| 元素 | 真实代码 | v1.6 原型 |
|---|---|---|
| 置顶 📌 / 代办 📋 图标 | ✅ `:50` `:52` | ❌ |
| tag-badgeVIP/招手/需介入/情绪/坐席名/待确认) | ✅ 6 类 `:56-82` | ❌ |
| 优先级图标(⛔👥⭐🔁) | ✅ 最多 4 个 `:84-95` | ❌ |
| 紧急度星级 | ✅ 5 星 `:107-114` | ❌ |
| 接手 / 退出 link 按钮 | ✅ `:116-136` | ❌ |
| 处理对象缩略头像 | ✅ `:141-148` | ❌ |
**若直接在 v1.6 上加三点按钮,会得出「条目右侧有大片空槽」的错误结论**——真实条目右侧已被 `.conv-target-avatar` 占满(`section !== 'history'` 时恒显示)。这是 §3.1 D-1 选择 v1.3 而非 v1.6 作为 v1.7 基线的直接原因。
### 1.3 本次真正的新增工作量在哪里
架构核查指出:现有 4 个按钮**几乎没有基于会话归属的可见性控制**——置顶/代办在中栏任何状态下都常驻且不禁用。这在中栏可接受(中栏只显示当前打开的会话,多为自己的);**但一旦下沉到左栏,左栏同时呈现「我的会话 / 同事会话 / 历史会话」三个分区**(`ConversationList.vue:43-76`),菜单项必须新增**归属维度**的可见性规则。
> **本次改造的产品设计核心不是"控件搬家",而是补齐一张此前不存在的「状态 × 归属 × 分区」菜单项可见性矩阵**(§5)。控件本身是低成本工作。
---
## 2. 产品目标
| # | 目标 | 衡量方式 |
|---|---|---|
| G-1 | 把会话的**列表管理动作**(接单/置顶/代办)下沉到动作对象所在处,消除「操作在中栏、效果在左栏」的焦点跳跃 | 置顶/代办操作后无需视线迁移即可看到条目重排 |
| G-2 | 在不牺牲对话过程效率的前提下精简中栏顶栏 | 中栏操作控件由 6 个降至 3 个(转接/摇人/结单),且**转接路径不退化**(保持 3 步) |
| G-3 | 菜单壳一次抽对,避免 PRD-011 Phase 2 与虚拟滚动落地时二次重写 | 新增 `kind`(审批/工单)时只加一个菜单项工厂,不改菜单壳 |
## 2.1 用户故事
| # | 故事 |
|---|---|
| US-1 | 作为坐席,我想在左栏直接对某条会话置顶/代办,这样我不必先打开它、再去中栏操作、再回来看列表变化 |
| US-2 | 作为坐席,我想在扫视排队会话时就地接单,这样我不必"先点开、再接单"两步走 |
| US-3 | 作为熟练坐席,我想右键会话条目直接出菜单,这样我不必先 hover 找到三点按钮 |
| US-4 | 作为坐席,我不想在同事的会话上看到「置顶」——点了会 403 报错,还不给我任何提示 |
| US-5 | 作为坐席,我在中栏读完员工描述判断"这不归我管"时,希望能就地转接,而不是回左栏重新定位这条会话 |
---
## 3. 已锁定决策
### 3.1 需求方已拍板项(D-1 ~ D-6
| # | 决策项 | 结论 | 依据 |
|---|---|---|---|
| **D-1** | 原型基线 | **以 v1.3 为基线复制为 v1.7**(保留高保真左/中/右三栏),再增量叠加 v1.4~v1.6 演进特征(B 方案分栏比例、排查步骤全屏流程图、智能推荐竖排、工具栏重组、Emoji 图标统一)。**不以 v1.6 为基线** | §1.2;架构报告 §1.4 / R-2 |
| **D-2** | 中栏按钮处理 | **删除「接单/置顶/代办」3 个,保留「转接」**。转接与既有的「摇人」「结单」构成语义一致的**对话过程动作组**(转接=交出去 / 摇人=拉进来 / 结单=做完了) | §6;架构报告 §5.1 |
| **D-3** | 交互形态 | **三点按钮为主入口 + 右键为快捷方式,两者并存**。二者共用同一份菜单项定义与 handler | §4.3;架构报告 §2.3 选项 3 |
| **D-4** | 菜单实例架构 | **菜单为单例**,挂在 `ConversationList` 层级,**不挂在条目内部** | §7;架构报告 §2.4-5 |
| **D-5** | 三点按钮落位 | **替换** `.conv-target-avatar`(同栅格位淡入淡出),非覆盖、非并列 | §4.1;架构报告 §4.3 |
| **D-6** | 菜单弹层定位 | `placement="bottom-end"`,宽 140px,视口下缘不足时翻转 `top-end`**DOM 必须 `position:fixed` 或 Teleport 到 body** | §4.2;架构报告 §1.3.4 |
### 3.2 D-2 的完整论证(为什么不是 4 个全删)
需求原文是「去除中栏上方 4 个按钮」。经操作路径推演,**4 个一刀切全删会造成分档不同的退化**:
| 操作 | 当前路径 | 全删后路径 | 退化判定 |
|---|---|---|---|
| 接单 | 中栏 1 步 | 左栏 定位→hover→三点→接单 | ⚪ **不退化,删除是净收益**。接单发生在打开会话**之前/之时**,左栏本就是接单的自然场所(Zendesk / ServiceNow 同构)。且中栏该按钮在 `status !== 'queued'` 时恒为 disabled 的「已接单」,长期占位不可点 |
| 置顶 | 中栏 1 步 | 左栏 4 步 | 🟡 **轻微退化,可接受**。置顶是**列表组织行为**,其效果(条目上浮)只在左栏可见。操作与反馈同处一栏,语义反而更内聚 |
| 代办 | 中栏 1 步 | 左栏 4 步 | 🟡 同上 |
| **转接** | `点转接 → 选坐席 → 确认` = **3 步** | `视线移左栏 → 定位当前条目 → hover → 点三点 → 点转接 → 选坐席 → 确认` = **6+ 步** | 🔴 **显著退化,不接受** |
**转接不可下放的三条理由**
1. **触发时机根本不同**。置顶/代办是坐席**扫视队列时**的列表管理动作;转接是坐席**读完员工描述、判断"这不归我管"那一刻**的对话过程动作——此时注意力与鼠标都在中栏。要求回左栏是**注意力焦点的强制迁移**。
2. **"定位当前条目"本身有成本**。左栏三分区、可滚动,且条目会因置顶/紧急度**动态重排**,当前会话未必在视口内。这一步不是零成本的。
3. **删了转接反而制造不一致**。中栏保留「摇人」「结单」是既定事实。若转接被赶到左栏,结果是**同为对话过程中的协作类动作,入口被劈成两处**——这比"全部在中栏"或"全部在左栏"都差。
**同时,左栏三点菜单仍提供「转接」**。两个入口共用同一 handler 与同一坐席选择 Dialog,符合"高频动作允许多入口"原则,不构成语义分裂。
> **给需求方的备选(如坚持"顶栏太挤"是主要动机)**:可将 3 个被删按钮收进中栏自己的「⋯」总菜单,视觉上顶栏只剩「摇人 / 结单 / ⋯」,功能零退化。此形态**未在 v1.7 中作为主方案呈现**,如需要请在评审时提出。
---
## 4. 交互设计
### 4.1 三点按钮(主入口)
| 项 | 规格 | 依据 |
|---|---|---|
| **位置** | `.conversation-item` 最右侧,**替换** `.conv-target-avatar` 所在栅格位 | D-5 |
| **切换方式** | 同一栅格位内:缩略头像 `opacity:0` 淡出、三点 `opacity:1` 淡入(`transition ≈ 140ms`)。**不改变布局盒模型,无抖动** | 架构报告 R-7 |
| **命中区** | 24 × 24 px;图标 `MoreFilled``@element-plus/icons-vue@^2.3.0` 已装)1416px | D-5 |
| **默认态** | 隐藏(`opacity:0; pointer-events:none` | — |
| **显示触发** | `.conversation-item:hover` `.is-menu-open` 按钮 `:focus-visible` | 保证键盘可达 |
| **菜单打开期间** | 按钮**强制常显**,条目加 `.is-menu-open` 类(背景保持 hover 态),避免"鼠标移出→按钮消失→菜单悬空"的观感断裂 | 架构报告 §2.3 选项 2 风险项 |
| **历史分区** | `section === 'history'` 本无缩略头像,三点直接占该位(可常显淡色 `opacity:.35`hover 加深) | D-5 |
| **事件** | 必须 `@click.stop` —— 根节点 `@click="$emit('click')"``ConversationItem.vue:20`,不阻断会连带切换会话。项目内已有正确先例(`:122` `:133``@click.stop` | 架构报告 §2.4-4 |
**为什么不选"覆盖"或"并列"**
- 覆盖(absolute 盖在缩略头像上):hover 时缩略头像被遮挡,信息丢失且视觉脏;
- 并列(插到缩略头像右侧):再吃掉 ~24px,`.conversation-info` 从 ~132px 压到 ~108px,第一行 6 类 tag + 4 个优先级图标严重挤压。左栏条目内部可用宽度仅 ≈ 228px260 margin 12 padding 20),没有余量。
### 4.2 菜单弹层
| 项 | 规格 |
|---|---|
| 宽度 | 140px(4 项中文两字 + 图标;不得超过侧栏 260px) |
| 定位 | `placement="bottom-end"`(右对齐向下) |
| 翻转 | 视口下缘空间不足时自动翻转 `top-end`;右缘不足时自动贴边 |
| **DOM 归属** | 🔴 **必须 `position:fixed` + 视口坐标,或 Teleport 到 body**。**禁止**以 `position:absolute` 挂在 `.conversation-item` 内部 |
| 层级 | 使用 EP popper 默认 z-index2000+ 自增)或新引入的 `--z-dropdown: 2000` token。**不得沿用 `MessageItem.vue` 的 1000**(低于 EP popper,会被 dropdown/messagebox 盖住) |
| 关闭时机 | 点击菜单外 ∪ Esc ∪ 列表滚动 ∪ 窗口 resize ∪ 切换会话 ∪ 在另一条目再次右键 |
| 互斥 | 全局仅一个菜单可见(单例天然满足,见 §7) |
**为什么 DOM 必须脱离左栏**(这是硬约束,不是优化建议):
```css
.workspace-sidebar { overflow: hidden; } /* global.css:277 */
.conversation-list-scroll { overflow-x: hidden; } /* global.css:359 */
```
双重裁剪导致:① 横向——260px 窄栏内放不下 140px 菜单向右展开,必被裁;② 纵向——列表首尾条目的菜单会被滚动容器裁掉。
### 4.3 右键(快捷方式)
| 项 | 规格 |
|---|---|
| 绑定 | `.conversation-item` 根节点 `@contextmenu.prevent.stop` |
| 定位 | 以鼠标 `clientX/clientY` 为锚点,`position:fixed` |
| 菜单内容 | **与三点按钮完全一致**——由同一个 `computed` 生成的菜单项数组驱动,不允许两套定义 |
| 覆写原生菜单 | 是(`.prevent`)。**仅在会话条目上覆写**,列表空白区、消息区、输入框保留浏览器原生右键 |
| 右键是否需先选中条目 | **否**。右键直接对该条目生效,不改变当前打开的会话(避免误切换丢失输入草稿) |
**为什么两者并存而不是二选一**
- 纯右键**发现性为零**。左栏是坐席每天使用频率最高的区域,且服务台存在**多人轮岗与新人**,不能假设全员知晓。零发现性交互在此位置是产品事故。
- 需求 1(问右键)与需求 2(要三点)**本就是同一菜单的两个触发器**,不是二选一。并存的边际成本仅 0.3–0.5 人日。
### 4.4 反馈与错误处理
| 场景 | 反馈 |
|---|---|
| 操作成功 | 条目即时反映状态变化(📌/📋 图标出现或消失、条目重排)。**不额外弹 toast**——视觉变化本身即反馈 |
| 接单成功 | 条目从「排队区」移入「我的会话」,并自动打开该会话 |
| 转接成功 | 条目移出「我的会话」,`ElMessage.success('已转接给 XXX')` |
| **后端 403 / 失败** | 🔴 必须给明确 `ElMessage.error` 文案。**现状是既有缺陷**`stores/conversation.ts:539-541``console.error`,用户零感知。本次一并修复(P0-7) |
| 无可用坐席 | 「转接」项 disabled + 灰字提示「暂无可用坐席」 |
---
## 5. 菜单项可见性矩阵(核心交付物)
> 按 会话 `status` × 归属(`is_mine` / `is_collaborator` / `can_grab`)× 分区(`section`)分派。
> 与架构报告 §4.4 完全一致,本节为其产品侧收口版本。
| 菜单项 | 显示条件 | 禁用条件 | 动态文案 | 后端权限 | 备注 |
|---|---|---|---|---|---|
| **接单** | `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` → 禁用,提示「暂无可用坐席」 | 「转接…」(省略号表示会开 Dialog) | `update:all` | 点击开独立 Dialog,见 Q-3 |
| **接手** | `can_grab && !is_mine && status === 'serving'` | — | 「接手」 | `update:all` | 与第三行 `grab-btn` 同义,双入口,见 Q-4 |
| **退出协作** | `is_collaborator && status === 'serving'` | — | 「退出协作」(danger 样式) | — | 与第三行 `leave-btn` 同义,见 Q-4 |
> ⚠️ **实现细节校准(影响 A-11 双入口一致性验收)**:第三行 `grab-btn` 的显示条件在两个分区**并不相同**——
> `ConversationList.vue:49`(我的会话区)传入 `show-grab = status==='serving' && !is_mine && !is_collaborator`
> `ConversationList.vue:62`(同事会话区)传入 `show-grab = status==='serving' && !is_mine`**少了 `!is_collaborator`**)。
> 条目内再与 `conversation.can_grab` 取交集(`ConversationItem.vue:117`)。
> 菜单工厂 `useConversationMenuItems.ts` **必须复用同一套条件**(建议把该判断上提为一个共享 `computed`,两处引用),
> 否则会出现「第三行有接手 link、菜单里没有」或反之的不一致,直接违反 A-11。
### 5.1 分区实例推演(原型必须逐一演示)
| 实例 | 状态 | 菜单实际渲染项 | 说明 |
|---|---|---|---|
| **A. 排队会话** | `status=queued``is_mine=false` | **接单** | 仅 1 项。置顶/代办不显示(非本人) |
| **B. 我的会话(serving** | `is_mine=true`, `is_pinned=false`, `is_todo=false` | 置顶 / 代办 / 转接… | 3 项。无接单(非 queued)、无接手(非 can_grab |
| **C. 我的会话(已置顶已代办)** | `is_mine=true`, `is_pinned=true`, `is_todo=true` | **取消置顶** / **取消代办** / 转接… | 文案动态翻转 |
| **D. 同事会话** | `is_mine=false`, `can_grab=true`, `status=serving` | **接手** | 仅 1 项。**置顶/代办被显示条件拦住**(点了会 403) |
| **E. 我协作中的会话** | `is_collaborator=true`, `status=serving` | 置顶 / 代办 / **退出协作**(danger) | 无转接(`is_mine=false` |
| **F. 历史会话** | `section=history`, `status=resolved` | **(空)** | 全部条件不满足 → 菜单显示「无可用操作」占位,或三点按钮直接不可点 |
| **G. 转接无可用坐席** | `is_mine=true`, `availableAgents=[]` | 置顶 / 代办 / ~~转接(灰)~~ | 转接项 disabledhover 提示「暂无可用坐席」 |
> **F 的产品决策**:历史会话菜单为空时,**三点按钮仍渲染但呈禁用态**`opacity:.35``cursor:default`,点击无响应),**不弹空菜单**。理由:弹出一个写着"无可用操作"的空菜单是无效交互;但完全不渲染按钮会让历史分区条目右侧出现空洞,与其它分区视觉不齐。
### 5.2 与「结单」的边界
「结单」**不进入**左栏菜单,理由见 Q-5。中栏保留其原有入口。
---
## 6. 中栏改造
### 6.1 变更清单(`UserInfoBar.vue:114-194`
| # | 控件 | 代码位置 | 本次处理 |
|---|---|---|---|
| 1 | 接单 | `:116-124` | ❌ **删除** |
| 2 | 置顶 | `:127-133` | ❌ **删除** |
| 3 | 代办 | `:136-142` | ❌ **删除** |
| 4 | **转接** | `:145-164` | ✅ **保留**D-2 |
| 5 | 摇人 | `:167-174` | ✅ 保留(不在本次范围) |
| 6 | 结单 / 等待确认 | `:177-193` | ✅ 保留(不在本次范围) |
| — | 历史会话开关 chip | `:100-110` | ✅ 保留(在 chips 区,非 actions 区) |
**改造后中栏 actions 区 = 转接 / 摇人 / 结单**,语义收敛为「对话过程动作组」。
### 6.2 转接入口一致性要求
中栏「转接」与左栏菜单「转接」必须:
- 调用同一 store action`transferConv`);
- 打开**同一个坐席选择 Dialog 组件**(Q-3 结论);
- 保留现有的 `ElMessageBox.confirm` 二次确认(`ChatArea.vue:531-547`)。
不得出现"中栏是下拉、左栏是 Dialog"的两套 UI。
---
## 7. 组件抽象边界与 PRD-011 兼容
### 7.1 抽象原则
> **抽「菜单壳」,不抽「业务动作」。**
```
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()
⛔ 不认识 conversation / approval / ticket,不 import 任何 store 与 api
composables/useConversationMenuItems.ts 【本次新建】会话菜单项工厂
入参:conversation + currentAgentId + section('my'|'colleague'|'history')
出参:MenuItem[](已按 §5 矩阵算好 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
```
### 7.2 `MenuItem` 类型契约
```ts
interface MenuItem {
key: string
label: string
icon?: string
danger?: boolean
disabled?: boolean
hidden?: boolean
tooltip?: string
/** 🔴 关键字段:决定容器采用哪种反馈策略 */
execMode: 'direct' | 'redirect' | 'disabled'
}
```
**`execMode` 为什么是必需字段**(对宋献"未来待办条目进入左栏后菜单怎么分派"的直接回答):
| kind | 操作性质 | 执行路径 | 反馈模型 |
|---|---|---|---|
| `conversation` | **服务端真闭环** | store → API → 后端端点 → 刷新列表 | 同步成功/失败 |
| `approval` | **降级跳转** | `<a target="_blank">` 跳企微 → `sys_approval_change` 回调回写 → WS 推送 | 异步最终一致 |
| `ticket` | **当前无对象** | `ITSMService.get_todo_list()` 无条件 `return []` | 无(PRD-011 U-1.2 |
> **必须分派,且不只是文案不同——是执行语义与反馈模型的根本不同。** 会话操作点完即生效;审批操作点完只是跳走,真正的状态变更在企微侧异步回写。**若三类共用一套「点击 → toast 成功」的反馈模型,就会重演 PRD-011 §3.1 定性为 P0 的那个错误**(绿色成功提示 + 外部系统实际未变更)。
### 7.3 与 PRD-011 Phase 2 的衔接检查表
| PRD-011 Phase 2 任务 | 本设计是否兼容 | 说明 |
|---|---|---|
| 定义统一 `ListItem`(含 `kind` 判别式) | ✅ | `MenuItem[]` 由 kind 专属工厂生成,菜单壳不感知 kind |
| 条目组件支持会话态 / 任务态两种渲染 | ✅ | 条目只 `emit('open-menu')`,两态共用同一菜单壳 |
| 左栏 Tab 分层(会话/待办/全部) | ✅ | 「全部」Tab 混排时,容器按 `item.kind` 选工厂 |
| **U-5 虚拟滚动** | ✅ | **单例架构天然兼容**(见下) |
| U-6 待办未读/变更标识 | ➖ | 与菜单无关 |
**D-4 单例架构的强制理由**PRD-011 U-5 已把虚拟滚动挂起。虚拟滚动会在滚动时**回收并复用条目 DOM**——若菜单实例挂在条目内部,reference 元素被销毁会导致菜单错位或残留。单例架构的实现成本与逐条挂载**相同**,却可免除未来上虚拟滚动时的整体返工。这正是宋献「避免三个月后第二次重做」诉求的具体着力点。
---
## 8. 产品经理自决项结论(Q-3 / Q-4 / Q-5
架构报告 §4.4 将三项留给产品决策。以下为结论与论证。
### Q-3 转接的坐席选择:二级菜单 还是 独立 Dialog?
> ### ✅ **结论:独立 Dialog。菜单项文案为「转接…」(带省略号,遵循"点击会开弹窗"的通用约定)。**
| 论据 | 说明 |
|---|---|
| **① 窄栏二级翻转过于脆弱** | 主菜单已 140px、`bottom-end` 右对齐。二级菜单需再向左或向右展开 ~160px。在 260px 侧栏 + 视口右缘的组合下,Popper 需同时做**水平翻转 + 垂直翻转**,边缘 case 组合爆炸,稳定性无法保证 |
| **② 坐席列表承载不下** | 二级菜单需展示 `姓名 (当前负载/最大负载)`,且在线坐席可能十余人,需要**滚动 + 搜索**。二级 popover 里塞搜索框是反模式 |
| **③ 悬停路径问题** | 二级菜单靠 hover 展开时,鼠标从主菜单项斜向移动到二级区域会经过其它菜单项,导致二级意外收起(经典 "diagonal problem"),需额外做安全三角形算法——成本高于直接开 Dialog |
| **④ 与中栏入口天然统一** | 中栏保留的「转接」也需要选坐席。共用同一个 Dialog 组件后,**两个入口 UI 完全一致**,符合 §6.2 的一致性要求。若左栏用二级菜单、中栏用下拉,就是两套 UI |
| **⑤ 转接是有后果的动作** | 转接后会话脱手。现有实现已带 `ElMessageBox.confirm` 二次确认。Dialog 形态与"需要慎重决策"的语义匹配,二级菜单的轻量感反而不合适 |
**Dialog 规格**:宽 380px,标题「转接会话」,含搜索框 + 坐席列表(头像 / 姓名 / `负载/上限`)+ 满载坐席置灰不可选 + 取消按钮。选中坐席后走原有二次确认。
**反方意见记录**:二级菜单少一次弹窗、路径更短。但转接**日频次不高**(非置顶/代办那类高频动作),为低频动作牺牲稳定性不划算。
---
### Q-4 「接手」「退出协作」是否收进三点菜单(收则移除第三行按钮)?
> ### ✅ **结论:收进菜单,但第三行 `grab-btn` / `leave-btn` 保留不动 —— 双入口并存。**
**这与架构建议("本次不动,保持第三行按钮,菜单只放 4 项")有分歧,理由如下:**
| 论据 | 说明 |
|---|---|
| **① 右键路径的完整性** | D-3 已定右键并存。坐席右键条目时,鼠标位置是任意的——若菜单里没有「接手」,坐席仍需**移动鼠标到第三行那个小 link 按钮**才能接手。这让右键快捷方式在最需要它的同事会话场景下失效,自相矛盾 |
| **② 移除第三行按钮才是真损失** | 第三行 grab/leave 是 `v-if` **数据驱动的常显 link**,零 hover 成本、一次点击即达。三点菜单需 hover→点击→点击共 3 步。**移除它是明确的效率退化**,不能为了"避免重复"而做 |
| **③ "同一动作两个入口"不是缺陷** | 这与 §3.2 中栏保留转接是同一条原则:**高频/关键动作允许多入口**,只要共用同一 handler。真正的缺陷是两个入口**行为不一致**,而非入口数量 |
| **④ 变更面反而更小** | 保留第三行按钮 = 不动既有 DOM,只在菜单工厂里多产出两个 MenuItem。移除按钮才需要改 `ConversationItem.vue:116-136` 并回归测试 |
**约束**:两个入口必须 `emit` 同一事件(`grab` / `leave`),走同一 store action。**禁止**在菜单里另写一份逻辑。
**验收要求**:同事会话条目上,第三行「接手」link 与菜单「接手」项必须同时可见、同时消失(受同一 `can_grab` 条件驱动)。
---
### Q-5 「结单」是否进左栏菜单?
> ### ✅ **结论:不进。维持中栏唯一入口。**
| 论据 | 说明 |
|---|---|
| **① 路径错位** | 结单需走摘要确认 Dialog(`ChatArea.vue:448-502`),坐席要在弹窗里确认/编辑会话摘要。**在左栏一个 24px 的三点按钮里触发一个需要阅读与编辑的模态流程**,是明确的路径错位 |
| **② 语义归属** | 结单属「对话过程动作组」(转接/摇人/结单),与置顶/代办的"列表管理"性质不同。§3.2 已确立该分组,结单进左栏会破坏刚刚建立的分组一致性 |
| **③ 结单前需要看对话内容** | 结单意味着"问题已解决",这个判断必须基于中栏的对话内容。坐席不可能在左栏扫视时判断某条会话可以结单——**动作发生地必然是中栏** |
| **④ 误操作成本高** | 结单会触发员工端确认流程(`pending_close` 态)。在密集列表中一个 hover 出现的小菜单里放置这种动作,误触成本过高 |
| **⑤ 用户未提** | 需求原文只提 4 个操作,不做需求扩张 |
---
## 9. 需求池(Requirements Pool
### P0 — Must have
| # | 需求 |
|---|---|
| P0-1 | 新建 `WorkItemActionMenu.vue` 单例菜单壳:fixed/Teleport 定位、视口翻转、Esc / 点击外部 / 滚动 / resize / 切换会话 关闭、z-index ≥ 2000、danger 项样式 |
| P0-2 | 新建 `useConversationMenuItems.ts`:完整实现 §5 矩阵,含动态文案翻转与 disabled 计算,**附单元测试覆盖 §5.1 的 A–G 七个实例** |
| P0-3 | `ConversationItem.vue` 新增三点按钮:§4.1 全部规格(同栅格位淡入淡出、24×24、hover/`.is-menu-open`/`:focus-visible` 显示、`@click.stop` |
| P0-4 | `ConversationItem.vue` 新增 `@contextmenu.prevent.stop`,与三点按钮共用同一菜单项定义 |
| P0-5 | `ConversationList.vue` 持有单例菜单实例,按 action 路由到对应 store action |
| P0-6 | `UserInfoBar.vue` 删除接单/置顶/代办 3 个按钮,**保留转接** |
| P0-7 | 修复既有缺陷:`stores/conversation.ts` 中 4 个操作的失败分支由 `console.error` 改为 `ElMessage.error` 明确文案(403 场景必须有用户可见反馈) |
| P0-8 | 转接坐席选择 Dialog 组件化,中栏与左栏共用 |
### P1 — Should have
| # | 需求 |
|---|---|
| P1-1 | 引入 z-index token`--z-dropdown: 2000` / `--z-modal: 3000` / `--z-toast: 9000`),替换菜单相关硬编码 |
| P1-2 | 菜单 `role="menu"` / 项 `role="menuitem"`,三点按钮用原生 `<button>``el-button`(可 Tab 聚焦、Enter/Space 激活) |
| P1-3 | 历史分区三点按钮禁用态(§5.1 F 的处理) |
| P1-4 | 转接 Dialog 支持坐席姓名搜索 |
### P2 — Nice to have
| # | 需求 |
|---|---|
| P2-1 | 菜单 ↑↓ 方向键导航 + Enter 确认(可挂接既有 `useKeyboardShortcuts.ts` |
| P2-2 | 将 `MessageItem.vue` 的自研右键菜单迁移到 `WorkItemActionMenu`,顺带修复其 5 项缺陷(无边界翻转 / z-index 1000 偏低 / 关闭时机不全 / 无 a11y / 无单例互斥) |
| P2-3 | 中栏按钮删除做成配置开关,支持 2 周内灰度回滚 |
| P2-4 | 为转接注册全局快捷键(如 `Ctrl+Shift+T` |
---
## 10. 验收标准
### 10.1 功能验收
| # | 验收项 | 通过标准 |
|---|---|---|
| A-1 | 三点按钮显隐 | 默认不可见;hover 条目出现且缩略头像淡出;**条目高度与各元素位置零位移** |
| A-2 | 点击不误触 | 点击三点按钮**不切换当前会话**(当前打开的会话保持不变) |
| A-3 | 右键一致性 | 同一条目上,右键菜单项与三点菜单项**逐项完全一致**(项数、顺序、文案、禁用态) |
| A-4 | 菜单不被裁剪 | 左栏**第一条**与**最后一条**会话的菜单完整可见,不被侧栏或滚动容器裁掉;视口下缘不足时向上翻转 |
| A-5 | 矩阵正确性 | §5.1 的 A–G 七个实例逐一验证,菜单项与预期完全一致 |
| A-6 | 403 拦截 | 同事会话菜单中**不出现**置顶/代办项;构造非法请求时前端有明确错误提示 |
| A-7 | 单例互斥 | 打开条目 A 的菜单后,右键条目 B,A 的菜单自动关闭,同屏只有一个菜单 |
| A-8 | 关闭时机 | Esc / 点击外部 / 滚动列表 / 窗口 resize / 切换会话 —— 五种情况菜单均关闭 |
| A-9 | 中栏改造 | 中栏 actions 区仅剩 转接 / 摇人 / 结单;**转接路径仍为 3 步** |
| A-10 | 转接双入口 | 中栏转接与左栏菜单转接打开**同一个 Dialog**,行为一致 |
| A-11 | 接手双入口 | 同事会话条目第三行「接手」link 与菜单「接手」项同时可见/同时消失,点击效果相同 |
| A-12 | 状态即时反映 | 置顶/代办后条目 📌/📋 图标即时出现或消失,列表按新权重重排 |
### 10.2 架构验收(防返工)
| # | 验收项 | 通过标准 |
|---|---|---|
| B-1 | 单例 | 全局 DOM 中 `.work-item-action-menu` 实例数恒 ≤ 1,**不随会话条目数增长** |
| B-2 | 壳零业务 | `WorkItemActionMenu.vue``import` 不含任何 store / api / `Conversation` 类型 |
| B-3 | 工厂可单测 | `useConversationMenuItems.ts` 为纯函数,可脱离组件树单测 |
| B-4 | DOM 归属 | 菜单 DOM 的 `parentElement``body` 或使用 `position: fixed` |
### 10.3 明确不作为验收依据
- ❌ toast 成功提示(沿用 PRD-011 §6 Phase 0 的验收原则)
- ❌ 原型截图(原型是设计依据,不是实现验收标准)
---
## 11. 风险登记
| 编号 | 风险 | 等级 | 缓解 |
|---|---|---|---|
| **R-1** | 转接若被误删将造成 3→6 步路径退化 | 🔴 高 → 🟢 已缓解 | D-2 决定中栏保留转接;若评审推翻,必须同时落地 P2-4 快捷键 |
| **R-2** | v1.7 若沿用 v1.6 低保真基线,菜单落位设计与实现对不上 | 🔴 高 → 🟢 已缓解 | D-1 改以 v1.3 为基线并回填真实字段 |
| **R-3** | z-index 冲突(项目现有 `100/999/1000/1200/3000/9999/999999` 混用,EP popper 默认 2000+ | 🟡 中 | P1-1 引入 token;优先用 EP popover 交由其管理 |
| **R-4** | 菜单可见性与后端权限不一致 → 403。`pin`/`todo``update:own` | 🟡 中 | §5 矩阵前端严格拦截 + P0-7 失败提示 |
| **R-5** | 复制 `MessageItem.vue` 右键实现,带入其 5 项缺陷 | 🟡 中 | 强制走 `WorkItemActionMenu` 新壳,禁止复制粘贴 |
| **R-6** | 未来虚拟滚动导致条目内菜单错位 | 🟡 中 → 🟢 已缓解 | D-4 本次即采用单例架构 |
| **R-7** | 三点按钮与缩略头像争位导致布局抖动 | 🟢 低 | D-5 同栅格位淡入淡出,不改盒模型;A-1 专项验收 |
| **R-8** | 右键在 VDI / 远程桌面 / 触摸屏环境下不可靠 | 🟢 低 | 三点按钮为主入口,右键仅为快捷方式,降级不影响可用性。**需宋献确认坐席实际办公环境(架构 Q-7)** |
| **R-9** | 坐席习惯迁移成本:老坐席习惯在中栏点置顶 | 🟢 低 | 变更公告 + 首次使用时的引导气泡(可选);P2-3 保留灰度回滚 |
---
## 12. 明确不在本次范围
- 中栏「摇人」「结单」的任何改动
- 待办条目(审批/工单)进入左栏 —— 属 PRD-011 Phase 2
- 左栏虚拟滚动 —— 属 PRD-011 U-5(本次仅在架构上预留兼容)
- 会话列表排序权重调整
- 后端任何改动(4 个端点及权限装饰器均已就绪,无需变更)
- 键盘方向键导航(列为 P2-1,不阻塞本次)
---
## 13. 待需求方(宋献)评审确认清单
| # | 待确认项 | 本 PRD 的默认取值 | 若推翻的影响 |
|---|---|---|---|
| **C-1** | 中栏**保留转接**是否接受?(原需求是 4 个全删) | 保留(D-2) | 推翻则转接路径 3→6 步,必须补 P2-4 快捷键,工期 +0.3 人日 |
| **C-2** | 是否接受**右键并存**? | 接受(D-3) | 推翻则去掉 P0-4,省 0.3–0.5 人日,但熟练坐席效率损失 |
| **C-3** | 是否接受 v1.7 **回填左栏真实字段**的额外工作量? | 接受(D-1) | 推翻则原型不可作为开发依据 |
| **C-4** | 坐席实际办公环境是否存在 **VDI / 远程桌面 / 触摸屏**? | 假定为普通 PC 浏览器 | 若有 VDI,右键可靠性下降,需重新评估 C-2 |
| **C-5** | 历史会话条目的三点按钮:**禁用态**还是**完全不渲染**? | 禁用态(§5.1 F) | 视觉偏好项,成本相同 |
| **C-6** | 备选形态:是否需要看「中栏 3 按钮收进中栏自己的『⋯』菜单」的 A/B 对比? | 未在 v1.7 中呈现 | 若需要,v1.7 需追加一版对比图 |
| **C-7** | Q-4 结论(接手/退出**双入口**,不移除第三行按钮)是否认可? | 双入口 | 若要求单入口,需明确保留哪一个 |
---
## 14. 工期估算
> 粗估,用于排序参考,不作为承诺。基于架构报告 §4.1 的分解并按本 PRD 结论调整。
| 阶段 | 内容 | 估时 |
|---|---|---|
| S1 | `WorkItemActionMenu.vue` 单例壳 | 1.0 人日 |
| S2 | `useConversationMenuItems.ts` + 单测(§5 矩阵) | 0.5 人日 |
| S3 | `ConversationItem` 三点按钮 + 右键触发 | 0.5 人日 |
| S4 | `ConversationList` 单例接线 + action 路由 | 0.5 人日 |
| S5 | 中栏删 3 留转接 | 0.2 人日 |
| S6 | 转接 Dialog 组件化(Q-3+ 中栏复用 | 0.5 人日 |
| S7 | P0-7 失败反馈修复 | 0.2 人日 |
| S8 | a11yP1-2+ 键盘导航(P2-1 | 0.3 人日(可 P2 单排) |
| — | **合计** | **3.43.7 人日** |
---
## 15. 变更记录
| 日期 | 版本 | 变更内容 | 变更人 |
|---|---|---|---|
| 2026-08-09 | v1.0 | 创建。基于架构核查报告 v1.0 与宋献口述需求,固化 D-1~D-6 六项决策;产出菜单项可见性矩阵(§5)与七实例推演;给出 Q-3/Q-4/Q-5 三项自决结论(Q-4 与架构建议存在分歧并说明理由);定义 `WorkItemActionMenu` + `useConversationMenuItems` 组件边界与 PRD-011 Phase 2 兼容检查表;登记 R-1~R-9 风险与 C-1~C-7 待确认项 | 许清楚(产品经理) |
</content>
</invoke>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long