聊天判断修正

This commit is contained in:
2025-12-28 16:47:38 +08:00
parent 8448a801ce
commit 579e63efb0
6 changed files with 246 additions and 159 deletions

View File

@@ -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);
} }
} }

View File

@@ -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);

View File

@@ -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);

View File

@@ -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();

View File

@@ -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>

View File

@@ -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>