Files
wecom_it_smart_desk/docs/02-技术文档/技术方案-REQ-会话-001-员工结束会话-v1.3.md
T
Simon 44e77dcb0e chore(docs): docs/ 目录全面重新编号 + 重组
**重构前**(旧编号 02-11):
- docs/02-产品需求/      → 00 产品规划/PRD
- docs/03-技术架构/      → 01-05 子目录散落
- docs/04-原型设计/      → 01-02 产品设计(HTML 原型)
- docs/05-原型设计/      → screens/
- docs/06-测试素材/      → 02-E2E / 03-功能 / 04-版本测试
- docs/07-项目管理/      → 任务说明书/日报/计划
- docs/08-安全审计/      → 审计报告
- docs/09-堡垒运维/      → toolbox / deploy
- docs/10-项目管理/      → 任务说明书(重复)
- docs/11-历史归档/      → deploy-nas-archived

**重构后**(新编号 00-07,语义化):
- docs/00-产品开发流程与文档管理规范.md
- docs/00-版本迭代总览.md
- docs/01-产品文档/      (PRD/原型/认证/会话/AI 服务/坐席/集成)
- docs/02-技术文档/      (技术方案/架构图/重构记录/前端改造/实现配置)
- docs/03-测试文档/      (E2E/功能用例/版本报告/缺陷单)
- docs/04-运维文档/      (部署运维/运维指南)
- docs/05-运营文档/      (品牌推广/用户手册)
- docs/06-安全审计/      (审计报告)
- docs/07-项目管理/      (任务说明书/日报/计划/看板)

**净收益**:
- 目录编号与产品文档管理规范对齐(按文档阶段 01-07 编号)
- 消除 02-产品需求 与 10-项目管理 的编号重叠
- 子目录按文档类型分组(如 01-产品文档/00-产品规划、01-产品文档/01-认证与登录)
- 把运维/安全/项目管理从 0X 散落改为 04/06/07

合计 494 文件 + 78495 行 / - 14076 行
2026-08-03 18:46:55 +08:00

773 lines
29 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 员工结束会话功能 - 技术方案
> **版本**: v1.3
> **日期**: 2026-07-31
> **关联PRD**: PRD-REQ-会话-001-员工结束会话-v1.3(§十一 v1.3 整合区增量)
> **关联原型**: 原型-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
<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`
```typescript
// 新增:顶部退出按钮是否可见(仅 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:状态类型扩展**
```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<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:按钮文案 + 图标扩展**
```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')
}
```
**改动 6emit 扩展**
```typescript
const emit = defineEmits<{
(e: 'call-agent'): void
(e: 'cancel-queue'): void
(e: 'end-conversation'): void // 🆕(操作按钮 end 态专用,复用 handleExitWithEvaluation
}>()
```
**改动 7:新增引导语渲染**
```vue
<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 助手会先帮你看看'
// 场景2AI <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**
```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
<!-- 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**
```typescript
// InputBar @end-conversation 事件处理
async function handleEndConversation(): Promise<void> {
// 复用现有 handleExitWithEvaluation 逻辑
await handleExitWithEvaluation()
}
```
**改动 3:模板绑定 InputBar 新事件**
```vue
<InputBar
@call-agent="handleDirectCall"
@cancel-queue="handleCancelQueue"
@end-conversation="handleEndConversation" <!-- 🆕 -->
/>
```
**改动 4:顶部按钮 tooltip / 弹窗文案**
```typescript
// 顶部按钮点击后弹窗文案调整
const showExitConfirm = ref<boolean>(false)
function handleExitWithEvaluation(): void {
// 弹窗文案强化"不再发送提醒"
showExitConfirm.value = true
}
// van-dialog message:
message: "退出后会话记录会清空,当前咨询进度将丢失,将不再发送未回复提醒。"
```
#### 3.4.3 QueueCapsule.vuev1.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`,共享防抖逻辑
```typescript
// 双重错误信息(参见 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 前端样式新增
```scss
// 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 |
**依赖**
- D1WebSocket `queue_position_update` 事件已实现
- D2`store.agentOnline` 字段已存在
- D3EvaluationDialog 组件已实现
- D4reopenConversation 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-mockinline 样式) |
| 🆕 **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 整合区常量
```typescript
// src/utils/shiftHours.tsv1.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-mockinline 样式 */
```
**不新增 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 接口
```typescript
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 接口
```typescript
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 演进路径)
```typescript
// 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` 接后端班次数据。