From 13add51a2070f62d4af3488dac6450f8f295ae94 Mon Sep 17 00:00:00 2001 From: yaoqin <497681109@qq.com> Date: Fri, 25 Apr 2025 14:09:32 +0800 Subject: [PATCH] =?UTF-8?q?1.=E8=81=8A=E5=A4=A9=E7=B3=BB=E7=BB=9F=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E5=AE=8C=E6=88=90=202.=E5=8A=9F=E8=83=BD=E5=8F=8A?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E8=81=94=E8=B0=83=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mining-pool/src/App.vue | 2 +- mining-pool/src/api/customerService.js | 18 +- mining-pool/src/components/ChatWidget.vue | 2104 ++++++++++------- mining-pool/src/main.js | 2 +- .../src/views/customerService/index.vue | 10 +- mining-pool/src/views/home/index.js | 56 +- 6 files changed, 1276 insertions(+), 916 deletions(-) diff --git a/mining-pool/src/App.vue b/mining-pool/src/App.vue index 56549b4..3519071 100644 --- a/mining-pool/src/App.vue +++ b/mining-pool/src/App.vue @@ -1,7 +1,7 @@ + }, + mounted() { + document.addEventListener("click", this.handleClickOutside); + // 添加聊天窗口滚动监听 + this.$nextTick(() => { + if (this.$refs.chatBody) { + this.$refs.chatBody.addEventListener("scroll", this.handleChatScroll); + } + }); + + // 添加页面可见性变化监听 + document.addEventListener("visibilitychange", this.handleVisibilityChange); + }, + methods: { + // 处理页面可见性变化 + handleVisibilityChange() { + // 当页面变为可见且聊天窗口已打开时,标记消息为已读 + if (!document.hidden && this.isChatOpen && this.roomId) { + this.markMessagesAsRead(); + } + }, + + // 标记消息为已读 + async markMessagesAsRead() { + try { + const data = { + roomId: this.roomId + }; + + const response = await getReadMessage(data); + + if (response && response.code === 200) { + console.log('消息已标记为已读'); + // 清除未读消息计数 + this.unreadMessages = 0; + // 更新所有用户消息的已读状态 + this.messages.forEach(msg => { + if (msg.type === 'user') { + msg.isRead = true; + } + }); + } else { + console.warn('标记消息已读失败', response); + } + } catch (error) { + console.error('标记消息已读出错:', error); + } + }, + + // 加载历史消息 + async loadHistoryMessages() { + if (this.isLoadingHistory || !this.hasMoreHistory || !this.roomId) return; + + this.isLoadingHistory = true; + + try { + // 显示加载中提示 + const loadingMsg = { + type: "system", + text: "正在加载历史消息...", + isLoading: true, + time: new Date(), + }; + this.messages.unshift(loadingMsg); + + // 获取7天内的最新聊天记录,传入 roomId + const response = await getHistory7({ roomId: this.roomId }); + + // 移除加载中提示 + this.messages = this.messages.filter((msg) => !msg.isLoading); + + if (response && response.code === 200 && response.data && response.data.length > 0) { + // 处理并添加历史消息 + const historyMessages = this.formatHistoryMessages(response.data); + + // 将历史消息添加到消息列表的前面 + this.messages = [...historyMessages, ...this.messages]; + + // 设置是否还有更多历史消息 + this.hasMoreHistory = historyMessages.length > 0; + } else { + // 添加提示信息 + this.messages.unshift({ + type: "system", + text: "暂无最近的聊天记录", + isSystemHint: true, + time: new Date(), + }); + this.hasMoreHistory = false; + } + } catch (error) { + console.error("加载历史消息失败:", error); + // 添加错误提示 + this.messages.unshift({ + type: "system", + text: "加载历史消息失败,请重试", + isError: true, + time: new Date(), + }); + } finally { + this.isLoadingHistory = false; + } + }, + + // 加载更多历史消息(超过7天的) + async loadMoreHistory() { + if (this.isLoadingHistory || !this.roomId) return; + + this.isLoadingHistory = true; + + try { + // 显示加载中提示 + const loadingMsg = { + type: "system", + text: "正在加载更多历史消息...", + isLoading: true, + time: new Date(), + }; + this.messages.unshift(loadingMsg); + + // 获取更早的聊天记录,传入 roomId + const response = await getHistory({ roomId: this.roomId }); + + // 移除加载中提示 + this.messages = this.messages.filter((msg) => !msg.isLoading); + + if (response && response.code === 200 && response.data && response.data.length > 0) { + // 处理并添加历史消息 + const historyMessages = this.formatHistoryMessages(response.data); + + // 将历史消息添加到消息列表的前面 + this.messages = [...historyMessages, ...this.messages]; + + // 如果没有数据返回,表示没有更多历史记录 + this.hasMoreHistory = historyMessages.length > 0; + + if (historyMessages.length === 0) { + this.messages.unshift({ + type: "system", + text: "没有更多历史消息了", + isSystemHint: true, + time: new Date(), + }); + } + } else { + this.hasMoreHistory = false; + this.messages.unshift({ + type: "system", + text: "没有更多历史消息了", + isSystemHint: true, + time: new Date(), + }); + } + } catch (error) { + console.error("加载更多历史消息失败:", error); + this.messages.unshift({ + type: "system", + text: "加载更多历史消息失败", + isError: true, + time: new Date(), + }); + } finally { + this.isLoadingHistory = false; + } + }, + + // 格式化历史消息数据 + formatHistoryMessages(messagesData) { + if (!messagesData || !Array.isArray(messagesData)) return []; + + return messagesData + .map((msg) => { + const isSelf = + msg.sendUserType === this.userType && + msg.sendUserEmail === this.userEmail; + + return { + type: isSelf ? "user" : "system", + text: msg.content || "", + isImage: msg.type === 1 || msg.type === "image", + imageUrl: + msg.type === 1 || msg.type === "image" ? msg.content : null, + time: msg.timestamp ? new Date(msg.timestamp) : new Date(), + id: msg.id, + roomId: msg.roomId, + sendUserType: msg.sendUserType, + isHistory: true, // 标记为历史消息 + isRead: msg.isRead || true, // 历史消息默认已读 + }; + }) + .sort((a, b) => a.time - b.time); // 按时间排序 +}, + + + // 修改 fetchUserid 方法,使其返回 Promise + async fetchUserid() { + try { + const res = await getUserid(); + if (res && res.code == 200) { + console.log('获取用户ID成功:', res); + this.receivingEmail = res.data.userEmail; + this.roomId = res.data.id; + return res.data; + } else { + console.warn('获取用户ID未返回有效数据'); + return null; + } + } catch (error) { + console.error('获取用户ID失败:', error); + throw error; + } + }, + + // 初始化 WebSocket 连接 + initWebSocket() { + this.determineUserType(); + this.connectWebSocket(); + }, + + // 确定用户类型和邮箱 + determineUserType() { + try { + const token = JSON.parse(localStorage.getItem("token") || "{}"); + const userInfo = JSON.parse( + localStorage.getItem("jurisdiction") || "{}" + ); + const email = JSON.parse(localStorage.getItem("userEmail") || "{}"); + if (token) { + if (userInfo.roleKey === "customer_service") { + // 客服用户 + this.userType = 2; + } else { + // 登录用户 + this.userType = 1; + } + this.userEmail = email; + } else { + //游客 + this.userType = 0; + this.userEmail = `guest_${Date.now()}_${Math.random() + .toString(36) + .substr(2, 9)}`; + } + } catch (error) { + console.error("获取用户信息失败:", error); + // 出错时默认为游客 + this.userType = 0; + this.userEmail = `guest_${Date.now()}_${Math.random() + .toString(36) + .substr(2, 9)}`; + } + }, + + // 连接 WebSocket + connectWebSocket() { + this.connectionStatus = "connecting"; + + try { + const wsUrl = `${process.env.VUE_APP_BASE_API}chat/ws`; + + // 创建 STOMP 客户端 + this.stompClient = new Client({ + brokerURL: wsUrl, + connectHeaders: { + email: this.userEmail, + type: this.userType, + }, + debug: function (str) { + console.log("STOMP: " + str); + }, + reconnectDelay: 5000, + heartbeatIncoming: 4000, + heartbeatOutgoing: 4000, + }); + + // 连接成功回调 + this.stompClient.onConnect = (frame) => { + console.log("连接成功: " + frame); + this.connectionStatus = "connected"; + + // 订阅个人消息频道 + this.stompClient.subscribe( + `${process.env.VUE_APP_BASE_API}user/queue/${this.userEmail}`, + this.onMessageReceived + ); + + // 根据用户类型显示不同的欢迎消息 + let welcomeMessage = ""; + switch (this.userType) { + case 0: + welcomeMessage = "您当前以游客身份访问,请问有什么可以帮您?"; + break; + case 1: + welcomeMessage = "欢迎回来,请问有什么可以帮您?"; + break; + case 2: + welcomeMessage = "您已以客服身份登录系统"; + break; + } + this.addSystemMessage(welcomeMessage); + }; + + // 连接错误回调 + this.stompClient.onStompError = (frame) => { + console.error("连接错误: " + frame.headers.message); + this.connectionStatus = "error"; + this.addSystemMessage("连接客服系统失败,请稍后重试。"); + }; + + // 启动连接 + this.stompClient.activate(); + } catch (error) { + console.error("初始化 WebSocket 失败:", error); + this.connectionStatus = "error"; + } + }, + + // 断开 WebSocket 连接 + disconnectWebSocket() { + if (this.stompClient && this.stompClient.connected) { + this.stompClient.deactivate(); + this.connectionStatus = "disconnected"; + } + }, + + // 添加新方法:更新消息已读状态 +updateMessageReadStatus(messageIds) { + if (!Array.isArray(messageIds) || messageIds.length === 0) { + // 如果没有具体的消息ID,就更新所有用户消息为已读 + this.messages.forEach(msg => { + if (msg.type === 'user') { + msg.isRead = true; + } + }); + } else { + // 更新指定ID的消息为已读 + this.messages.forEach(msg => { + if (msg.id && messageIds.includes(msg.id)) { + msg.isRead = true; + } + }); + } +}, + + // 接收消息处理 + onMessageReceived(message) { + console.log("收到消息:", message.body); + try { + const data = JSON.parse(message.body); + + // 处理已读回执消息 + if (data.type === 'read_receipt') { + // 更新对应消息的已读状态 + this.updateMessageReadStatus(data.messageIds || []); + return; + } + + // 添加客服消息 + this.messages.push({ + type: "system", + text: data.content, + isImage: data.type === "image", + imageUrl: data.type === "image" ? data.content : null, + time: new Date(), + roomId: data.roomId || this.roomId, + }); + + // 如果聊天窗口已打开,标记为已读 + if (this.isChatOpen && this.roomId) { + this.markMessagesAsRead(); + } else { + // 如果聊天窗口没有打开,显示未读消息数 + this.unreadMessages++; + } + + this.$nextTick(() => { + this.scrollToBottom(); + }); + } catch (error) { + console.error("解析消息失败:", error); + } +}, + + // 切换聊天窗口 + async toggleChat() { + this.isChatOpen = !this.isChatOpen; + + if (this.isChatOpen) { + this.unreadMessages = 0; + + try { + // 先获取用户ID,等待完成 + await this.fetchUserid(); + + // 如果未连接,则连接 WebSocket + if (this.connectionStatus === "disconnected") { + this.initWebSocket(); + } + + // 获取到 roomId 后才查询历史消息 + if (this.roomId && this.messages.length === 0) { + this.loadHistoryMessages(); + } + + // 新增:标记消息为已读 + if (this.roomId) { + this.markMessagesAsRead(); + } + + this.$nextTick(() => { + this.scrollToBottom(); + }); + } catch (error) { + console.error('初始化聊天失败:', error); + } + } + }, + + minimizeChat() { + this.isChatOpen = false; + }, + + closeChat() { + this.isChatOpen = false; + this.messages = []; + this.disconnectWebSocket(); + }, + + // 发送消息 + sendMessage() { + if (!this.inputMessage.trim() || this.connectionStatus !== "connected") + return; + + const messageText = this.inputMessage.trim(); + + // 添加用户消息到界面 + this.messages.push({ + type: "user", + text: messageText, + time: new Date(), + email: this.receivingEmail, + sendUserType: this.userType, + roomId: this.roomId, + isRead: false, // 新发送的消息默认未读 + }); + + // 通过 WebSocket 发送消息 + if (this.stompClient && this.stompClient.connected) { + this.stompClient.publish({ + destination: "/send/message", + body: JSON.stringify({ + content: messageText, + type: 0, + email: this.receivingEmail, + sendUserType: this.userType, + roomId: this.roomId, + }), + }); + } else { + this.handleAutoResponse(messageText); + } + + this.inputMessage = ""; + + this.$nextTick(() => { + this.scrollToBottom(); + }); +}, + + // 添加系统消息 + addSystemMessage(text) { + this.messages.push({ + type: "system", + text: text, + isImage: false, + time: new Date(), + }); + + // 滚动到底部 + this.$nextTick(() => { + this.scrollToBottom(); + }); + }, + + // 自动回复 (仅在无法连接服务器时使用) + handleAutoResponse(message) { + setTimeout(() => { + let response = + "抱歉,我暂时无法回答这个问题。请排队等待人工客服或提交工单。"; + + // 检查是否匹配自动回复关键词 + for (const [keyword, reply] of Object.entries(this.autoResponses)) { + if (message.toLowerCase().includes(keyword.toLowerCase())) { + response = reply; + break; + } + } + + // 添加系统回复 + this.messages.push({ + type: "system", + text: response, + isImage: false, + time: new Date(), + }); + + if (!this.isChatOpen) { + this.unreadMessages++; + } + + this.$nextTick(() => { + this.scrollToBottom(); + }); + }, 1000); + }, + + // 滚动到消息列表顶部检测,用于加载更多历史消息 + handleChatScroll() { + if (!this.$refs.chatBody) return; + + const { scrollTop } = this.$refs.chatBody; + // 当滚动到顶部时,加载更多历史消息 + if (scrollTop < 50 && this.hasMoreHistory && !this.isLoadingHistory) { + this.loadMoreHistory(); + } + }, + + scrollToBottom() { + if (this.$refs.chatBody) { + this.$refs.chatBody.scrollTop = this.$refs.chatBody.scrollHeight; + } + }, + + formatTime(date) { + return date.toLocaleTimeString([], { + hour: "2-digit", + minute: "2-digit", + }); + }, + + handleClickOutside(event) { + if (this.isChatOpen) { + const chatElement = this.$el.querySelector(".chat-dialog"); + const chatIcon = this.$el.querySelector(".chat-icon"); + + if ( + chatElement && + !chatElement.contains(event.target) && + !chatIcon.contains(event.target) + ) { + this.isChatOpen = false; + } + } + }, + + // 处理图片上传 + handleImageUpload(event) { + if (this.connectionStatus !== "connected") return; + + const file = event.target.files[0]; + if (!file) return; + + // 检查是否为图片 + if (!file.type.startsWith("image/")) { + this.$message({ + message: this.$t("chat.onlyImages") || "只能上传图片文件!", + type: "warning", + }); + return; + } + + // 检查文件大小 (限制为5MB) + const maxSize = 5 * 1024 * 1024; + if (file.size > maxSize) { + this.$message({ + message: this.$t("chat.imageTooLarge") || "图片大小不能超过5MB!", + type: "warning", + }); + return; + } + + const reader = new FileReader(); + reader.onload = (e) => { + const imageUrl = e.target.result; + + // 添加用户图片消息到界面 + this.messages.push({ + type: "user", // 修改为 "user" 以符合消息类型格式 + text: "", + isImage: true, + imageUrl: imageUrl, + time: new Date(), + email: this.receivingEmail, // 接收者邮箱 + sendUserType: this.userType, // 发送者类型 + roomId: this.roomId, // 聊天室ID + isRead: false, // 新发送的图片消息默认未读 + }); + + // 通过 WebSocket 发送图片消息 + if (this.stompClient && this.stompClient.connected) { + this.stompClient.publish({ + destination: "/send/message", + body: JSON.stringify({ + content: imageUrl, + type: "image", + email: this.receivingEmail, + sendUserType: this.userType, + roomId: this.roomId + }), + }); + } + + this.$nextTick(() => { + this.scrollToBottom(); + }); + }; + + reader.readAsDataURL(file); + this.$refs.imageUpload.value = ""; + }, + + // 预览图片 + previewImage(imageUrl) { + this.previewImageUrl = imageUrl; + this.showImagePreview = true; + }, + + // 关闭图片预览 + closeImagePreview() { + this.showImagePreview = false; + this.previewImageUrl = ""; + }, + }, + + beforeDestroy() { + this.disconnectWebSocket(); + document.removeEventListener("click", this.handleClickOutside); + // 移除滚动监听 + if (this.$refs.chatBody) { + this.$refs.chatBody.removeEventListener("scroll", this.handleChatScroll); + } + // 移除页面可见性变化监听 + document.removeEventListener("visibilitychange", this.handleVisibilityChange); + }, +}; + \ No newline at end of file +} + + + +.message-footer { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 4px; + font-size: 11px; +} + +.message-time { + color: #999; +} + +.message-read-status { + color: #999; + font-size: 10px; + margin-left: 5px; +} + +.chat-message-user .message-read-status { + color: rgba(255, 255, 255, 0.7); +} + \ No newline at end of file diff --git a/mining-pool/src/main.js b/mining-pool/src/main.js index 0c37564..d79dae9 100644 --- a/mining-pool/src/main.js +++ b/mining-pool/src/main.js @@ -23,7 +23,7 @@ Vue.use(ElementUI, { }); Vue.prototype.$axios = axios -console.log = ()=>{} //全局关闭打印 +// console.log = ()=>{} //全局关闭打印 // 全局注册混入 Vue.mixin(loadingStateMixin); Vue.mixin(networkRecoveryMixin); diff --git a/mining-pool/src/views/customerService/index.vue b/mining-pool/src/views/customerService/index.vue index 13caa90..c9736e0 100644 --- a/mining-pool/src/views/customerService/index.vue +++ b/mining-pool/src/views/customerService/index.vue @@ -250,10 +250,12 @@ export default { try { this.loadingRooms = true; const response = await getRoomList(); - if (response && response.code === 200 && response.data) { - this.contacts = response.data.map(room => ({ - roomId: room.roomId, - name: room.roomName || '未命名聊天室', + + console.log(response,"获取聊天室列表"); + if (response && response.code === 200 ) { + this.contacts = response.rows.map(room => ({ + roomId: room.id, + name: room.userEmail || '未命名聊天室', avatar: this.getDefaultAvatar(room.roomName || '未命名聊天室'), // 使用默认头像 lastMessage: room.lastMessage || '暂无消息', lastTime: room.lastTime || new Date(), diff --git a/mining-pool/src/views/home/index.js b/mining-pool/src/views/home/index.js index a19fa35..348c381 100644 --- a/mining-pool/src/views/home/index.js +++ b/mining-pool/src/views/home/index.js @@ -978,61 +978,7 @@ export default { }, 200), - // async getPoolPowerData(params) { - // this.minerChartLoading = true - // const data = await getPoolPower(params) - // if (!data) { - // this.minerChartLoading = false - // if (this.myChart) { - // this.myChart.dispose()//销毁图表实列 - // } - // return - // } - // let chartData = data.data - // let xData = [] - // let pvData = [] - // let rejectRate = [] - // let price = [] - // chartData.forEach(item => { - - // if (item.date.includes(`T`) && params.interval == `rt`) { - - // item.date = `${item.date.split("T")[0]} ${item.date.split("T")[1].split(`.`)[0]}` - // } else if (item.date.includes(`T`) && params.interval == `1d`) { - // item.date = item.date.split("T")[0] - // } - - - // xData.push(item.date) - // pvData.push(Number(item.pv).toFixed(2)) - // // rejectRate.push((item.rejectRate * 100).toFixed(2)) - // if (item.price == 0) { - // price.push(item.price) - // } else if (item.price < 1) { - // price.push(Number(item.price).toFixed(8)) - - // } else { - // price.push(Number(item.price).toFixed(2)) - // } - - - - // }); - // // this.maxValue = Math.max(...rejectRate); - // // let leftYMaxData = Math.max(...pvData); - // // this.option.yAxis[0].max =leftYMaxData*2 - // this.option.xAxis.data = xData - // this.option.series[0].data = pvData - // // this.option.series[1].data = rejectRate - // this.option.series[1].data = price - // this.$nextTick(() => { - // this.inCharts() - // }) - - - - - // }, + getPoolPowerData: Debounce(async function (params) { // this.minerChartLoading = true this.setLoading('minerChartLoading', true);