聊天室完成
This commit is contained in:
@@ -260,6 +260,31 @@
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.loading-mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: #f4f5f7;
|
||||
z-index: 999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.loading-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.loading-more {
|
||||
padding: 20rpx;
|
||||
display: flex;
|
||||
|
||||
@@ -46,6 +46,12 @@
|
||||
:style="{ top: (headerPaddingTop + 88) + 'px' }"
|
||||
@scrolltoupper="loadMoreMessages"
|
||||
upper-threshold="50">
|
||||
<!-- 加载遮罩层 -->
|
||||
<view v-if="showLoadingMask" class="loading-mask">
|
||||
<view class="loading-content">
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 加载更多提示 -->
|
||||
<view v-if="loadingMore" class="loading-more">
|
||||
<text class="loading-more-text">加载中...</text>
|
||||
@@ -113,7 +119,7 @@ import WorkcaseCreator from '@/components/WorkcaseCreator/WorkcaseCreator.uvue'
|
||||
import type { ChatRoomMessageVO, CustomerVO, ChatMemberVO, TbChatRoomMessageDTO } from '@/types/workcase'
|
||||
import { workcaseChatAPI } from '@/api/workcase'
|
||||
import { wsClient } from '@/utils/websocket'
|
||||
|
||||
import { WS_HOST } from '@/config'
|
||||
// 响应式数据
|
||||
const headerPaddingTop = ref<number>(44)
|
||||
const headerTotalHeight = ref<number>(88)
|
||||
@@ -148,6 +154,8 @@ function loadUserInfo() {
|
||||
|
||||
// 消息列表
|
||||
const messages = reactive<ChatRoomMessageVO[]>([])
|
||||
// 初始加载遮罩(自动填充期间显示,完成后隐藏)
|
||||
const showLoadingMask = ref<boolean>(true)
|
||||
|
||||
// 所有默认客服
|
||||
const defaultWorkers = reactive<CustomerVO[]>([])
|
||||
@@ -254,13 +262,28 @@ watch(roomId, (newRoomId, oldRoomId) => {
|
||||
})
|
||||
|
||||
// 加载聊天室
|
||||
const PAGE_SIZE = 5
|
||||
const PAGE_SIZE = 20
|
||||
const messageTotal = ref<number>(0)
|
||||
|
||||
async function loadChatRoom() {
|
||||
if (!roomId.value) return
|
||||
loading.value = true
|
||||
try {
|
||||
// 自动加入聊天室成员表(如果不存在)
|
||||
try {
|
||||
const userId = uni.getStorageSync('userId') || currentUserId.value
|
||||
const userName = uni.getStorageSync('userName') || currentUserName.value
|
||||
await workcaseChatAPI.addChatRoomMember({
|
||||
roomId: roomId.value,
|
||||
userId: userId,
|
||||
userName: userName,
|
||||
userType: 'guest'
|
||||
})
|
||||
} catch (error) {
|
||||
// 已存在成员或其他错误,忽略
|
||||
console.debug('[chatRoom] 加入聊天室成员表:', error)
|
||||
}
|
||||
|
||||
// 获取聊天室信息
|
||||
const roomRes = await workcaseChatAPI.getChatRoomById(roomId.value)
|
||||
if (roomRes.success && roomRes.data) {
|
||||
@@ -300,13 +323,116 @@ async function loadMessages() {
|
||||
const reversedList = [...msgRes.dataList].reverse()
|
||||
messages.splice(0, messages.length, ...reversedList)
|
||||
console.log('[loadMessages] 加载完成, 消息数:', messages.length)
|
||||
nextTick(() => scrollToBottom())
|
||||
|
||||
// 加载完第一页后检查是否需要自动填充
|
||||
if (currentPage.value === 1) {
|
||||
await autoFillMessages() // 自动填充结束后会自动滚动到底部
|
||||
} else {
|
||||
// 非首次加载(如刷新等),直接滚动到底部
|
||||
nextTick(() => scrollToBottom())
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载消息列表失败:', e)
|
||||
}
|
||||
}
|
||||
|
||||
// 自动填充消息直到出现滚动条
|
||||
async function autoFillMessages() {
|
||||
console.log('[autoFill] 开始自动填充, hasMore:', hasMore.value, 'messages:', messages.length)
|
||||
|
||||
// 等待DOM渲染
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
|
||||
// 持续加载直到真正出现滚动或没有更多数据
|
||||
let attempts = 0
|
||||
const maxAttempts = 20 // 最多20次,防止死循环
|
||||
|
||||
while (hasMore.value && attempts < maxAttempts) {
|
||||
attempts++
|
||||
|
||||
try {
|
||||
// 获取 scroll-view 容器高度
|
||||
const query = uni.createSelectorQuery()
|
||||
const chatAreaHeight: number = await new Promise((resolve) => {
|
||||
query.select('.chat-area').boundingClientRect().exec((res: any[]) => {
|
||||
const rect = res[0]
|
||||
console.log('[autoFill] chat-area rect:', rect)
|
||||
resolve(rect?.height || 0)
|
||||
})
|
||||
})
|
||||
|
||||
// 获取消息列表容器高度(.message-list)
|
||||
const query2 = uni.createSelectorQuery()
|
||||
const contentHeight: number = await new Promise((resolve) => {
|
||||
query2.select('.message-list').boundingClientRect().exec((res: any[]) => {
|
||||
const rect = res[0]
|
||||
console.log('[autoFill] message-list rect:', rect)
|
||||
resolve(rect?.height || 0)
|
||||
})
|
||||
})
|
||||
|
||||
const fillPercent = chatAreaHeight > 0 ? Math.round(contentHeight / chatAreaHeight * 100) : 0
|
||||
console.log(`[autoFill] 第${attempts}次检查 - 容器: ${chatAreaHeight}px, 内容: ${contentHeight}px, 填充率: ${fillPercent}%, 消息数: ${messages.length}, hasMore: ${hasMore.value}`)
|
||||
|
||||
// 判断是否已经溢出(内容高度 >= 容器高度)
|
||||
if (chatAreaHeight > 0 && contentHeight >= chatAreaHeight) {
|
||||
console.log(`[autoFill] ✓ 内容已溢出(${fillPercent}%),可以滚动!停止加载`)
|
||||
break
|
||||
}
|
||||
|
||||
// 内容不足,继续加载下一页
|
||||
if (chatAreaHeight > 0) {
|
||||
console.log('[autoFill] → 内容不足,继续加载历史消息...')
|
||||
const nextPage = currentPage.value + 1
|
||||
const msgRes = await workcaseChatAPI.getChatMessagePage({
|
||||
filter: { roomId: roomId.value },
|
||||
pageParam: { page: nextPage, pageSize: PAGE_SIZE }
|
||||
})
|
||||
|
||||
if (msgRes.success && msgRes.dataList && msgRes.dataList.length > 0) {
|
||||
const pageInfo = msgRes.pageDomain?.pageParam
|
||||
const actualTotalPages = pageInfo?.totalPages || 1
|
||||
currentPage.value = nextPage
|
||||
hasMore.value = actualTotalPages > currentPage.value
|
||||
|
||||
// 反转后插入到列表前面
|
||||
const reversedList = [...msgRes.dataList].reverse()
|
||||
messages.unshift(...reversedList)
|
||||
console.log(`[autoFill] ✓ 加载第${nextPage}页完成, 新增${msgRes.dataList.length}条, 总消息数: ${messages.length}, hasMore: ${hasMore.value}`)
|
||||
|
||||
// 等待DOM更新
|
||||
await new Promise(resolve => setTimeout(resolve, 300))
|
||||
} else {
|
||||
console.log('[autoFill] ✗ 没有更多数据了')
|
||||
hasMore.value = false
|
||||
break
|
||||
}
|
||||
} else {
|
||||
console.error('[autoFill] ✗ 无法获取容器高度')
|
||||
break
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[autoFill] ✗ 检查失败:', error)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (attempts >= maxAttempts) {
|
||||
console.warn('[autoFill] ⚠ 达到最大尝试次数,停止自动填充')
|
||||
}
|
||||
|
||||
console.log(`[autoFill] 自动填充结束 - 共尝试${attempts}次, 最终消息数: ${messages.length}, hasMore: ${hasMore.value}`)
|
||||
|
||||
// 移除加载遮罩并滚动到底部
|
||||
showLoadingMask.value = false
|
||||
await new Promise(resolve => setTimeout(resolve, 50))
|
||||
nextTick(() => {
|
||||
scrollToBottom()
|
||||
console.log('[autoFill] 遮罩已移除,已滚动到底部')
|
||||
})
|
||||
}
|
||||
|
||||
// 加载更多历史消息(滚动到顶部触发,加载下一页更早的消息)
|
||||
async function loadMoreMessages() {
|
||||
console.log('[loadMoreMessages] 触发, roomId:', roomId.value, 'loadingMore:', loadingMore.value, 'hasMore:', hasMore.value, 'currentPage:', currentPage.value)
|
||||
@@ -475,9 +601,11 @@ async function initWebSocket() {
|
||||
}
|
||||
|
||||
// 构建WebSocket URL
|
||||
const protocol = 'wss:' // 生产环境使用wss
|
||||
const host = 'your-domain.com' // 需要替换为实际域名
|
||||
const wsUrl = `${protocol}//${host}/api/urban-lifeline/workcase/ws/chat-sockjs?token=${encodeURIComponent(token)}`
|
||||
// 开发环境:ws://localhost:8180 或 ws://192.168.x.x:8180
|
||||
// 生产环境:wss://your-domain.com
|
||||
const protocol = 'ws:' // 开发环境使用ws,生产环境改为wss
|
||||
// 小程序使用原生WebSocket端点(不是SockJS端点)
|
||||
const wsUrl = `${protocol}//${WS_HOST}/urban-lifeline/workcase/ws/chat?token=${encodeURIComponent(token)}`
|
||||
|
||||
console.log('[chatRoom] 开始连接WebSocket')
|
||||
await wsClient.connect(wsUrl, token)
|
||||
|
||||
@@ -50,8 +50,10 @@
|
||||
}
|
||||
|
||||
.list {
|
||||
height: calc(100vh - 88rpx); /* 减去导航栏高度 */
|
||||
padding: 20rpx 24rpx;
|
||||
padding-bottom: 60rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.room-card {
|
||||
|
||||
@@ -54,6 +54,7 @@ import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { workcaseChatAPI } from '@/api'
|
||||
import type { ChatRoomVO, TbChatRoomDTO, PageRequest, ChatRoomMessageVO } from '@/types'
|
||||
import { wsClient } from '@/utils/websocket'
|
||||
import { WS_HOST } from '@/config'
|
||||
|
||||
// 导航栏
|
||||
const navPaddingTop = ref<number>(0)
|
||||
@@ -168,6 +169,7 @@ function getStatusText(status?: string): string {
|
||||
|
||||
// 进入聊天室
|
||||
function enterRoom(room: ChatRoomVO) {
|
||||
room.unreadCount = 0
|
||||
uni.navigateTo({
|
||||
url: `/pages/chatRoom/chatRoom/chatRoom?roomId=${room.roomId}&workcaseId=${room.workcaseId || ''}`
|
||||
})
|
||||
@@ -190,9 +192,11 @@ async function initWebSocket() {
|
||||
}
|
||||
|
||||
// 构建WebSocket URL
|
||||
const protocol = 'wss:' // 生产环境使用wss
|
||||
const host = 'your-domain.com' // 需要替换为实际域名
|
||||
const wsUrl = `${protocol}//${host}/api/urban-lifeline/workcase/ws/chat-sockjs?token=${encodeURIComponent(token)}`
|
||||
// 开发环境:ws://localhost:8180 或 ws://192.168.x.x:8180
|
||||
// 生产环境:wss://your-domain.com
|
||||
const protocol = 'ws:' // 开发环境使用ws,生产环境改为wss
|
||||
// 小程序使用原生WebSocket端点(不是SockJS端点)
|
||||
const wsUrl = `${protocol}//${WS_HOST}/urban-lifeline/workcase/ws/chat?token=${encodeURIComponent(token)}`
|
||||
|
||||
console.log('[chatRoomList] 开始连接WebSocket')
|
||||
await wsClient.connect(wsUrl, token)
|
||||
@@ -216,16 +220,32 @@ function disconnectWebSocket() {
|
||||
}
|
||||
|
||||
// 处理列表更新消息
|
||||
function handleListUpdate(message: ChatRoomMessageVO) {
|
||||
async function handleListUpdate(message: ChatRoomMessageVO) {
|
||||
console.log('[chatRoomList] 收到列表更新消息:', message)
|
||||
|
||||
// 更新对应聊天室的lastMessage和lastMessageTime
|
||||
const roomIndex = chatRooms.value.findIndex((r: ChatRoomVO) => r.roomId === message.roomId)
|
||||
if (roomIndex !== -1) {
|
||||
// 查询当前用户在该聊天室的未读数
|
||||
let unreadCount = 0
|
||||
try {
|
||||
const userInfo = uni.getStorageSync('userInfo')
|
||||
const userId = typeof userInfo === 'string' ? JSON.parse(userInfo).userId : userInfo?.userId
|
||||
if (userId) {
|
||||
const unreadResult = await workcaseChatAPI.getUnreadCount(message.roomId, userId)
|
||||
if (unreadResult.success && unreadResult.data !== undefined) {
|
||||
unreadCount = unreadResult.data
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[chatRoomList] 查询未读数失败:', error)
|
||||
}
|
||||
|
||||
chatRooms.value[roomIndex] = {
|
||||
...chatRooms.value[roomIndex],
|
||||
lastMessage: message.content || '',
|
||||
lastMessageTime: message.sendTime || ''
|
||||
lastMessageTime: message.sendTime || '',
|
||||
unreadCount: unreadCount
|
||||
}
|
||||
|
||||
// 将更新的聊天室移到列表顶部
|
||||
|
||||
Reference in New Issue
Block a user