工单详情

This commit is contained in:
2025-12-24 18:22:13 +08:00
parent 6109bc2505
commit a613eb4fa1
15 changed files with 1790 additions and 739 deletions

View File

@@ -0,0 +1,199 @@
.chat-message-container {
display: flex;
flex-direction: column;
height: 100%;
background: #f8fafc;
}
.chat-header {
padding: 20px 24px;
border-bottom: 1px solid #e5e7eb;
background: #fff;
}
.header-content {
.header-title {
font-size: 18px;
font-weight: 700;
color: #4b87ff;
margin-bottom: 4px;
}
.header-subtitle {
font-size: 12px;
color: #9ca3af;
.separator {
color: #d1d5db;
}
}
}
.tabs-container {
padding: 0 24px;
background: #fff;
}
.tabs-wrapper {
display: flex;
align-items: center;
gap: 32px;
border-bottom: 1px solid #e5e7eb;
}
.tab-button {
padding: 12px 0;
font-size: 14px;
font-weight: 500;
color: #6b7280;
background: none;
border: none;
border-bottom: 2px solid transparent;
cursor: pointer;
transition: all 0.2s;
position: relative;
bottom: -1px;
&:hover {
color: #111827;
}
&.active {
color: #4b87ff;
border-bottom-color: #4b87ff;
}
}
.chat-main {
flex: 1;
overflow: hidden;
padding: 24px;
}
.messages-container {
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 12px;
padding: 16px;
height: 420px;
overflow-y: auto;
}
.messages-list {
display: flex;
flex-direction: column;
gap: 16px;
}
.message-item {
display: flex;
gap: 12px;
}
.message-left {
justify-content: flex-start;
}
.message-right {
justify-content: flex-end;
}
.avatar {
width: 36px;
height: 36px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 14px;
font-weight: 500;
flex-shrink: 0;
}
.avatar-ai {
background: #a855f7;
}
.avatar-guest {
background: #d1d5db;
}
.message-content {
display: flex;
flex-direction: column;
max-width: 70%;
}
.message-meta {
font-size: 12px;
color: #9ca3af;
margin-bottom: 4px;
}
.message-meta-right {
text-align: right;
}
.message-bubble {
padding: 12px 16px;
border-radius: 16px;
font-size: 14px;
line-height: 1.5;
word-break: break-word;
white-space: pre-wrap;
}
.message-bubble-left {
background: #f9fafb;
border: 1px solid #e5e7eb;
color: #374151;
border-top-left-radius: 4px;
}
.message-bubble-right {
background: #4b87ff;
color: #fff;
border-top-right-radius: 4px;
}
.empty-state {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
min-height: 200px;
}
.empty-text {
font-size: 14px;
color: #9ca3af;
}
.summary-container {
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 12px;
padding: 20px;
}
.summary-content {
display: flex;
flex-direction: column;
gap: 16px;
}
.summary-section {
.summary-title {
font-size: 14px;
font-weight: 700;
color: #111827;
margin-bottom: 8px;
}
.summary-text {
font-size: 14px;
color: #374151;
line-height: 1.6;
}
}

View File

@@ -0,0 +1,181 @@
<template>
<div class="chat-message-container">
<!-- 头部信息 -->
<header class="chat-header">
<div class="header-content">
<div class="header-title">{{ chatRoom?.workcaseId || '' }} 对话详情</div>
<div class="header-subtitle">
<span>客户{{ chatRoom?.guestName || '未知' }}</span>
<span class="separator"> </span>
<span>聊天室{{ chatRoom?.roomName || '未知' }}</span>
</div>
</div>
</header>
<!-- Tab 切换 -->
<div class="tabs-container">
<div class="tabs-wrapper">
<button
class="tab-button"
:class="{ active: activeTab === 'record' }"
@click="activeTab = 'record'"
>
对话记录
</button>
<button
class="tab-button"
:class="{ active: activeTab === 'summary' }"
@click="activeTab = 'summary'"
>
对话纪要
</button>
</div>
</div>
<!-- 内容区域 -->
<main class="chat-main">
<!-- 对话记录 -->
<div v-if="activeTab === 'record'" class="messages-container">
<div class="messages-list">
<div v-for="message in messages" :key="message.messageId"
class="message-item"
:class="message.senderType === 'guest' ? 'message-right' : 'message-left'"
>
<!-- 客服/AI消息左侧 -->
<template v-if="message.senderType !== 'guest'">
<div class="avatar avatar-ai">{{ getAvatarText(message) }}</div>
<div class="message-content">
<div class="message-meta">{{ message.senderName || '小电' }} {{ formatTime(message.sendTime) }}</div>
<div class="message-bubble message-bubble-left">
{{ message.content }}
</div>
</div>
</template>
<!-- 客户消息右侧 -->
<template v-else>
<div class="message-content">
<div class="message-meta message-meta-right">{{ message.senderName || '客户' }} {{ formatTime(message.sendTime) }}</div>
<div class="message-bubble message-bubble-right">
{{ message.content }}
</div>
</div>
<div class="avatar avatar-guest">{{ getAvatarText(message) }}</div>
</template>
</div>
<!-- 空状态 -->
<div v-if="messages.length === 0" class="empty-state">
<div class="empty-text">暂无对话记录</div>
</div>
</div>
</div>
<!-- 对话纪要 -->
<div v-else class="summary-container">
<div class="summary-content">
<div class="summary-section">
<div class="summary-title">问题概述</div>
<div class="summary-text">
{{ summary.overview || '暂无概述' }}
</div>
</div>
<div class="summary-section">
<div class="summary-title">客户诉求</div>
<div class="summary-text">
{{ summary.demand || '暂无诉求' }}
</div>
</div>
</div>
</div>
</main>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import type { ChatRoomVO, ChatRoomMessageVO } from '@/types/workcase/chatRoom'
import { workcaseChatAPI } from '@/api/workcase/workcaseChat'
interface Summary {
overview?: string
demand?: string
}
interface Props {
chatRoom?: ChatRoomVO
roomId?: string
}
const props = withDefaults(defineProps<Props>(), {
chatRoom: undefined,
roomId: ''
})
const activeTab = ref<'record' | 'summary'>('record')
const messages = ref<ChatRoomMessageVO[]>([])
const summary = ref<Summary>({
overview: '',
demand: ''
})
// 格式化时间
const formatTime = (timeStr?: string) => {
if (!timeStr) return ''
try {
const date = new Date(timeStr)
return date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', second: '2-digit' })
} catch {
return timeStr
}
}
// 获取头像文本
const getAvatarText = (message: ChatRoomMessageVO) => {
if (message.senderType === 'guest') {
return message.senderName?.charAt(0) || '客'
}
return message.senderName?.charAt(0) || '电'
}
// 加载消息数据
const loadMessages = async () => {
const targetRoomId = props.roomId || props.chatRoom?.roomId
if (!targetRoomId) return
try {
const res = await workcaseChatAPI.getChatMessagePage({
filter: { roomId: targetRoomId },
pageParam: { page: 1, pageSize: 100 }
})
if (res.success && res.dataList) {
// 后端降序返回,需要反转
messages.value = [...res.dataList].reverse()
}
} catch (error) {
console.error('加载消息失败:', error)
}
}
// 生成对话纪要可以后续接入AI生成
const generateSummary = () => {
if (messages.value.length === 0) return
// 简单提取第一条和最后一条消息作为概述
const firstMsg = messages.value[0]
const lastMsg = messages.value[messages.value.length - 1]
summary.value = {
overview: `客户反馈:${firstMsg.content?.substring(0, 50) || ''}`,
demand: lastMsg.content?.substring(0, 100) || ''
}
}
onMounted(async () => {
await loadMessages()
generateSummary()
})
</script>
<style scoped lang="scss">
@import url("./ChatMessage.scss");
</style>

View File

@@ -0,0 +1 @@
export {default as ChatMessage} from './ChatMessage/ChatMessage.vue'