Files
wecom_it_smart_desk/docs/02-技术文档/01-架构设计/架构-REQ-会话-001-工具栏统一设计-v1.3.md
T
Simon facc04aa65 chore: docs 结构整改 + compose 双目录对齐(合并重建提交)
本提交为 .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-*/
2026-08-07 22:31:32 +08:00

40 KiB
Raw Blame History

工具栏统一设计 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. 实现方案
  2. 框架选型
  3. 文件列表及相对路径
  4. 数据结构和接口(类图)
  5. 程序调用流程(时序图)
  6. 任务列表(有序、含依赖关系)
  7. 依赖包列表
  8. 共享知识(跨文件约定)
  9. 待明确事项

1. 实现方案

1.1 总体思路(两步走)

PRD 目标单一:删除 IntegrationZone.vue 中的"人工坐席"按钮(带锁图标 🔒 那个),让 H5 用户端坐席入口唯一化(仅 InputBar 工具栏头像按钮)。

但实际仓库现状偏离基线,需要两个连续的还原+删除动作

  1. Step A — 还原:本地 InputBar.vue 当前是 commit f9c25147 留下的"v1.3 错误实施"状态(3 按钮 + 工具栏在 InputBar 底部),与线上 v1.2 不一致。需要先还原到 v1.2 形态4 按钮 + 工具栏在 InputBar 内上方居中 + 坐席头像带 5 色徽标)。
  2. 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,会出现"线上无坐席入口"的事故窗口
D2InputBar 坐席按钮直接调 store 副作用(不 emit 给 ChatPanel 沿用 f9c25147 commit 的"v1.3.4 单入口"设计;emit 链路需要 ChatPanel 中转,与"工具栏按钮为唯一入口"的轻量化目标冲突
D3ChatPanel 父组件仅清理 @call-agent 监听器 + handleDirectCall 函数 @cancel-queue / @end-conversation / @reopen-conversation 仍由 QueueCapsule 透传触发(Q3 限定范围)
D4IntegrationZone 模板仅删除 1 个 <button class="call-agent-btn"> 节点 QueueCapsule + 引导语 + 容器样式全部保留(PRD REQ-07/08
D5IntegrationZone 4 个 emits 仅删除 callAgent,保留其他 3 个 cancelQueue / endConversation / reopenConversation 仍由 QueueCapsule 透传
D6helper 文件(inputBarCallAgentState.ts / integrationZoneLogic.ts / inputBarGuideText.ts)不动 Q4 暂搁,6 个月后评估
D7InputBar.test.ts 重写而非追加 当前测试是 v1.3 错误实施"3 按钮 + slideDown"的契约,需要回退到 v1.2"4 按钮 + slideUp"的契约

1.3 边界与不变量

  • B1IntegrationZone.vue 的 QueueCapsule 子组件继续接收 :agent-online propv1.2 透传契约不变)。
  • B2InputBar.vue 的 emoji / 文件 / 语音 3 个按钮行为完全不变(仅位置从底部回退到 InputBar 内上方居中)。
  • B3store.shakeAgent() / cancelQueue() / reopenCurrentConversation() / closeCurrentConversation() 4 个 action 全部保留。
  • B4inputBarCallAgentState.computeCallAgentState 6 态优先级与 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.24 按钮工具栏 + 工具栏 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.vuesrc/frontend-h5/src/components/chat/inputBarCallAgentState.ts(仅 import)、src/frontend-h5/src/components/chat/integrationZoneLogic.ts(仅 import
负责方 工程师(前端)

详细步骤

  1. 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-handleinput-bar__rowemoji-panelinput-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)。
  2. 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)。
  3. Step 1.3 — script 调整

    • 恢复以下 5 个 computed(从 f9c25147 commit 的反向 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 处 importcomputeCallAgentBtnText / computeCallAgentBtnTitle / computeIntegrationBtnClass / computeCallAction / CallAgentState
    • 移除 void callAgentState 标注(InputBar.vue:319)。
  4. 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-filterInputBar.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-btn 6 态 modifier 命名)。
    • 恢复 .agent-badge 5 色徽标样式(online=绿/urgent=红/waiting=橙/offline=灰/end=蓝)。
    • 恢复 slideUp keyframes(删除 slideDownInputBar.vue:991-1002)。
  5. Step 1.5 — 验证

    • pnpm vue-tsc --noEmit 0 错误。
    • 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-A1InputBar.vue 模板包含 4 个按钮(emoji / 文件 / 语音 / 坐席头像)。
  • REQ-A2:工具栏位于 input-bar__row 之后v1.2 位置:InputBar 内上方居中)。
  • REQ-A3:坐席头像按钮可见 agent-badge 子节点,class 随 5 色切换。
  • REQ-A4vue-tsc 0 错误。
  • REQ-A5pnpm 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
负责方 工程师(前端)

详细步骤

  1. 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 + 引导语"。
  2. Step 2.2 — Script 删除

    • 删除 defineEmits<{ (e: 'callAgent'): void ... }> 中的 callAgent 声明(IntegrationZone.vue:102)。
    • 保留 cancelQueue / endConversation / reopenConversation 3 个 emit(仍由 QueueCapsule 透传)。
    • 删除 btnText / btnIcon / btnTitle / btnClass 4 个 computedIntegrationZone.vue:126, 131, 136, 141)。
    • 删除 handleClick() 函数(IntegrationZone.vue:167-186)。
    • 清理 importIntegrationZone.vue:83-91):
      • 删除 computeIntegrationBtnText / computeIntegrationBtnIcon / computeIntegrationBtnTitle / computeIntegrationBtnClass(无消费者)
      • 保留 computeIntegrationGuideClass / computeCallAction(虽然 computeCallAction 也无消费者,但保留以备未来重新引入按钮时复用 — 标注 void 抑制 noUnusedLocals)。
      • 保留 IntegrationZoneProps type import。
  3. Step 2.3 — Style 删除

    • 删除 .integration-zone .call-agent-btn 容器样式(IntegrationZone.vue:213-232)。
    • 删除 .integration-zone .call-agent-btn--disabled/--active/--urgent/--waiting/--end/--reopen 6 个 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* 样式。
  4. Step 2.4 — 验证

    • pnpm vue-tsc --noEmit 0 错误。
    • 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-B1IntegrationZone.vue 模板仅渲染 2 个元素(QueueCapsule + 引导语)。
  • REQ-B2IntegrationZone.vue 浏览器开发者工具中无 <button class="call-agent-btn"> DOM 节点。
  • REQ-B3IntegrationZone.vue 的 defineEmits 仅声明 3 个事件(cancelQueue / endConversation / reopenConversation)。
  • REQ-B4vue-tsc 0 错误。
  • REQ-B5integrationZoneLogic.test.ts 36 用例仍 PASS(不删除任何用例)。

T03 — 清理 ChatPanel.vue 中对 IntegrationZone 已失效的事件监听器

维度 内容
任务 ID T03
任务名 清理 ChatPanel.vue 中 @call-agent="handleDirectCall" 监听器 + handleDirectCall 函数(保守范围)
依赖 T02IntegrationZone 坐席按钮已删除)
优先级 P1
涉及文件 src/frontend-h5/src/components/chat/ChatPanel.vue
负责方 工程师(前端)

详细步骤

  1. Step 3.1 — Template 清理

    • 删除 ChatPanel.vue:116@call-agent="handleDirectCall" 监听器。
    • 保留 ChatPanel.vue:117-119 的 3 个监听器(@cancel-queue / @end-conversation / @reopen-conversation),因为它们仍由 QueueCapsule 透传触发。
  2. Step 3.2 — Script 清理

    • 删除 handleDirectCall() 函数(ChatPanel.vue:339-360)。
    • 保留 handleCancelQueue() / handleEndConversation() / handleReopenConversation() 3 个函数(仍被 QueueCapsule 透传触发)。
    • 更新文件头注释 ChatPanel.vue:9-11,标记"v1.3 已删除 handleDirectCall 监听器"。
  3. Step 3.3 — 不删除的清单

    • 保留 IntegrationZonecallAgent emit 已经被删除(D2),但 QueueCapsule 的 3 个 emit 仍存在。
    • 保留 callAgentStateguideText 2 个 computed(仍透传给 IntegrationZone 的 QueueCapsule + 引导语)。
    • 保留 computeCallAgentState import(仍用于 callAgentState computed)。
    • 保留 computeGuideText import(仍用于 guideText computed)。
  4. Step 3.4 — 验证

    • pnpm vue-tsc --noEmit 0 错误。
    • 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-C1ChatPanel.vue 模板中无 @call-agent 监听器。
  • REQ-C2ChatPanel.vue script 中无 handleDirectCall 函数定义。
  • REQ-C3ChatPanel.vue 模板仍保留 3 个 QueueCapsule 透传监听器。
  • REQ-C4vue-tsc 0 错误。

设计说明(为何不删除全部 4 个监听器):QueueCapsule 内部仍触发 cancel-queue / reopen-conversation 2 个 emitIntegrationZone 仍转发 end-conversation 1 个 emit 给 ChatPanel。3 个监听器在 v1.3 后仍然有效,不应作为"代码冗余"清理。


T04 — 补充 InputBar.test.ts 中 v1.3 专项测试

维度 内容
任务 ID T04
任务名 重写 InputBar.test.ts 为 v1.3 = v1.2 的契约测试(4 按钮 + 5 色徽标映射 + slideUp 动画 + 6 态按钮)
依赖 T01InputBar.vue 已恢复 v1.2 形态)
优先级 P0
涉及文件 src/frontend-h5/src/components/chat/InputBar.test.ts
负责方 QA + 工程师

详细步骤

  1. 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)。
  2. 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 之前)。
  3. 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 行为)
  4. Step 4.4 — 新增 6 态按钮 contract 测试

    • 验证 callAgentBtnClass 包含正确的 modifier key(如 call-agent-btn--active / --urgent / --waiting / --end / --reopen / --disabled)。
    • 验证 callAgentBtnIcon 在 6 态下分别返回 🔒 / 🎧 / 🚨 / / 📴 / 🔄
    • 验证 callAgentBtnText 在 6 态下分别返回 "人工坐席" / "人工坐席" / "人工坐席" / "排队取消" / "结束咨询" / "重新打开"。
  5. Step 4.5 — 新增 handleCallAgentClick 路由测试

    • state=active → 调 store.shakeAgent()
    • state=urgent → 调 store.shakeAgent()
    • state=waiting → 调 store.cancelQueue()
    • state=reopen → 调 store.reopenCurrentConversation()
    • state=end → 不调(实际由顶部按钮走)
    • state=disabled → 不响应
  6. Step 4.6 — 验证

    • pnpm vitest run InputBar.test.ts 全 PASS(预计 50-55 个用例,包含 v1.2 沿用 + v1.3 新增 5 色徽标 + 6 态按钮 + handleCallAgentClick 路由)。

验收标准

  • REQ-D1InputBar.test.ts 中 V13_TOOLBAR_BUTTONS 长度为 4(包含 'agent')。
  • REQ-D2InputBar.test.ts 中新增 computeAgentBadgeClass 5 色映射测试 5-6 个用例。
  • REQ-D3InputBar.test.ts 中新增 callAgentBtnClass / callAgentBtnIcon / callAgentBtnText 6 态测试 18 个用例。
  • REQ-D4InputBar.test.ts 中新增 handleCallAgentClick 路由测试 5-6 个用例。
  • REQ-D5:所有用例 PASS(无失败、无 skip)。

T05 — 全局一致性审查

维度 内容
任务 ID T05
任务名 全局一致性审查(IS_PASS: YES/NO
依赖 T01、T02、T03、T04
优先级 P0
涉及文件 全 chat 模块(grep 验证)
负责方 架构师(高见远)+ 工程师

详细步骤

  1. Step 5.1 — 跨文件 import 审查

    • 验证 ChatPanel.vue 不再 import handleDirectCall 相关依赖。
    • 验证 InputBar.vue import 5 个 helpercomputeCallAgentState / computeCallAgentBtnText / computeCallAgentBtnTitle / computeIntegrationBtnClass / computeCallAction)。
    • 验证 IntegrationZone.vue import 仍包含 QueueCapsule + computeIntegrationGuideClass
  2. 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 应输出非空。
  3. Step 5.3 — 数据流审查

    • 验证坐席状态从 store.agentOnline / store.canCallAgent / store.canReopencomputeCallAgentStateInputBar.callAgentStatecallAgentBtnClass/Icon/Text/Title 的数据流不断裂。
    • 验证坐席动作从 InputBar.handleCallAgentClickstore.shakeAgent/cancelQueue/reopenCurrentConversation → 后端 API → store state → 响应式更新 InputBar 工具栏徽标。
  4. Step 5.4 — 测试覆盖审查

    • InputBar.test.ts PASS4 按钮 + 5 色徽标 + 6 态按钮 + handleCallAgentClick 路由)
    • inputBarCallAgentState.test.ts PASS6 态计算 + v1.2y Bug 修复)
    • integrationZoneLogic.test.ts PASS(按钮 class / 引导语 class / callAction 映射)
    • inputBarGuideText.test.ts PASS4 种场景 → v1.3.5 后仅 2 场景生效)
    • QueueCapsule.test.ts PASS36 用例)
    • conversation-v1.2.test.ts PASSstore 4 个 action
  5. 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 关键约定

  1. 坐席入口唯一化v1.3 后 H5 端坐席入口为 InputBar 工具栏坐席头像按钮(图片头像 + 5 色徽标)。IntegrationZone 中的旧"人工坐席"按钮已完全删除(无 DOM 节点、无 emit、无样式)。
  2. 6 态按钮位置v1.3 = v1.2 形态,6 态按钮在 InputBar 工具栏最右侧emoji / 文件 / 语音 / 坐席),不是在 IntegrationZone 容器中。
  3. 数据流统一:坐席按钮 6 态计算由 InputBar.vue 内部完成(基于 inputBarCallAgentState.computeCallAgentState + storeToRefs(store)),不依赖 IntegrationZone 的 prop 透传。IntegrationZone 的 callAgentState prop 仅用于 QueueCapsule 内部降级判定。
  4. Action 调用路径:坐席按钮直接调 store.shakeAgent() / cancelQueue() / reopenCurrentConversation()不经 ChatPanel 中转D2 决策)。end 态例外:实际由 ChatPanel 顶部"⏏"按钮走,因为 v1.2 P1 修复已让 InputBar 工具栏坐席按钮在 serving 态保持 disabled。
  5. helper 文件保留inputBarCallAgentState.ts / integrationZoneLogic.ts / inputBarGuideText.ts 三个 helper 文件完整保留(Q4 暂搁),未来若整合区恢复坐席按钮可直接复用。
  6. 事件冒泡规则
    • InputBar 坐席按钮 → 不 emit,直接调 store
    • QueueCapsule 内部按钮 → 直接调 storereopenCurrentConversation+ 同步 emitcancel-queue / reopen-conversation)给 IntegrationZone 透传
    • IntegrationZone → 仅透传 QueueCapsule 的 emitcancelQueue / 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.vuev1.3 恢复)
computeCallAgentBtnTitle(state, agentOnline) inputBarCallAgentState.ts:99 (state, agentOnline) => string InputBar.vuev1.3 恢复)
computeIntegrationBtnClass(state) integrationZoneLogic.ts:129 (state) => Record<string, boolean> InputBar.vuev1.3 恢复,沿用 .call-agent-btn--* 命名)
computeIntegrationGuideClass(text) integrationZoneLogic.ts:144 (text) => Record<string, boolean> IntegrationZone.vue(保留)
computeCallAction(state) integrationZoneLogic.ts:160 (state) => EmitEvent | 'none' InputBar.vuev1.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 用 PiniadefineStore + 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)。

理由

  1. QueueCapsule 内部仍直接调 store.reopenCurrentConversation(),但 QueueCapsule 的 reopen-conversation emit 仍被 IntegrationZone 透传给 ChatPanel(虽然 ChatPanel 的 handleReopenConversation 调用是幂等的)。
  2. QueueCapsule 的 cancel-queue emit 仍被 IntegrationZone 透传给 ChatPanel。
  3. IntegrationZone 容器未来可能新增"end 态按钮"(产品 P2 提案),提前保留 end-conversation 监听器可避免未来重新添加。

请主理人确认:是否接受"保守范围"清理,还是一次性清掉全部 4 个无效监听器(同时移除 IntegrationZone 的 3 个 emit 透传)?

风险评估

  • 保守范围:低风险(保留的 3 个监听器仍由 QueueCapsule 触发,是有效代码)。
  • 全清理:中风险(需要确认 QueueCapsule 内部是否完全独立于 ChatPanel 的 3 个 handler,否则可能漏改)。

其他无

其余 4 个 PRD 待确认问题(Q1/Q2/Q4)主理人已答:

  • Q1:恢复 InputBar.vue 到 v1.2T01 已覆盖)
  • Q2:不新建 IntegrationZone.test.ts(沿用现有 integrationZoneLogic.test.ts
  • Q4:helper 文件保留(按主理人指示,6 个月后评估)

评审签字

  • 架构师:高见远
  • 产品经理:许清楚
  • 测试负责人:(待评审)
  • 主理人:Duckula