731 lines
26 KiB
Vue
731 lines
26 KiB
Vue
<template>
|
||
<div class="chat-room-container">
|
||
<!-- 折叠状态的侧边栏 -->
|
||
<div v-if="!isSidebarOpen" class="sidebar-collapsed">
|
||
<button class="sidebar-toggle-btn" @click="toggleSidebar" title="展开聊天室列表">
|
||
<MessageCircle :size="20" />
|
||
</button>
|
||
<div class="sidebar-icons">
|
||
<button
|
||
v-for="room in filteredRooms.slice(0, 8)"
|
||
:key="room.roomId"
|
||
@click="selectRoom(room)"
|
||
class="sidebar-icon-btn"
|
||
:class="{ active: currentRoomId === room.roomId }"
|
||
:title="room.roomName"
|
||
>
|
||
{{ room.guestName?.substring(0, 1) || '?' }}
|
||
</button>
|
||
</div>
|
||
<button class="expand-btn" @click="toggleSidebar" title="展开聊天室列表">
|
||
<ChevronRight :size="18" />
|
||
</button>
|
||
</div>
|
||
|
||
<!-- 展开时的关闭按钮 -->
|
||
<button v-if="isSidebarOpen" class="sidebar-close-btn" @click="toggleSidebar">
|
||
<ChevronLeft :size="18" />
|
||
</button>
|
||
|
||
<!-- 聊天室列表侧边栏 -->
|
||
<aside class="room-list-sidebar" :class="{ open: isSidebarOpen }">
|
||
<div class="sidebar-inner">
|
||
<!-- 头部 -->
|
||
<div class="sidebar-header">
|
||
<span class="title">聊天室</span>
|
||
</div>
|
||
|
||
<!-- 搜索框 -->
|
||
<div class="search-box">
|
||
<ElInput
|
||
v-model="searchText"
|
||
placeholder="搜索工单号、来客姓名、电话..."
|
||
:prefix-icon="Search"
|
||
clearable
|
||
/>
|
||
</div>
|
||
|
||
<!-- chatRoom列表 -->
|
||
<div class="room-list-container">
|
||
<div v-if="filteredRooms.length === 0" class="empty-tip">
|
||
暂无聊天室
|
||
</div>
|
||
<div
|
||
v-for="room in filteredRooms"
|
||
:key="room.roomId"
|
||
class="room-item"
|
||
:class="{ active: currentRoomId === room.roomId }"
|
||
@click="selectRoom(room)"
|
||
>
|
||
<!-- 头像 -->
|
||
<div class="room-avatar">
|
||
{{ room.guestName?.substring(0, 1) || '?' }}
|
||
</div>
|
||
|
||
<!-- 信息 -->
|
||
<div class="room-info">
|
||
<div class="room-header">
|
||
<div class="room-name">{{ room.roomName }}</div>
|
||
<div class="room-time">{{ formatTime(room.lastMessageTime) }}</div>
|
||
</div>
|
||
<div class="last-message-row">
|
||
<div class="last-message">{{ room.lastMessage || '暂无消息' }}</div>
|
||
<!-- 未读红点 -->
|
||
<div v-if="(room.unreadCount ?? 0) > 0" class="unread-badge">
|
||
{{ (room.unreadCount ?? 0) > 99 ? '99+' : room.unreadCount }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</aside>
|
||
|
||
<!-- 主聊天区域 -->
|
||
<main class="chat-main" :class="{ 'sidebar-open': isSidebarOpen }">
|
||
<template v-if="currentRoomId">
|
||
<ChatRoom
|
||
ref="chatRoomRef"
|
||
:messages="messages"
|
||
:current-user-id="loginDomain.user.userId"
|
||
:room-id="currentRoomId"
|
||
:workcase-id="currentWorkcaseId"
|
||
:room-name="currentRoom?.roomName"
|
||
:file-download-url="FILE_DOWNLOAD_URL"
|
||
:has-more="hasMore"
|
||
:loading-more="loadingMore"
|
||
@send-message="handleSendMessage"
|
||
@start-meeting="startMeeting"
|
||
@download-file="downloadFile"
|
||
@load-more="loadMoreMessages"
|
||
>
|
||
<template #header>
|
||
<div class="chat-room-header">
|
||
<div class="header-left">
|
||
<div class="room-avatar-small">
|
||
{{ currentRoom?.guestName?.substring(0, 1) }}
|
||
</div>
|
||
<div class="room-title-group">
|
||
<div class="room-name-text">{{ currentRoom?.roomName }}</div>
|
||
<div class="room-subtitle">
|
||
工单 #{{ currentRoom?.workcaseId }} · {{ currentRoom?.guestName }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<template #action-area>
|
||
<ElButton type="primary" @click="handleWorkcaseAction">
|
||
<FileText :size="16" />
|
||
{{ currentWorkcaseId ? '查看工单' : '创建工单' }}
|
||
</ElButton>
|
||
</template>
|
||
</ChatRoom>
|
||
</template>
|
||
|
||
<!-- 空状态 -->
|
||
<div v-else class="empty-state">
|
||
<div class="empty-content">
|
||
<div class="empty-icon">
|
||
<MessageSquare :size="40" />
|
||
</div>
|
||
<h3 class="empty-title">选择一个聊天室开始对话</h3>
|
||
<p class="empty-desc">从左侧列表中选择一个聊天室查看消息</p>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
|
||
<!-- 工单详情对话框 -->
|
||
<ElDialog
|
||
v-model="showWorkcaseDetail"
|
||
title="工单详情"
|
||
width="800px"
|
||
class="workcase-dialog"
|
||
destroy-on-close
|
||
>
|
||
<WorkcaseDetail
|
||
mode="view"
|
||
:workcase-id="currentWorkcaseId"
|
||
@cancel="showWorkcaseDetail = false"
|
||
/>
|
||
</ElDialog>
|
||
|
||
<!-- 工单创建对话框 -->
|
||
<ElDialog
|
||
v-model="showWorkcaseCreator"
|
||
title="创建工单"
|
||
width="800px"
|
||
class="workcase-dialog"
|
||
destroy-on-close
|
||
>
|
||
<WorkcaseDetail
|
||
mode="create"
|
||
:room-id="currentRoomId!"
|
||
@cancel="showWorkcaseCreator = false"
|
||
@created="onWorkcaseCreated"
|
||
/>
|
||
</ElDialog>
|
||
</div>
|
||
</template>
|
||
<script setup lang="ts">
|
||
import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue'
|
||
import { ElButton, ElInput, ElDialog, ElMessage } from 'element-plus'
|
||
import { Search, FileText, MessageSquare, MessageCircle, ChevronLeft, ChevronRight } from 'lucide-vue-next'
|
||
import ChatRoom from './chatRoom/ChatRoom.vue'
|
||
import WorkcaseDetail from '@/views/public/workcase/WorkcaseDetail/WorkcaseDetail.vue'
|
||
import { workcaseChatAPI } from '@/api/workcase'
|
||
import { fileAPI } from 'shared/api/file'
|
||
import { FILE_DOWNLOAD_URL } from '@/config'
|
||
import type { ChatRoomVO, ChatRoomMessageVO, TbChatRoomMessageDTO, TbChatRoomMemberDTO } from '@/types/workcase'
|
||
import SockJS from 'sockjs-client'
|
||
import { Client } from '@stomp/stompjs'
|
||
|
||
// WebSocket配置 (通过Nginx代理访问网关,再到workcase服务)
|
||
// SockJS URL (http://)
|
||
const getWsUrl = () => {
|
||
const token = JSON.parse(localStorage.getItem('token')).value || ''
|
||
const protocol = window.location.protocol
|
||
const host = window.location.host
|
||
return `${protocol}//${host}/api/urban-lifeline/workcase/ws/chat-sockjs?token=${encodeURIComponent(token)}`
|
||
}
|
||
|
||
// STOMP客户端
|
||
let stompClient: any = null
|
||
let roomSubscription: any = null
|
||
let listSubscription: any = null
|
||
|
||
// 当前用户ID(从登录状态获取)
|
||
const loginDomain = JSON.parse(localStorage.getItem('loginDomain')!)
|
||
|
||
// 侧边栏展开状态
|
||
const isSidebarOpen = ref(false)
|
||
|
||
// 切换侧边栏
|
||
const toggleSidebar = () => {
|
||
isSidebarOpen.value = !isSidebarOpen.value
|
||
}
|
||
|
||
// 搜索文本
|
||
const searchText = ref('')
|
||
const userType = true //web端固定这个
|
||
// 加载状态
|
||
const loading = ref(false)
|
||
const messageLoading = ref(false)
|
||
const loadingMore = ref(false)
|
||
// 自动填充加载状态(已禁用)
|
||
// const autoFilling = ref(false)
|
||
|
||
// 分页状态
|
||
const PAGE_SIZE = 20
|
||
const currentPage = ref(1)
|
||
const hasMore = ref(true)
|
||
|
||
// 聊天室列表
|
||
const chatRooms = ref<ChatRoomVO[]>([])
|
||
|
||
// 当前选中的聊天室ID
|
||
const currentRoomId = ref<string | null>(null)
|
||
|
||
// 当前聊天室
|
||
const currentRoom = computed(() =>
|
||
chatRooms.value.find((r: ChatRoomVO) => r.roomId === currentRoomId.value)
|
||
)
|
||
|
||
// 当前工单ID
|
||
const currentWorkcaseId = computed(() => currentRoom.value?.workcaseId || '')
|
||
|
||
// 过滤后的聊天室列表
|
||
const filteredRooms = computed(() => {
|
||
if (!searchText.value) return chatRooms.value
|
||
const keyword = searchText.value.toLowerCase()
|
||
return chatRooms.value.filter((room: ChatRoomVO) =>
|
||
room.roomName?.toLowerCase().includes(keyword) ||
|
||
room.guestName?.toLowerCase().includes(keyword) ||
|
||
room.workcaseId?.toLowerCase().includes(keyword)
|
||
)
|
||
})
|
||
|
||
// 消息列表
|
||
const messages = ref<ChatRoomMessageVO[]>([])
|
||
|
||
// 工单详情对话框
|
||
const showWorkcaseDetail = ref(false)
|
||
// 工单创建对话框
|
||
const showWorkcaseCreator = ref(false)
|
||
|
||
// ChatRoom组件引用
|
||
const chatRoomRef = ref<InstanceType<typeof ChatRoom> | null>(null)
|
||
|
||
// 获取聊天室列表
|
||
const fetchChatRooms = async () => {
|
||
loading.value = true
|
||
try {
|
||
const result = await workcaseChatAPI.getChatRoomPage({
|
||
filter: {
|
||
status: 'active'
|
||
},
|
||
pageParam: { page: 1, pageSize: 100, total: 0 }
|
||
})
|
||
if (result.success && result.pageDomain) {
|
||
chatRooms.value = result.pageDomain.dataList || []
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('获取聊天室列表失败:', error)
|
||
ElMessage.error('获取聊天室列表失败')
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
// 选择聊天室
|
||
const selectRoom = async (room: ChatRoomVO) => {
|
||
currentRoomId.value = room.roomId!
|
||
|
||
// 自动加入聊天室成员表(如果不存在)
|
||
try {
|
||
const memberData: TbChatRoomMemberDTO = {
|
||
roomId: room.roomId,
|
||
userId: loginDomain.user.userId,
|
||
userName: loginDomain.userInfo.username,
|
||
userType: 'staff'
|
||
}
|
||
await workcaseChatAPI.addChatRoomMember(memberData)
|
||
room.unreadCount = 0
|
||
} catch (error) {
|
||
// 已存在成员或其他错误,忽略
|
||
console.debug('加入聊天室:', error)
|
||
}
|
||
|
||
loadMessages(room.roomId!)
|
||
}
|
||
|
||
// 加载消息(初始加载page1,后端降序返回)
|
||
const loadMessages = async (roomId: string) => {
|
||
messageLoading.value = true
|
||
currentPage.value = 1
|
||
hasMore.value = true
|
||
try {
|
||
const result = await workcaseChatAPI.getChatMessagePage({
|
||
filter: { roomId },
|
||
pageParam: { page: 1, pageSize: PAGE_SIZE }
|
||
})
|
||
if (result.success && result.pageDomain) {
|
||
const pageInfo = result.pageDomain.pageParam
|
||
const actualTotalPages = pageInfo?.totalPages || 1
|
||
hasMore.value = actualTotalPages > currentPage.value
|
||
|
||
// 后端降序返回,需要反转后显示(早的在上,新的在下)
|
||
const dataList = result.pageDomain.dataList || []
|
||
messages.value = [...dataList].reverse()
|
||
|
||
// 首次加载后自动填充消息直到出现滚动条(已禁用)
|
||
// await autoFillMessages(roomId)
|
||
}
|
||
// 加载完成后滚动到底部
|
||
scrollToBottom()
|
||
} catch (error) {
|
||
console.error('加载消息失败:', error)
|
||
ElMessage.error('加载消息失败')
|
||
} finally {
|
||
messageLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 自动填充消息直到出现滚动条(已禁用)
|
||
/*
|
||
const autoFillMessages = async (roomId: string) => {
|
||
// autoFilling.value = true
|
||
console.log('[autoFill] 开始检查消息高度, hasMore:', hasMore.value, 'messages:', messages.value.length)
|
||
|
||
// 等待DOM渲染
|
||
await nextTick()
|
||
await new Promise(resolve => setTimeout(resolve, 300))
|
||
|
||
let attempts = 0
|
||
const maxAttempts = 20
|
||
|
||
while (hasMore.value && attempts < maxAttempts) {
|
||
attempts++
|
||
|
||
const container = chatRoomRef.value?.$el?.querySelector?.('.messages-container')
|
||
const messagesList = chatRoomRef.value?.$el?.querySelector?.('.messages-list')
|
||
|
||
if (!container || !messagesList) {
|
||
console.warn('[autoFill] 找不到容器或消息列表')
|
||
break
|
||
}
|
||
|
||
// 容器高度(可视区域)
|
||
const containerHeight = container.clientHeight
|
||
// 消息列表实际高度(内容)
|
||
const listHeight = messagesList.offsetHeight
|
||
const fillPercent = containerHeight > 0 ? Math.round(listHeight / containerHeight * 100) : 0
|
||
|
||
console.log(`[autoFill] 第${attempts}次检查 - 容器高度: ${containerHeight}px, 列表高度: ${listHeight}px, 填充率: ${fillPercent}%, 消息数: ${messages.value.length}`)
|
||
|
||
// 判断是否已经溢出(列表高度 >= 容器高度)
|
||
if (containerHeight > 0 && listHeight >= containerHeight) {
|
||
console.log(`[autoFill] ✓ 列表已溢出(${fillPercent}%),可以滚动!停止加载`)
|
||
break
|
||
}
|
||
|
||
// 内容不足,继续加载下一页
|
||
console.log('[autoFill] → 内容不足,继续加载历史消息...')
|
||
const nextPage = currentPage.value + 1
|
||
const result = await workcaseChatAPI.getChatMessagePage({
|
||
filter: { roomId },
|
||
pageParam: { page: nextPage, pageSize: PAGE_SIZE }
|
||
})
|
||
|
||
if (result.success && result.pageDomain) {
|
||
const pageInfo = result.pageDomain.pageParam
|
||
const actualTotalPages = pageInfo?.totalPages || 1
|
||
const dataList = result.pageDomain.dataList || []
|
||
|
||
if (dataList.length > 0) {
|
||
currentPage.value = nextPage
|
||
hasMore.value = actualTotalPages > currentPage.value
|
||
|
||
// 反转后插入到列表前面
|
||
const reversedList = [...dataList].reverse()
|
||
messages.value.unshift(...reversedList)
|
||
console.log(`[autoFill] ✓ 加载第${nextPage}页完成, 新增${dataList.length}条, 总消息数: ${messages.value.length}`)
|
||
|
||
await nextTick()
|
||
await new Promise(resolve => setTimeout(resolve, 200))
|
||
} else {
|
||
hasMore.value = false
|
||
break
|
||
}
|
||
} else {
|
||
break
|
||
}
|
||
}
|
||
|
||
if (attempts >= maxAttempts) {
|
||
console.warn('[autoFill] ⚠ 达到最大尝试次数')
|
||
}
|
||
|
||
console.log(`[autoFill] 自动填充结束 - 共尝试${attempts}次, 最终消息数: ${messages.value.length}, hasMore: ${hasMore.value}`)
|
||
|
||
// autoFilling.value = false
|
||
}
|
||
*/
|
||
|
||
// 加载更多历史消息(滚动到顶部触发)
|
||
const loadMoreMessages = async () => {
|
||
if (!currentRoomId.value || loadingMore.value || !hasMore.value) return
|
||
|
||
const nextPage = currentPage.value + 1
|
||
loadingMore.value = true
|
||
try {
|
||
const result = await workcaseChatAPI.getChatMessagePage({
|
||
filter: { roomId: currentRoomId.value },
|
||
pageParam: { page: nextPage, pageSize: PAGE_SIZE }
|
||
})
|
||
if (result.success && result.pageDomain) {
|
||
const pageInfo = result.pageDomain.pageParam
|
||
const actualTotalPages = pageInfo?.totalPages || 1
|
||
const dataList = result.pageDomain.dataList || []
|
||
|
||
if (dataList.length > 0) {
|
||
currentPage.value = nextPage
|
||
hasMore.value = actualTotalPages > currentPage.value
|
||
// 后端降序返回,反转后插入到列表前面
|
||
const reversedList = [...dataList].reverse()
|
||
messages.value.unshift(...reversedList)
|
||
} else {
|
||
hasMore.value = false
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('加载更多消息失败:', error)
|
||
} finally {
|
||
loadingMore.value = false
|
||
}
|
||
}
|
||
|
||
// 处理发送消息(从ChatRoom组件触发)
|
||
const handleSendMessage = async (content: string, files: File[]) => {
|
||
if (!currentRoomId.value) return
|
||
|
||
try {
|
||
// 上传文件获取fileIds
|
||
let fileIds: string[] = []
|
||
if (files.length > 0) {
|
||
const uploadResult = await fileAPI.batchUpload(files, 'chatroom')
|
||
if (uploadResult.success && uploadResult.dataList) {
|
||
fileIds = uploadResult.dataList.map((f: any) => f.fileId)
|
||
}
|
||
}
|
||
|
||
// 构造消息
|
||
const messageData: TbChatRoomMessageDTO = {
|
||
roomId: currentRoomId.value,
|
||
senderId: loginDomain.user.userId,
|
||
senderName: loginDomain.userInfo.username,
|
||
senderType: 'agent',
|
||
content,
|
||
files: fileIds,
|
||
messageType: 'text'
|
||
}
|
||
|
||
// 发送消息
|
||
const result = await workcaseChatAPI.sendMessage(messageData)
|
||
if (result.success && result.data) {
|
||
// 添加到消息列表
|
||
messages.value.push(result.data as ChatRoomMessageVO)
|
||
scrollToBottom()
|
||
} else {
|
||
ElMessage.error(result.message || '发送失败')
|
||
}
|
||
} catch (error) {
|
||
console.error('发送消息失败:', error)
|
||
ElMessage.error('发送消息失败')
|
||
}
|
||
}
|
||
|
||
// 下载文件
|
||
const downloadFile = (fileId: string) => {
|
||
window.open(`${FILE_DOWNLOAD_URL}/${fileId}`, '_blank')
|
||
}
|
||
|
||
// 处理工单操作(创建或查看)
|
||
const handleWorkcaseAction = () => {
|
||
if (currentWorkcaseId.value) {
|
||
// 已有工单,查看详情
|
||
showWorkcaseDetail.value = true
|
||
} else {
|
||
// 无工单,创建新工单
|
||
showWorkcaseCreator.value = true
|
||
}
|
||
}
|
||
|
||
// 工单创建成功
|
||
const onWorkcaseCreated = (workcaseId: string) => {
|
||
showWorkcaseCreator.value = false
|
||
// 更新当前聊天室的工单ID
|
||
if (currentRoom.value) {
|
||
currentRoom.value.workcaseId = workcaseId
|
||
}
|
||
// 刷新聊天室列表
|
||
fetchChatRooms()
|
||
ElMessage.success('工单创建成功')
|
||
}
|
||
|
||
// 发起会议
|
||
const startMeeting = async () => {
|
||
if (!currentRoomId.value) return
|
||
|
||
try {
|
||
// 先检查是否有活跃会议
|
||
const activeResult = await workcaseChatAPI.getActiveMeeting(currentRoomId.value)
|
||
if (activeResult.success && activeResult.data) {
|
||
// 已有活跃会议,直接加入
|
||
currentMeetingId.value = activeResult.data.meetingId!
|
||
const joinResult = await workcaseChatAPI.joinVideoMeeting(currentMeetingId.value!)
|
||
if (joinResult.success && joinResult.data?.iframeUrl) {
|
||
// 使用router跳转到JitsiMeetingView页面,附加roomId参数用于返回
|
||
const meetingUrl = joinResult.data.iframeUrl + `&roomId=${currentRoomId.value}`
|
||
router.push(meetingUrl)
|
||
} else {
|
||
ElMessage.error(joinResult.message || '加入会议失败')
|
||
}
|
||
return
|
||
}
|
||
|
||
// 没有活跃会议,创建新会议
|
||
const createResult = await workcaseChatAPI.createVideoMeeting({
|
||
roomId: currentRoomId.value,
|
||
meetingName: currentRoom.value?.roomName || '视频会议'
|
||
})
|
||
|
||
if (createResult.success && createResult.data) {
|
||
currentMeetingId.value = createResult.data.meetingId!
|
||
|
||
// 开始会议
|
||
await workcaseChatAPI.startVideoMeeting(currentMeetingId.value!)
|
||
|
||
// 加入会议获取会议页面URL
|
||
const joinResult = await workcaseChatAPI.joinVideoMeeting(currentMeetingId.value!)
|
||
if (joinResult.success && joinResult.data?.iframeUrl) {
|
||
// 使用router跳转到JitsiMeetingView页面,附加roomId参数用于返回
|
||
const meetingUrl = joinResult.data.iframeUrl + `&roomId=${currentRoomId.value}`
|
||
router.push(meetingUrl)
|
||
ElMessage.success('会议已创建')
|
||
} else {
|
||
ElMessage.error(joinResult.message || '获取会议链接失败')
|
||
}
|
||
} else {
|
||
ElMessage.error(createResult.message || '创建会议失败')
|
||
}
|
||
} catch (error) {
|
||
console.error('发起会议失败:', error)
|
||
ElMessage.error('发起会议失败')
|
||
}
|
||
}
|
||
|
||
// 滚动聊天消息到底部
|
||
const scrollToBottom = () => {
|
||
nextTick(() => {
|
||
chatRoomRef.value?.scrollToBottom()
|
||
})
|
||
}
|
||
|
||
// 格式化时间(用于聊天室列表)
|
||
const formatTime = (time: string | null | undefined) => {
|
||
if (!time) return ''
|
||
const date = new Date(time)
|
||
const now = new Date()
|
||
const diff = now.getTime() - date.getTime()
|
||
|
||
if (diff < 60000) return '刚刚'
|
||
if (diff < 3600000) return Math.floor(diff / 60000) + '分钟前'
|
||
if (diff < 86400000) return Math.floor(diff / 3600000) + '小时前'
|
||
|
||
return date.toLocaleDateString('zh-CN', { month: '2-digit', day: '2-digit' })
|
||
}
|
||
|
||
// ==================== WebSocket连接管理 ====================
|
||
|
||
// 初始化WebSocket连接(支持SockJS降级)
|
||
const initWebSocket = () => {
|
||
const token = localStorage.getItem('token') || ''
|
||
const wsUrl = getWsUrl()
|
||
|
||
console.log('WebSocket连接URL:', wsUrl)
|
||
|
||
// 创建STOMP客户端,使用SockJS(支持降级)
|
||
stompClient = new Client({
|
||
webSocketFactory: () => new SockJS(wsUrl),
|
||
connectHeaders: {
|
||
Authorization: `Bearer ${token}`
|
||
},
|
||
reconnectDelay: 5000,
|
||
heartbeatIncoming: 4000,
|
||
heartbeatOutgoing: 4000,
|
||
debug: (str: string) => {
|
||
console.log('[STOMP]', str)
|
||
},
|
||
onConnect: () => {
|
||
console.log('WebSocket已连接')
|
||
// 订阅聊天室列表更新
|
||
subscribeToListUpdate()
|
||
// 如果当前有选中的聊天室,订阅该聊天室消息
|
||
if (currentRoomId.value) {
|
||
subscribeToRoom(currentRoomId.value)
|
||
}
|
||
},
|
||
onDisconnect: () => {
|
||
console.log('WebSocket已断开')
|
||
},
|
||
onStompError: (frame: any) => {
|
||
console.error('STOMP错误:', frame)
|
||
},
|
||
onWebSocketError: (event: any) => {
|
||
console.error('WebSocket错误:', event)
|
||
}
|
||
})
|
||
|
||
stompClient.activate()
|
||
}
|
||
|
||
// 订阅聊天室列表更新 (用于更新列表中的lastMessage和未读数)
|
||
const subscribeToListUpdate = () => {
|
||
if (!stompClient || !stompClient.connected) return
|
||
|
||
listSubscription = stompClient.subscribe('/topic/chat/list-update', async (message: any) => {
|
||
const chatMessage = JSON.parse(message.body)
|
||
// 更新对应聊天室的lastMessage和lastMessageTime
|
||
const roomIndex = chatRooms.value.findIndex((r: ChatRoomVO) => r.roomId === chatMessage.roomId)
|
||
if (roomIndex !== -1) {
|
||
// 查询当前用户在该聊天室的未读数
|
||
let unreadCount = 0
|
||
try {
|
||
const unreadResult = await workcaseChatAPI.getUnreadCount(
|
||
chatMessage.roomId,
|
||
loginDomain.user.userId
|
||
)
|
||
if (unreadResult.success && unreadResult.data !== undefined) {
|
||
unreadCount = unreadResult.data
|
||
}
|
||
} catch (error) {
|
||
console.error('查询未读数失败:', error)
|
||
}
|
||
|
||
chatRooms.value[roomIndex] = {
|
||
...chatRooms.value[roomIndex],
|
||
lastMessage: chatMessage.content,
|
||
lastMessageTime: chatMessage.sendTime,
|
||
unreadCount: unreadCount
|
||
}
|
||
|
||
// 将更新的聊天室移到列表顶部
|
||
const updatedRoom = chatRooms.value[roomIndex]
|
||
chatRooms.value.splice(roomIndex, 1)
|
||
chatRooms.value.unshift(updatedRoom)
|
||
}
|
||
})
|
||
}
|
||
|
||
// 订阅指定聊天室消息 (用于实时接收消息)
|
||
const subscribeToRoom = (roomId: string) => {
|
||
if (!stompClient || !stompClient.connected) return
|
||
|
||
// 先取消之前的订阅
|
||
if (roomSubscription) {
|
||
roomSubscription.unsubscribe()
|
||
roomSubscription = null
|
||
}
|
||
|
||
roomSubscription = stompClient.subscribe(`/topic/chat/${roomId}`, (message: any) => {
|
||
const chatMessage = JSON.parse(message.body) as ChatRoomMessageVO
|
||
// 避免重复添加自己发送的普通消息
|
||
// 但会议消息(meet类型)始终添加,因为它是系统生成的通知
|
||
if (chatMessage.messageType === 'meet' || chatMessage.senderId !== loginDomain.user.userId) {
|
||
messages.value.push(chatMessage)
|
||
scrollToBottom()
|
||
}
|
||
})
|
||
}
|
||
|
||
// 断开WebSocket连接
|
||
const disconnectWebSocket = () => {
|
||
if (roomSubscription) {
|
||
roomSubscription.unsubscribe()
|
||
roomSubscription = null
|
||
}
|
||
if (listSubscription) {
|
||
listSubscription.unsubscribe()
|
||
listSubscription = null
|
||
}
|
||
if (stompClient) {
|
||
stompClient.deactivate()
|
||
stompClient = null
|
||
}
|
||
}
|
||
|
||
// 监听currentRoomId变化,切换聊天室时重新订阅
|
||
watch(currentRoomId, (newRoomId) => {
|
||
if (newRoomId && stompClient?.connected) {
|
||
subscribeToRoom(newRoomId)
|
||
}
|
||
})
|
||
|
||
// 初始化
|
||
onMounted(() => {
|
||
fetchChatRooms()
|
||
initWebSocket()
|
||
})
|
||
|
||
// 组件卸载时断开连接
|
||
onUnmounted(() => {
|
||
disconnectWebSocket()
|
||
})
|
||
</script>
|
||
<style scoped lang="scss">
|
||
@import url("./ChatRoomView.scss");
|
||
</style> |