web端聊天室优化
This commit is contained in:
@@ -1,69 +1,102 @@
|
||||
<template>
|
||||
<div class="chat-room-container">
|
||||
<!-- 聊天室列表侧边栏 -->
|
||||
<aside class="room-list-sidebar">
|
||||
<!-- 头部 -->
|
||||
<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"
|
||||
<!-- 折叠状态的侧边栏 -->
|
||||
<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"
|
||||
class="room-item"
|
||||
@click="selectRoom(room.roomId!); toggleSidebar()"
|
||||
class="sidebar-icon-btn"
|
||||
:class="{ active: currentRoomId === room.roomId }"
|
||||
@click="selectRoom(room.roomId!)"
|
||||
:title="room.roomName"
|
||||
>
|
||||
<!-- 头像 -->
|
||||
<div class="room-avatar">
|
||||
{{ room.guestName?.substring(0, 1) || '?' }}
|
||||
</div>
|
||||
{{ room.guestName?.substring(0, 1) || '?' }}
|
||||
</button>
|
||||
</div>
|
||||
<button class="expand-btn" @click="toggleSidebar" title="展开聊天室列表">
|
||||
<ChevronRight :size="18" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 信息 -->
|
||||
<div class="room-info">
|
||||
<div class="room-header">
|
||||
<div class="room-name">{{ room.roomName }}</div>
|
||||
<div class="room-time">{{ formatTime(room.lastMessageTime) }}</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.roomId!)"
|
||||
>
|
||||
<!-- 头像 -->
|
||||
<div class="room-avatar">
|
||||
{{ room.guestName?.substring(0, 1) || '?' }}
|
||||
</div>
|
||||
<div class="last-message">{{ room.lastMessage || '暂无消息' }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 未读红点 -->
|
||||
<div v-if="(room.unreadCount ?? 0) > 0" class="unread-badge">
|
||||
{{ (room.unreadCount ?? 0) > 99 ? '99+' : room.unreadCount }}
|
||||
<!-- 信息 -->
|
||||
<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">{{ room.lastMessage || '暂无消息' }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 未读红点 -->
|
||||
<div v-if="(room.unreadCount ?? 0) > 0" class="unread-badge">
|
||||
{{ (room.unreadCount ?? 0) > 99 ? '99+' : room.unreadCount }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- 主聊天区域 -->
|
||||
<main class="chat-main">
|
||||
<main class="chat-main" :class="{ 'sidebar-open': isSidebarOpen }">
|
||||
<template v-if="currentRoomId">
|
||||
<ChatRoom
|
||||
ref="chatRoomRef"
|
||||
:messages="messages"
|
||||
:current-user-id="userId"
|
||||
:room-name="currentRoom?.roomName"
|
||||
:meeting-url="currentMeetingUrl"
|
||||
:show-meeting="showMeetingIframe"
|
||||
: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">
|
||||
@@ -113,10 +146,11 @@
|
||||
</ElDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue'
|
||||
import { ElButton, ElInput, ElDialog, ElMessage } from 'element-plus'
|
||||
import { Search, FileText, MessageSquare } from 'lucide-vue-next'
|
||||
import { Search, FileText, MessageSquare, MessageCircle, ChevronLeft, ChevronRight } from 'lucide-vue-next'
|
||||
import { ChatRoom } from '@/components/chatRoom'
|
||||
import WorkcaseDetail from '@/views/public/workcase/WorkcaseDetail/WorkcaseDetail.vue'
|
||||
import { workcaseChatAPI } from '@/api/workcase'
|
||||
@@ -143,12 +177,26 @@ let listSubscription: any = null
|
||||
// 当前用户ID(从登录状态获取)
|
||||
const userId = ref(localStorage.getItem('userId') || '')
|
||||
|
||||
// 侧边栏展开状态
|
||||
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 PAGE_SIZE = 5
|
||||
const currentPage = ref(1)
|
||||
const hasMore = ref(true)
|
||||
|
||||
// 聊天室列表
|
||||
const chatRooms = ref<ChatRoomVO[]>([])
|
||||
@@ -185,6 +233,9 @@ const showWorkcaseDetail = ref(false)
|
||||
const currentMeetingUrl = ref('')
|
||||
const showMeetingIframe = ref(false)
|
||||
|
||||
// ChatRoom组件引用
|
||||
const chatRoomRef = ref<InstanceType<typeof ChatRoom> | null>(null)
|
||||
|
||||
// 获取聊天室列表
|
||||
const fetchChatRooms = async () => {
|
||||
loading.value = true
|
||||
@@ -196,6 +247,7 @@ const fetchChatRooms = async () => {
|
||||
if (result.success && result.pageDomain) {
|
||||
chatRooms.value = result.pageDomain.dataList || []
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取聊天室列表失败:', error)
|
||||
ElMessage.error('获取聊天室列表失败')
|
||||
@@ -210,17 +262,26 @@ const selectRoom = (roomId: string) => {
|
||||
loadMessages(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: 100, total: 0 }
|
||||
pageParam: { page: 1, pageSize: PAGE_SIZE }
|
||||
})
|
||||
if (result.success && result.pageDomain) {
|
||||
messages.value = result.pageDomain.dataList || []
|
||||
const pageInfo = result.pageDomain.pageParam
|
||||
const actualTotalPages = pageInfo?.totalPages || 1
|
||||
hasMore.value = actualTotalPages > currentPage.value
|
||||
|
||||
// 后端降序返回,需要反转后显示(早的在上,新的在下)
|
||||
const dataList = result.pageDomain.dataList || []
|
||||
messages.value = [...dataList].reverse()
|
||||
}
|
||||
// 加载完成后滚动到底部
|
||||
scrollToBottom()
|
||||
} catch (error) {
|
||||
console.error('加载消息失败:', error)
|
||||
@@ -230,6 +291,39 @@ const loadMessages = async (roomId: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 加载更多历史消息(滚动到顶部触发)
|
||||
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
|
||||
@@ -284,9 +378,11 @@ const startMeeting = async () => {
|
||||
showMeetingIframe.value = true
|
||||
}
|
||||
|
||||
// 滚动到底部
|
||||
// 滚动聊天消息到底部
|
||||
const scrollToBottom = () => {
|
||||
// TODO: 滚动到ChatRoom组件底部
|
||||
nextTick(() => {
|
||||
chatRoomRef.value?.scrollToBottom()
|
||||
})
|
||||
}
|
||||
|
||||
// 格式化时间(用于聊天室列表)
|
||||
|
||||
Reference in New Issue
Block a user