facc04aa65
本提交为 .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-*/
747 lines
40 KiB
Markdown
747 lines
40 KiB
Markdown
# 工具栏统一设计 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-实现方案)
|
||
2. [框架选型](#2-框架选型)
|
||
3. [文件列表及相对路径](#3-文件列表及相对路径)
|
||
4. [数据结构和接口(类图)](#4-数据结构和接口类图)
|
||
5. [程序调用流程(时序图)](#5-程序调用流程时序图)
|
||
6. [任务列表(有序、含依赖关系)](#6-任务列表有序含依赖关系)
|
||
7. [依赖包列表](#7-依赖包列表)
|
||
8. [共享知识(跨文件约定)](#8-共享知识跨文件约定)
|
||
9. [待明确事项](#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,会出现"线上无坐席入口"的事故窗口 |
|
||
| **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-online` prop(v1.2 透传契约不变)。
|
||
- **B2**:InputBar.vue 的 emoji / 文件 / 语音 3 个按钮行为完全不变(仅位置从底部回退到 InputBar 内上方居中)。
|
||
- **B3**:`store.shakeAgent()` / `cancelQueue()` / `reopenCurrentConversation()` / `closeCurrentConversation()` 4 个 action 全部保留。
|
||
- **B4**:`inputBarCallAgentState.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.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. 数据结构和接口(类图)
|
||
|
||
```mermaid
|
||
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 后唯一的坐席入口点击流程
|
||
|
||
```mermaid
|
||
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 旁路化)
|
||
|
||
```mermaid
|
||
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) |
|
||
| **负责方** | 工程师(前端) |
|
||
|
||
**详细步骤**:
|
||
|
||
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-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`)。
|
||
|
||
2. **Step 1.2 — 坐席头像按钮恢复**:
|
||
- 在工具栏内、语音按钮**之后**插入 1 个 `<button class="input-bar__call-agent-btn">` 节点:
|
||
```html
|
||
<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 处 import:`computeCallAgentBtnText` / `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-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-btn` 6 态 modifier 命名)。
|
||
- 恢复 `.agent-badge` 5 色徽标样式(online=绿/urgent=红/waiting=橙/offline=灰/end=蓝)。
|
||
- 恢复 `slideUp` keyframes(删除 `slideDown`,InputBar.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-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` |
|
||
| **负责方** | 工程师(前端) |
|
||
|
||
**详细步骤**:
|
||
|
||
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 个 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)。
|
||
- 保留 `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-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` |
|
||
| **负责方** | 工程师(前端) |
|
||
|
||
**详细步骤**:
|
||
|
||
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 — 不删除的清单**:
|
||
- 保留 `IntegrationZone` 的 `callAgent` emit 已经被删除(D2),但 QueueCapsule 的 3 个 emit 仍存在。
|
||
- 保留 `callAgentState` 和 `guideText` 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-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-conversation` 2 个 emit,IntegrationZone 仍转发 `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 态按钮) |
|
||
| **依赖** | T01(InputBar.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-D1:InputBar.test.ts 中 `V13_TOOLBAR_BUTTONS` 长度为 4(包含 'agent')。
|
||
- [ ] REQ-D2:InputBar.test.ts 中新增 `computeAgentBadgeClass` 5 色映射测试 5-6 个用例。
|
||
- [ ] REQ-D3:InputBar.test.ts 中新增 `callAgentBtnClass` / `callAgentBtnIcon` / `callAgentBtnText` 6 态测试 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 验证) |
|
||
| **负责方** | 架构师(高见远)+ 工程师 |
|
||
|
||
**详细步骤**:
|
||
|
||
1. **Step 5.1 — 跨文件 import 审查**:
|
||
- 验证 `ChatPanel.vue` 不再 import `handleDirectCall` 相关依赖。
|
||
- 验证 `InputBar.vue` import 5 个 helper(`computeCallAgentState` / `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.canReopen` → `computeCallAgentState` → `InputBar.callAgentState` → `callAgentBtnClass/Icon/Text/Title` 的数据流不断裂。
|
||
- 验证坐席动作从 `InputBar.handleCallAgentClick` → `store.shakeAgent/cancelQueue/reopenCurrentConversation` → 后端 API → store state → 响应式更新 InputBar 工具栏徽标。
|
||
|
||
4. **Step 5.4 — 测试覆盖审查**:
|
||
- `InputBar.test.ts` PASS(4 按钮 + 5 色徽标 + 6 态按钮 + handleCallAgentClick 路由)
|
||
- `inputBarCallAgentState.test.ts` PASS(6 态计算 + v1.2y Bug 修复)
|
||
- `integrationZoneLogic.test.ts` PASS(按钮 class / 引导语 class / callAction 映射)
|
||
- `inputBarGuideText.test.ts` PASS(4 种场景 → v1.3.5 后仅 2 场景生效)
|
||
- `QueueCapsule.test.ts` PASS(36 用例)
|
||
- `conversation-v1.2.test.ts` PASS(store 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 任务依赖图
|
||
|
||
```mermaid
|
||
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 基线锁定。
|
||
|
||
```yaml
|
||
# 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 内部按钮 → **直接调 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`)。
|
||
|
||
**理由**:
|
||
|
||
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.2(T01 已覆盖)
|
||
- **Q2**:不新建 IntegrationZone.test.ts(沿用现有 integrationZoneLogic.test.ts)
|
||
- **Q4**:helper 文件保留(按主理人指示,6 个月后评估)
|
||
|
||
---
|
||
|
||
## 评审签字
|
||
|
||
- [ ] 架构师:高见远
|
||
- [ ] 产品经理:许清楚
|
||
- [ ] 测试负责人:(待评审)
|
||
- [ ] 主理人:Duckula
|