聊天判断修正
This commit is contained in:
@@ -68,7 +68,7 @@ public class ChatController {
|
|||||||
chat.setUserType(false);
|
chat.setUserType(false);
|
||||||
if(NonUtils.isNotEmpty(token)){
|
if(NonUtils.isNotEmpty(token)){
|
||||||
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
|
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
|
||||||
if (NonUtils.isNotEmpty(loginDomain) && loginDomain.getUser().getStatus()!="guest") {
|
if (NonUtils.isNotEmpty(loginDomain) && !"guest".equals(loginDomain.getUser().getStatus())) {
|
||||||
chat.setUserType(true);
|
chat.setUserType(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,7 +97,7 @@ public class ChatController {
|
|||||||
chat.setUserType(false);
|
chat.setUserType(false);
|
||||||
if(NonUtils.isNotEmpty(token)){
|
if(NonUtils.isNotEmpty(token)){
|
||||||
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
|
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
|
||||||
if (NonUtils.isNotEmpty(loginDomain) && loginDomain.getUser().getStatus()!="guest") {
|
if (NonUtils.isNotEmpty(loginDomain) && !"guest".equals(loginDomain.getUser().getStatus())) {
|
||||||
chat.setUserType(true);
|
chat.setUserType(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,7 +116,7 @@ public class ChatController {
|
|||||||
chat.setUserType(false);
|
chat.setUserType(false);
|
||||||
if(NonUtils.isNotEmpty(token)){
|
if(NonUtils.isNotEmpty(token)){
|
||||||
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
|
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
|
||||||
if (NonUtils.isNotEmpty(loginDomain) && loginDomain.getUser().getStatus()!="guest") {
|
if (NonUtils.isNotEmpty(loginDomain) && !"guest".equals(loginDomain.getUser().getStatus())) {
|
||||||
chat.setUserType(true);
|
chat.setUserType(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -137,7 +137,7 @@ public class ChatController {
|
|||||||
filter.setUserType(false);
|
filter.setUserType(false);
|
||||||
if(NonUtils.isNotEmpty(token)){
|
if(NonUtils.isNotEmpty(token)){
|
||||||
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
|
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
|
||||||
if (NonUtils.isNotEmpty(loginDomain) && loginDomain.getUser().getStatus()!="guest") {
|
if (NonUtils.isNotEmpty(loginDomain) && !"guest".equals(loginDomain.getUser().getStatus())) {
|
||||||
filter.setUserType(true);
|
filter.setUserType(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -157,7 +157,7 @@ public class ChatController {
|
|||||||
pageRequest.getFilter().setUserType(false);
|
pageRequest.getFilter().setUserType(false);
|
||||||
if(NonUtils.isNotEmpty(token)){
|
if(NonUtils.isNotEmpty(token)){
|
||||||
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
|
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
|
||||||
if (NonUtils.isNotEmpty(loginDomain) && loginDomain.getUser().getStatus()!="guest") {
|
if (NonUtils.isNotEmpty(loginDomain) && !"guest".equals(loginDomain.getUser().getStatus())) {
|
||||||
pageRequest.getFilter().setUserType(true);
|
pageRequest.getFilter().setUserType(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -183,7 +183,7 @@ public class ChatController {
|
|||||||
filter.setUserType(false);
|
filter.setUserType(false);
|
||||||
if(NonUtils.isNotEmpty(token)){
|
if(NonUtils.isNotEmpty(token)){
|
||||||
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
|
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
|
||||||
if (NonUtils.isNotEmpty(loginDomain) && loginDomain.getUser().getStatus()!="guest") {
|
if (NonUtils.isNotEmpty(loginDomain) && !"guest".equals(loginDomain.getUser().getStatus())) {
|
||||||
filter.setUserType(true);
|
filter.setUserType(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -214,7 +214,7 @@ public class ChatController {
|
|||||||
chatPrepareData.setUserType(false);
|
chatPrepareData.setUserType(false);
|
||||||
if(NonUtils.isNotEmpty(token)){
|
if(NonUtils.isNotEmpty(token)){
|
||||||
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
|
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
|
||||||
if (NonUtils.isNotEmpty(loginDomain) && loginDomain.getUser().getStatus()!="guest") {
|
if (NonUtils.isNotEmpty(loginDomain) && !"guest".equals(loginDomain.getUser().getStatus())) {
|
||||||
chatPrepareData.setUserType(true);
|
chatPrepareData.setUserType(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -267,7 +267,7 @@ public class ChatController {
|
|||||||
filter.setUserType(false);
|
filter.setUserType(false);
|
||||||
if(NonUtils.isNotEmpty(token)){
|
if(NonUtils.isNotEmpty(token)){
|
||||||
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
|
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
|
||||||
if (NonUtils.isNotEmpty(loginDomain) && loginDomain.getUser().getStatus()!="guest") {
|
if (NonUtils.isNotEmpty(loginDomain) && !"guest".equals(loginDomain.getUser().getStatus())) {
|
||||||
filter.setUserType(true);
|
filter.setUserType(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -300,7 +300,7 @@ public class ChatController {
|
|||||||
filter.setUserType(false);
|
filter.setUserType(false);
|
||||||
if(NonUtils.isNotEmpty(token)){
|
if(NonUtils.isNotEmpty(token)){
|
||||||
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
|
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
|
||||||
if (NonUtils.isNotEmpty(loginDomain) && loginDomain.getUser().getStatus()!="guest") {
|
if (NonUtils.isNotEmpty(loginDomain) && !"guest".equals(loginDomain.getUser().getStatus())) {
|
||||||
filter.setUserType(true);
|
filter.setUserType(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ public class WorkcaseChatContorller {
|
|||||||
|
|
||||||
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
|
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
|
||||||
String userId = loginDomain.getUser().getUserId();
|
String userId = loginDomain.getUser().getUserId();
|
||||||
if("guest".equals(loginDomain.getUser().getStatus())){
|
if(!"guest".equals(loginDomain.getUser().getStatus())){
|
||||||
pageRequest.getFilter().setGuestId(userId);
|
pageRequest.getFilter().setGuestId(userId);
|
||||||
}
|
}
|
||||||
return chatRoomService.getChatRoomPage(pageRequest, userId);
|
return chatRoomService.getChatRoomPage(pageRequest, userId);
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ public class WorkcaseController {
|
|||||||
@PostMapping("/list")
|
@PostMapping("/list")
|
||||||
public ResultDomain<TbWorkcaseDTO> getWorkcaseList(@RequestBody TbWorkcaseDTO filter) {
|
public ResultDomain<TbWorkcaseDTO> getWorkcaseList(@RequestBody TbWorkcaseDTO filter) {
|
||||||
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
|
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
|
||||||
if ("guest".equals(loginDomain.getUser().getStatus())) {
|
if (!"guest".equals(loginDomain.getUser().getStatus())) {
|
||||||
filter.setUserId(loginDomain.getUser().getUserId());
|
filter.setUserId(loginDomain.getUser().getUserId());
|
||||||
}
|
}
|
||||||
return workcaseService.getWorkcaseList(filter);
|
return workcaseService.getWorkcaseList(filter);
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ public class VideoMeetingServiceImpl implements VideoMeetingService {
|
|||||||
List<ChatMemberVO> members = chatRoomMemberMapper.selectChatRoomMemberList(memberFilter);
|
List<ChatMemberVO> members = chatRoomMemberMapper.selectChatRoomMemberList(memberFilter);
|
||||||
|
|
||||||
String userName = loginDomain.getUserInfo().getUsername();
|
String userName = loginDomain.getUserInfo().getUsername();
|
||||||
String userType = "guest".equals(loginDomain.getUser().getStatus())?"guest":"user";
|
String userType = !"guest".equals(loginDomain.getUser().getStatus())?"guest":"user";
|
||||||
if (members != null && !members.isEmpty()) {
|
if (members != null && !members.isEmpty()) {
|
||||||
ChatMemberVO member = members.get(0);
|
ChatMemberVO member = members.get(0);
|
||||||
userName = member.getUserName();
|
userName = member.getUserName();
|
||||||
|
|||||||
@@ -15,160 +15,172 @@
|
|||||||
|
|
||||||
<div class="filter-right">
|
<div class="filter-right">
|
||||||
<el-select v-model="statusFilter" placeholder="对话状态" clearable style="width: 120px;">
|
<el-select v-model="statusFilter" placeholder="对话状态" clearable style="width: 120px;">
|
||||||
<el-option label="进行中" value="ongoing" />
|
<el-option label="进行中" value="active" />
|
||||||
<el-option label="已结束" value="ended" />
|
<el-option label="已结束" value="closed" />
|
||||||
<el-option label="已转工单" value="converted" />
|
|
||||||
</el-select>
|
</el-select>
|
||||||
<el-select v-model="satisfactionFilter" placeholder="满意度" clearable style="width: 120px;">
|
<el-input v-model="searchKeyword" placeholder="搜索客户/聊天室" style="width: 200px;" :prefix-icon="Search" clearable @keyup.enter="loadChatRooms" />
|
||||||
<el-option label="满意" value="satisfied" />
|
<el-button type="primary" @click="loadChatRooms">搜索</el-button>
|
||||||
<el-option label="一般" value="normal" />
|
|
||||||
<el-option label="不满意" value="unsatisfied" />
|
|
||||||
</el-select>
|
|
||||||
<el-input v-model="searchKeyword" placeholder="搜索客户/内容" style="width: 200px;" :prefix-icon="Search" clearable />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<!-- 对话列表 -->
|
<!-- 对话列表 -->
|
||||||
<el-card>
|
<el-card v-loading="loading">
|
||||||
<el-table :data="filteredChats" style="width: 100%">
|
<el-table :data="chatRooms" style="width: 100%">
|
||||||
<el-table-column prop="chatId" label="对话ID" width="140">
|
<el-table-column prop="roomId" label="聊天室ID" width="180">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span style="color: #409eff; font-weight: 500;">{{ row.chatId }}</span>
|
<span style="color: #409eff; font-weight: 500;">{{ row.roomId?.substring(0, 8) }}...</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="customerName" label="客户信息" width="150">
|
<el-table-column prop="roomName" label="聊天室名称" width="180" />
|
||||||
|
<el-table-column prop="guestName" label="客户" width="120" />
|
||||||
|
<el-table-column prop="workcaseId" label="关联工单" width="140">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="customer-info">
|
<span v-if="row.workcaseId" style="color: #67c23a;">{{ row.workcaseId }}</span>
|
||||||
<span class="name">{{ row.customerName }}</span>
|
<span v-else style="color: #909399;">-</span>
|
||||||
<span class="phone">{{ row.customerPhone }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="agentName" label="客服人员" width="100" />
|
|
||||||
<el-table-column prop="messageCount" label="消息数" width="80" align="center" />
|
|
||||||
<el-table-column prop="duration" label="对话时长" width="100" />
|
|
||||||
<el-table-column prop="status" label="状态" width="100">
|
<el-table-column prop="status" label="状态" width="100">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag :type="getStatusType(row.status)" size="small">
|
<el-tag :type="getStatusType(row.status)" size="small">
|
||||||
{{ row.statusName }}
|
{{ getStatusName(row.status) }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="satisfaction" label="满意度" width="100">
|
<el-table-column prop="createTime" label="创建时间" width="160" />
|
||||||
<template #default="{ row }">
|
<el-table-column label="操作" width="150" fixed="right">
|
||||||
<el-rate v-model="row.satisfaction" disabled allow-half />
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="startTime" label="开始时间" width="160" />
|
|
||||||
<el-table-column label="操作" width="200" fixed="right">
|
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button type="primary" link size="small" @click="viewChat(row)">查看</el-button>
|
<el-button type="primary" link size="small" @click="viewChat(row)">查看</el-button>
|
||||||
<el-button type="success" link size="small" @click="downloadChat(row)">下载</el-button>
|
<el-button v-if="row.status === 'active'" type="warning" link size="small" @click="closeChat(row)">关闭</el-button>
|
||||||
<el-button v-if="row.status === 'ongoing'" type="warning" link size="small" @click="endChat(row)">结束</el-button>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|
||||||
<div class="table-pagination">
|
<div class="table-pagination">
|
||||||
<el-pagination v-model:current-page="currentPage" :page-size="10" :total="chats.length" layout="total, prev, pager, next" />
|
<el-pagination
|
||||||
|
v-model:current-page="currentPage"
|
||||||
|
:page-size="pageSize"
|
||||||
|
:total="total"
|
||||||
|
layout="total, prev, pager, next"
|
||||||
|
@current-change="loadChatRooms"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 查看对话详情弹窗 -->
|
<!-- 查看对话详情弹窗 - 复用 ChatMessage 组件 -->
|
||||||
<el-dialog v-model="showChatDialog" title="对话详情" width="700px">
|
<el-dialog v-model="showChatDialog" title="对话详情" width="800px" class="chat-dialog">
|
||||||
<div class="chat-messages">
|
<ChatMessage v-if="showChatDialog && currentRoomId" :room-id="currentRoomId" :chat-room="currentChatRoom" />
|
||||||
<div v-for="(msg, idx) in currentChatMessages" :key="idx" class="message-item" :class="msg.type">
|
|
||||||
<div class="message-header">
|
|
||||||
<span class="sender">{{ msg.sender }}</span>
|
|
||||||
<span class="time">{{ msg.time }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="message-content">{{ msg.content }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</AdminLayout>
|
</AdminLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import AdminLayout from '@/views/admin/AdminLayout.vue'
|
import AdminLayout from '@/views/admin/AdminLayout.vue'
|
||||||
|
import { ChatMessage } from '@/views/public/ChatRoom/'
|
||||||
import { Download, Search } from 'lucide-vue-next'
|
import { Download, Search } from 'lucide-vue-next'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { workcaseChatAPI } from '@/api/workcase/workcaseChat'
|
||||||
|
import type { ChatRoomVO } from '@/types/workcase/chatRoom'
|
||||||
|
|
||||||
const dateRange = ref<[Date, Date] | null>(null)
|
const dateRange = ref<[Date, Date] | null>(null)
|
||||||
const statusFilter = ref('')
|
const statusFilter = ref('')
|
||||||
const satisfactionFilter = ref('')
|
|
||||||
const searchKeyword = ref('')
|
const searchKeyword = ref('')
|
||||||
const currentPage = ref(1)
|
const currentPage = ref(1)
|
||||||
|
const pageSize = ref(10)
|
||||||
|
const total = ref(0)
|
||||||
|
const loading = ref(false)
|
||||||
const showChatDialog = ref(false)
|
const showChatDialog = ref(false)
|
||||||
const currentChatMessages = ref<any[]>([])
|
const currentRoomId = ref('')
|
||||||
|
const currentChatRoom = ref<ChatRoomVO | undefined>(undefined)
|
||||||
|
|
||||||
const chats = ref([
|
// 聊天室列表
|
||||||
{ chatId: 'CH001', customerName: '张三', customerPhone: '13800138000', agentName: '王五', messageCount: 24, duration: '15分钟', status: 'ended', statusName: '已结束', satisfaction: 4, startTime: '2024-12-13 10:30' },
|
const chatRooms = ref<ChatRoomVO[]>([])
|
||||||
{ chatId: 'CH002', customerName: '李四', customerPhone: '13800138001', agentName: '赵六', messageCount: 18, duration: '12分钟', status: 'ended', statusName: '已结束', satisfaction: 5, startTime: '2024-12-13 09:15' },
|
|
||||||
{ chatId: 'CH003', customerName: '王五', customerPhone: '13800138002', agentName: '孙七', messageCount: 32, duration: '22分钟', status: 'converted', statusName: '已转工单', satisfaction: 3, startTime: '2024-12-12 14:20' },
|
|
||||||
{ chatId: 'CH004', customerName: '赵六', customerPhone: '13800138003', agentName: '李四', messageCount: 15, duration: '10分钟', status: 'ongoing', statusName: '进行中', satisfaction: 0, startTime: '2024-12-13 11:00' },
|
|
||||||
{ chatId: 'CH005', customerName: '孙七', customerPhone: '13800138004', agentName: '王五', messageCount: 28, duration: '18分钟', status: 'ended', statusName: '已结束', satisfaction: 4.5, startTime: '2024-12-13 08:45' }
|
|
||||||
])
|
|
||||||
|
|
||||||
const filteredChats = computed(() => {
|
// 加载聊天室列表
|
||||||
let result = chats.value
|
const loadChatRooms = async () => {
|
||||||
if (statusFilter.value) {
|
loading.value = true
|
||||||
result = result.filter(c => c.status === statusFilter.value)
|
try {
|
||||||
}
|
const filter: any = {}
|
||||||
if (satisfactionFilter.value) {
|
if (statusFilter.value) {
|
||||||
const satisfaction = satisfactionFilter.value === 'satisfied' ? 4 : satisfactionFilter.value === 'normal' ? 2.5 : 1
|
filter.status = statusFilter.value
|
||||||
result = result.filter(c => {
|
}
|
||||||
if (satisfactionFilter.value === 'satisfied') return c.satisfaction >= 4
|
if (searchKeyword.value) {
|
||||||
if (satisfactionFilter.value === 'normal') return c.satisfaction >= 2 && c.satisfaction < 4
|
filter.roomName = searchKeyword.value
|
||||||
if (satisfactionFilter.value === 'unsatisfied') return c.satisfaction < 2
|
}
|
||||||
return true
|
|
||||||
|
const res = await workcaseChatAPI.getChatRoomPage({
|
||||||
|
filter,
|
||||||
|
pageParam: {
|
||||||
|
page: currentPage.value,
|
||||||
|
pageSize: pageSize.value
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (res.success) {
|
||||||
|
chatRooms.value = res.dataList || []
|
||||||
|
total.value = res.total || 0
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.message || '加载聊天室列表失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载聊天室列表失败:', error)
|
||||||
|
ElMessage.error('加载聊天室列表失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
}
|
}
|
||||||
if (searchKeyword.value) {
|
}
|
||||||
const keyword = searchKeyword.value.toLowerCase()
|
|
||||||
result = result.filter(c =>
|
|
||||||
c.customerName.toLowerCase().includes(keyword) ||
|
|
||||||
c.customerPhone.includes(keyword)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return result.slice((currentPage.value - 1) * 10, currentPage.value * 10)
|
|
||||||
})
|
|
||||||
|
|
||||||
const getStatusType = (status: string) => {
|
const getStatusType = (status: string) => {
|
||||||
const map: Record<string, string> = {
|
const map: Record<string, string> = {
|
||||||
ongoing: 'success',
|
active: 'success',
|
||||||
ended: 'info',
|
closed: 'info'
|
||||||
converted: 'warning'
|
|
||||||
}
|
}
|
||||||
return map[status] || 'info'
|
return map[status] || 'info'
|
||||||
}
|
}
|
||||||
|
|
||||||
const viewChat = (row: any) => {
|
const getStatusName = (status: string) => {
|
||||||
currentChatMessages.value = [
|
const map: Record<string, string> = {
|
||||||
{ type: 'customer', sender: row.customerName, time: '10:30:15', content: '你好,我的设备出现了故障' },
|
active: '进行中',
|
||||||
{ type: 'agent', sender: row.agentName, time: '10:30:45', content: '您好,感谢您的咨询。请问是什么故障呢?' },
|
closed: '已结束'
|
||||||
{ type: 'customer', sender: row.customerName, time: '10:31:20', content: '显示屏不亮了,但是有声音' },
|
}
|
||||||
{ type: 'agent', sender: row.agentName, time: '10:31:50', content: '好的,这可能是显示屏的问题。请问您的设备型号是什么?' },
|
return map[status] || status
|
||||||
{ type: 'customer', sender: row.customerName, time: '10:32:30', content: 'TH-500GF' },
|
}
|
||||||
{ type: 'agent', sender: row.agentName, time: '10:33:00', content: '好的,我为您创建了一个工单,技术人员会尽快联系您' }
|
|
||||||
]
|
const viewChat = (row: ChatRoomVO) => {
|
||||||
|
currentRoomId.value = row.roomId || ''
|
||||||
|
currentChatRoom.value = row
|
||||||
showChatDialog.value = true
|
showChatDialog.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const downloadChat = (row: any) => {
|
const closeChat = async (row: ChatRoomVO) => {
|
||||||
ElMessage.success(`下载对话: ${row.chatId}`)
|
if (!row.roomId) return
|
||||||
}
|
|
||||||
|
|
||||||
const endChat = (row: any) => {
|
try {
|
||||||
ElMessage.info(`结束对话: ${row.chatId}`)
|
const loginDomain = JSON.parse(localStorage.getItem('loginDomain') || '{}')
|
||||||
|
const userId = loginDomain?.userInfo?.userId || ''
|
||||||
|
|
||||||
|
const res = await workcaseChatAPI.closeChatRoom(row.roomId, userId)
|
||||||
|
if (res.success) {
|
||||||
|
ElMessage.success('聊天室已关闭')
|
||||||
|
await loadChatRooms()
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.message || '关闭失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('关闭聊天室失败:', error)
|
||||||
|
ElMessage.error('关闭聊天室失败')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const exportData = () => {
|
const exportData = () => {
|
||||||
ElMessage.success('数据导出成功')
|
ElMessage.success('数据导出功能开发中')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadChatRooms()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -196,50 +208,11 @@ const exportData = () => {
|
|||||||
color: #909399;
|
color: #909399;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-messages {
|
.chat-dialog {
|
||||||
display: flex;
|
:deep(.el-dialog__body) {
|
||||||
flex-direction: column;
|
padding: 0;
|
||||||
gap: 12px;
|
max-height: 600px;
|
||||||
max-height: 400px;
|
overflow: hidden;
|
||||||
overflow-y: auto;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.message-item {
|
|
||||||
padding: 10px 12px;
|
|
||||||
border-radius: 8px;
|
|
||||||
background: #f5f7fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-item.customer {
|
|
||||||
background: #e6f7ff;
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-item.agent {
|
|
||||||
background: #f0f9ff;
|
|
||||||
margin-right: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-header .sender {
|
|
||||||
font-weight: 500;
|
|
||||||
color: #303133;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-header .time {
|
|
||||||
color: #909399;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-content {
|
|
||||||
color: #303133;
|
|
||||||
line-height: 1.5;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -35,7 +35,15 @@
|
|||||||
<!-- 内容区域 -->
|
<!-- 内容区域 -->
|
||||||
<main class="chat-main">
|
<main class="chat-main">
|
||||||
<!-- 对话记录 -->
|
<!-- 对话记录 -->
|
||||||
<div v-if="activeTab === 'record'" class="messages-container">
|
<div v-if="activeTab === 'record'" class="messages-container" ref="messagesContainerRef" @scroll="handleScroll">
|
||||||
|
<!-- 加载更多提示 -->
|
||||||
|
<div v-if="loadingMore" class="loading-more">
|
||||||
|
<span>加载中...</span>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="!hasMore && messages.length > 0" class="no-more">
|
||||||
|
<span>没有更多消息了</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="messages-list">
|
<div class="messages-list">
|
||||||
<div v-for="message in messages" :key="message.messageId"
|
<div v-for="message in messages" :key="message.messageId"
|
||||||
class="message-item"
|
class="message-item"
|
||||||
@@ -65,7 +73,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 空状态 -->
|
<!-- 空状态 -->
|
||||||
<div v-if="messages.length === 0" class="empty-state">
|
<div v-if="messages.length === 0 && !loading" class="empty-state">
|
||||||
<div class="empty-text">暂无对话记录</div>
|
<div class="empty-text">暂无对话记录</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -92,7 +100,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted, watch, computed, nextTick } from 'vue'
|
||||||
import type { ChatRoomVO, ChatRoomMessageVO } from '@/types/workcase/chatRoom'
|
import type { ChatRoomVO, ChatRoomMessageVO } from '@/types/workcase/chatRoom'
|
||||||
import { workcaseChatAPI } from '@/api/workcase/workcaseChat'
|
import { workcaseChatAPI } from '@/api/workcase/workcaseChat'
|
||||||
|
|
||||||
@@ -113,11 +121,25 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
|
|
||||||
const activeTab = ref<'record' | 'summary'>('record')
|
const activeTab = ref<'record' | 'summary'>('record')
|
||||||
const messages = ref<ChatRoomMessageVO[]>([])
|
const messages = ref<ChatRoomMessageVO[]>([])
|
||||||
|
const loading = ref(false)
|
||||||
|
const loadingMore = ref(false)
|
||||||
const summary = ref<Summary>({
|
const summary = ref<Summary>({
|
||||||
overview: '',
|
overview: '',
|
||||||
demand: ''
|
demand: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 分页相关
|
||||||
|
const currentPage = ref(1)
|
||||||
|
const pageSize = 20
|
||||||
|
const total = ref(0)
|
||||||
|
const hasMore = computed(() => messages.value.length < total.value)
|
||||||
|
|
||||||
|
// 滚动容器引用
|
||||||
|
const messagesContainerRef = ref<HTMLElement | null>(null)
|
||||||
|
|
||||||
|
// 计算目标 roomId
|
||||||
|
const targetRoomId = computed(() => props.roomId || props.chatRoom?.roomId || '')
|
||||||
|
|
||||||
// 格式化时间
|
// 格式化时间
|
||||||
const formatTime = (timeStr?: string) => {
|
const formatTime = (timeStr?: string) => {
|
||||||
if (!timeStr) return ''
|
if (!timeStr) return ''
|
||||||
@@ -137,23 +159,85 @@ const getAvatarText = (message: ChatRoomMessageVO) => {
|
|||||||
return message.senderName?.charAt(0) || '电'
|
return message.senderName?.charAt(0) || '电'
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载消息数据
|
// 加载消息数据(首次加载,获取最新消息)
|
||||||
const loadMessages = async () => {
|
const loadMessages = async () => {
|
||||||
const targetRoomId = props.roomId || props.chatRoom?.roomId
|
if (!targetRoomId.value) return
|
||||||
if (!targetRoomId) return
|
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
currentPage.value = 1
|
||||||
try {
|
try {
|
||||||
const res = await workcaseChatAPI.getChatMessagePage({
|
const res = await workcaseChatAPI.getChatMessagePage({
|
||||||
filter: { roomId: targetRoomId },
|
filter: { roomId: targetRoomId.value },
|
||||||
pageParam: { page: 1, pageSize: 100 }
|
pageParam: { page: 1, pageSize }
|
||||||
})
|
})
|
||||||
|
|
||||||
if (res.success && res.dataList) {
|
if (res.success && res.dataList) {
|
||||||
// 后端降序返回,需要反转
|
// 后端降序返回(最新在前),需要反转显示(最新在下)
|
||||||
messages.value = [...res.dataList].reverse()
|
messages.value = [...res.dataList].reverse()
|
||||||
|
total.value = res.total || 0
|
||||||
|
|
||||||
|
// 滚动到底部
|
||||||
|
await nextTick()
|
||||||
|
scrollToBottom()
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载消息失败:', error)
|
console.error('加载消息失败:', error)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载更多历史消息(滚动到顶部时触发)
|
||||||
|
const loadMoreMessages = async () => {
|
||||||
|
if (!targetRoomId.value || loadingMore.value || !hasMore.value) return
|
||||||
|
|
||||||
|
loadingMore.value = true
|
||||||
|
const container = messagesContainerRef.value
|
||||||
|
const oldScrollHeight = container?.scrollHeight || 0
|
||||||
|
|
||||||
|
try {
|
||||||
|
currentPage.value++
|
||||||
|
const res = await workcaseChatAPI.getChatMessagePage({
|
||||||
|
filter: { roomId: targetRoomId.value },
|
||||||
|
pageParam: { page: currentPage.value, pageSize }
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res.success && res.dataList && res.dataList.length > 0) {
|
||||||
|
// 后端降序返回,反转后插入到消息列表前面
|
||||||
|
const olderMessages = [...res.dataList].reverse()
|
||||||
|
messages.value = [...olderMessages, ...messages.value]
|
||||||
|
|
||||||
|
// 保持滚动位置(新内容加载后保持原来的可视区域)
|
||||||
|
await nextTick()
|
||||||
|
if (container) {
|
||||||
|
const newScrollHeight = container.scrollHeight
|
||||||
|
container.scrollTop = newScrollHeight - oldScrollHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载更多消息失败:', error)
|
||||||
|
currentPage.value-- // 回退页码
|
||||||
|
} finally {
|
||||||
|
loadingMore.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 滚动到底部
|
||||||
|
const scrollToBottom = () => {
|
||||||
|
const container = messagesContainerRef.value
|
||||||
|
if (container) {
|
||||||
|
container.scrollTop = container.scrollHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理滚动事件
|
||||||
|
const handleScroll = () => {
|
||||||
|
const container = messagesContainerRef.value
|
||||||
|
if (!container) return
|
||||||
|
|
||||||
|
// 滚动到顶部附近时加载更多(距离顶部50px以内)
|
||||||
|
if (container.scrollTop < 50 && hasMore.value && !loadingMore.value) {
|
||||||
|
loadMoreMessages()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +259,37 @@ onMounted(async () => {
|
|||||||
await loadMessages()
|
await loadMessages()
|
||||||
generateSummary()
|
generateSummary()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 监听 roomId 变化,重新加载数据
|
||||||
|
watch(targetRoomId, async (newVal) => {
|
||||||
|
if (newVal) {
|
||||||
|
messages.value = []
|
||||||
|
summary.value = { overview: '', demand: '' }
|
||||||
|
currentPage.value = 1
|
||||||
|
total.value = 0
|
||||||
|
await loadMessages()
|
||||||
|
generateSummary()
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@import url("./ChatMessage.scss");
|
@import url("./ChatMessage.scss");
|
||||||
|
|
||||||
|
.loading-more,
|
||||||
|
.no-more {
|
||||||
|
text-align: center;
|
||||||
|
padding: 12px;
|
||||||
|
color: #909399;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-more span {
|
||||||
|
display: inline-block;
|
||||||
|
animation: pulse 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% { opacity: 0.5; }
|
||||||
|
50% { opacity: 1; }
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
Reference in New Issue
Block a user