客服系统:1.滚动到列表下方分页请求历史用户列表拼接 完成 2.游客功能添加、删除列表离线游客 目前游客断开没有返回关闭信息 3.中英文翻译 处理中 4.客服页面添加回到底部功能 增加用户体验 完成

This commit is contained in:
yaoqin 2025-05-23 14:46:29 +08:00
parent d3ac95af75
commit 38fbb4e625
24 changed files with 1123 additions and 516 deletions

View File

@ -1,7 +1,7 @@
<template> <template>
<div id="app"> <div id="app">
<router-view class="page" /> <router-view class="page" />
<!-- <ChatWidget v-if="!$route.path.includes('/customerService')" /> --> <ChatWidget v-if="!$route.path.includes('/customerService')" />
</div> </div>
</template> </template>
<script > <script >

View File

@ -1,11 +1,16 @@
<template> <template>
<div class="chat-widget"> <div class="chat-widget">
<!-- 添加网络状态提示 -->
<div v-if="networkStatus === 'offline'" class="network-status">
<i class="el-icon-warning"></i>
<span>{{ $t("chat.networkError") || "网络连接已断开" }}</span>
</div>
<!-- 聊天图标 --> <!-- 聊天图标 -->
<div <div
class="chat-icon" class="chat-icon"
@click="toggleChat" @click="toggleChat"
:class="{ active: isChatOpen }" :class="{ active: isChatOpen }"
aria-label="打开客服聊天" :aria-label="$t('chat.openCustomerService') || '打开客服聊天'"
tabindex="0" tabindex="0"
@keydown.enter="toggleChat" @keydown.enter="toggleChat"
@keydown.space="toggleChat" @keydown.space="toggleChat"
@ -33,16 +38,16 @@
class="chat-status connecting" class="chat-status connecting"
> >
<i class="el-icon-loading"></i> <i class="el-icon-loading"></i>
<p>正在连接客服系统...</p> <p>{{ $t("chat.connectToCustomerService") || "正在连接客服系统..." }}</p>
</div> </div>
<div <div
v-else-if="connectionStatus === 'error'" v-else-if="connectionStatus === 'error'"
class="chat-status error" class="chat-status error"
> >
<i class="el-icon-warning"></i> <i class="el-icon-warning"></i>
<p>连接失败请稍后重试</p> <p>{{ $t("chat.connectionFailed") || "连接失败,请稍后重试" }}</p>
<button @click="connectWebSocket" class="retry-button"> <button @click="connectWebSocket" class="retry-button">
重试连接 {{ $t("chat.tryConnectingAgain") || "重试连接" }}
</button> </button>
</div> </div>
@ -56,7 +61,7 @@
> >
<i class="el-icon-arrow-up"></i> <i class="el-icon-arrow-up"></i>
<span>{{ <span>{{
isLoadingHistory ? "加载中..." : "加载更多历史消息" isLoadingHistory ? $t("chat.loading") || "加载中..." : $t("chat.loadMore") || "加载更多历史消息"
}}</span> }}</span>
</div> </div>
@ -103,7 +108,7 @@
<img <img
:src="msg.imageUrl" :src="msg.imageUrl"
@click="previewImage(msg.imageUrl)" @click="previewImage(msg.imageUrl)"
alt="聊天图片" :alt="$t('chat.picture') || '聊天图片'"
/> />
</div> </div>
@ -114,7 +119,7 @@
v-if="msg.type === 'user'" v-if="msg.type === 'user'"
class="message-read-status" class="message-read-status"
> >
{{ msg.isRead ? "已读" : "未读" }} {{ msg.isRead ? $t("chat.read") || "已读" : $t("chat.unread") || "未读" }}
</span> </span>
</div> </div>
</div> </div>
@ -225,6 +230,11 @@ export default {
maxReconnectAttempts: 5, maxReconnectAttempts: 5,
reconnectInterval: 5000, // 5 reconnectInterval: 5000, // 5
isReconnecting: false, isReconnecting: false,
lastActivityTime: Date.now(),
activityCheckInterval: null,
networkStatus: "online",
reconnectTimer: null,
}; };
}, },
@ -247,6 +257,17 @@ export default {
// //
document.addEventListener("visibilitychange", this.handleVisibilityChange); document.addEventListener("visibilitychange", this.handleVisibilityChange);
//
window.addEventListener("online", this.handleNetworkChange);
window.addEventListener("offline", this.handleNetworkChange);
//
this.startActivityCheck();
//
document.addEventListener("mousemove", this.updateLastActivityTime);
document.addEventListener("keydown", this.updateLastActivityTime);
document.addEventListener("click", this.updateLastActivityTime);
}, },
methods: { methods: {
// //
@ -305,7 +326,6 @@ export default {
this.userType = 1; this.userType = 1;
this.userEmail = email; this.userEmail = email;
} }
} catch (parseError) { } catch (parseError) {
console.error("解析用户信息失败:", parseError); console.error("解析用户信息失败:", parseError);
// //
@ -332,7 +352,7 @@ export default {
// //
this.stompClient.subscribe( this.stompClient.subscribe(
`/sub/queue/user/${this.userEmail}`, `/sub/queue/user/${this.userEmail}`,
this.onMessageReceived, this.onMessageReceived
// { // {
// id: `chat_${this.userEmail}`, // id: `chat_${this.userEmail}`,
// } // }
@ -341,7 +361,10 @@ export default {
console.log("成功订阅消息频道:", `/sub/queue/user/${this.userEmail}`); console.log("成功订阅消息频道:", `/sub/queue/user/${this.userEmail}`);
} catch (error) { } catch (error) {
console.error("订阅消息失败:", error); console.error("订阅消息失败:", error);
this.$message.error("消息订阅失败,可能无法接收新消息"); this.message({
message:this.$t("chat.subscriptionFailed")|| "消息订阅失败,可能无法接收新消息",
type:"error"
})
} }
}, },
@ -400,7 +423,7 @@ export default {
this.subscribeToPersonalMessages(); this.subscribeToPersonalMessages();
// //
this.$message.success("连接成功"); // this.$message.success("");
}, },
(error) => { (error) => {
console.error("WebSocket Error:", error); console.error("WebSocket Error:", error);
@ -418,22 +441,31 @@ export default {
}, },
// 5 // 5
handleDisconnect() { handleDisconnect() {
if (this.isReconnecting) return;
this.isWebSocketConnected = false; this.isWebSocketConnected = false;
this.connectionStatus = "error"; this.connectionStatus = "error";
this.isReconnecting = false; this.isReconnecting = true;
// //
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
}
// 使
if (this.reconnectAttempts < this.maxReconnectAttempts) { if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++; this.reconnectAttempts++;
console.log( console.log(
`尝试重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts})...` `尝试重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts})...`
); );
//${this.reconnectInterval / 1000}...
this.$message.warning( this.$message.warning(
`连接断开,${this.reconnectInterval / 1000}秒后重试...` `${this.$t("chat.break")},${this.reconnectInterval / 1000}${this.$t("chat.retry")}...`
); );
setTimeout(() => {
this.reconnectTimer = setTimeout(() => {
if (!this.isWebSocketConnected) { if (!this.isWebSocketConnected) {
this.connectWebSocket(); this.connectWebSocket();
} }
@ -441,10 +473,42 @@ export default {
} else { } else {
console.log("达到最大重连次数,停止重连"); console.log("达到最大重连次数,停止重连");
this.$message.error("连接失败,请刷新页面重试"); this.$message.error("连接失败,请刷新页面重试");
this.isReconnecting = false;
} }
}, },
//
handleNetworkChange() {
this.networkStatus = navigator.onLine ? "online" : "offline";
// if (navigator.onLine) {
//
if (!this.isWebSocketConnected) {
this.handleDisconnect();
}
} else {
//
this.$message.warning("网络连接已断开,正在等待重连...");
}
},
//
startActivityCheck() {
this.activityCheckInterval = setInterval(() => {
const now = Date.now();
const inactiveTime = now - this.lastActivityTime;
// 5
if (inactiveTime > 5 * 60 * 1000 && !this.isWebSocketConnected) {
this.handleDisconnect();
}
}, 60000); //
},
//
updateLastActivityTime() {
this.lastActivityTime = Date.now();
},
//
handleBeforeUnload() { handleBeforeUnload() {
this.disconnectWebSocket(); this.disconnectWebSocket();
}, },
@ -455,8 +519,8 @@ export default {
// WebSocket // WebSocket
if (!this.stompClient || !this.stompClient.connected) { if (!this.stompClient || !this.stompClient.connected) {
console.log('发送消息时连接已断开,尝试重连...'); console.log("发送消息时连接已断开,尝试重连...");
this.$message.warning('连接已断开,正在重新连接...'); this.$message.warning("连接已断开,正在重新连接...");
this.handleDisconnect(); this.handleDisconnect();
return; return;
} }
@ -539,11 +603,22 @@ export default {
} }
}, },
// //
//
handleVisibilityChange() { handleVisibilityChange() {
// //
if (!document.hidden && this.isChatOpen && this.roomId) { if (!document.hidden && this.isChatOpen && this.roomId) {
this.markMessagesAsRead(); this.markMessagesAsRead();
} }
//
if (!document.hidden) {
//
if (!this.isWebSocketConnected) {
this.handleDisconnect();
}
//
this.updateLastActivityTime();
}
}, },
// //
@ -552,6 +627,7 @@ export default {
const data = { const data = {
roomId: this.roomId, roomId: this.roomId,
userType: this.userType, userType: this.userType,
email: this.userEmail,
}; };
const response = await getReadMessage(data); const response = await getReadMessage(data);
@ -638,7 +714,7 @@ export default {
} finally { } finally {
this.isLoadingHistory = false; this.isLoadingHistory = false;
} }
}, },
// 7 // 7
async loadMoreHistory() { async loadMoreHistory() {
@ -648,9 +724,11 @@ export default {
try { try {
// ID // ID
const oldestMessage = this.messages.find(msg => !msg.isSystemHint && !msg.isLoading); const oldestMessage = this.messages.find(
(msg) => !msg.isSystemHint && !msg.isLoading
);
if (!oldestMessage || !oldestMessage.id) { if (!oldestMessage || !oldestMessage.id) {
console.warn('没有找到有效的消息ID'); console.warn("没有找到有效的消息ID");
this.hasMoreHistory = false; this.hasMoreHistory = false;
return; return;
} }
@ -669,7 +747,7 @@ export default {
roomId: this.roomId, roomId: this.roomId,
userType: this.userType, userType: this.userType,
email: this.userEmail, email: this.userEmail,
id: oldestMessage.id // ID id: oldestMessage.id, // ID
}); });
// //
@ -718,7 +796,7 @@ export default {
} finally { } finally {
this.isLoadingHistory = false; this.isLoadingHistory = false;
} }
}, },
// //
formatHistoryMessages(messagesData) { formatHistoryMessages(messagesData) {
@ -796,7 +874,7 @@ export default {
// //
const messageObj = { const messageObj = {
type: data.sendUserType === this.userType ? "user" : "system", // type: data.sendEmail === this.userEmail ? "user" : "system",
text: data.content, text: data.content,
isImage: data.type === 2, isImage: data.type === 2,
imageUrl: data.type === 2 ? data.content : null, imageUrl: data.type === 2 ? data.content : null,
@ -902,7 +980,10 @@ export default {
this.determineUserType(); this.determineUserType();
// WebSocket // WebSocket
if (!this.isWebSocketConnected || this.connectionStatus === "disconnected") { if (
!this.isWebSocketConnected ||
this.connectionStatus === "disconnected"
) {
await this.connectWebSocket(); await this.connectWebSocket();
} }
@ -924,7 +1005,7 @@ export default {
this.$message.error("初始化聊天失败,请重试"); this.$message.error("初始化聊天失败,请重试");
} }
} }
}, },
minimizeChat() { minimizeChat() {
this.isChatOpen = false; this.isChatOpen = false;
@ -1000,7 +1081,7 @@ export default {
const scrollOptions = { const scrollOptions = {
top: this.$refs.chatBody.scrollHeight, top: this.$refs.chatBody.scrollHeight,
behavior: force ? 'auto' : 'smooth' // 使 'auto' behavior: force ? "auto" : "smooth", // 使 'auto'
}; };
try { try {
@ -1009,7 +1090,7 @@ export default {
// //
this.$refs.chatBody.scrollTop = this.$refs.chatBody.scrollHeight; this.$refs.chatBody.scrollTop = this.$refs.chatBody.scrollHeight;
} }
}, },
formatTime(date) { formatTime(date) {
if (!date || !(date instanceof Date) || isNaN(date.getTime())) { if (!date || !(date instanceof Date) || isNaN(date.getTime())) {
@ -1170,19 +1251,6 @@ export default {
} }
}, },
// URL
formatImageUrl(url) {
if (!url) return "";
// URL
if (url.startsWith("http://") || url.startsWith("https://")) {
return url;
}
//
return process.env.VUE_APP_BASE_URL + url;
},
// //
previewImage(imageUrl) { previewImage(imageUrl) {
this.previewImageUrl = imageUrl; this.previewImageUrl = imageUrl;
@ -1197,8 +1265,6 @@ export default {
}, },
beforeDestroy() { beforeDestroy() {
// //
if (this.$refs.chatBody) { if (this.$refs.chatBody) {
this.$refs.chatBody.removeEventListener("scroll", this.handleChatScroll); this.$refs.chatBody.removeEventListener("scroll", this.handleChatScroll);
@ -1215,6 +1281,23 @@ export default {
} }
// WebSocket // WebSocket
this.disconnectWebSocket(); this.disconnectWebSocket();
//
if (this.activityCheckInterval) {
clearInterval(this.activityCheckInterval);
}
//
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
}
//
window.removeEventListener("online", this.handleNetworkChange);
window.removeEventListener("offline", this.handleNetworkChange);
document.removeEventListener("mousemove", this.updateLastActivityTime);
document.removeEventListener("keydown", this.updateLastActivityTime);
document.removeEventListener("click", this.updateLastActivityTime);
}, },
}; };
</script> </script>
@ -1664,4 +1747,19 @@ export default {
.chat-message-user .message-read-status { .chat-message-user .message-read-status {
color: rgba(255, 255, 255, 0.7); color: rgba(255, 255, 255, 0.7);
} }
.network-status {
position: fixed;
top: 20px;
right: 20px;
padding: 8px 16px;
border-radius: 4px;
display: flex;
align-items: center;
gap: 8px;
z-index: 1000;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
background-color: #fef0f0;
color: #f56c6c;
}
</style> </style>

View File

@ -10,6 +10,70 @@ export const ChatWidget_zh = {
onlyImages:"只能上传图片文件!", onlyImages:"只能上传图片文件!",
imageTooLarge:"图片大小不能超过5MB!", imageTooLarge:"图片大小不能超过5MB!",
imageReceived:"已收到您的图片,我们会尽快处理您的问题。", imageReceived:"已收到您的图片,我们会尽快处理您的问题。",
networkError:"网络连接已断开,请刷新页面重试",
openCustomerService:"打开客服聊天",
connectToCustomerService:"正在连接客服系统...",
connectionFailed:"连接失败,请刷新页面重试",
tryConnectingAgain:"重试连接",
loading:"加载中...",
loadMore:"加载更多历史消息",
welcomeToUse:"欢迎使用在线客服,请问有什么可以帮您?",
picture:"聊天图片",
read:"已读",
unread:"未读",
subscriptionFailed:"消息订阅失败,可能无法接收新消息",
break:"连接断开",
retry:"秒后重试",
disconnectWaiting:"断线重连中...",
sendFailed:"发送消息失败,请重试",
noHistory:"暂无历史消息",
historicalFailure:"加载历史消息失败,请重试",
loadingHistory:"正在加载更多历史消息...",
noMoreHistory:"没有更多历史消息了",
Loaded:"已加载历史消息",
newMessage:"新消息",
pictureMessage:"图片消息",
initializationFailed:"初始化失败,请刷新页面重试",
beSorry:"抱歉,我暂时无法回答这个问题。请排队等待人工客服或提交工单。",
today:"今天",
yesterday:"昨天",
canOnlyUploadImages:"只能上传图片文件!",
imageSizeExceeded:"图片大小不能超过5MB!",
uploading:"正在上传图片...",
pictureFailed:"发送图片失败,请重试",
readImage:"读取图片失败,请重试",
processingFailed:"图片处理失败,请重试",
Disconnected:"连接已断开",
reconnecting:"正在重连...",
contactList:"联系列表",
search:"搜索最近联系人",
tourist:"游客",
important:"重要",
markAsImportant:"标记为重要",
cancelImportant:"已取消重要标记",
markingFailed:"标记操作失败,请重试",
select:"请选择联系人",
notSelected:"您尚未选择联系人",
None:"暂无消息记录",
sendPicture:"发送图片",
inputMessage:"请输入消息按Enter键发送按Ctrl+Enter键换行",
bottom:"回到底部",
Preview:"预览图片",
chatRoom:"聊天室",
CLOSED:"已关闭",
picture2:"图片",
Unnamed:"未命名聊天室",
noNewsAtTheMoment:"暂无消息",
contactFailed:"加载更多联系人失败",
listException:"获取聊天室列表异常",
my:"我",
unknownSender:"未知发送者",
recordFailed:"加载聊天记录失败",
messageException:"加载消息异常",
chooseFirst:"请先选择联系人",
chatDisconnected:"聊天连接已断开,请刷新页面重试",
pictureSuccessful:"图片已发送",
}, },
@ -28,5 +92,73 @@ export const ChatWidget_en = {
onlyImages:"Only image files can be uploaded!", onlyImages:"Only image files can be uploaded!",
imageTooLarge:"The image size cannot exceed 5MB!", imageTooLarge:"The image size cannot exceed 5MB!",
imageReceived:"We have received your image, and we will handle your question as soon as possible.", imageReceived:"We have received your image, and we will handle your question as soon as possible.",
networkError: "Network disconnected, please refresh page",
openCustomerService: "Open customer service chat",
connectToCustomerService: "Connecting to customer service...",
connectionFailed: "Connection failed, please refresh page",
tryConnectingAgain: "Retry connection",
loading: "Loading...",
loadMore: "Load more history",
welcomeToUse: "Welcome to online service, how can I help you?",
picture: "Chat image",
read: "Read",
unread: "Unread",
subscriptionFailed: "Message subscription failed",
break: "Connection lost",
retry: "Retry in seconds",
disconnectWaiting: "Reconnecting...",
sendFailed: "Send failed, please retry",
noHistory: "No history",
historicalFailure: "Failed to load history",
loadingHistory: "Loading more history...",
noMoreHistory: "No more history",
Loaded: "History loaded",
newMessage: "New message",
pictureMessage: "Image message",
initializationFailed: "Initialization failed, please refresh",
beSorry: "Sorry, I cannot answer this. Please wait for agent or submit ticket",
today: "Today",
yesterday: "Yesterday",
canOnlyUploadImages: "Only image files allowed",
imageSizeExceeded: "Image size exceeds 5MB",
uploading: "Uploading image...",
pictureFailed: "Failed to send image",
readImage: "Failed to read image",
processingFailed: "Image processing failed",
Disconnected: "Disconnected",
reconnecting: "Reconnecting...",
contactList: "Contact list",
search: "Search contacts",
tourist: "Guest",
important: "Important",
markAsImportant: "Mark as important",
cancelImportant: "Unmarked as important",
markingFailed: "Marking failed",
select: "Select contact",
notSelected: "No contact selected",
None: "No messages",
sendPicture: "Send image",
inputMessage: "Type message, Enter to send, Ctrl+Enter for new line",
bottom: "Back to bottom",
Preview: "Preview image",
chatRoom: "Chat room",
CLOSED: "Closed",
picture2: "Image",
Unnamed: "Unnamed chat",
noNewsAtTheMoment: "No messages",
contactFailed: "Failed to load contacts",
listException: "Failed to get chat list",
my: "Me",
unknownSender: "Unknown sender",
recordFailed: "Failed to load chat records",
messageException: "Failed to load messages",
chooseFirst: "Please select contact first",
chatDisconnected: "Chat disconnected, please refresh",
pictureSuccessful: "Image sent",
} }
} }

View File

@ -1,5 +1,10 @@
<template> <template>
<div class="cs-chat-container"> <div class="cs-chat-container">
<!-- 添加连接状态提示 -->
<div v-if="connectionStatus !== 'connected'" class="connection-status" :class="connectionStatus">
<i :class="connectionStatus === 'error' ? 'el-icon-warning' : 'el-icon-loading'"></i>
<span>{{ connectionStatus === 'error' ? '连接已断开' : '正在连接...' }}</span>
</div>
<!-- 聊天窗口主体 --> <!-- 聊天窗口主体 -->
<div class="cs-chat-wrapper"> <div class="cs-chat-wrapper">
<!-- 左侧联系人列表 --> <!-- 左侧联系人列表 -->
@ -22,6 +27,7 @@
class="cs-contact-item" class="cs-contact-item"
:class="{ active: currentContactId === contact.roomId }" :class="{ active: currentContactId === contact.roomId }"
@click="selectContact(contact.roomId)" @click="selectContact(contact.roomId)"
:title="contact.name"
> >
<div class="cs-avatar"> <div class="cs-avatar">
<i class="iconfont icon-icon28" style="font-size: 2vw"></i> <i class="iconfont icon-icon28" style="font-size: 2vw"></i>
@ -104,7 +110,11 @@
</div> </div>
<!-- 聊天内容区域 --> <!-- 聊天内容区域 -->
<div class="cs-chat-messages" ref="messageContainer"> <div
class="cs-chat-messages"
ref="messageContainer"
@scroll="handleScroll"
>
<div v-if="!currentContact" class="cs-empty-chat"> <div v-if="!currentContact" class="cs-empty-chat">
<i class="el-icon-chat-dot-round"></i> <i class="el-icon-chat-dot-round"></i>
<p>您尚未选择联系人</p> <p>您尚未选择联系人</p>
@ -120,6 +130,8 @@
text-align: center; text-align: center;
color: #409eff; color: #409eff;
margin-bottom: 10px; margin-bottom: 10px;
font-size: 0.7vw;
" "
> >
<i class="el-icon-arrow-up"></i> <i class="el-icon-arrow-up"></i>
@ -222,6 +234,17 @@
</div> </div>
</div> </div>
<div
v-if="showScrollButton"
class="scroll-to-bottom"
@click="scrollToBottom(true)"
>
回到底部 <i class="el-icon-arrow-down"></i>
</div>
<!-- 图片预览 --> <!-- 图片预览 -->
<el-dialog <el-dialog
:visible.sync="previewVisible" :visible.sync="previewVisible"
@ -285,6 +308,16 @@ export default {
chatRooms: [], // chatRooms: [], //
isWebSocketConnected: false, isWebSocketConnected: false,
connectionStatus: "disconnected", connectionStatus: "disconnected",
isLoadingMoreContacts: false, //
lastContactTime: null, //
showScrollButton: false,
visibilityHandler: null, //
reconnectTimer: null, //
maxReconnectAttempts: 5, //
reconnectInterval: 5000, // (ms)
reconnectAttempts: 0, //
lastActivityTime: Date.now(), //
activityCheckInterval: null, //
}; };
}, },
computed: { computed: {
@ -342,7 +375,6 @@ export default {
window.addEventListener("setItem", () => { window.addEventListener("setItem", () => {
let userEmail = localStorage.getItem("userEmail"); let userEmail = localStorage.getItem("userEmail");
this.userEmail = JSON.parse(userEmail); this.userEmail = JSON.parse(userEmail);
}); });
// //
this.$nextTick(() => { this.$nextTick(() => {
@ -357,6 +389,25 @@ export default {
); );
} }
}); });
//
this.$nextTick(() => {
const contactList = document.querySelector(".cs-contacts");
if (contactList) {
contactList.addEventListener("scroll", this.handleContactListScroll);
}
});
//
this.visibilityHandler = () => {
if (document.visibilityState === "visible") {
//
this.checkAndReconnect();
}
};
document.addEventListener("visibilitychange", this.visibilityHandler);
//
this.startActivityCheck();
}, },
methods: { methods: {
handleKeyDown(e) { handleKeyDown(e) {
@ -399,20 +450,24 @@ export default {
type: this.userType, type: this.userType,
}; };
//
this.stompClient.connect( this.stompClient.connect(
headers, headers,
(frame) => { (frame) => {
console.log("[客服系统] WebSocket 连接成功", frame); console.log('[客服系统] WebSocket 连接成功', frame);
this.isWebSocketConnected = true; this.isWebSocketConnected = true;
this.connectionStatus = "connected"; this.connectionStatus = 'connected';
this.reconnectAttempts = 0;
this.subscribeToMessages(); this.subscribeToMessages();
this.updateLastActivityTime();
}, },
(error) => { (error) => {
console.error("[客服系统] WebSocket 错误:", error); console.error('[客服系统] WebSocket 错误:', error);
this.handleDisconnect(); this.handleDisconnect();
} }
); );
// //
this.stompClient.heartbeat.outgoing = 20000; this.stompClient.heartbeat.outgoing = 20000;
this.stompClient.heartbeat.incoming = 20000; this.stompClient.heartbeat.incoming = 20000;
@ -435,23 +490,68 @@ export default {
id: `customer_${this.userEmail}`, id: `customer_${this.userEmail}`,
} }
); );
// this.stompClient.subscribe(
// `/user/queue/customer_service`, // //
// this.handleIncomingMessage, this.stompClient.subscribe(
// { `/sub/queue/close/room/${this.userEmail}`,
// id: `customer_service_${this.userEmail}`, this.handleRoomClose,
// }
// ); );
console.log( console.log(
"CustomerService 成功订阅消息频道:", "CustomerService 成功订阅消息频道:",
`/sub/queue/customer/${this.userEmail}` `/sub/queue/customer/${this.userEmail}`
); );
console.log(
"CustomerService 成功订阅关闭消息频道:",
`/sub/queue/close/room/${this.userEmail}`
);
} catch (error) { } catch (error) {
console.error("CustomerService 订阅消息失败:", error); console.error("CustomerService 订阅消息失败:", error);
} }
}, },
//
handleRoomClose(message) {
try {
//
const closedUserEmail = message.body;
console.log("收到聊天室关闭通知:", closedUserEmail);
//
const contactIndex = this.contacts.findIndex(
(contact) => contact.name === closedUserEmail
);
if (contactIndex !== -1) {
//
if (this.currentContactId === this.contacts[contactIndex].roomId) {
this.currentContactId = null;
}
//
this.contacts.splice(contactIndex, 1);
//
this.$delete(this.messages, this.contacts[contactIndex].roomId);
//
const manualRoomIndex = this.manualCreatedRooms.findIndex(
(room) => room.name === closedUserEmail
);
if (manualRoomIndex !== -1) {
this.manualCreatedRooms.splice(manualRoomIndex, 1);
this.saveManualCreatedRooms();
}
this.$message.info(`聊天室 ${closedUserEmail} 已关闭`);
}
} catch (error) {
console.error("处理聊天室关闭消息失败:", error);
}
},
// //
disconnectWebSocket() { disconnectWebSocket() {
if (this.stompClient) { if (this.stompClient) {
@ -476,16 +576,51 @@ export default {
// //
handleDisconnect() { handleDisconnect() {
this.isWebSocketConnected = false; this.isWebSocketConnected = false;
this.connectionStatus = "error"; this.connectionStatus = 'error';
// if (this.reconnectAttempts < this.maxReconnectAttempts) {
setTimeout(() => { this.reconnectAttempts++;
console.log(`尝试重新连接 (${this.reconnectAttempts}/${this.maxReconnectAttempts})...`);
this.reconnectTimer = setTimeout(() => {
if (!this.isWebSocketConnected) { if (!this.isWebSocketConnected) {
this.initWebSocket(); this.initWebSocket();
} }
}, 5000); }, this.reconnectInterval);
} else {
console.log('达到最大重连次数,停止重连');
this.$message.error('连接已断开,请刷新页面重试');
}
}, },
//
async checkAndReconnect() {
if (!this.isWebSocketConnected) {
console.log('页面恢复可见,尝试重新连接...');
await this.initWebSocket();
}
},
//
startActivityCheck() {
this.activityCheckInterval = setInterval(() => {
const now = Date.now();
const inactiveTime = now - this.lastActivityTime;
// 5
if (inactiveTime > 5 * 60 * 1000) {
this.disconnectWebSocket();
}
}, 60000); //
},
//
updateLastActivityTime() {
this.lastActivityTime = Date.now();
},
// UTC // UTC
getUTCTime() { getUTCTime() {
const now = new Date(); const now = new Date();
@ -610,7 +745,9 @@ export default {
this.$set(this.messages, messageData.roomId, []); this.$set(this.messages, messageData.roomId, []);
} else { } else {
// //
existingContact.lastMessage = messageData.isImage ? "[图片]" : messageData.content; existingContact.lastMessage = messageData.isImage
? "[图片]"
: messageData.content;
existingContact.lastTime = messageData.time; existingContact.lastTime = messageData.time;
} }
@ -631,12 +768,15 @@ export default {
roomId: messageData.roomId, roomId: messageData.roomId,
}); });
// //
if (messageData.roomId === this.currentContactId) { if (messageData.roomId === this.currentContactId) {
this.markMessagesAsRead(messageData.roomId); this.markMessagesAsRead(messageData.roomId);
this.scrollToBottom(true); // 使
} else { } else {
// //
const contact = this.contacts.find((c) => c.roomId === messageData.roomId); const contact = this.contacts.find(
(c) => c.roomId === messageData.roomId
);
if (contact) { if (contact) {
contact.unread = (contact.unread || 0) + 1; contact.unread = (contact.unread || 0) + 1;
} }
@ -647,8 +787,113 @@ export default {
} catch (error) { } catch (error) {
console.error("处理新消息失败:", error); console.error("处理新消息失败:", error);
} }
}, },
//
handleContactListScroll(e) {
const container = e.target;
// 2px
if (
container.scrollHeight - container.scrollTop - container.clientHeight <
2
) {
this.loadMoreContacts();
}
},
//
async loadMoreContacts() {
//
if (this.isLoadingMoreContacts) return;
//
const lastContact = this.contacts[this.contacts.length - 1];
if (!lastContact) return;
this.isLoadingMoreContacts = true;
try {
const formatDateTime = (date) => {
if (!date) return null;
const d = new Date(date);
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, "0");
const day = String(d.getDate()).padStart(2, "0");
const hours = String(d.getHours()).padStart(2, "0");
const minutes = String(d.getMinutes()).padStart(2, "0");
const seconds = String(d.getSeconds()).padStart(2, "0");
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
};
//
const requestData = {
sendDateTime: formatDateTime(lastContact.lastTime),
userType: 2, //
email: this.userEmail,
};
const response = await getRoomList(requestData);
if (response?.code === 200 && response.rows?.length > 0) {
//
const newContacts = response.rows.map((room) => {
const existingContact = this.contacts.find(
(c) => c.roomId === room.id
);
const manualRoom = this.manualCreatedRooms.find(
(c) => c.roomId === room.id
);
const isImportant =
room.flag === 1
? true
: room.flag === 0
? false
: room.important === 1
? true
: existingContact
? existingContact.important
: false;
return {
roomId: room.id,
name: room.userEmail || "未命名聊天室",
avatar: this.getDefaultAvatar(room.roomName || "未命名聊天室"),
lastMessage:
room.lastMessage ||
(existingContact ? existingContact.lastMessage : "暂无消息"),
lastTime: room.lastUserSendTime
? new Date(room.lastUserSendTime)
: new Date(),
unread: existingContact?.unread ?? room.unreadCount ?? 0,
important: isImportant,
isManualCreated: manualRoom ? true : false,
sendUserType: room.sendUserType,
isGuest: room.sendUserType === 0,
};
});
//
const uniqueNewContacts = newContacts.filter(
(newContact) =>
!this.contacts.some(
(existingContact) =>
existingContact.roomId === newContact.roomId
)
);
//
if (uniqueNewContacts.length > 0) {
this.contacts = [...this.contacts, ...uniqueNewContacts];
this.sortContacts();
}
}
} catch (error) {
console.error("加载更多联系人失败:", error);
this.$message.error("加载更多联系人失败");
} finally {
this.isLoadingMoreContacts = false;
}
},
// //
handleNewChatRoom(messageData) { handleNewChatRoom(messageData) {
// //
@ -832,7 +1077,13 @@ export default {
async fetchRoomList() { async fetchRoomList() {
try { try {
this.loadingRooms = true; this.loadingRooms = true;
const response = await getRoomList(); const requestData = {
lastTime: null, //
userType: 2,
email: this.userEmail,
};
const response = await getRoomList(requestData);
if (response?.code === 200) { if (response?.code === 200) {
const newContacts = response.rows.map((room) => { const newContacts = response.rows.map((room) => {
const existingContact = this.contacts.find( const existingContact = this.contacts.find(
@ -858,8 +1109,9 @@ export default {
roomId: room.id, roomId: room.id,
name: room.userEmail || "未命名聊天室", name: room.userEmail || "未命名聊天室",
avatar: this.getDefaultAvatar(room.roomName || "未命名聊天室"), avatar: this.getDefaultAvatar(room.roomName || "未命名聊天室"),
// 使 lastMessage:
lastMessage: room.lastMessage || (existingContact ? existingContact.lastMessage : "暂无消息"), room.lastMessage ||
(existingContact ? existingContact.lastMessage : "暂无消息"),
lastTime: room.lastUserSendTime lastTime: room.lastUserSendTime
? new Date(room.lastUserSendTime) ? new Date(room.lastUserSendTime)
: new Date(), : new Date(),
@ -881,7 +1133,7 @@ export default {
} finally { } finally {
this.loadingRooms = false; this.loadingRooms = false;
} }
}, },
// //
async loadMoreHistory() { async loadMoreHistory() {
@ -1047,7 +1299,9 @@ export default {
const message = { const message = {
id: messageData.id || Date.now(), // id使 id: messageData.id || Date.now(), // id使
sender: messageData.sender, sender: messageData.sender,
avatar: messageData.avatar || (messageData.isSelf ? "iconfont icon-icon28" : "iconfont icon-user"), avatar:
messageData.avatar ||
(messageData.isSelf ? "iconfont icon-icon28" : "iconfont icon-user"),
content: messageData.content, content: messageData.content,
time: messageData.time || new Date(), time: messageData.time || new Date(),
isSelf: messageData.isSelf, isSelf: messageData.isSelf,
@ -1073,7 +1327,7 @@ export default {
this.scrollToBottom(); this.scrollToBottom();
}); });
} }
}, },
// //
async handleImageUpload(event) { async handleImageUpload(event) {
// WebSocket // WebSocket
@ -1142,7 +1396,7 @@ export default {
this.updateContactLastMessage({ this.updateContactLastMessage({
roomId: this.currentContactId, roomId: this.currentContactId,
content: "[图片]", content: "[图片]",
isImage: true isImage: true,
}); });
this.$message.success("图片已发送"); this.$message.success("图片已发送");
@ -1155,7 +1409,6 @@ export default {
// //
reader.readAsDataURL(file); reader.readAsDataURL(file);
} catch (error) { } catch (error) {
console.error("上传图片异常:", error); console.error("上传图片异常:", error);
this.$message.error("上传图片失败,请重试"); this.$message.error("上传图片失败,请重试");
@ -1164,9 +1417,7 @@ export default {
// //
this.$refs.imageInput.value = ""; this.$refs.imageInput.value = "";
} }
}, },
// //
updateContactLastMessage(message) { updateContactLastMessage(message) {
@ -1249,14 +1500,24 @@ export default {
}, },
// //
scrollToBottom() { scrollToBottom(force = false) {
const container = this.$refs.messageContainer; const container = this.$refs.messageContainer;
if (!container) return; if (!container) return;
// 使 // 使 nextTick DOM
this.$nextTick(() => {
//
setTimeout(() => {
container.scrollTo({ container.scrollTo({
top: container.scrollHeight, top: container.scrollHeight,
behavior: "smooth", behavior: force ? "auto" : "smooth",
});
//
if (force) {
this.showScrollButton = false;
}
}, 100);
}); });
}, },
@ -1363,6 +1624,12 @@ export default {
}, },
// //
handleScroll() { handleScroll() {
const container = this.$refs.messageContainer;
if (!container) return;
//
this.showScrollButton = !this.isAtBottom();
// //
if (this.isAtBottom()) { if (this.isAtBottom()) {
this.userViewHistory = false; this.userViewHistory = false;
@ -1455,9 +1722,7 @@ export default {
// //
this.disconnectWebSocket(); this.disconnectWebSocket();
// if (this.roomListInterval) {
// clearInterval(this.roomListInterval);
// }
// //
if (this.$refs.messageContainer) { if (this.$refs.messageContainer) {
@ -1466,12 +1731,25 @@ export default {
this.handleScroll this.handleScroll
); );
} }
//
if (this.visibilityHandler) {
document.removeEventListener('visibilitychange', this.visibilityHandler);
}
if (this.activityCheckInterval) {
clearInterval(this.activityCheckInterval);
}
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
}
}, },
}; };
</script> </script>
<style scoped> <style scoped>
.cs-chat-container { .cs-chat-container {
width: 70%; width: 65%;
height: 600px; height: 600px;
margin: 0 auto; margin: 0 auto;
background-color: #f5f6f7; background-color: #f5f6f7;
@ -1481,6 +1759,7 @@ export default {
margin-top: 50px; margin-top: 50px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
position: relative;
} }
.cs-chat-wrapper { .cs-chat-wrapper {
@ -1490,12 +1769,14 @@ export default {
/* 联系人列表样式 */ /* 联系人列表样式 */
.cs-contact-list { .cs-contact-list {
width: 260px; width: 290px;
min-width: 260px; /* 添加最小宽度 */
border-right: 1px solid #e0e0e0; border-right: 1px solid #e0e0e0;
background-color: #fff; background-color: #fff;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
overflow: hidden; /* 防止整体出现滚动条 */
} }
.cs-header { .cs-header {
@ -1533,9 +1814,11 @@ export default {
background-color: #e6f7ff; background-color: #e6f7ff;
} }
/* 修改头像区域样式 */
.cs-avatar { .cs-avatar {
position: relative; position: relative;
margin-right: 10px; margin-right: 10px;
flex-shrink: 0; /* 防止头像被压缩 */
} }
.unread-badge { .unread-badge {
@ -1555,7 +1838,8 @@ export default {
.cs-contact-info { .cs-contact-info {
flex: 1; flex: 1;
min-width: 0; min-width: 0; /* 允许内容收缩 */
overflow: hidden; /* 防止内容溢出 */
} }
.cs-contact-name { .cs-contact-name {
@ -1565,6 +1849,9 @@ export default {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
margin-bottom: 4px; margin-bottom: 4px;
white-space: nowrap; /* 防止换行 */
overflow: hidden; /* 隐藏溢出内容 */
text-overflow: ellipsis; /* 显示省略号 */
} }
.cs-contact-time { .cs-contact-time {
@ -1579,6 +1866,7 @@ export default {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
max-width: 100%; /* 限制最大宽度 */
} }
.important-tag { .important-tag {
@ -1629,6 +1917,8 @@ export default {
padding: 15px; padding: 15px;
overflow-y: auto; overflow-y: auto;
background-color: #f5f5f5; background-color: #f5f5f5;
height: calc(100% - 180px); /* 减去头部和输入框的高度 */
position: relative; /* 添加相对定位 */
} }
.cs-loading, .cs-loading,
@ -1648,9 +1938,11 @@ export default {
color: #dcdfe6; color: #dcdfe6;
} }
/* 确保消息列表正确显示 */
.cs-message-list { .cs-message-list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding-bottom: 20px; /* 添加底部间距 */
} }
.cs-message { .cs-message {
@ -1678,6 +1970,7 @@ export default {
margin-left: 10px; margin-left: 10px;
} }
/* 调整消息气泡样式 */
.cs-bubble { .cs-bubble {
max-width: 70%; max-width: 70%;
padding: 8px 12px; padding: 8px 12px;
@ -1685,6 +1978,7 @@ export default {
background-color: #fff; background-color: #fff;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
position: relative; position: relative;
margin-bottom: 4px; /* 添加消息间距 */
} }
.cs-message-self .cs-bubble { .cs-message-self .cs-bubble {
@ -1703,11 +1997,13 @@ export default {
word-break: break-word; word-break: break-word;
} }
/* 确保图片消息正确显示 */
.cs-image img { .cs-image img {
max-width: 200px; max-width: 200px;
max-height: 200px; max-height: 200px;
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
display: block; /* 确保图片正确显示 */
} }
/* 输入区域样式 */ /* 输入区域样式 */
@ -1780,7 +2076,7 @@ export default {
} }
} }
/* 添加重要标记样式 */ /* 重要标记图标样式 */
.important-star { .important-star {
display: flex; display: flex;
align-items: center; align-items: center;
@ -1789,8 +2085,9 @@ export default {
height: 30px; height: 30px;
cursor: pointer; cursor: pointer;
margin-left: 5px; margin-left: 5px;
color: #c0c4cc; /* 默认灰色 */ color: #c0c4cc;
transition: color 0.3s; transition: color 0.3s;
flex-shrink: 0; /* 防止图标被压缩 */
} }
.important-star:hover { .important-star:hover {
@ -1812,7 +2109,9 @@ export default {
cursor: pointer; cursor: pointer;
border-bottom: 1px solid #f0f0f0; border-bottom: 1px solid #f0f0f0;
transition: background-color 0.2s; transition: background-color 0.2s;
align-items: center; /* 确保垂直居中 */ align-items: center;
width: 100%;
box-sizing: border-box; /* 确保padding不会导致溢出 */
} }
/* 重要标签的样式调整 */ /* 重要标签的样式调整 */
@ -1842,4 +2141,58 @@ export default {
.cs-contact-item.is-guest { .cs-contact-item.is-guest {
background-color: #f9f9f9; background-color: #f9f9f9;
} }
/* 滚动按钮样式 */
.scroll-to-bottom {
position: absolute; /* 改为 fixed 定位 */
right: 4px; /* 根据容器宽度调整位置 */
bottom: 184px;
background-color: #fff;
border-radius: 5px 0px 0px 5px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s;
z-index: 1000; /* 确保按钮在最上层 */
padding: 5px 1vw;
font-size: 0.7vw;
color: #7638ff;
}
.scroll-to-bottom:hover {
background-color: #f0f0f0;
transform: translateY(-2px);
}
.scroll-to-bottom i {
font-size: 0.8vw;
color: #7638ff;
margin-left: 5px;
}
/* 添加连接状态样式 */
.connection-status {
position: fixed;
top: 20px;
right: 20px;
padding: 8px 16px;
border-radius: 4px;
display: flex;
align-items: center;
gap: 8px;
z-index: 1000;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
.connection-status.error {
background-color: #fef0f0;
color: #f56c6c;
}
.connection-status.connecting {
background-color: #f0f9eb;
color: #67c23a;
}
</style> </style>

View File

@ -800,6 +800,7 @@ export default {
.view{ .view{
color: #5721e4; color: #5721e4;
margin-left: 5px; margin-left: 5px;
cursor: pointer;
} }
} }
@ -826,6 +827,7 @@ export default {
// overflow: hidden; // overflow: hidden;
padding: 5px 5px; padding: 5px 5px;
box-sizing: border-box; box-sizing: border-box;
cursor: pointer;
// background: palegoldenrod; // background: palegoldenrod;
img { img {
@ -841,7 +843,13 @@ export default {
text-transform: capitalize; text-transform: capitalize;
} }
} }
} }
.moveCurrencyBox li:hover {
box-shadow: 0px 0px 5px 2px #d2c3ea;
}
.currencySelect{ .currencySelect{
display: flex; display: flex;
align-items: center; align-items: center;
@ -1330,6 +1338,7 @@ export default {
.view{ .view{
color: #5721e4; color: #5721e4;
margin-left: 5px; margin-left: 5px;
cursor: pointer;
} }
} }
@ -1359,6 +1368,7 @@ export default {
padding: 5px 5px; padding: 5px 5px;
box-sizing: border-box; box-sizing: border-box;
// background: palegoldenrod; // background: palegoldenrod;
cursor: pointer;
img { img {
width: 25px; width: 25px;
@ -1373,6 +1383,12 @@ export default {
text-transform: capitalize; text-transform: capitalize;
} }
} }
}
.moveCurrencyBox li:hover {
box-shadow: 0px 0px 5px 2px #d2c3ea;
} }
.currencySelect{ .currencySelect{
display: flex; display: flex;
@ -3269,6 +3285,7 @@ export default {
cursor: pointer; cursor: pointer;
margin-left: 8px; margin-left: 8px;
color: #6E3EDB; color: #6E3EDB;
// background: palegoldenrod;
} }
.view:hover{ .view:hover{

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -1 +1 @@
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><meta name=google-site-verification content=pKAZogQ0NQ6L4j9-V58WJMjm7zYCFwkJXSJzWu9UDM8><meta name=robots content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1"><meta name=googlebot content="index, follow"><meta name=googlebot-news content="index, follow"><meta name=bingbot content="index, follow"><link rel=alternate hreflang=zh href=https://m2pool.com/zh><link rel=alternate hreflang=en href=https://m2pool.com/en><link rel=alternate hreflang=x-default href=https://m2pool.com/en><meta property=og:title content="M2pool - Stable leading high-yield mining pool"><meta property=og:description content="M2Pool provides professional mining services, supporting multiple cryptocurrency mining"><meta property=og:url content=https://m2pool.com/en><meta property=og:site_name content=M2Pool><meta property=og:type content=website><meta property=og:image content=https://m2pool.com/logo.png><link rel=icon href=/favicon.ico><link rel=stylesheet href=//at.alicdn.com/t/c/font_4582735_7i8wfzc0art.css><title>M2pool - Stable leading high-yield mining pool</title><meta name=keywords content="M2Pool, cryptocurrency mining pool,Entropyx(enx),entropyx, bitcoin mining, DGB mining, mining pool service, 加密货币矿池, 比特币挖矿, DGB挖矿"><meta name=description content="M2Pool provides professional mining services, supporting multiple cryptocurrency mining, including nexa, grs, mona, dgb, rxd, enx"><meta name=format-detection content="telephone=no"><meta name=apple-mobile-web-app-capable content=yes><script defer src=/js/chunk-vendors-945ce2fe.648a91a9.js></script><script defer src=/js/chunk-vendors-aacc2dbb.d317c558.js></script><script defer src=/js/chunk-vendors-bc050c32.3f2f14d2.js></script><script defer src=/js/chunk-vendors-3003db77.d0b93d36.js></script><script defer src=/js/chunk-vendors-9d134daf.bb668c99.js></script><script defer src=/js/chunk-vendors-439af1fa.48a48f35.js></script><script defer src=/js/chunk-vendors-5c533fba.b9c00e08.js></script><script defer src=/js/chunk-vendors-96cecd74.a7d9b845.js></script><script defer src=/js/chunk-vendors-c2f7d60e.3710fdc2.js></script><script defer src=/js/chunk-vendors-89d5c698.2190b4ca.js></script><script defer src=/js/chunk-vendors-377fed06.159de137.js></script><script defer src=/js/chunk-vendors-5a805870.4cfc0ae8.js></script><script defer src=/js/chunk-vendors-cf2e0a28.c6e99da0.js></script><script defer src=/js/app-42f9d7e6.3d6812c5.js></script><script defer src=/js/app-5c551db8.068e1f5e.js></script><script defer src=/js/app-01dc9ae1.e746f05c.js></script><script defer src=/js/app-8e0489d9.e6feb21e.js></script><script defer src=/js/app-72600b29.ec821a84.js></script><script defer src=/js/app-f035d474.30e8939b.js></script><script defer src=/js/app-113c6c50.c73554a4.js></script><link href=/css/chunk-vendors-5c533fba.6f97509c.css rel=stylesheet><link href=/css/app-42f9d7e6.21e533d7.css rel=stylesheet><link href=/css/app-01dc9ae1.04da7d85.css rel=stylesheet><link href=/css/app-8e0489d9.105c6ba3.css rel=stylesheet><link href=/css/app-72600b29.f0fc86b8.css rel=stylesheet><link href=/css/app-f035d474.0e6b8898.css rel=stylesheet><link href=/css/app-113c6c50.729eb983.css rel=stylesheet></head><body><div id=app></div></body></html> <!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><meta name=google-site-verification content=pKAZogQ0NQ6L4j9-V58WJMjm7zYCFwkJXSJzWu9UDM8><meta name=robots content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1"><meta name=googlebot content="index, follow"><meta name=googlebot-news content="index, follow"><meta name=bingbot content="index, follow"><link rel=alternate hreflang=zh href=https://m2pool.com/zh><link rel=alternate hreflang=en href=https://m2pool.com/en><link rel=alternate hreflang=x-default href=https://m2pool.com/en><meta property=og:title content="M2pool - Stable leading high-yield mining pool"><meta property=og:description content="M2Pool provides professional mining services, supporting multiple cryptocurrency mining"><meta property=og:url content=https://m2pool.com/en><meta property=og:site_name content=M2Pool><meta property=og:type content=website><meta property=og:image content=https://m2pool.com/logo.png><link rel=icon href=/favicon.ico><link rel=stylesheet href=//at.alicdn.com/t/c/font_4582735_7i8wfzc0art.css><title>M2pool - Stable leading high-yield mining pool</title><meta name=keywords content="M2Pool, cryptocurrency mining pool,Entropyx(enx),entropyx, bitcoin mining, DGB mining, mining pool service, 加密货币矿池, 比特币挖矿, DGB挖矿"><meta name=description content="M2Pool provides professional mining services, supporting multiple cryptocurrency mining, including nexa, grs, mona, dgb, rxd, enx"><meta name=format-detection content="telephone=no"><meta name=apple-mobile-web-app-capable content=yes><script defer src=/js/chunk-vendors-945ce2fe.648a91a9.js></script><script defer src=/js/chunk-vendors-aacc2dbb.d317c558.js></script><script defer src=/js/chunk-vendors-bc050c32.3f2f14d2.js></script><script defer src=/js/chunk-vendors-3003db77.d0b93d36.js></script><script defer src=/js/chunk-vendors-9d134daf.bb668c99.js></script><script defer src=/js/chunk-vendors-439af1fa.48a48f35.js></script><script defer src=/js/chunk-vendors-5c533fba.b9c00e08.js></script><script defer src=/js/chunk-vendors-96cecd74.a7d9b845.js></script><script defer src=/js/chunk-vendors-c2f7d60e.3710fdc2.js></script><script defer src=/js/chunk-vendors-89d5c698.2190b4ca.js></script><script defer src=/js/chunk-vendors-377fed06.159de137.js></script><script defer src=/js/chunk-vendors-5a805870.4cfc0ae8.js></script><script defer src=/js/chunk-vendors-cf2e0a28.c6e99da0.js></script><script defer src=/js/app-42f9d7e6.12c435f1.js></script><script defer src=/js/app-5c551db8.69e18ab2.js></script><script defer src=/js/app-01dc9ae1.e746f05c.js></script><script defer src=/js/app-8e0489d9.5adbe93c.js></script><script defer src=/js/app-72600b29.e3c70da1.js></script><script defer src=/js/app-f035d474.30e8939b.js></script><script defer src=/js/app-113c6c50.c73554a4.js></script><link href=/css/chunk-vendors-5c533fba.6f97509c.css rel=stylesheet><link href=/css/app-42f9d7e6.e8e56d1b.css rel=stylesheet><link href=/css/app-01dc9ae1.04da7d85.css rel=stylesheet><link href=/css/app-8e0489d9.7553f600.css rel=stylesheet><link href=/css/app-72600b29.f02b800f.css rel=stylesheet><link href=/css/app-f035d474.0e6b8898.css rel=stylesheet><link href=/css/app-113c6c50.729eb983.css rel=stylesheet></head><body><div id=app></div></body></html>

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -1 +1 @@
<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"><url><loc>https://m2pool.com/en</loc><lastmod>2025-05-16T08:36:13.289Z</lastmod><changefreq>daily</changefreq><priority>1.0</priority></url><url><loc>https://m2pool.com/en/dataDisplay</loc><lastmod>2025-05-16T08:36:13.290Z</lastmod><changefreq>weekly</changefreq><priority>0.8</priority></url><url><loc>https://m2pool.com/en/ServiceTerms</loc><lastmod>2025-05-16T08:36:13.290Z</lastmod><changefreq>monthly</changefreq><priority>0.6</priority></url><url><loc>https://m2pool.com/en/apiFile</loc><lastmod>2025-05-16T08:36:13.290Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url><url><loc>https://m2pool.com/en/rate</loc><lastmod>2025-05-16T08:36:13.290Z</lastmod><changefreq>weekly</changefreq><priority>0.8</priority></url><url><loc>https://m2pool.com/en/AccessMiningPool/nexaAccess</loc><lastmod>2025-05-16T08:36:13.290Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url><url><loc>https://m2pool.com/en/AccessMiningPool/grsAccess</loc><lastmod>2025-05-16T08:36:13.290Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url><url><loc>https://m2pool.com/en/AccessMiningPool/monaAccess</loc><lastmod>2025-05-16T08:36:13.290Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url><url><loc>https://m2pool.com/en/AccessMiningPool/dgbsAccess</loc><lastmod>2025-05-16T08:36:13.290Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url><url><loc>https://m2pool.com/en/AccessMiningPool/dgbqAccess</loc><lastmod>2025-05-16T08:36:13.290Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url><url><loc>https://m2pool.com/en/AccessMiningPool/dgboAccess</loc><lastmod>2025-05-16T08:36:13.290Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url><url><loc>https://m2pool.com/en/AccessMiningPool/rxdAccess</loc><lastmod>2025-05-16T08:36:13.290Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url></urlset> <?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"><url><loc>https://m2pool.com/en</loc><lastmod>2025-05-23T06:27:17.672Z</lastmod><changefreq>daily</changefreq><priority>1.0</priority></url><url><loc>https://m2pool.com/en/dataDisplay</loc><lastmod>2025-05-23T06:27:17.672Z</lastmod><changefreq>weekly</changefreq><priority>0.8</priority></url><url><loc>https://m2pool.com/en/ServiceTerms</loc><lastmod>2025-05-23T06:27:17.672Z</lastmod><changefreq>monthly</changefreq><priority>0.6</priority></url><url><loc>https://m2pool.com/en/apiFile</loc><lastmod>2025-05-23T06:27:17.672Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url><url><loc>https://m2pool.com/en/rate</loc><lastmod>2025-05-23T06:27:17.672Z</lastmod><changefreq>weekly</changefreq><priority>0.8</priority></url><url><loc>https://m2pool.com/en/AccessMiningPool/nexaAccess</loc><lastmod>2025-05-23T06:27:17.672Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url><url><loc>https://m2pool.com/en/AccessMiningPool/grsAccess</loc><lastmod>2025-05-23T06:27:17.672Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url><url><loc>https://m2pool.com/en/AccessMiningPool/monaAccess</loc><lastmod>2025-05-23T06:27:17.672Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url><url><loc>https://m2pool.com/en/AccessMiningPool/dgbsAccess</loc><lastmod>2025-05-23T06:27:17.672Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url><url><loc>https://m2pool.com/en/AccessMiningPool/dgbqAccess</loc><lastmod>2025-05-23T06:27:17.672Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url><url><loc>https://m2pool.com/en/AccessMiningPool/dgboAccess</loc><lastmod>2025-05-23T06:27:17.672Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url><url><loc>https://m2pool.com/en/AccessMiningPool/rxdAccess</loc><lastmod>2025-05-23T06:27:17.672Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url></urlset>

Binary file not shown.

View File

@ -1 +1 @@
<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"><url><loc>https://m2pool.com/zh</loc><lastmod>2025-05-16T08:36:13.279Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url><url><loc>https://m2pool.com/zh/dataDisplay</loc><lastmod>2025-05-16T08:36:13.279Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url><url><loc>https://m2pool.com/zh/ServiceTerms</loc><lastmod>2025-05-16T08:36:13.279Z</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url><url><loc>https://m2pool.com/zh/apiFile</loc><lastmod>2025-05-16T08:36:13.279Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url><url><loc>https://m2pool.com/zh/rate</loc><lastmod>2025-05-16T08:36:13.279Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url><url><loc>https://m2pool.com/zh/AccessMiningPool/nexaAccess</loc><lastmod>2025-05-16T08:36:13.279Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url><url><loc>https://m2pool.com/zh/AccessMiningPool/grsAccess</loc><lastmod>2025-05-16T08:36:13.279Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url><url><loc>https://m2pool.com/zh/AccessMiningPool/monaAccess</loc><lastmod>2025-05-16T08:36:13.279Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url><url><loc>https://m2pool.com/zh/AccessMiningPool/dgbsAccess</loc><lastmod>2025-05-16T08:36:13.279Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url><url><loc>https://m2pool.com/zh/AccessMiningPool/dgbqAccess</loc><lastmod>2025-05-16T08:36:13.279Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url><url><loc>https://m2pool.com/zh/AccessMiningPool/dgboAccess</loc><lastmod>2025-05-16T08:36:13.279Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url><url><loc>https://m2pool.com/zh/AccessMiningPool/rxdAccess</loc><lastmod>2025-05-16T08:36:13.279Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url></urlset> <?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"><url><loc>https://m2pool.com/zh</loc><lastmod>2025-05-23T06:27:17.662Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url><url><loc>https://m2pool.com/zh/dataDisplay</loc><lastmod>2025-05-23T06:27:17.662Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url><url><loc>https://m2pool.com/zh/ServiceTerms</loc><lastmod>2025-05-23T06:27:17.662Z</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url><url><loc>https://m2pool.com/zh/apiFile</loc><lastmod>2025-05-23T06:27:17.662Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url><url><loc>https://m2pool.com/zh/rate</loc><lastmod>2025-05-23T06:27:17.662Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url><url><loc>https://m2pool.com/zh/AccessMiningPool/nexaAccess</loc><lastmod>2025-05-23T06:27:17.662Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url><url><loc>https://m2pool.com/zh/AccessMiningPool/grsAccess</loc><lastmod>2025-05-23T06:27:17.662Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url><url><loc>https://m2pool.com/zh/AccessMiningPool/monaAccess</loc><lastmod>2025-05-23T06:27:17.662Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url><url><loc>https://m2pool.com/zh/AccessMiningPool/dgbsAccess</loc><lastmod>2025-05-23T06:27:17.662Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url><url><loc>https://m2pool.com/zh/AccessMiningPool/dgbqAccess</loc><lastmod>2025-05-23T06:27:17.662Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url><url><loc>https://m2pool.com/zh/AccessMiningPool/dgboAccess</loc><lastmod>2025-05-23T06:27:17.662Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url><url><loc>https://m2pool.com/zh/AccessMiningPool/rxdAccess</loc><lastmod>2025-05-23T06:27:17.662Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url></urlset>

Binary file not shown.