# 员工结束会话功能 - 技术方案 > **版本**: v1.3 > **日期**: 2026-07-31 > **关联PRD**: ../01-产品文档/02-会话管理/PRD-REQ-会话-001-员工结束会话-v1.3.md(§十一 v1.3 整合区增量) > **关联原型**: ../01-产品文档/02-会话管理/原型-REQ-会话-001-结束会话流程-v1.3.html §⑨ > **状态**: 已拍板(方案 A 5 项细节 2026-07-31 落地) --- ## 一、概述 本文档描述 v1.2 调整的技术实现方案。**核心变更**: 1. 操作按钮从 5 态(hidden/disabled/active/urgent/waiting)扩展为 **6 态**(移除 hidden / 恢复 end / 新增 reopen) 2. 新增 **4 种引导语**渲染逻辑 3. store 新增 `showHeaderExitBtn` 字段,**控制顶部退出按钮仅 AI 场景显示** 4. 双入口(顶部退出 + 操作按钮 end)**场景互斥**,从根源避免双入口混乱 5. 24h 重新打开按钮实现 > 沿用 v1.1:排队进度底部消息胶囊、右栏移除 queue section、清理 api/queue 和 api/quiz。 --- ## 二、技术架构 ### 2.1 现有代码结构(v1.2 基准) ``` frontend-h5/src/components/chat/ ├── InputBar.vue # 输入栏(含操作按钮 6 态 + 引导语)— v1.2 改造 ├── ChatPanel.vue # 对话区(含顶部退出按钮 v-show)— v1.2 改造 ├── MessageBubble.vue # 消息气泡(含"会话已关闭"系统消息样式) ├── EvaluationDialog.vue # 满意度评价弹窗 ├── ResolveConfirmCard.vue # 坐席结单确认卡片 └── ... 其他 frontend-h5/src/stores/ └── conversation.ts # 新增 showHeaderExitBtn 字段 + reopenSession action frontend-h5/src/components/assistant/ ├── RightPanel.vue # 右栏(v1.1 已移除 queue section)— v1.2 沿用 ├── QueueWaiting.vue # 标记弃用,保留文件以备未来恢复 └── ... ``` ### 2.2 关键现有代码(InputBar.vue:194-243 v1.1 现状) ```typescript type CallAgentState = 'hidden' | 'disabled' | 'active' | 'urgent' | 'waiting' ``` **v1.2 改造**:扩展为 6 态,详细见 §3.1。 ### 2.3 关键现有代码(ChatPanel.vue:59-65 顶部退出按钮) ```vue ``` **v1.2 改造**:增加 `v-show="store.showHeaderExitBtn"`,详见 §3.5。 --- ## 三、方案设计 ### 3.1 操作按钮 6 态状态机(v1.2 核心变更) **位置**:InputBar.vue 控件区第一层(行 78-88) **6 态定义**: | 状态值 | 触发条件 | 按钮文案 | 按钮图标 | 样式 | 可点击 | |--------|---------|---------|---------|------|--------| | `disabled` | 无会话 / AI<3 轮 / 坐席离线 / 会话过期 | 人工坐席 | 🔒 | 灰色禁用 | ❌ | | `active` | AI≥3 轮 / 无紧急词 | 人工坐席 | 🎧 | 绿色描边 | ✅ | | `urgent` | 检测到紧急关键词 | 人工坐席 | 🚨 | 红色脉冲 | ✅ | | `waiting` | 排队中 | 排队等待 | ⏳ | 橙色描边 | ✅(取消排队)| | `end` 🆕 | 坐席已接入(serving) | 结束咨询 | 📴 | 红色填充 | ✅(弹评价)| | `reopen` 🆕 | 会话已关闭 + 24h 内 | 重新打开 | 🔄 | 蓝色填充 | ✅(reopen API)| **v1.2 移除**: - ❌ `hidden` 态 — 完全移除,所有场景都有按钮呈现 **优先级顺序**(v1.2 修订): ``` end > reopen > waiting > active/urgent > disabled ``` ### 3.2 状态流转图 ``` ┌── (打开应用 / 重新进入) ↓ ┌──────────────────┐ │ disabled │ ← 无会话 │ "先说问题" │ └────────┬─────────┘ │ 用户输入 + AI ≥3 轮 ↓ ┌──────────────────┐ │ active │ ── 检测到紧急词 ──→ urgent │ "人工坐席" │ │ └────────┬─────────┘ │ │ 点击 / 紧急关键词 │ ↓ ↓ ┌──────────────────┐ ┌──────────────────┐ │ waiting │ ←─────────────│ urgent │ │ "排队等待" │ │ "人工坐席" │ └────────┬─────────┘ └──────────────────┘ │ 坐席接听 ↓ ┌──────────────────┐ │ end 🆕 │ │ "结束咨询" │ └────────┬─────────┘ │ 坐席挂断 / 用户点结束 ↓ (close + 评价) ┌──────────────────┐ │ reopen 🆕 │ ← 会话已关闭 + 24h 内 │ "重新打开" │ └────────┬─────────┘ │ 点击 reopen ↓ ┌──────────────────┐ │ active │ (回到正常会话流程) └──────────────────┘ ``` ### 3.3 store 新增字段(v1.2 关键) **文件**:`stores/conversation.ts` ```typescript // 新增:顶部退出按钮是否可见(仅 AI 会话显示) const showHeaderExitBtn = computed(() => { const conv = currentConversation.value if (!conv) return false // 无会话:隐藏 if (conv.status === 'resolved') return false // 会话已关闭:隐藏 // 人工咨询启动后(waiting / serving):隐藏 const isHumanActive = conv.status === 'waiting' || conv.status === 'serving' || !!queuePositionData.value // 排队中 if (isHumanActive) return false // AI 对话中:显示 return true }) // 新增:会话是否在 24h 重新打开窗口内 const canReopen = computed(() => { const conv = currentConversation.value if (!conv || conv.status !== 'resolved') return false if (!conv.resolved_at) return false const elapsed = Date.now() - new Date(conv.resolved_at).getTime() return elapsed < 24 * 60 * 60 * 1000 // 24h 内 }) // 新增:重新打开会话 action(调用 closing.ts 已有的 reopenConversation) async function reopenCurrentConversation(): Promise { const conv = currentConversation.value if (!conv || !canReopen.value) return const result = await reopenConversation(conv.conversation_id) // 更新 store,触发 UI 切回 active 态 if (result) { // ... 复用现有 openConversation 流程 } } ``` ### 3.4 组件改动 #### 3.4.1 InputBar.vue **改动 1:状态类型扩展** ```typescript // v1.1 type CallAgentState = 'hidden' | 'disabled' | 'active' | 'urgent' | 'waiting' // v1.2 type CallAgentState = 'disabled' | 'active' | 'urgent' | 'waiting' | 'end' | 'reopen' ``` **改动 2:状态计算逻辑** ```typescript const callAgentState = computed(() => { const conv = store.currentConversation if (!conv) return 'disabled' // v1.2: 无会话 = disabled(不再 hidden) // 🆕 会话已关闭 + 24h 内 → reopen if (conv.status === 'resolved' && store.canReopen) return 'reopen' // 🆕 坐席已接入 → end if (conv.status === 'serving') return 'end' // 🆕 顶部按钮可见性也通过 store.showHeaderExitBtn 暴露给父组件 if (conv.status === 'waiting') return 'waiting' // 紧急关键词检测 if (checkUrgentKeywords()) return 'urgent' // AI ≥3 轮 / canCallAgent if (store.canCallAgent) return 'active' // 兜底:disabled(含 AI<3 轮、坐席离线、过期等场景) return 'disabled' }) ``` **改动 3:按钮文案 + 图标扩展** ```typescript const callAgentBtnText = computed(() => { if (callAgentState.value === 'waiting') return '排队等待' if (callAgentState.value === 'end') return '结束咨询' // 🆕 if (callAgentState.value === 'reopen') return '重新打开' // 🆕 return '人工坐席' }) const callAgentBtnIcon = computed(() => { if (callAgentState.value === 'waiting') return '⏳' if (callAgentState.value === 'urgent') return '🚨' if (callAgentState.value === 'active') return '🎧' if (callAgentState.value === 'end') return '📴' // 🆕 if (callAgentState.value === 'reopen') return '🔄' // 🆕 return '🔒' }) ``` **改动 4:按钮样式扩展** ```typescript const callAgentBtnClass = computed(() => ({ 'call-agent-btn--disabled': callAgentState.value === 'disabled', 'call-agent-btn--active': callAgentState.value === 'active', 'call-agent-btn--urgent': callAgentState.value === 'urgent', 'call-agent-btn--waiting': callAgentState.value === 'waiting', 'call-agent-btn--end': callAgentState.value === 'end', // 🆕 'call-agent-btn--reopen': callAgentState.value === 'reopen', // 🆕 })) ``` **改动 5:点击行为扩展(end / reopen 分支)** ```typescript function handleCallAgent(): void { if (callAgentState.value === 'disabled') return // 🆕 end 态:触发结束会话流程(与顶部按钮共用 emit) if (callAgentState.value === 'end') { emit('end-conversation') return } // 🆕 reopen 态:调用 reopen API if (callAgentState.value === 'reopen') { store.reopenCurrentConversation() return } if (callAgentState.value === 'waiting') { emit('cancel-queue') return } // active / urgent → 呼叫坐席 emit('call-agent') } ``` **改动 6:emit 扩展** ```typescript const emit = defineEmits<{ (e: 'call-agent'): void (e: 'cancel-queue'): void (e: 'end-conversation'): void // 🆕(操作按钮 end 态专用,复用 handleExitWithEvaluation) }>() ``` **改动 7:新增引导语渲染** ```vue ``` **改动 8:样式新增(end / reopen)** ```scss // 🆕 end 态:红色填充(最显眼的"危险/结束"语义) .call-agent-btn--end { border-color: var(--color-danger); background: var(--color-danger); color: #fff; font-weight: 700; &:hover { background: #dc2626; } &:active { transform: scale(0.96); } } // 🆕 reopen 态:蓝色填充(中性"恢复"语义) .call-agent-btn--reopen { border-color: var(--info, #1989fa); background: var(--info-light, #ecf5ff); color: var(--info, #1989fa); &:hover { background: var(--info, #1989fa); color: #fff; } &:active { transform: scale(0.96); } } ``` #### 3.4.2 ChatPanel.vue(顶部按钮可见性) **改动 1:顶部按钮 v-show 绑定** ```vue