本提交为 .git 对象库损坏后的重建提交,内容等价于原先三个本地提交 (5e2fd4c2 / 57a53c98 / 5d7e1873)的累积结果,未做任何额外改动。 一、docs 结构整改(整改 #14) 根因:重构时新结构为 untracked 文件,执行 git stash(未带 -u)未纳入, 随后 git reset 拉回 HEAD 旧 tracked 树,导致旧树复活、新旧两棵目录 树并存于 docs/,共 791 文件、双分类体系冲突。 修复动作: - b2 同名异主题文件改名迁移保全 9 个 - C 类 39 个孤立文件按主题正确归类 - A/B1 类 222 个重复文件删除(新结构已有内容副本) - 9 个旧独有空目录删除 - 270 处内部引用按 verified 映射改写 - 整改记录 #14 登记于 04-运维文档/部署运维 结果:docs 791 → 569 文件,顶层仅规范 8 类 + 治理文件,单树恢复。 残留:约 20 处指向从未存在文件的陈旧死链,归入独立文档卫生任务。 二、compose 双目录对齐(消除踩坑 A) - docker-compose.yml:nginx 前端挂载全部由根目录 frontend-*/dist 改为 src/frontend-*/dist(h5 / agent / admin / terminal) - docker-compose.dev.yml:dev 服务 build context 与卷同步改 src/ - 效果:本地 docker compose up 不再把根目录 stale dist 挂回, 与线上一致,分叉隐患消除(已 docker compose config 校验通过) 防复发铁律: - 重构须提交;仓库修复须 git stash -u 或先 commit - 新结构须 git add 并提交,避免再次 untracked 复活 - H5 改动只动 src/frontend-h5/,禁改根目录遗留 frontend-*/
40 KiB
工具栏统一设计 v1.3 — 删除 IntegrationZone 坐席按钮 — 架构设计
版本: v1.3 日期: 2026-08-04 作者: 高见远(架构师) 需求编号: REQ-会话-001 PRD 基线:
docs/01-产品文档/02-会话管理/PRD-REQ-会话-001-工具栏统一设计-v1.3.md原型基线:docs/01-产品文档/02-会话管理/原型-REQ-会话-001-工具栏统一设计v1.3-删除IntegrationZone坐席.html状态: [待评审]
目录
1. 实现方案
1.1 总体思路(两步走)
PRD 目标单一:删除 IntegrationZone.vue 中的"人工坐席"按钮(带锁图标 🔒 那个),让 H5 用户端坐席入口唯一化(仅 InputBar 工具栏头像按钮)。
但实际仓库现状偏离基线,需要两个连续的还原+删除动作:
- Step A — 还原:本地
InputBar.vue当前是 commitf9c25147留下的"v1.3 错误实施"状态(3 按钮 + 工具栏在 InputBar 底部),与线上 v1.2 不一致。需要先还原到 v1.2 形态(4 按钮 + 工具栏在 InputBar 内上方居中 + 坐席头像带 5 色徽标)。 - Step B — 删除:在 InputBar 处于 v1.2 形态后,再按 PRD 删
IntegrationZone.vue中的坐席按钮。
历史教训锚点:v1.3 之前的"删除 InputBar 工具栏头像 + 移动到 InputBar 底部"曾被紧急回滚到 v1.2。本次 v1.3 真实意图 = 恢复 v1.2 形态 + 删 IntegrationZone 冗余入口,绝不可与中间错误实施混为一谈。
1.2 关键设计决策
| 决策 | 理由 |
|---|---|
| D1:先恢复 InputBar 再删 IntegrationZone(顺序强制) | 若 InputBar 还未恢复就先删 IntegrationZone,会出现"线上无坐席入口"的事故窗口 |
D2:InputBar 坐席按钮直接调 store 副作用(不 emit 给 ChatPanel) |
沿用 f9c25147 commit 的"v1.3.4 单入口"设计;emit 链路需要 ChatPanel 中转,与"工具栏按钮为唯一入口"的轻量化目标冲突 |
D3:ChatPanel 父组件仅清理 @call-agent 监听器 + handleDirectCall 函数 |
@cancel-queue / @end-conversation / @reopen-conversation 仍由 QueueCapsule 透传触发(Q3 限定范围) |
D4:IntegrationZone 模板仅删除 1 个 <button class="call-agent-btn"> 节点 |
QueueCapsule + 引导语 + 容器样式全部保留(PRD REQ-07/08) |
D5:IntegrationZone 4 个 emits 仅删除 callAgent,保留其他 3 个 |
cancelQueue / endConversation / reopenConversation 仍由 QueueCapsule 透传 |
| D6:helper 文件(inputBarCallAgentState.ts / integrationZoneLogic.ts / inputBarGuideText.ts)不动 | Q4 暂搁,6 个月后评估 |
| D7:InputBar.test.ts 重写而非追加 | 当前测试是 v1.3 错误实施"3 按钮 + slideDown"的契约,需要回退到 v1.2"4 按钮 + slideUp"的契约 |
1.3 边界与不变量
- B1:IntegrationZone.vue 的 QueueCapsule 子组件继续接收
:agent-onlineprop(v1.2 透传契约不变)。 - B2:InputBar.vue 的 emoji / 文件 / 语音 3 个按钮行为完全不变(仅位置从底部回退到 InputBar 内上方居中)。
- B3:
store.shakeAgent()/cancelQueue()/reopenCurrentConversation()/closeCurrentConversation()4 个 action 全部保留。 - B4:
inputBarCallAgentState.computeCallAgentState6 态优先级与 P1 Bug 修复(waiting + !agentOnline → disabled)不变。 - B5:容器组件层级
ChatPanel > IntegrationZone > QueueCapsule不变。
2. 框架选型
无任何框架变更,沿用 v1.2 基线:
| 维度 | 选型 | 版本 | 不变原因 |
|---|---|---|---|
| 前端框架 | Vue 3 (Composition API + <script setup>) |
^3.4 | H5 用户端基线 |
| 状态管理 | Pinia | ^2.1 | store 单例(useConversationStore) |
| UI 组件库 | Vant 4 | ^4.8 | H5 移动端基线 |
| 构建工具 | Vite | ^5.0 | H5 项目基线 |
| 样式预处理 | 原生 CSS + scoped(无 Sass/Less) | — | 与现有代码风格一致 |
| 测试框架 | Vitest(纯函数契约测试) | ^1.0 | 沿用 helper 文件的测试范式 |
| 类型系统 | TypeScript | ^5.0 | <script setup lang="ts"> |
架构模式:本任务不涉及架构模式变更(仍是 Component + Store + Helper 三层)。仅在前端 H5 端做局部 DOM/逻辑调整。
3. 文件列表及相对路径
| 文件 | 改动 | 涉及阶段 | 备注 |
|---|---|---|---|
src/frontend-h5/src/components/chat/InputBar.vue |
还原到 v1.2(4 按钮工具栏 + 工具栏 InputBar 内上方居中 + 坐席头像按钮 + 5 色徽标) | T01 | 从 f9c25147 commit 的反向 patch 或手动重做 |
src/frontend-h5/src/components/chat/IntegrationZone.vue |
删除 1 个 <button class="call-agent-btn"> 节点 + 1 个 callAgent emit + 4 个相关 computed + 1 个 handleClick 函数 + 1 处 .integration-zone .call-agent-btn* 样式 |
T02 | QueueCapsule + 引导语 + 容器样式全部保留 |
src/frontend-h5/src/components/chat/ChatPanel.vue |
清理 @call-agent="handleDirectCall" 监听器 + handleDirectCall 函数(保留 3 个 QueueCapsule 透传监听器) |
T03 | Q3 限定范围:仅 call-agent |
src/frontend-h5/src/components/chat/InputBar.test.ts |
重写为 v1.3 = v1.2 的契约测试(4 按钮 + 5 色徽标映射 + slideUp 动画) | T04 | 现有测试是错误实施的"3 按钮"契约 |
src/frontend-h5/src/components/chat/integrationZoneLogic.test.ts |
不动 | — | 纯函数不变(Q4) |
src/frontend-h5/src/components/chat/integrationZoneLogic.ts |
不动 | — | helper 保留 |
src/frontend-h5/src/components/chat/inputBarCallAgentState.ts |
不动 | — | helper 保留 |
src/frontend-h5/src/components/chat/inputBarGuideText.ts |
不动 | — | helper 保留 |
src/frontend-h5/src/components/chat/QueueCapsule.vue |
不动 | — | 子组件不变 |
src/frontend-h5/src/components/chat/QueueCapsule.test.ts |
不动 | — | 子组件测试不变(36 用例已 PASS) |
src/frontend-h5/src/stores/conversation.ts |
不动 | — | 4 个 action 全部保留 |
src/frontend-h5/src/stores/integrationZone.ts |
不动 | — | shiftHours 班次能力预留 |
新增文件:无。 删除文件:无。
4. 数据结构和接口(类图)
classDiagram
direction LR
class ChatPanel {
+useConversationStore() ConversationStore
+useEmployeeStore() EmployeeStore
+useIntegrationZoneStore() IntegrationZoneStore
-callAgentState: ComputedRef~CallAgentState~
-guideText: ComputedRef~string|null~
-showExitConfirm: Ref~boolean~
+scrollToBottom() Promise~void~
+handleExitWithEvaluation() Promise~void~
+handleCancelQueue() Promise~void~ // 保留
+handleEndConversation() Promise~void~ // 保留
+handleReopenConversation() Promise~void~ // 保留
-handleDirectCall() Promise~void~ // ⚠️ v1.3 删除(无效监听器)
}
class IntegrationZone {
+props: IntegrationZoneProps
-emits: callAgent // ⚠️ v1.3 删除
-emits: cancelQueue // 保留
-emits: endConversation // 保留
-emits: reopenConversation // 保留
-btnText: ComputedRef~string~ // ⚠️ v1.3 删除
-btnIcon: ComputedRef~string~ // ⚠️ v1.3 删除
-btnTitle: ComputedRef~string~ // ⚠️ v1.3 删除
-btnClass: ComputedRef~Record~ // ⚠️ v1.3 删除
-guideClass: ComputedRef~Record~ // 保留
-handleClick() void // ⚠️ v1.3 删除
-render QueueCapsule // 保留
-render guide div // 保留
}
class InputBar {
+URGENT_KEYWORDS: RegExp[]
-hasUrgentKeywords: ComputedRef~boolean~
-showEmojiPanel: Ref~boolean~
-inputText: Ref~string~
-callAgentState: ComputedRef~CallAgentState~ // ⚠️ v1.3 重新绑定到模板
-callAgentBtnClass: ComputedRef~Record~ // 🆕 v1.3 恢复
-callAgentBtnIcon: ComputedRef~string~ // 🆕 v1.3 恢复
-callAgentBtnText: ComputedRef~string~ // 🆕 v1.3 恢复
-callAgentBtnTitle: ComputedRef~string~ // 🆕 v1.3 恢复
-voiceBtnState: ComputedRef
-voiceBtnClass: ComputedRef~Record~
-showVoiceButton: ComputedRef~boolean~
+handleEmoji() void
+handleFile() void
+handleVoiceToggle() Promise~void~
+handleCallAgentClick() Promise~void~ // 🆕 v1.3 恢复
+handleSend() Promise~void~
}
class QueueCapsule {
+props: QueueCapsuleProps
-emits: cancel-queue
-emits: reopen-conversation
+handleClick() void
}
class ConversationStore {
+agentOnline: boolean
+currentConversation: Conversation
+canReopen: boolean
+canCallAgent: boolean
+messages: Message[]
+shakeAgent() Promise~string~
+cancelQueue() Promise~void~
+closeCurrentConversation() Promise~void~
+reopenCurrentConversation() Promise~void~
}
class inputBarCallAgentState {
<<module>>
+computeCallAgentState(ctx) CallAgentState
+computeCallAgentBtnText(state) string
+computeCallAgentBtnTitle(state, agentOnline) string
}
class integrationZoneLogic {
<<module>>
+computeIntegrationBtnText(state) string
+computeIntegrationBtnIcon(state) string
+computeIntegrationBtnTitle(state, agentOnline) string
+computeIntegrationBtnClass(state) Record
+computeIntegrationGuideClass(text) Record
+computeCallAction(state) EmitEvent|'none'
}
class inputBarGuideText {
<<module>>
+computeGuideText(ctx) string|null
+isGuideTextWarn(text) boolean
}
ChatPanel "1" --> "1" IntegrationZone : 父→子
ChatPanel "1" --> "1" InputBar : 父→子
IntegrationZone "1" --> "1" QueueCapsule : 父→子
ChatPanel ..> ConversationStore : uses
ChatPanel ..> inputBarCallAgentState : computeCallAgentState
ChatPanel ..> inputBarGuideText : computeGuideText
IntegrationZone ..> integrationZoneLogic : btn/guide/click
InputBar ..> inputBarCallAgentState : 6 态 + 文案 + title
InputBar ..> integrationZoneLogic : btnClass + callAction
InputBar ..> ConversationStore : shakeAgent
QueueCapsule ..> ConversationStore : reopenCurrentConversation
图例:🆕 = v1.3 恢复;⚠️ = v1.3 删除;保留/不动 = 默认无标记。
4.1 v1.3 删除的接口(IntegrationZone)
| 接口 | 类型 | 位置 | 删除原因 |
|---|---|---|---|
callAgent emit |
(e: 'callAgent'): void |
IntegrationZone.vue:101-102 |
按钮已删除,emit 不再有触发源 |
btnText computed |
ComputedRef<string> |
IntegrationZone.vue:126 |
依赖 computeIntegrationBtnText(无消费者) |
btnIcon computed |
ComputedRef<string> |
IntegrationZone.vue:131 |
同上 |
btnTitle computed |
ComputedRef<string> |
IntegrationZone.vue:136 |
同上 |
btnClass computed |
ComputedRef<Record<string, boolean>> |
IntegrationZone.vue:141 |
同上 |
handleClick() function |
(): void |
IntegrationZone.vue:167-186 |
无按钮可点击 |
call-agent-btn template node |
<button class="call-agent-btn"> |
IntegrationZone.vue:34-43 |
PRD REQ-01 |
.integration-zone .call-agent-btn* styles |
CSS (~110 行) | IntegrationZone.vue:213-321 |
PRD REQ-04 |
4.2 v1.3 恢复的接口(InputBar)
| 接口 | 类型 | 说明 |
|---|---|---|
callAgentBtnClass |
ComputedRef<Record<string, boolean>> |
6 态 modifier 映射(沿用 .call-agent-btn--* 命名) |
callAgentBtnIcon |
ComputedRef<string> |
6 态 emoji(🔒 / 🎧 / 🚨 / ⏳ / 📴 / 🔄) |
callAgentBtnText |
ComputedRef<string> |
6 态文案("人工坐席" / "排队取消" / "结束咨询" / "重新打开") |
callAgentBtnTitle |
ComputedRef<string> |
6 态 title(沿用 computeCallAgentBtnTitle) |
agent-badge DOM 节点 |
<div class="agent-badge is-{state}"> |
5 色徽标(online/urgent/waiting/offline/end) |
handleCallAgentClick() |
(): Promise<void> |
直接调 store.shakeAgent() / cancelQueue() / reopenCurrentConversation()(按 state 路由) |
5. 程序调用流程(时序图)
5.1 v1.3 后唯一的坐席入口点击流程
sequenceDiagram
autonumber
actor User as H5 用户
participant Toolbar as InputBar 工具栏<br/>(emoji/file/voice/👤坐席)
participant Logic as inputBarCallAgentState.ts
participant Store as useConversationStore
participant Toast as Vant Toast
participant API as 后端 API
User->>Toolbar: 点击坐席头像按钮
activate Toolbar
Toolbar->>Logic: computeCallAgentState(ctx)
Logic-->>Toolbar: CallAgentState (active/urgent/waiting/end/reopen/disabled)
alt state === 'disabled'
Toolbar-->>User: 无响应(按钮 :disabled)
else state === 'active' or 'urgent'
Toolbar->>Store: shakeAgent()
Store->>API: POST /api/v1/conversations/{id}/shake-agent
alt 成功
API-->>Store: 200 OK + funny_phrase
Store->>Toast: showToast(funny_phrase)
else 失败
API-->>Store: 4xx/5xx
Store->>Toast: showToast('呼叫失败,请稍后重试')
end
else state === 'waiting'
Toolbar->>Store: cancelQueue()
Store->>API: POST /api/v1/conversations/{id}/cancel-queue
API-->>Store: 200 OK
Store->>Toast: showToast('已取消排队')
else state === 'reopen'
Toolbar->>Store: reopenCurrentConversation()
Store->>API: POST /api/v1/conversations/{id}/reopen
API-->>Store: 200 OK
else state === 'end'
Toolbar->>Store: closeCurrentConversation()
Note over Toolbar,Store: ⚠️ v1.3 实际: 走 ChatPanel 顶部"结束会话"按钮<br/>(头像右侧红色 ⏏ 按钮),<br/>因为 serving 态工具栏坐席按钮=disabled (v1.2 P1 修复)
end
deactivate Toolbar
5.2 v1.3 后事件流全景(IntegrationZone 旁路化)
sequenceDiagram
autonumber
participant User
participant InputBar
participant ChatPanel
participant IntegrationZone as IntegrationZone<br/>(无坐席按钮)
participant QueueCapsule
participant Store
participant Backend
rect rgb(240, 248, 255)
Note over User,Backend: 🆕 v1.3 真实事件流(InputBar 工具栏头像为唯一坐席入口)
User->>InputBar: 点击坐席头像
InputBar->>InputBar: handleCallAgentClick()
InputBar->>Store: shakeAgent() / cancelQueue() / reopenCurrentConversation()
Store->>Backend: API call
Backend-->>Store: 200 OK
Store-->>User: toast + UI 更新
end
rect rgb(255, 248, 248)
Note over User,Backend: 🔻 v1.3 已断开的路径(IntegrationZone 坐席按钮已删除)
User-xIntegrationZone: 点击 IntegrationZone 坐席按钮
Note right of IntegrationZone: ⚠️ DOM 节点已删除,<br/>无 click handler 可触发
end
rect rgb(248, 255, 248)
Note over User,Backend: ✅ 仍生效的路径(QueueCapsule 内部触发)
User->>QueueCapsule: 点击"取消排队"按钮
QueueCapsule->>Store: cancelQueue()(v1.2y 行为:内部直接调)
QueueCapsule-->>ChatPanel: emit('cancel-queue')(被 ChatPanel 接收)
Note right of ChatPanel: ChatPanel.handleCancelQueue()<br/>仅作为兜底(QueueCapsule 已<br/>直接调 store,重复调是幂等的)
end
5.3 InputBar 6 态按钮颜色映射(v1.3 恢复)
| state | modifier class | icon | text | title | 触发 action |
|---|---|---|---|---|---|
disabled |
call-agent-btn--disabled |
🔒 | 人工坐席 | "再多描述几句话即可激活" | 无(按钮 :disabled) |
active |
call-agent-btn--active |
🎧 | 人工坐席 | "点击呼叫人工坐席" | store.shakeAgent() |
urgent |
call-agent-btn--urgent |
🚨 | 人工坐席 | "检测到紧急问题,直接呼叫人工坐席" | store.shakeAgent() |
waiting |
call-agent-btn--waiting |
⏳ | 排队取消 | "点击取消排队" | store.cancelQueue() |
end |
call-agent-btn--end |
📴 | 结束咨询 | "点击结束本次人工咨询" | store.closeCurrentConversation()(v1.3 实际由顶部按钮走) |
reopen |
call-agent-btn--reopen |
🔄 | 重新打开 | "24小时内可重新打开此会话" | store.reopenCurrentConversation() |
6. 任务列表(有序、含依赖关系)
T01 — 恢复 InputBar.vue 到 v1.2 状态
| 维度 | 内容 |
|---|---|
| 任务 ID | T01 |
| 任务名 | 恢复 InputBar.vue 到 v1.2 形态(4 按钮工具栏 + InputBar 内上方居中 + 坐席头像 + 5 色徽标) |
| 依赖 | 无 |
| 优先级 | P0 |
| 涉及文件 | src/frontend-h5/src/components/chat/InputBar.vue、src/frontend-h5/src/components/chat/inputBarCallAgentState.ts(仅 import)、src/frontend-h5/src/components/chat/integrationZoneLogic.ts(仅 import) |
| 负责方 | 工程师(前端) |
详细步骤:
-
Step 1.1 — 模板调整:
- 删除当前 v1.3 错误实施的
<div class="input-bar__toolbar-wrapper">(位于 InputBar 底部,InputBar.vue:115-198)。 - 在
<div class="input-bar__row">之后插入 v1.2 形态的工具栏(4 按钮:emoji / 文件 / 语音 / 坐席头像)。 - 工具栏容器使用
class="input-bar__toolbar",位置在输入行下方、表情面板下方(v1.2 DOM 顺序:resize-handle→input-bar__row→emoji-panel→input-bar__toolbar)。 - 删除 SVG defs 中的"v1.3 高端 SVG 图标"(
g-emoji/g-doc-*/g-mic-*等,InputBar.vue:27-65),恢复 v1.2 的简单 emoji 字符(😊 / 📎 / 🎤)+ agent 按钮的图片头像(沿用原型agent-avatar-v0.8.jpg)。 - 表情按钮的激活态 class 从
is-active-emoji保持不变;移除 emoji 面板的slideDown动画,恢复slideUp动画(from translateY(20px) → 0)。
- 删除当前 v1.3 错误实施的
-
Step 1.2 — 坐席头像按钮恢复:
- 在工具栏内、语音按钮之后插入 1 个
<button class="input-bar__call-agent-btn">节点:<button class="input-bar__call-agent-btn" :class="callAgentBtnClass" :title="callAgentBtnTitle" :aria-label="callAgentBtnTitle" :disabled="callAgentState === 'disabled'" @click="handleCallAgentClick" > <span class="input-bar__call-agent-btn__icon">{{ callAgentBtnIcon }}</span> <span class="input-bar__call-agent-btn__text">{{ callAgentBtnText }}</span> <span class="agent-badge" :class="agentBadgeClass"></span> </button> - 移除错误注释
<!-- 🆕 v1.3:坐席按钮已彻底移除(由 IntegrationZone 接管 6 态 agent 入口) -->(InputBar.vue:196)。
- 在工具栏内、语音按钮之后插入 1 个
-
Step 1.3 — script 调整:
- 恢复以下 5 个 computed(从
f9c25147commit 的反向 patch 中取):callAgentState(已存在,需重新绑定到模板 + 移除void callAgentState标注)callAgentBtnClass: ComputedRef<Record<string, boolean>>(基于computeIntegrationBtnClass)callAgentBtnIcon: ComputedRef<string>(内联 6 行 switch,沿用 IntegrationZone 逻辑)callAgentBtnText: ComputedRef<string>(基于computeCallAgentBtnText)callAgentBtnTitle: ComputedRef<string>(基于computeCallAgentBtnTitle)agentBadgeClass: ComputedRef<Record<string, boolean>>(5 色徽标:is-online/urgent/waiting/offline/end)
- 恢复
handleCallAgentClick()函数(按computeCallAction路由到store.shakeAgent/cancelQueue/reopenCurrentConversation)。 - 恢复 5 处 import:
computeCallAgentBtnText/computeCallAgentBtnTitle/computeIntegrationBtnClass/computeCallAction/CallAgentState。 - 移除
void callAgentState标注(InputBar.vue:319)。
- 恢复以下 5 个 computed(从
-
Step 1.4 — 样式调整:
- 删除
.input-bar__toolbar-wrapper样式(InputBar.vue:828-834)。 - 删除
.input-bar__toolbar的"v1.3 玻璃拟态 + 多层 box-shadow + radial 渐变"样式(InputBar.vue:836-876),恢复 v1.2 的简单 flex 居中。 - 删除
.input-bar__btn的 v1.3 高端 SVG 适配样式(width: 40px圆形玻璃 + backdrop-filter,InputBar.vue:881-925),恢复 v1.2 的 32px 简单圆形按钮。 - 删除
.input-bar__btn--voice.is-voice-recording/.is-voice-recognized的 3 态渐变样式(InputBar.vue:942-971),恢复 v1.2 的简单 🎤 字符按钮。 - 恢复
.input-bar__call-agent-btn样式(沿用 IntegrationZone 的.call-agent-btn6 态 modifier 命名)。 - 恢复
.agent-badge5 色徽标样式(online=绿/urgent=红/waiting=橙/offline=灰/end=蓝)。 - 恢复
slideUpkeyframes(删除slideDown,InputBar.vue:991-1002)。
- 删除
-
Step 1.5 — 验证:
pnpm vue-tsc --noEmit0 错误。grep -n "callAgentBtnClass\|callAgentBtnIcon\|callAgentBtnText\|callAgentBtnTitle\|agentBadgeClass\|input-bar__call-agent-btn" src/frontend-h5/src/components/chat/InputBar.vue输出非空。grep -n "void callAgentState\|v1.3.*坐席按钮已彻底移除" src/frontend-h5/src/components/chat/InputBar.vue输出为空。
验收标准:
- REQ-A1:InputBar.vue 模板包含 4 个按钮(emoji / 文件 / 语音 / 坐席头像)。
- REQ-A2:工具栏位于
input-bar__row之后(v1.2 位置:InputBar 内上方居中)。 - REQ-A3:坐席头像按钮可见
agent-badge子节点,class 随 5 色切换。 - REQ-A4:vue-tsc 0 错误。
- REQ-A5:
pnpm vitest run InputBar.test.ts当前仍 PASS(错误实施的契约测试,不影响 v1.2 实现)。
T02 — 删除 IntegrationZone.vue 坐席按钮
| 维度 | 内容 |
|---|---|
| 任务 ID | T02 |
| 任务名 | 删除 IntegrationZone.vue 中的"人工坐席"按钮(template / script / style / emits 4 处) |
| 依赖 | T01(确认 InputBar.vue 已恢复 v1.2 形态) |
| 优先级 | P0 |
| 涉及文件 | src/frontend-h5/src/components/chat/IntegrationZone.vue |
| 负责方 | 工程师(前端) |
详细步骤:
-
Step 2.1 — Template 删除:
- 删除
IntegrationZone.vue:30-65中的<button class="call-agent-btn">节点(4 行结构 + 2 个<span>子节点)。 - 保留
QueueCapsule+guide div节点(不变)。 - 更新文件头注释
IntegrationZone.vue:5-15,标记"v1.3 已删除坐席按钮,仅保留 QueueCapsule + 引导语"。
- 删除
-
Step 2.2 — Script 删除:
- 删除
defineEmits<{ (e: 'callAgent'): void ... }>中的callAgent声明(IntegrationZone.vue:102)。 - 保留
cancelQueue/endConversation/reopenConversation3 个 emit(仍由 QueueCapsule 透传)。 - 删除
btnText/btnIcon/btnTitle/btnClass4 个 computed(IntegrationZone.vue:126, 131, 136, 141)。 - 删除
handleClick()函数(IntegrationZone.vue:167-186)。 - 清理 import(IntegrationZone.vue:83-91):
- 删除
computeIntegrationBtnText/computeIntegrationBtnIcon/computeIntegrationBtnTitle/computeIntegrationBtnClass(无消费者) - 保留
computeIntegrationGuideClass/computeCallAction(虽然computeCallAction也无消费者,但保留以备未来重新引入按钮时复用 — 标注void抑制 noUnusedLocals)。 - 保留
IntegrationZonePropstype import。
- 删除
- 删除
-
Step 2.3 — Style 删除:
- 删除
.integration-zone .call-agent-btn容器样式(IntegrationZone.vue:213-232)。 - 删除
.integration-zone .call-agent-btn--disabled/--active/--urgent/--waiting/--end/--reopen6 个 modifier 样式(IntegrationZone.vue:235-312)。 - 删除
.integration-zone .call-agent-btn__icon/.__text样式(IntegrationZone.vue:315-321)。 - 保留
.integration-zone容器 +.integration-zone :deep(.queue-capsule)+.integration-zone__guide*样式。
- 删除
-
Step 2.4 — 验证:
pnpm vue-tsc --noEmit0 错误。grep -n "call-agent-btn\|btnText\|btnIcon\|btnTitle\|btnClass\|handleClick\|callAgent" src/frontend-h5/src/components/chat/IntegrationZone.vue输出为空。grep -n "QueueCapsule\|guide" src/frontend-h5/src/components/chat/IntegrationZone.vue输出非空。pnpm vitest run integrationZoneLogic.test.ts仍 PASS(纯函数不变)。
验收标准:
- REQ-B1:IntegrationZone.vue 模板仅渲染 2 个元素(QueueCapsule + 引导语)。
- REQ-B2:IntegrationZone.vue 浏览器开发者工具中无
<button class="call-agent-btn">DOM 节点。 - REQ-B3:IntegrationZone.vue 的
defineEmits仅声明 3 个事件(cancelQueue / endConversation / reopenConversation)。 - REQ-B4:vue-tsc 0 错误。
- REQ-B5:integrationZoneLogic.test.ts 36 用例仍 PASS(不删除任何用例)。
T03 — 清理 ChatPanel.vue 中对 IntegrationZone 已失效的事件监听器
| 维度 | 内容 |
|---|---|
| 任务 ID | T03 |
| 任务名 | 清理 ChatPanel.vue 中 @call-agent="handleDirectCall" 监听器 + handleDirectCall 函数(保守范围) |
| 依赖 | T02(IntegrationZone 坐席按钮已删除) |
| 优先级 | P1 |
| 涉及文件 | src/frontend-h5/src/components/chat/ChatPanel.vue |
| 负责方 | 工程师(前端) |
详细步骤:
-
Step 3.1 — Template 清理:
- 删除
ChatPanel.vue:116的@call-agent="handleDirectCall"监听器。 - 保留
ChatPanel.vue:117-119的 3 个监听器(@cancel-queue/@end-conversation/@reopen-conversation),因为它们仍由 QueueCapsule 透传触发。
- 删除
-
Step 3.2 — Script 清理:
- 删除
handleDirectCall()函数(ChatPanel.vue:339-360)。 - 保留
handleCancelQueue()/handleEndConversation()/handleReopenConversation()3 个函数(仍被 QueueCapsule 透传触发)。 - 更新文件头注释
ChatPanel.vue:9-11,标记"v1.3 已删除 handleDirectCall 监听器"。
- 删除
-
Step 3.3 — 不删除的清单:
- 保留
IntegrationZone的callAgentemit 已经被删除(D2),但 QueueCapsule 的 3 个 emit 仍存在。 - 保留
callAgentState和guideText2 个 computed(仍透传给 IntegrationZone 的 QueueCapsule + 引导语)。 - 保留
computeCallAgentStateimport(仍用于 callAgentState computed)。 - 保留
computeGuideTextimport(仍用于 guideText computed)。
- 保留
-
Step 3.4 — 验证:
pnpm vue-tsc --noEmit0 错误。grep -n "handleDirectCall\|@call-agent" src/frontend-h5/src/components/chat/ChatPanel.vue输出为空。grep -n "handleCancelQueue\|handleEndConversation\|handleReopenConversation\|@cancel-queue\|@end-conversation\|@reopen-conversation" src/frontend-h5/src/components/chat/ChatPanel.vue输出非空。
验收标准:
- REQ-C1:ChatPanel.vue 模板中无
@call-agent监听器。 - REQ-C2:ChatPanel.vue script 中无
handleDirectCall函数定义。 - REQ-C3:ChatPanel.vue 模板仍保留 3 个 QueueCapsule 透传监听器。
- REQ-C4:vue-tsc 0 错误。
设计说明(为何不删除全部 4 个监听器):QueueCapsule 内部仍触发
cancel-queue/reopen-conversation2 个 emit,IntegrationZone 仍转发end-conversation1 个 emit 给 ChatPanel。3 个监听器在 v1.3 后仍然有效,不应作为"代码冗余"清理。
T04 — 补充 InputBar.test.ts 中 v1.3 专项测试
| 维度 | 内容 |
|---|---|
| 任务 ID | T04 |
| 任务名 | 重写 InputBar.test.ts 为 v1.3 = v1.2 的契约测试(4 按钮 + 5 色徽标映射 + slideUp 动画 + 6 态按钮) |
| 依赖 | T01(InputBar.vue 已恢复 v1.2 形态) |
| 优先级 | P0 |
| 涉及文件 | src/frontend-h5/src/components/chat/InputBar.test.ts |
| 负责方 | QA + 工程师 |
详细步骤:
-
Step 4.1 — 删除错误契约:
- 删除
V13_TOOLBAR_BUTTONS = ['emoji', 'file', 'voice'](InputBar.test.ts:118-119)。 - 删除
TOOLBAR_POSITION_INDEX = 5的"最末位置"断言(InputBar.test.ts:108)。 - 删除
V13_SLIDE_DIRECTION = 'down'(InputBar.test.ts:128)。
- 删除
-
Step 4.2 — 恢复 v1.2 契约:
- 恢复
V13_TOOLBAR_BUTTONS = ['emoji', 'file', 'voice', 'agent'](4 按钮)。 - 恢复
V12_TOOLBAR_BUTTONS = ['emoji', 'file', 'voice', 'agent'](v1.2 与 v1.3 相同,便于对比)。 - 恢复
V13_SLIDE_DIRECTION = 'up'(slideUp 动画)。 - 恢复
TOOLBAR_POSITION_INDEX = 2(工具栏在input-bar__row之后、emoji-panel之前)。
- 恢复
-
Step 4.3 — 新增 5 色徽标契约测试:
computeAgentBadgeClass(state)纯函数:active→{ 'is-online': true }urgent→{ 'is-urgent': true }waiting→{ 'is-waiting': true }disabled→{ 'is-offline': true }end→{ 'is-end': true }reopen→{ 'is-end': true }(reopen 复用 end 蓝色,PRD 沿用 v1.2 行为)
-
Step 4.4 — 新增 6 态按钮 contract 测试:
- 验证
callAgentBtnClass包含正确的 modifier key(如call-agent-btn--active/--urgent/--waiting/--end/--reopen/--disabled)。 - 验证
callAgentBtnIcon在 6 态下分别返回 🔒 / 🎧 / 🚨 / ⏳ / 📴 / 🔄。 - 验证
callAgentBtnText在 6 态下分别返回 "人工坐席" / "人工坐席" / "人工坐席" / "排队取消" / "结束咨询" / "重新打开"。
- 验证
-
Step 4.5 — 新增 handleCallAgentClick 路由测试:
- state=active → 调
store.shakeAgent() - state=urgent → 调
store.shakeAgent() - state=waiting → 调
store.cancelQueue() - state=reopen → 调
store.reopenCurrentConversation() - state=end → 不调(实际由顶部按钮走)
- state=disabled → 不响应
- state=active → 调
-
Step 4.6 — 验证:
pnpm vitest run InputBar.test.ts全 PASS(预计 50-55 个用例,包含 v1.2 沿用 + v1.3 新增 5 色徽标 + 6 态按钮 + handleCallAgentClick 路由)。
验收标准:
- REQ-D1:InputBar.test.ts 中
V13_TOOLBAR_BUTTONS长度为 4(包含 'agent')。 - REQ-D2:InputBar.test.ts 中新增
computeAgentBadgeClass5 色映射测试 5-6 个用例。 - REQ-D3:InputBar.test.ts 中新增
callAgentBtnClass/callAgentBtnIcon/callAgentBtnText6 态测试 18 个用例。 - REQ-D4:InputBar.test.ts 中新增
handleCallAgentClick路由测试 5-6 个用例。 - REQ-D5:所有用例 PASS(无失败、无 skip)。
T05 — 全局一致性审查
| 维度 | 内容 |
|---|---|
| 任务 ID | T05 |
| 任务名 | 全局一致性审查(IS_PASS: YES/NO) |
| 依赖 | T01、T02、T03、T04 |
| 优先级 | P0 |
| 涉及文件 | 全 chat 模块(grep 验证) |
| 负责方 | 架构师(高见远)+ 工程师 |
详细步骤:
-
Step 5.1 — 跨文件 import 审查:
- 验证
ChatPanel.vue不再 importhandleDirectCall相关依赖。 - 验证
InputBar.vueimport 5 个 helper(computeCallAgentState/computeCallAgentBtnText/computeCallAgentBtnTitle/computeIntegrationBtnClass/computeCallAction)。 - 验证
IntegrationZone.vueimport 仍包含QueueCapsule+computeIntegrationGuideClass。
- 验证
-
Step 5.2 — 跨文件函数引用审查:
grep -rn "handleDirectCall" src/frontend-h5/src/应输出为空。grep -rn "@call-agent" src/frontend-h5/src/components/chat/应输出为空。grep -rn "call-agent-btn" src/frontend-h5/src/components/chat/IntegrationZone.vue应输出为空;InputBar.vue应输出非空。
-
Step 5.3 — 数据流审查:
- 验证坐席状态从
store.agentOnline/store.canCallAgent/store.canReopen→computeCallAgentState→InputBar.callAgentState→callAgentBtnClass/Icon/Text/Title的数据流不断裂。 - 验证坐席动作从
InputBar.handleCallAgentClick→store.shakeAgent/cancelQueue/reopenCurrentConversation→ 后端 API → store state → 响应式更新 InputBar 工具栏徽标。
- 验证坐席状态从
-
Step 5.4 — 测试覆盖审查:
InputBar.test.tsPASS(4 按钮 + 5 色徽标 + 6 态按钮 + handleCallAgentClick 路由)inputBarCallAgentState.test.tsPASS(6 态计算 + v1.2y Bug 修复)integrationZoneLogic.test.tsPASS(按钮 class / 引导语 class / callAction 映射)inputBarGuideText.test.tsPASS(4 种场景 → v1.3.5 后仅 2 场景生效)QueueCapsule.test.tsPASS(36 用例)conversation-v1.2.test.tsPASS(store 4 个 action)
-
Step 5.5 — 浏览器实测:
- 登录 H5 → 打开新会话 → 确认 InputBar 工具栏 4 按钮(emoji / 文件 / 语音 / 坐席头像)正确显示
- 确认 IntegrationZone 容器仅渲染 QueueCapsule + 引导语(无坐席按钮)
- 浏览器开发者工具:搜索 "call-agent-btn" DOM 节点应输出为空
- 浏览器控制台无 Vue 警告、无 prop 类型错误
验收标准:
- REQ-E1:所有 grep 验证通过(详见 Step 5.1-5.3)。
- REQ-E2:所有 vitest 测试通过。
- REQ-E3:浏览器实测无视觉异常、控制台无报错。
- REQ-E4:架构师签发 IS_PASS: YES(写在本任务清单末尾)。
6.x 任务依赖图
graph TD
T01["T01: 恢复 InputBar.vue<br/>到 v1.2 状态<br/>(4 按钮 + 坐席头像)"]
T02["T02: 删除 IntegrationZone.vue<br/>坐席按钮"]
T03["T03: 清理 ChatPanel.vue<br/>@call-agent 监听器"]
T04["T04: 重写 InputBar.test.ts<br/>v1.3 = v1.2 契约测试"]
T05["T05: 全局一致性审查<br/>(IS_PASS: YES/NO)"]
T01 --> T02
T02 --> T03
T01 --> T04
T01 --> T05
T02 --> T05
T03 --> T05
T04 --> T05
classDef p0 fill:#fee,stroke:#c00,color:#000
classDef p1 fill:#efe,stroke:#0a0,color:#000
class T01,T02,T04,T05 p0
class T03 p1
任务统计:5 个任务(满足 ≤5 硬性上限)。P0 = 4 个,P1 = 1 个。
7. 依赖包列表
无新增依赖。所有依赖已在 v1.2 基线锁定。
# package.json 锁定版本(无需变更)
dependencies:
vue: ^3.4.0 # Composition API + <script setup>
pinia: ^2.1.0 # store 单例
vant: ^4.8.0 # UI 组件库
@vueuse/core: ^10.0.0 # 工具 composables
socket.io-client: ^4.7.0 # WebSocket
axios: ^1.6.0 # HTTP
devDependencies:
vite: ^5.0.0
typescript: ^5.0.0
vitest: ^1.0.0
@vue/test-utils: ^2.4.0
vue-tsc: ^1.8.0
8. 共享知识(跨文件约定)
8.1 关键约定
- 坐席入口唯一化:v1.3 后 H5 端坐席入口仅为 InputBar 工具栏坐席头像按钮(图片头像 + 5 色徽标)。IntegrationZone 中的旧"人工坐席"按钮已完全删除(无 DOM 节点、无 emit、无样式)。
- 6 态按钮位置:v1.3 = v1.2 形态,6 态按钮在 InputBar 工具栏最右侧(emoji / 文件 / 语音 / 坐席),不是在 IntegrationZone 容器中。
- 数据流统一:坐席按钮 6 态计算由
InputBar.vue内部完成(基于inputBarCallAgentState.computeCallAgentState+storeToRefs(store)),不依赖 IntegrationZone 的 prop 透传。IntegrationZone 的callAgentStateprop 仅用于 QueueCapsule 内部降级判定。 - Action 调用路径:坐席按钮直接调
store.shakeAgent() / cancelQueue() / reopenCurrentConversation(),不经 ChatPanel 中转(D2 决策)。end态例外:实际由 ChatPanel 顶部"⏏"按钮走,因为 v1.2 P1 修复已让 InputBar 工具栏坐席按钮在serving态保持 disabled。 - helper 文件保留:
inputBarCallAgentState.ts/integrationZoneLogic.ts/inputBarGuideText.ts三个 helper 文件完整保留(Q4 暂搁),未来若整合区恢复坐席按钮可直接复用。 - 事件冒泡规则:
- InputBar 坐席按钮 → 不 emit,直接调 store
- QueueCapsule 内部按钮 → 直接调 store(
reopenCurrentConversation)+ 同步 emit(cancel-queue/reopen-conversation)给 IntegrationZone 透传 - IntegrationZone → 仅透传 QueueCapsule 的 emit(
cancelQueue/endConversation/reopenConversation)给 ChatPanel
8.2 跨文件函数契约
| 函数 | 文件 | 签名 | 消费者 |
|---|---|---|---|
computeCallAgentState(ctx) |
inputBarCallAgentState.ts:63 |
(ctx: CallAgentStateContext) => CallAgentState |
InputBar.vue、ChatPanel.vue |
computeCallAgentBtnText(state) |
inputBarCallAgentState.ts:88 |
(state: CallAgentState) => string |
InputBar.vue(v1.3 恢复) |
computeCallAgentBtnTitle(state, agentOnline) |
inputBarCallAgentState.ts:99 |
(state, agentOnline) => string |
InputBar.vue(v1.3 恢复) |
computeIntegrationBtnClass(state) |
integrationZoneLogic.ts:129 |
(state) => Record<string, boolean> |
InputBar.vue(v1.3 恢复,沿用 .call-agent-btn--* 命名) |
computeIntegrationGuideClass(text) |
integrationZoneLogic.ts:144 |
(text) => Record<string, boolean> |
IntegrationZone.vue(保留) |
computeCallAction(state) |
integrationZoneLogic.ts:160 |
(state) => EmitEvent | 'none' |
InputBar.vue(v1.3 恢复,按结果路由 store action) |
computeGuideText(ctx) |
inputBarGuideText.ts:67 |
(ctx) => string | null |
ChatPanel.vue(透传给 IntegrationZone) |
8.3 编码风格约束
- 不使用 Sass/Less:所有样式为原生 CSS +
scoped属性。 - 不使用 TypeScript class:所有组件用
<script setup lang="ts">函数式写法。 - 不使用 Vuex:所有 store 用 Pinia(
defineStore+storeToRefs)。 - 不使用 prop drilling:跨组件状态通过 Pinia store 共享。
- helper 文件范式:纯函数 + 显式 ctx 入参,无 Vue/DOM 依赖,单测用
pnpm vitest run跑通。
8.4 测试范式(沿用 inputBarCallAgentState.ts 范式)
- 不直接 mount SFC:避免
@vue/test-utils依赖 - 纯函数复刻 + 断言:在测试文件中以纯函数复刻组件 computed 的语义,断言纯函数输出
- mock store / composables:使用
vi.fn()+vi.useFakeTimers()隔离副作用
9. 待明确事项
Q3 细化(仅 call-agent 清理 vs 全 4 个清理)
当前架构师建议(已写入 T03):保守范围,仅清理 @call-agent + handleDirectCall,保留其他 3 个监听器(@cancel-queue / @end-conversation / @reopen-conversation)。
理由:
- QueueCapsule 内部仍直接调
store.reopenCurrentConversation(),但 QueueCapsule 的reopen-conversationemit 仍被 IntegrationZone 透传给 ChatPanel(虽然 ChatPanel 的handleReopenConversation调用是幂等的)。 - QueueCapsule 的
cancel-queueemit 仍被 IntegrationZone 透传给 ChatPanel。 - IntegrationZone 容器未来可能新增"end 态按钮"(产品 P2 提案),提前保留
end-conversation监听器可避免未来重新添加。
请主理人确认:是否接受"保守范围"清理,还是一次性清掉全部 4 个无效监听器(同时移除 IntegrationZone 的 3 个 emit 透传)?
风险评估:
- 保守范围:低风险(保留的 3 个监听器仍由 QueueCapsule 触发,是有效代码)。
- 全清理:中风险(需要确认 QueueCapsule 内部是否完全独立于 ChatPanel 的 3 个 handler,否则可能漏改)。
其他无
其余 4 个 PRD 待确认问题(Q1/Q2/Q4)主理人已答:
- Q1:恢复 InputBar.vue 到 v1.2(T01 已覆盖)
- Q2:不新建 IntegrationZone.test.ts(沿用现有 integrationZoneLogic.test.ts)
- Q4:helper 文件保留(按主理人指示,6 个月后评估)
评审签字
- 架构师:高见远
- 产品经理:许清楚
- 测试负责人:(待评审)
- 主理人:Duckula