工单详情
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -0,0 +1 @@
|
||||
export {default as ChatMessage} from './ChatMessage/ChatMessage.vue'
|
||||
Reference in New Issue
Block a user