REQ-会话-001 员工结束会话: - PRD/原型 v1.0/v1.1/v1.2 归档为 .archive,现行收敛至单一 v1.3 - 技术方案 v1.2 归档,v1.3 为现行版本(位于 docs/02-技术文档/) - PRD v1.3 §六 关联文档表指向 v1.3 原型与技术方案实际路径 - 三份任务说明书 + BUG-003 + TC-用户-008 引用同步至 .archive/v1.3 REQ-用户-005 头像菜单退出: - PRD/原型/技术方案文件名 v1.0 -> v1.1,追平内容版本(铁律2) - 三件套 + 任务说明书 + TC 互引版本对齐,相关 PRD 指向会话-001 v1.3 其他: - 修复 4 处 UTF-8 乱码(U+FFFD) - 新增 TR-会话-001-结束会话-v1.3.2 端到端测试报告(单测 75/75 + 生产构建通过) - 整改记录 #9 写入 00-文档规范化整改记录.md 遗留: PRD v1.3「状态条永久显示」与代码 v1.3.5「状态条已删除」存在分歧,待产品拍板
29 KiB
员工结束会话功能 - 技术方案
版本: 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 调整的技术实现方案。核心变更:
- 操作按钮从 5 态(hidden/disabled/active/urgent/waiting)扩展为 6 态(移除 hidden / 恢复 end / 新增 reopen)
- 新增 4 种引导语渲染逻辑
- store 新增
showHeaderExitBtn字段,控制顶部退出按钮仅 AI 场景显示 - 双入口(顶部退出 + 操作按钮 end)场景互斥,从根源避免双入口混乱
- 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 现状)
type CallAgentState = 'hidden' | 'disabled' | 'active' | 'urgent' | 'waiting'
v1.2 改造:扩展为 6 态,详细见 §3.1。
2.3 关键现有代码(ChatPanel.vue:59-65 顶部退出按钮)
<button class="chat-panel__exit-btn" title="结束会话" @click="handleExitWithEvaluation">
<svg ...>...</svg>
</button>
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
// 新增:顶部退出按钮是否可见(仅 AI 会话显示)
const showHeaderExitBtn = computed<boolean>(() => {
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<boolean>(() => {
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<void> {
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:状态类型扩展
// v1.1
type CallAgentState = 'hidden' | 'disabled' | 'active' | 'urgent' | 'waiting'
// v1.2
type CallAgentState = 'disabled' | 'active' | 'urgent' | 'waiting' | 'end' | 'reopen'
改动 2:状态计算逻辑
const callAgentState = computed<CallAgentState>(() => {
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:按钮文案 + 图标扩展
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:按钮样式扩展
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 分支)
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 扩展
const emit = defineEmits<{
(e: 'call-agent'): void
(e: 'cancel-queue'): void
(e: 'end-conversation'): void // 🆕(操作按钮 end 态专用,复用 handleExitWithEvaluation)
}>()
改动 7:新增引导语渲染
<template>
<!-- 操作按钮 + 引导语 -->
<div class="input-bar__controls">
<button
v-if="callAgentState !== 'reopen'" <!-- 🆕 reopen 态不显示"人工坐席"图标按钮 -->
class="call-agent-btn"
:class="callAgentBtnClass"
...
>
<span class="call-agent-btn__icon">{{ callAgentBtnIcon }}</span>
<span class="call-agent-btn__text">{{ callAgentBtnText }}</span>
</button>
<!-- 🆕 引导语:4 种场景 -->
<div v-if="guideText" class="input-bar__guide" :class="guideClass">
{{ guideText }}
</div>
</div>
</template>
<script setup>
// 🆕 引导语计算
const guideText = computed<string | null>(() => {
const conv = store.currentConversation
const state = callAgentState.value
// 仅 disabled 态显示引导语(active/urgent/waiting/end/reopen 不显示)
// 注意:这里 state 是 CallAgentState 按钮态(含 'active'),不是会话 status
if (state !== 'disabled') return null
// 场景1:无会话
if (!conv) return '💡 先描述一下你遇到的问题,AI 助手会先帮你看看'
// 场景2:AI <3 轮(用 canCallAgent 反推)
// v1.2 P1 Bug 修复(QA 第 1 轮 2026-07-30):原代码误用 'active',
// 但 ConversationInfo.status 取值为 'ai_handling' | 'waiting' | 'serving' | 'resolved'
// (见 src/frontend-h5/src/api/conversation.ts:44),
// 导致引导语场景 2 永远不显示。正确应为 'ai_handling'。
if (conv.status === 'ai_handling' && !store.canCallAgent) {
return '请继续描述您的问题或需求'
}
// 场景3:坐席离线
if (!store.agentOnline) {
return '⚠️ 坐席当前离线,建议先用 AI 解答;如紧急可刷新重试'
}
// 场景4:会话过期(>24h)
if (conv.status === 'resolved' && !store.canReopen) {
return '⏰ 上一会话已过期,开始新对话吧'
}
return null
})
const guideClass = computed(() => ({
'input-bar__guide--warn': guideText.value?.includes('⚠️'),
}))
</script>
改动 8:样式新增(end / reopen)
// 🆕 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 绑定
<!-- v1.1:始终显示 -->
<button class="chat-panel__exit-btn" @click="handleExitWithEvaluation">
<!-- v1.2:仅 AI 场景显示 -->
<button
v-show="store.showHeaderExitBtn"
class="chat-panel__exit-btn"
title="结束会话(不再发送提醒)"
@click="handleExitWithEvaluation"
>
改动 2:处理 end 态的 emit
// InputBar @end-conversation 事件处理
async function handleEndConversation(): Promise<void> {
// 复用现有 handleExitWithEvaluation 逻辑
await handleExitWithEvaluation()
}
改动 3:模板绑定 InputBar 新事件
<InputBar
@call-agent="handleDirectCall"
@cancel-queue="handleCancelQueue"
@end-conversation="handleEndConversation" <!-- 🆕 -->
/>
改动 4:顶部按钮 tooltip / 弹窗文案
// 顶部按钮点击后弹窗文案调整
const showExitConfirm = ref<boolean>(false)
function handleExitWithEvaluation(): void {
// 弹窗文案强化"不再发送提醒"
showExitConfirm.value = true
}
// van-dialog message:
message: "退出后会话记录会清空,当前咨询进度将丢失,将不再发送未回复提醒。"
3.4.3 QueueCapsule.vue(v1.1 已规划,v1.2 沿用)
沿用 v1.1 设计,此处略。详见 v1.1 技术方案。
3.5 顶部按钮可见性规则(v1.2 关键)
| 场景 | showHeaderExitBtn |
说明 |
|---|---|---|
| 无会话 | false |
用户还没开始 |
| AI 对话(active / urgent / disabled) | true |
AI 场景专属可用 |
| 坐席离线 | true |
仍属 AI 场景 |
| 排队中(waiting) | false |
操作按钮 waiting 接管 |
| 坐席服务中(serving) | false |
操作按钮 end 接管 |
| 会话已关闭(reopen / disabled 过期) | false |
都不需要顶部按钮 |
store 计算逻辑:见 §3.3。
3.6 引导语设计
| 场景 | 引导语 | 颜色 |
|---|---|---|
| 无会话 | 💡 先描述一下你遇到的问题,AI 助手会先帮你看看 | 默认灰 |
| AI <3 轮 | 请继续描述您的问题或需求 | 默认灰 |
| 坐席离线 | ⚠️ 坐席当前离线,建议先用 AI 解答;如紧急可刷新重试 | 橙色(warn) |
| 会话过期(>24h) | ⏰ 上一会话已过期,开始新对话吧 | 默认灰 |
实现细节:仅 disabled 态显示,其他态不显示。详见 §3.4.1 改动 7。
3.7 重新打开按钮实现
API:api/closing.ts:119 的 reopenConversation 已存在,v1.2 直接复用。
前端流程:
点击"🔄 重新打开"按钮
↓
调用 store.reopenCurrentConversation()
↓
POST /h5/conversations/current/reopen
↓
后端创建新会话并关联原会话ID
↓
前端更新 store.currentConversation
↓
按钮态自动切回 active / waiting / serving
24h 边界判断:
- 前端:基于
conv.resolved_at计算 elapsed,与 24h 比较 - 后端:reopen API 做权威校验(防止客户端改时间绕过)
- 双保险:即使前端误判,后端也会拒绝 24h 外的 reopen
3.8 双入口防抖/互斥(v1.2 关键)
场景互斥保证(不需要 debounce):
- 顶部按钮 + end 态按钮由
store.showHeaderExitBtn和callAgentState联合控制 - 任何时候两者最多一个可点击
- 不存在"同时存在两个结束入口"的 UI 状态
async handler 三件套(防抖 + 同步 store + try/finally 重置):
- 顶部按钮
handleExitWithEvaluation:沿用 ChatPanel.vue:403 已有修复 - 操作按钮 end 态:复用
handleEndConversation→handleExitWithEvaluation,共享防抖逻辑
// 双重错误信息(参见 memory 里 BUG-用户-003 修复)
async function handleExitWithEvaluation(): Promise<void> {
if (isExiting.value) return // 防抖
isExiting.value = true
try {
// ... close + 评价 + 关闭
} catch (e: any) {
// catch 兜底优先显示后端真实 message
showToast(e?.message || '退出失败,请稍后重试')
} finally {
isExiting.value = false // try/finally 重置
}
}
四、API 需求
4.1 现有 API(已存在)
| API | 说明 |
|---|---|
POST /h5/conversations/current/close |
员工主动关闭会话 |
POST /api/conversations/{id}/evaluation |
提交满意度评价 |
POST /h5/conversations/current/reopen |
24h 内重开(v1.2 复用) |
4.2 需新增/确认
| API | 说明 |
|---|---|
| WebSocket 推送 | 排队状态变更通知(waiting → serving → resolved) |
conv.resolved_at 字段 |
后端返回会话关闭时间,前端计算 24h 边界 |
五、非技术变更
5.1 前端样式新增
// InputBar 新增样式(见 §3.4.1 改动 8)
.call-agent-btn--end { ... }
.call-agent-btn--reopen { ... }
// ChatPanel 顶部按钮 v-show + tooltip(见 §3.4.2 改动 1)
5.2 文案修订
| 位置 | v1.1 | v1.2 |
|---|---|---|
| 顶部按钮 tooltip | "结束会话" | "结束会话(不再发送提醒)" |
| 退出确认弹窗 | "退出后会话记录会清空..." | "...将不再发送未回复提醒。" |
六、风险与依赖
| 风险 | 影响 | 缓解措施 |
|---|---|---|
| R1:双入口虽然场景互斥,但文案相似 | 用户可能误操作 | 顶部按钮 tooltip 强调"不再发送提醒",end 按钮文案"结束咨询"明确"挂断" |
R2:showHeaderExitBtn 计算错误 |
顶部按钮在错误场景显示 | 单元测试覆盖所有 9 种场景 |
| R3:24h 边界依赖客户端时钟 | 改时间绕过 | 后端 reopen API 权威校验 |
| R4:end 态 + 顶部按钮同时误触发 | 重复 close 请求 | isExiting 防抖 + store 同步(沿用 BUG-用户-003 修复) |
| R5:引导语渲染与按钮态不同步 | UI 状态不一致 | 引导语渲染条件与 callAgentState 共享同一 computed |
依赖:
- D1:WebSocket
queue_position_update事件已实现 - D2:
store.agentOnline字段已存在 - D3:EvaluationDialog 组件已实现
- D4:reopenConversation API 已在
closing.ts:119实现 - D5:
conv.resolved_at字段后端返回(需确认)
七、验收标准
| # | 验收条件 |
|---|---|
| AC1 | 6 态按钮正确切换(disabled/active/urgent/waiting/end/reopen) |
| AC2 | 4 种引导语按场景正确显示 |
| AC3 | 顶部按钮仅 AI 场景显示(waiting/serving 时隐藏) |
| AC4 | 操作按钮 end 态仅 serving 时显示,点击触发评价弹窗 |
| AC5 | 「重新打开」按钮在 24h 内可见,点击调用 reopen API |
| AC6 | 引导语与按钮态同步,无错位 |
| AC7 | 双入口场景互斥(任何时候只有一个结束入口可用) |
| AC8 | 评价提交后窗口自动关闭 |
| AC9 | 重新进入应用显示全新/重开会话 |
八、实施计划
| 阶段 | 任务 | 工时 |
|---|---|---|
| 1 | store 新增 showHeaderExitBtn + canReopen + reopenCurrentConversation | 1h |
| 2 | InputBar.vue 6 态扩展 + 引导语 + end/reopen 样式 | 2h |
| 3 | ChatPanel.vue 顶部按钮 v-show + 文案修订 + end-conversation 处理 | 1h |
| 4 | 单元测试(状态机 + showHeaderExitBtn + canReopen) | 2h |
| 5 | 联调测试 + 视觉回归 | 2h |
| 6 | 部署 + 灰度 | 1h |
| 合计 | 9h |
九、变更记录(v1.1 → v1.2 → v1.3)
| 变更项 | v1.1 技术方案 | v1.2 技术方案 | v1.3 技术方案(已拍板 ✅) |
|---|---|---|---|
| 状态机 | 5 态(含 hidden) | 6 态(移除 hidden / 恢复 end / 新增 reopen) | 沿用 6 态(位置从 InputBar 移至整合区) |
| 引导语 | 无 | 新增 4 种引导语计算逻辑(InputBar 按钮下方) | 迁移到整合区按钮下方 |
| store 新字段 | 无 | showHeaderExitBtn + canReopen + reopenCurrentConversation |
沿用 + 新增 shiftHours(v1.4 接后端) |
| 顶部按钮 | 始终显示 | v-show="store.showHeaderExitBtn" 仅 AI 场景显示 |
沿用 |
| 顶部按钮文案 | 无 tooltip | 加 tooltip「结束会话(不再发送提醒)」 | 沿用 |
| 退出确认弹窗 | "退出后会话记录会清空..." | "...将不再发送未回复提醒。" | 沿用 |
| 操作按钮 end 态 | 已移除 | 恢复(红色填充) | 沿用(位置迁移) |
| 重新打开按钮 | 无 | 新增(蓝色填充) | 沿用(位置迁移) |
| 24h 边界 | 无 | 前端基于 resolved_at 计算 + 后端权威校验 |
沿用 |
| 双入口防抖 | 无显式防抖 | isExiting 共享 + 场景互斥(无需 debounce) |
沿用 |
| 🆕 整合区(方案 A) | 无 | 无 | 新增 IntegrationZone.vue 容器:状态条 + 操作按钮 + 进度胶囊 + 引导语 三件套纵向堆叠 |
| 🆕 状态条策略 | 无 | 无 | 永久显示(沿用 v1.2 .header-mock__status--online/offline) |
| 🆕 状态条文案 | 无 | 无 | "在线 · 9:00-18:00"(前端硬编码 SHIFT_HOURS = '9:00-18:00',v1.4 接 store.shiftHours) |
| 🆕 整合区背景色 | 无 | 无 | #fafafa 浅灰(沿用 chat-mock,inline 样式) |
| 🆕 InputBar 工具栏 | 含操作按钮 | 含操作按钮 | 移除操作按钮(更简洁:😊📎[输入框][发送]) |
| 🆕 标题栏坐席徽章 | 🟢/⚫ | 🟢/⚫ | 移除(迁移至整合区顶部状态条) |
| 🆕 QueueCapsule | 消息流底部独立 | 消息流底部独立 | 集成到 IntegrationZone 内部 |
| 🆕 移动端 | — | — | 不折叠,默认展开(移动端与桌面端一致) |
十、v1.3 整合区实施要点(已拍板 ✅)
拍板日期:2026-07-31 下午 关联 PRD:§十一 v1.3 整合区增量 关联原型:§⑨ 方案 A 整合区设计(v1.3 已拍板)
A. 组件拆分
v1.3 把 v1.2 中分散在 4 个位置(标题栏 / InputBar / 消息流底部 / 顶部按钮)的元素,整合到一个新建的容器组件 IntegrationZone.vue:
| 组件 | v1.2 角色 | v1.3 角色 | 变更 |
|---|---|---|---|
IntegrationZone.vue 🆕 |
— | 整合区容器(状态条 + 按钮 + 胶囊 + 引导语) | 新建 |
QueueCapsule.vue |
消息流底部独立显示 | 集成到 IntegrationZone 内部 | 位置迁移 |
InputBar.vue |
含操作按钮 + 引导语 | 移除操作按钮 + 移除引导语容器 | 瘦身 |
ChatPanel.vue |
标题栏坐席徽章 + 顶部退出 | 移除坐席徽章 + 集成 IntegrationZone | 瘦身 + 集成 |
B. 文件清单(新增 / 修改)
| 操作 | 文件 | 说明 |
|---|---|---|
| 🆕 新建 | src/components/IntegrationZone.vue |
整合区容器(状态条 + 操作按钮 + 进度胶囊 + 引导语 4 元素纵向堆叠) |
| 🆕 新建 | src/stores/integrationZone.ts |
整合区状态管理(可选,整合状态条三态逻辑) |
| 🆕 新建 | src/utils/shiftHours.ts |
硬编码 SHIFT_HOURS = '9:00-18:00' 常量,v1.4 接 store.shiftHours 后端班次 |
| ✏️ 修改 | src/components/InputBar.vue |
移除操作按钮 + 移除引导语容器,工具栏更简洁(😊📎[输入框][发送]) |
| ✏️ 修改 | src/components/ChatPanel.vue |
移除标题栏坐席徽章(.header-mock__status--online/offline 元素移除)+ 集成 <IntegrationZone> 组件 |
| ✏️ 修改 | src/components/QueueCapsule.vue |
不再独立显示,集成到 IntegrationZone 内部,作为整合区下部"按需胶囊" |
C. 共享知识(跨文件约定)
C.1 整合区常量
// src/utils/shiftHours.ts(v1.3 新建)
export const SHIFT_HOURS = '9:00-18:00' // 前端硬编码,v1.4 引入 store.shiftHours 后由 store 覆盖
C.2 状态条三态文案格式
{icon} {statusText} · {shiftHours}
示例:
- 🟢 在线 · 9:00-18:00
- 🟡 繁忙 · 预计 N 分钟
- ⚫ 客服暂休 · 9:00-18:00
C.3 整合区背景色
background: #fafafa /* 浅灰,沿用 chat-mock,inline 样式 */
不新增 CSS 类(沿用 v1.2 全部 72 个 class,约束与原型图保持一致)。
C.4 不动 v1.2y 已 PASS 内容(关键约束)
| # | 不动内容 | 文件 / 位置 |
|---|---|---|
| 1 | 4 种引导语文案与按钮态对应关系 | inputBarGuideText.ts |
| 2 | 操作按钮 6 态状态机(end > reopen > waiting > active/urgent > disabled) | inputBarCallAgentState.ts |
| 3 | "会话已关闭"消息文本(含 emoji 兼容) | conversation.ts:1911-1925 getResolveMessageText |
| 4 | reopen API 调用与 24h 边界 | closing.ts:119 reopenConversation |
v1.3 增量 = 整合区结构 + 5 元素位置迁移,不修改上述 4 处已 PASS 内容。
D. 数据结构
D.1 IntegrationZone Props 接口
interface IntegrationZoneProps {
agentOnline: boolean // 状态条三态依据(true/false)
shiftHours: string // 硬编码 '9:00-18:00'(v1.4 由 store.shiftHours 注入)
callAgentState: CallAgentState // 6 态按钮(disabled/active/urgent/waiting/end/reopen)
queueCapsuleState: QueueCapsuleState // 4 态胶囊(hidden/queued/serving/resolved)
guideText: string | null // 4 种场景引导语(null 表示无引导语)
}
D.2 IntegrationZone Emits 接口
interface IntegrationZoneEmits {
(e: 'call-agent'): void // 呼叫坐席(active / urgent 态)
(e: 'cancel-queue'): void // 取消排队(waiting 态)
(e: 'end-conversation'): void // 结束人工(end 态,复用 handleExitWithEvaluation)
(e: 'reopen-conversation'): void // 重新打开(reopen 态,调用 reopenCurrentConversation)
}
D.3 shiftHours 工具常量(v1.4 演进路径)
// v1.3(当前):纯常量
export const SHIFT_HOURS = '9:00-18:00'
// v1.4(未来):从 store 注入
// const shiftHours = computed(() => store.shiftHours ?? '9:00-18:00')
// 后端字段名 / 接口路径待定(详见 §F)
E. 任务列表(有序)
| # | 任务 | 依赖 | 预计 |
|---|---|---|---|
| 1 | 新建 shiftHours.ts 工具常量 |
— | 0.1d |
| 2 | 新建 IntegrationZone.vue 容器组件 |
— | 0.5d |
| 3 | 新建 integrationZone.ts 状态 store |
— | 0.3d |
| 4 | 改造 InputBar.vue 移除操作按钮 + 移除引导语 |
1 | 0.3d |
| 5 | 改造 ChatPanel.vue 移除坐席徽章 + 集成 IntegrationZone |
2, 3 | 0.5d |
| 6 | 改造 QueueCapsule.vue 集成到 IntegrationZone |
2 | 0.3d |
| 7 | vitest 单测(整合区组件 + store) | 2, 3, 6 | 0.5d |
| 8 | 联调测试 + 视觉回归 | 4, 5, 6, 7 | 0.5d |
| 合计 | 3.0d |
F. 待明确事项
| # | 待明确 | 影响 | 解决路径 |
|---|---|---|---|
| 1 | 后端班次字段名 | v1.4 引入 store.shiftHours |
等待后端 API 文档(/api/agent/shift-hours 或类似) |
| 2 | 后端班次接口路径 | 决定前端调用方式 | 后端联调前确认 REST vs GraphQL |
| 3 | 班次数据缓存策略 | 性能 + 实时性 | 决定是否用 sessionStorage 还是实时拉取 |
v1.3 前端硬编码
SHIFT_HOURS = '9:00-18:00',v1.4 引入 store.shiftHours 后由后端覆盖,无需修改组件渲染逻辑。
十一、v1.3 拍板记录
| 日期 | 决策 | 决策人 | 影响范围 |
|---|---|---|---|
| 2026-07-31 下午 | 状态条永久显示 | 用户拍板 | §A 状态条策略 |
| 2026-07-31 下午 | 状态条文案"在线 · 9:00-18:00" | 用户拍板 | §C.1 + §C.2 + §D.3 |
| 2026-07-31 下午 | 整合区背景色 #fafafa 浅灰 |
用户拍板 | §C.3 |
| 2026-07-31 下午 | 引导语放整合区按钮下方 | 用户拍板 | §A 引导语位置 |
| 2026-07-31 下午 | 移动端不折叠,默认展开 | 用户拍板 | §B ChatPanel.vue 移动端逻辑 |
三件套落地:PRD v1.3 §十一 + 技术方案 v1.3 §十(本文档)+ 任务说明书 v1.3 M8-M14 同步发布;v1.4 路线图引入
store.shiftHours接后端班次数据。