chenjj 3 months ago
parent
commit
ceaa3cad72
3 changed files with 304 additions and 210 deletions
  1. 4 4
      src/views/message/WebSocketClient.js
  2. 298 204
      src/views/message/index.vue
  3. 2 2
      vite.config.js

+ 4 - 4
src/views/message/WebSocketClient.js

@@ -1,13 +1,13 @@
-
+import { getToken } from "@/utils/auth";
 class WebSocketClient {
     constructor(userId, options = {}) {
       this.system = '3';
       this.userId = userId;
-      // const baseURL = import.meta.env.VITE_APP_BASE_API
-      const baseURL = 'https://goldshulin.com/prod-api';
+      const baseURL = 'http://192.168.100.128:9527';
+      // const baseURL = 'https://goldshulin.com/prod-api';
       const url = baseURL.split('/')[2];
       const header = baseURL.split('/')[0] === 'https:' ? 'wss' : 'ws';
-      this.url = `${header}://${url}/websocket/${this.system}/${this.userId}`;
+      this.url = `${header}://${url}/websocket/${this.system}/${this.userId}?authorization=${"Bearer " + getToken()}`;
 			console.log("TCL: WebSocketClient -> constructor -> this.url", this.url)
       this.options = {
         reconnectInterval: 3000, // 重连间隔时间

+ 298 - 204
src/views/message/index.vue

@@ -4,6 +4,7 @@
         <div class="user-list">
             <div class="user-list-header">
                 <el-input v-model="searchQuery" placeholder="搜索" prefix-icon="el-icon-search" clearable />
+  
             </div>
             <div class="user-list-content">
                 <div v-for="user in filteredUsers" :key="user.userId" class="user-item"
@@ -17,7 +18,7 @@
                 </div>
             </div>
         </div>
-
+  
         <!-- 右侧聊天区域 -->
         <div class="chat-area">
             <template v-if="currentUser">
@@ -25,7 +26,11 @@
                     <span>{{ currentUser.nickname }}</span>
                 </div>
                 <div class="message-list" ref="messageList">
-                    <div v-for="message in currentMessages.vos" :key="message.senderId" class="message-item"
+                    <div v-if="isLoading" class="loading-messages">
+                        <el-icon class="is-loading"><Loading /></el-icon>
+                        <span>加载更多消息...</span>
+                    </div>
+                    <div v-for="message in messages" :key="message.senderId" class="message-item"
                         :class="{ 'message-self': message.senderId === userId }">
                         <el-avatar :size="40"
                             :src="message.senderId === userId ? userStore.avatar : currentMessages.conversationAvatar" />
@@ -64,47 +69,52 @@
             </div>
         </div>
     </div>
-</template>
-
-<script setup>
-import { ref, computed, onMounted, nextTick } from 'vue'
-import dayjs from 'dayjs'
-import { ElMessage } from 'element-plus'
-import { getList, sendMsg, getListConversationInfo, setRead } from '@/api/conversation'
-import useUserStore from '@/store/modules/user'
-import WebSocketClient from './WebSocketClient.js';
-const userStore = useUserStore();
-
-const userId = computed(() => {
-    return userStore.user.deptId
-})
-console.log("TCL: userId -> userId", userId)
-
-
-// 模拟消息数据
-const messages = ref([])
-
-const searchQuery = ref('')
-const currentUser = ref(null)
-const messageInput = ref('')
-const messageList = ref(null)
-
-// 用户列表
-const filteredUsers = ref([]);
-
-// 当前用户的消息
-const currentMessages = ref({
+  </template>
+  
+  <script setup>
+  import { ref, computed, onMounted, nextTick, onUnmounted } from 'vue'
+  import dayjs from 'dayjs'
+  import { ElMessage } from 'element-plus'
+  import { getList, sendMsg, getListConversationInfo, setRead,getHistoryMsg } from '@/api/conversation'
+  import useUserStore from '@/store/modules/user'
+  import WebSocketClient from './WebSocketClient.js';
+  import {debounce} from '@/utils/index.js';
+
+  const userStore = useUserStore();
+  
+  const userId = computed(() => {
+    return userStore.user.userId
+  })
+  console.log("TCL: userId -> userId", userId)
+  
+  
+  // 模拟消息数据
+  const messages = ref([])
+  
+  const searchQuery = ref('')
+  const currentUser = ref(null)
+  const messageInput = ref('')
+  const messageList = ref(null)
+  const isLoading = ref(false)
+  const currentPage = ref(1)
+  const hasMore = ref(true)
+  
+  // 用户列表
+  const filteredUsers = ref([]);
+  
+  // 当前用户的消息
+  const currentMessages = ref({
     vos: []
-})
-
-const isToday = (date) => {
+  })
+  
+  const isToday = (date) => {
     return dayjs(date).isSame(dayjs(), 'day');
-};
-
-const isYesterday = (date) => {
+  };
+  
+  const isYesterday = (date) => {
     return dayjs(date).isSame(dayjs().subtract(1, 'day'), 'day');
-};
-const handlerData = (dates) => {
+  };
+  const handlerData = (dates) => {
     const date = dayjs(dates);
     if (isToday(dates)) {
         return date.format('HH:MM');;
@@ -113,45 +123,106 @@ const handlerData = (dates) => {
     } else {
         return date.format('YY/MM/DD'); // 或者其他格式如 'YYYY年MM月DD日'
     }
-}
-// 格式化时间
-const formatTime = (time) => {
+  }
+  // 格式化时间
+  const formatTime = (time) => {
     return handlerData(time)
-}
-
-// 选择用户
-const selectUser = async(user) => {
+  }
+  
+  // 选择用户
+  const selectUser = async(user) => {
     try {
         console.log("TCL: selectUser -> user", user)
+        currentPage.value = 1
+        hasMore.value = true
         const res = await getListConversationInfo({
-            conversationRecordId:  user.conversationRecordId,
+            conversationRecordId: user.conversationRecordId,
+            pageNum: currentPage.value,
+            pageSize: 20
         })
         const data = res.data;
         data.vos = data.vos.reverse(); //倒叙处理
         currentMessages.value = data;
+        messages.value= data.vos;
         currentUser.value = user
-    nextTick(() => {
-        scrollToBottom()
-    })
+        nextTick(() => {
+            scrollToBottom()
+        })
     } catch (error) {
         
     }
-	
-}
+  }
+  
+  // 加载更多消息
+  const loadMoreMessages =debounce(async () => {
+    if (isLoading.value || !hasMore.value || !currentUser.value) return
+
+    // isLoading.value = true
+    // console.log('xx');
+    // setTimeout(() => {
+    //   isLoading.value = false
+    // }, 1000)
+    
+    // return
+    
+    try {
+        isLoading.value = true
+        const oldScrollHeight = messageList.value.scrollHeight
+        const conversationMsgRecordId = messages.value[messages.value.length - 1].conversationMsgRecordId;
+        const res = await getHistoryMsg({ 
+            conversationMsgRecordId, 
+            conversationRecordId: currentUser.value.conversationRecordId
+         })
+        
+        const data = res.data
+		console.log("TCL: loadMoreMessages -> data", data)
+        if (data && data.length > 0) {
+            const data_reverse = data.reverse()
+			console.log("TCL: loadMoreMessages -> data_reverse", data_reverse)
+            const mes = [...data, ...messages.value]
+            messages.value = mes;
+			console.log("TCL: loadMoreMessages -> messages.value", messages.value)
+
+        } else {
+            hasMore.value = false
+        }
+        
+        nextTick(() => {
+            const newScrollHeight = messageList.value.scrollHeight
+            messageList.value.scrollTop = newScrollHeight - oldScrollHeight
+        })
+    } catch (error) {
+        console.error('加载更多消息失败:', error)
+    } finally {
+        isLoading.value = false
+    }
+  },500) 
+  
+  // 处理滚动事件
+  const handleScroll = () => {
+    if (!messageList.value) return
+    
+    const { scrollTop } = messageList.value
 
-// 发送消息
-const sendMessage = async (contentType) => {
+    if (scrollTop < 50 && !isLoading.value) {
+  
+        loadMoreMessages()
+    }
+  }
+  
+  // 发送消息
+  const sendMessage = async (contentType) => {
     try {
         if (!messageInput.value.trim()) {
             ElMessage.warning('消息不能为空')
             return
         }
-
+  
         if (!currentUser.value) {
             ElMessage.warning('请先选择一个聊天对象')
             return
         }
-
+  
         const newMessage = {
             conversationRecordId: currentUser.value.conversationRecordId,
             msgContent: messageInput.value,
@@ -161,9 +232,9 @@ const sendMessage = async (contentType) => {
             senderId: userId.value,
             system: 3,
         }
-
+  
     
-
+  
         await sendMsg(newMessage);
         currentMessages.value.vos.push(newMessage);
         messageInput.value = ''
@@ -172,46 +243,46 @@ const sendMessage = async (contentType) => {
         })
     } catch (error) {
         console.log("TCL: sendMessage -> error", error)
-
+  
     }
-}
-
-
-
-// 处理图片上传
-// const handleImageUpload = (file) => {
-//     if (!currentUser.value) {
-//         ElMessage.warning('请先选择一个聊天对象')
-//         return
-//     }
-
-//     const reader = new FileReader()
-//     reader.onload = (e) => {
-//         const newMessage = {
-//             id: Date.now(),
-//             type: 'image',
-//             content: e.target.result,
-//             time: new Date(),
-//             isSelf: true,
-//         }
-
-
-//         currentMessages.value.push(newMessage)
-//         nextTick(() => {
-//             scrollToBottom()
-//         })
-//     }
-//     reader.readAsDataURL(file.raw)
-// }
-
-// 滚动到底部
-const scrollToBottom = () => {
+  }
+  
+  
+  
+  // 处理图片上传
+  // const handleImageUpload = (file) => {
+  //     if (!currentUser.value) {
+  //         ElMessage.warning('请先选择一个聊天对象')
+  //         return
+  //     }
+  
+  //     const reader = new FileReader()
+  //     reader.onload = (e) => {
+  //         const newMessage = {
+  //             id: Date.now(),
+  //             type: 'image',
+  //             content: e.target.result,
+  //             time: new Date(),
+  //             isSelf: true,
+  //         }
+  
+  
+  //         currentMessages.value.push(newMessage)
+  //         nextTick(() => {
+  //             scrollToBottom()
+  //         })
+  //     }
+  //     reader.readAsDataURL(file.raw)
+  // }
+  
+  // 滚动到底部
+  const scrollToBottom = () => {
     if (messageList.value) {
         messageList.value.scrollTop = messageList.value.scrollHeight
     }
-}
-
-const getMList = async () => {
+  }
+  
+  const getMList = async () => {
     try {
         const res = await getList({
             system: 3
@@ -219,24 +290,24 @@ const getMList = async () => {
         filteredUsers.value = res.rows;
         res.rows[0] && selectUser(res.rows[0])
     } catch (error) {
-
+  
     }
-}
-let ws = null;
-
-
-const soketInit = () => {
+  }
+  let ws = null;
+  
+  
+  const soketInit = () => {
     try {
         //获取账户时,连接soket 
         ws = new WebSocketClient(userId.value);
         // 连接WebSocket
         ws.connect()
-
+  
         // 监听连接事件
         ws.on('connect', () => {
             console.log('连接成功')
         })
-
+  
         // 监听断开连接事件
         ws.on('disconnect', () => {
             console.log('连接断开')
@@ -249,195 +320,218 @@ const soketInit = () => {
         ws.on('message', (res) => {
             console.log('收到消息:', res)
             const data = JSON.parse(res.data);
-			console.log("TCL: soketInit -> data", data)
+      console.log("TCL: soketInit -> data", data)
             if (res.type === 'msgNew') {
                 currentMessages.value.push({...res.data})
-			}
+      }
         })
-
+  
     } catch (error) {
         console.log("TCL: soketInit -> error", error)
-
+  
     }
-}
-
-onMounted(() => {
-
+  }
+  
+  onMounted(() => {
     soketInit();
-
-
-
-
     getMList();//获取用户列表
-})
-</script>
-
-<style scoped>
-.chat-container {
+    
+   setTimeout(() => { 
+     // 添加滚动监听
+     if (messageList.value) {
+        messageList.value.addEventListener('scroll', handleScroll)
+    }
+   }, 500)
+  })
+  
+  onUnmounted(() => {
+    // 移除滚动监听
+    if (messageList.value) {
+        messageList.value.removeEventListener('scroll', handleScroll)
+    }
+  })
+  </script>
+  
+  <style scoped>
+  .chat-container {
     display: flex;
     height: 100vh;
     background-color: #f5f5f5;
-}
-
-.user-list {
+  }
+  
+  .user-list {
     width: 300px;
     background-color: #fff;
     border-right: 1px solid #e6e6e6;
     display: flex;
     flex-direction: column;
-}
-
-.user-list-header {
+  }
+  
+  .user-list-header {
     padding: 10px;
     border-bottom: 1px solid #e6e6e6;
-}
-
-.user-list-content {
+  }
+  
+  .user-list-content {
     flex: 1;
     overflow-y: auto;
-}
-
-.user-item {
+  }
+  
+  .user-item {
     display: flex;
     align-items: center;
     padding: 10px;
     cursor: pointer;
     transition: background-color 0.3s;
-}
-
-.user-item:hover {
+  }
+  
+  .user-item:hover {
     background-color: #f5f5f5;
-}
-
-.user-item.active {
+  }
+  
+  .user-item.active {
     background-color: #e6f7ff;
-}
-
-.user-info {
+  }
+  
+  .user-info {
     flex: 1;
     margin-left: 10px;
     overflow: hidden;
-}
-
-.user-name {
+  }
+  
+  .user-name {
     font-weight: bold;
     margin-bottom: 4px;
-}
-
-.last-message {
+  }
+  
+  .last-message {
     color: #999;
     font-size: 12px;
     white-space: nowrap;
     overflow: hidden;
     text-overflow: ellipsis;
-}
-
-.message-time {
+  }
+  
+  .message-time {
     font-size: 12px;
     color: #999;
-
-}
-
-.chat-area {
+  
+  }
+  
+  .chat-area {
     flex: 1;
     display: flex;
     flex-direction: column;
     background-color: #fff;
-}
-
-.chat-header {
+  }
+  
+  .chat-header {
     padding: 10px;
     border-bottom: 1px solid #e6e6e6;
     font-weight: bold;
-}
-
-.message-list {
+  }
+  
+  .message-list {
     flex: 1;
     overflow-y: auto;
     padding: 20px;
-}
-
-.message-item {
+  }
+  
+  .message-item {
     display: flex;
     margin-bottom: 20px;
-}
-
-.message-item.message-self {
+  }
+  
+  .message-item.message-self {
     flex-direction: row-reverse;
-}
-
-.message-content {
+  }
+  
+  .message-content {
     margin: 0 10px;
     max-width: 60%;
-}
-
-.message-time {
+  }
+  
+  .message-time {
     font-size: 12px;
     color: #999;
     margin-bottom: 4px;
     text-align: left;
-}
-
-.message-bubble {
+  }
+  
+  .message-bubble {
     background-color: #f5f5f5;
     padding: 10px;
     border-radius: 4px;
     word-break: break-all;
    
-}
-
-.message-self .message-bubble {
+  }
+  
+  .message-self .message-bubble {
     background-color: #95ec69;
     text-align: right;
     width: 100%;
-}
-
-.message-image {
+  }
+  
+  .message-image {
     max-width: 200px;
     max-height: 200px;
     border-radius: 4px;
-}
-
-.message-input {
+  }
+  
+  .message-input {
     border-top: 1px solid #e6e6e6;
     padding: 10px;
     display: flex;
-}
-
-.input-toolbar {
+  }
+  
+  .input-toolbar {
     display: flex;
     align-items: center;
     gap: 8px;
     padding: 8px 0;
-}
-
-.input-toolbar .el-button {
+  }
+  
+  .input-toolbar .el-button {
     padding: 4px 8px;
     font-size: 14px;
-}
-
-.input-toolbar .el-button:hover {
+  }
+  
+  .input-toolbar .el-button:hover {
     background-color: #f5f5f5;
     border-radius: 4px;
-}
-
-.input-area {
+  }
+  
+  .input-area {
     display: flex;
     gap: 10px;
     padding-top: 10px;
     flex: 1;
-}
-
-.input-area .el-textarea {
+  }
+  
+  .input-area .el-textarea {
     flex: 1;
-}
-
-.no-chat-selected {
+  }
+  
+  .no-chat-selected {
     display: flex;
     align-items: center;
     justify-content: center;
     height: 100%;
     color: #999;
     font-size: 16px;
-}
-</style>
+  }
+  
+  .loading-messages {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 10px;
+    color: #999;
+    font-size: 14px;
+    gap: 8px;
+  }
+  
+  .loading-messages .el-icon {
+    font-size: 16px;
+  }
+  </style>

+ 2 - 2
vite.config.js

@@ -39,8 +39,8 @@ export default defineConfig(({ mode, command }) => {
         '/dev-api': {
           // target: 'http://192.168.100.139:9527',
           // target: 'https://zybooks.tech/prod-api', 
-          // target: 'http://192.168.100.128:9527',
-          target: 'https://goldshulin.com/prod-api',
+          target: 'http://192.168.100.128:9527',
+          // target: 'https://goldshulin.com/prod-api',
           changeOrigin: true,
           rewrite: (p) => p.replace(/^\/dev-api/, '')
         },