更新服务条款页面内容

This commit is contained in:
2025-06-06 15:31:04 +08:00
parent e0a7fb8ee2
commit ac85206085
27 changed files with 464 additions and 243 deletions

View File

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

View File

@@ -112,39 +112,41 @@
</div>
<!-- 消息项 -->
<div
v-for="(msg, index) in messages"
:key="index"
class="chat-message"
:class="{
'chat-message-user': msg.type === 'user',
'chat-message-system': msg.type === 'system',
'chat-message-loading': msg.isLoading,
'chat-message-hint': msg.isSystemHint,
'chat-message-history': msg.isHistory,
}"
>
<div v-for="(msg, index) in displayMessages" :key="msg.id || index">
<!-- 时间分割条 -->
<div v-if="msg.isTimeDivider" class="chat-time-divider">
{{ formatTimeDivider(msg.time) }}
</div>
<!-- 系统提示消息如加载中无更多消息等 -->
<div v-if="msg.isLoading || msg.isSystemHint" class="system-hint">
<div
v-else-if="msg.isLoading || msg.isSystemHint"
class="system-hint"
>
<i v-if="msg.isLoading" class="el-icon-loading"></i>
<span>{{ msg.text }}</span>
</div>
<!-- 普通消息 -->
<template v-else>
<div
v-else
class="chat-message"
:class="{
'chat-message-user': msg.type === 'user',
'chat-message-system': msg.type === 'system',
'chat-message-loading': msg.isLoading,
'chat-message-hint': msg.isSystemHint,
'chat-message-history': msg.isHistory,
}"
>
<div class="message-avatar">
<i v-if="msg.type === 'system'" class="el-icon-service"></i>
<i v-else class="el-icon-user"></i>
</div>
<div class="message-content">
<!-- 时间显示在右上角 -->
<!-- <span class="message-time">{{ formatTime(msg.time) }}</span> -->
<!-- 文本消息 -->
<!-- 时间显示在右上角可选建议注释掉 -->
<!-- <span class="message-time">{{ formatTime(msg.time) }}</span> -->
<div v-if="!msg.isImage" class="message-text">
{{ msg.text }}
</div>
<!-- 图片消息 -->
<div v-else class="message-image">
<img
:src="msg.imageUrl"
@@ -153,10 +155,7 @@
@load="handleImageLoad"
/>
</div>
<div class="message-footer">
<!-- <span class="message-time">{{ formatTime(msg.time) }}</span> -->
<!-- 添加已读状态显示 -->
<span
v-if="msg.type === 'user'"
class="message-read-status"
@@ -169,7 +168,7 @@
</span>
</div>
</div>
</template>
</div>
</div>
</template>
</div>
@@ -193,18 +192,21 @@
:disabled="connectionStatus !== 'connected'"
/>
</div>
<div class="chat-input-wrapper" style="display: flex;align-items: center;">
<input
type="text"
class="chat-input"
v-model="inputMessage"
:maxlength="maxMessageLength"
@input="handleInputMessage"
:placeholder="$t('chat.inputPlaceholder') || '请输入您的问题...'"
:disabled="connectionStatus !== 'connected'"
/>
<!-- <span class="input-counter">{{ maxMessageLength - inputMessage.length }}</span> -->
</div>
<div
class="chat-input-wrapper"
style="display: flex; align-items: center"
>
<input
type="text"
class="chat-input"
v-model="inputMessage"
:maxlength="maxMessageLength"
@input="handleInputMessage"
:placeholder="$t('chat.inputPlaceholder') || '请输入您的问题...'"
:disabled="connectionStatus !== 'connected'"
/>
<!-- <span class="input-counter">{{ maxMessageLength - inputMessage.length }}</span> -->
</div>
<button
class="chat-send"
@click="sendMessage"
@@ -292,6 +294,33 @@ export default {
maxMessageLength: 300,
};
},
computed: {
/**
* 生成带有时间分割条的消息列表
* @returns {Array} 消息和分割条混合数组
*/
displayMessages() {
const result = [];
const interval = 5 * 60 * 1000; // 5分钟
let lastTime = null;
this.messages.forEach((msg, idx) => {
if (!msg.isSystemHint && !msg.isLoading) {
const msgTime = new Date(msg.time); // 直接new即可
if (!lastTime || msgTime - lastTime > interval) {
result.push({
isTimeDivider: true,
time: msg.time, // 直接用字符串
id: `divider-${msg.time}-${idx}`,
});
lastTime = msgTime;
}
}
result.push(msg);
});
return result;
}
},
async created() {
this.determineUserType();
@@ -321,6 +350,9 @@ export default {
document.addEventListener("mousemove", this.updateLastActivityTime);
document.addEventListener("keydown", this.updateLastActivityTime);
document.addEventListener("click", this.updateLastActivityTime);
// 监听退出登录事件
this.$bus.$on("user-logged-out", this.handleLogout);
},
methods: {
// 初始化聊天系统
@@ -447,7 +479,9 @@ export default {
this.connectionError = null;
try {
const wsUrl = `${process.env.VUE_APP_BASE_API}chat/ws`;
// 将 https 替换为 wss
const baseUrl = process.env.VUE_APP_BASE_API.replace("https", "wss");
const wsUrl = `${baseUrl}chat/ws`;
this.stompClient = Stomp.client(wsUrl);
this.stompClient.splitLargeFrames = true;
const headers = {
@@ -479,11 +513,12 @@ export default {
(error) => {
console.error("WebSocket Error:", error);
if (error.message.includes("503")) {
this.connectionError = this.$t("chat.server500");//服务器暂时不可用,请稍后重试
this.connectionError = this.$t("chat.server500"); //服务器暂时不可用,请稍后重试
} else if (error.message.includes("handshake")) {
this.connectionError = this.$t("chat.CheckNetwork");//"连接失败,请检查网络后重试"
this.connectionError = this.$t("chat.CheckNetwork"); //"连接失败,请检查网络后重试"
} else {
this.connectionError = error.message || this.$t("chat.connectionFailed"); // "连接失败,请刷新页面重试";
this.connectionError =
error.message || this.$t("chat.connectionFailed"); // "连接失败,请刷新页面重试";
}
this.isReconnecting = false;
this.handleDisconnect();
@@ -496,7 +531,7 @@ export default {
});
} catch (error) {
console.error("初始化 WebSocket 失败:", error);
this.connectionError = this.$t("chat.initializationFailed");//"初始化连接失败,请刷新页面重试";
this.connectionError = this.$t("chat.initializationFailed"); //"初始化连接失败,请刷新页面重试";
this.isReconnecting = false;
this.handleDisconnect();
return Promise.reject(error);
@@ -515,12 +550,12 @@ export default {
clearTimeout(this.reconnectTimer);
}
// === 新增:统一处理连接数上限错误 ===
// ===统一处理连接数上限错误 ===
if (
this.handleConnectionError({
code: this.connectionError?.code,
error: this.connectionError?.error,
message: this.connectionError
message: this.connectionError,
})
) {
return;
@@ -537,20 +572,19 @@ export default {
// 其余错误类型提示
let retryMessage = "";
if (this.connectionError && this.connectionError.includes("503")) {
retryMessage =this.$t(`chat.server500`);//服务器暂时不可用,正在重试
retryMessage = this.$t(`chat.server500`); //服务器暂时不可用,正在重试
} else if (
this.connectionError &&
this.connectionError.includes("handshake")
) {
retryMessage =this.$t(`chat.connectionFailed`);//连接失败,请刷新页面重试
retryMessage = this.$t(`chat.connectionFailed`); //连接失败,请刷新页面重试
} else {
//三秒重试
retryMessage = `${this.$t("chat.break")},${
this.reconnectInterval / 1000
}${this.$t("chat.retry")}...`;
}
this.$message.warning(retryMessage);
this.$message({
message: retryMessage,
type: "warning",
@@ -584,7 +618,7 @@ export default {
}
} else {
// 网络断开时,显示提示
this.$message.warning(this.$t("chat.CheckNetwork"));//连接失败,请检查网络后重试
this.$message.warning(this.$t("chat.CheckNetwork")); //连接失败,请检查网络后重试
}
},
// 开始活动检测
@@ -611,22 +645,22 @@ export default {
},
//只能输入300个字符
handleInputMessage() {
if (this.inputMessage.length > this.maxMessageLength) {
this.inputMessage = this.inputMessage.slice(0, this.maxMessageLength);
}
},
if (this.inputMessage.length > this.maxMessageLength) {
this.inputMessage = this.inputMessage.slice(0, this.maxMessageLength);
}
},
// 发送消息
sendMessage() {
if (!this.inputMessage.trim()) return;
if (this.inputMessage.length > this.maxMessageLength) {
this.$message.warning(`消息不能超过${this.maxMessageLength}个字符`);
return;
}
this.$message.warning(`消息不能超过${this.maxMessageLength}个字符`);
return;
}
// 检查 WebSocket 连接状态
if (!this.stompClient || !this.stompClient.connected) {
console.log("发送消息时连接已断开,尝试重连...");
this.$message.warning(this.$t("chat.attemptToReconnect"));//连接已断开,正在尝试重连...
this.$message.warning(this.$t("chat.attemptToReconnect")); //连接已断开,正在尝试重连...
this.handleDisconnect();
return;
}
@@ -681,7 +715,7 @@ export default {
});
} catch (error) {
console.error("发送消息失败:", error);
this.$message.error(this.$t("chat.sendFailed"));//发送消息失败,请重试
this.$message.error(this.$t("chat.sendFailed")); //发送消息失败,请重试
}
},
@@ -830,7 +864,9 @@ export default {
text: msg.content,
isImage: msg.type === 2,
imageUrl: msg.type === 2 ? msg.content : null,
time: new Date(msg.createTime),
time: typeof msg.createTime === 'string'
? msg.createTime
: new Date(msg.createTime).toISOString(),
id: msg.id,
roomId: msg.roomId,
sender: msg.sendEmail,
@@ -857,7 +893,7 @@ export default {
this.messages = [
{
type: "system",
text: this.$t("chat.noHistory")||"暂无历史消息",
text: this.$t("chat.noHistory") || "暂无历史消息",
isSystemHint: true,
time: new Date(),
},
@@ -865,11 +901,12 @@ export default {
}
} catch (error) {
console.error("加载历史消息失败:", error);
this.$message.error(this.$t("chat.historicalFailure"));//加载历史消息失败
this.$message.error(this.$t("chat.historicalFailure")); //加载历史消息失败
this.messages = [
{
type: "system",
text: this.$t("chat.historicalFailure")||"加载历史消息失败,请重试",
text:
this.$t("chat.historicalFailure") || "加载历史消息失败,请重试",
isSystemHint: true,
time: new Date().toISOString(),
isError: true,
@@ -900,7 +937,7 @@ export default {
// 添加没有更多消息的提示
this.messages.unshift({
type: "system",
text: this.$t("chat.noMoreHistory")||"没有更多历史消息了",
text: this.$t("chat.noMoreHistory") || "没有更多历史消息了",
isSystemHint: true,
time: new Date(),
});
@@ -910,7 +947,7 @@ export default {
// 显示加载中提示
const loadingMsg = {
type: "system",
text: this.$t("chat.loadingHistory")||"正在加载更多历史消息...",
text: this.$t("chat.loadingHistory") || "正在加载更多历史消息...",
isLoading: true,
time: new Date(),
};
@@ -945,7 +982,7 @@ export default {
if (historyMessages.length === 0) {
this.messages.unshift({
type: "system",
text: this.$t("chat.noMoreHistory")||"没有更多历史消息了",
text: this.$t("chat.noMoreHistory") || "没有更多历史消息了",
isSystemHint: true,
time: new Date(),
});
@@ -964,7 +1001,7 @@ export default {
console.error("加载更多历史消息失败:", error);
this.messages.unshift({
type: "system",
text: this.$t("chat.historicalFailure")||"加载更多历史消息失败",
text: this.$t("chat.historicalFailure") || "加载更多历史消息失败",
isError: true,
time: new Date(),
});
@@ -986,7 +1023,9 @@ export default {
text: msg.content || "",
isImage: msg.type === 2,
imageUrl: msg.type === 2 ? msg.content : null, // 图片消息直接使用content作为imageUrl
time: new Date(msg.createTime),
time: typeof msg.createTime === 'string'
? msg.createTime
: new Date(msg.createTime).toISOString(),
id: msg.id,
roomId: msg.roomId,
sender: msg.sendEmail,
@@ -1056,7 +1095,9 @@ export default {
text: data.content,
isImage: data.type === 2,
imageUrl: data.type === 2 ? data.content : null, // 图片消息直接使用content作为imageUrl
time: new Date(data.sendTime).toISOString(),
time: typeof data.sendTime === 'string'
? data.sendTime
: new Date(data.sendTime).toISOString(),
id: data.id,
roomId: data.roomId,
sender: data.sendEmail,
@@ -1111,7 +1152,9 @@ export default {
// 创建通知
createNotification(message) {
const notification = new Notification("新消息", {
body: message.isImage ? `[ ${this.$t("chat.pictureMessage")}]`|| "[图片消息]" : message.text,
body: message.isImage
? `[ ${this.$t("chat.pictureMessage")}]` || "[图片消息]"
: message.text,
icon: "/path/to/notification-icon.png", // 添加适当的图标
});
@@ -1143,22 +1186,16 @@ export default {
this.isChatOpen = !this.isChatOpen;
// 1. 判别身份
const userInfo = JSON.parse(
localStorage.getItem("jurisdiction") || "{}"
);
if (userInfo.roleKey === "customer_service") {
// 客服用户 跳转到客服页面
this.userType = 2;
const lang = this.$i18n.locale;
this.$router.push(`/${lang}/customerService`);
return;
// this.userEmail = "";
}
const userInfo = JSON.parse(localStorage.getItem("jurisdiction") || "{}");
if (userInfo.roleKey === "customer_service") {
// 客服用户 跳转到客服页面
this.userType = 2;
const lang = this.$i18n.locale;
this.$router.push(`/${lang}/customerService`);
return;
// this.userEmail = "";
}
if (this.isChatOpen) {
try {
@@ -1190,7 +1227,9 @@ export default {
});
} catch (error) {
console.error("初始化聊天失败:", error);
this.$message.error(this.$t("chat.initializationFailed")||"初始化聊天失败,请重试");
this.$message.error(
this.$t("chat.initializationFailed") || "初始化聊天失败,请重试"
);
}
}
},
@@ -1225,7 +1264,8 @@ export default {
handleAutoResponse(message) {
setTimeout(() => {
let response =
this.$t("chat.beSorry")||"抱歉,我暂时无法回答这个问题。请排队等待人工客服或提交工单。";
this.$t("chat.beSorry") ||
"抱歉,我暂时无法回答这个问题。请排队等待人工客服或提交工单。";
// 检查是否匹配自动回复关键词
for (const [keyword, reply] of Object.entries(this.autoResponses)) {
@@ -1330,9 +1370,9 @@ export default {
// 判断消息是今天、昨天还是更早的日期
if (messageDate.getTime() === today.getTime()) {
return `${this.$t("chat.today")} ${timeString}`;//今天
return `${this.$t("chat.today")} ${timeString}`; //今天
} else if (messageDate.getTime() === yesterday.getTime()) {
return `${this.$t("chat.yesterday")} ${timeString}`;//昨天
return `${this.$t("chat.yesterday")} ${timeString}`; //昨天
} else {
// 超过两天的消息显示完整日期
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(
@@ -1342,6 +1382,42 @@ export default {
}
},
// 聊天分割条时间格式化
/**
* 聊天分割条时间格式化
* @param {string|Date} date
* @returns {string}
*/
formatTimeDivider(date) {
if (!date) return '';
let d = typeof date === 'string' ? new Date(date) : date;
if (!(d instanceof Date) || isNaN(d.getTime())) return '';
// 获取UTC年月日
const now = new Date();
const today = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
const yesterday = new Date(today);
yesterday.setUTCDate(today.getUTCDate() - 1);
const msgDate = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
// 获取UTC小时和分钟
const hour = String(d.getUTCHours()).padStart(2, '0');
const min = String(d.getUTCMinutes()).padStart(2, '0');
if (msgDate.getTime() === today.getTime()) {
return `${this.$t("chat.today")} ${hour}:${min}`|| `今天 ${hour}:${min}`;
} else if (msgDate.getTime() === yesterday.getTime()) {
return `${this.$t("chat.yesterday")} ${hour}:${min}`|| `昨天 ${hour}:${min}`;
} else {
const y = d.getUTCFullYear();
const m = String(d.getUTCMonth() + 1).padStart(2, '0');
const day = String(d.getUTCDate()).padStart(2, '0');
return `${y}-${m}-${day} ${hour}:${min}`;
}
},
handleClickOutside(event) {
if (this.isChatOpen) {
const chatElement = this.$el.querySelector(".chat-dialog");
@@ -1387,7 +1463,10 @@ export default {
return;
}
try {
this.$message({ message: this.$t("chat.uploading")||"正在上传图片...", type: "info" });
this.$message({
message: this.$t("chat.uploading") || "正在上传图片...",
type: "info",
});
// 创建 FormData
const formData = new FormData();
formData.append("file", file);
@@ -1427,11 +1506,17 @@ export default {
};
reader.readAsDataURL(file);
} else {
throw new Error(response.data.msg || this.$t("chat.pictureFailed")||"发送图片失败,请重试");
throw new Error(
response.data.msg ||
this.$t("chat.pictureFailed") ||
"发送图片失败,请重试"
);
}
} catch (error) {
console.error("图片处理失败:", error);
this.$message.error(this.$t("chat.processingFailed")||"图片处理失败,请重试");
this.$message.error(
this.$t("chat.processingFailed") || "图片处理失败,请重试"
);
} finally {
this.$refs.imageUpload.value = "";
}
@@ -1441,7 +1526,9 @@ export default {
sendImageMessage(imageUrl) {
if (!this.stompClient || !this.stompClient.connected) {
console.log("发送消息时连接已断开,尝试重连...");
this.$message.warning(this.$t("chat.attemptToReconnect")||"连接已断开,正在尝试重连...");
this.$message.warning(
this.$t("chat.attemptToReconnect") || "连接已断开,正在尝试重连..."
);
this.handleDisconnect();
return;
}
@@ -1462,7 +1549,9 @@ export default {
);
} catch (error) {
console.error("发送图片消息失败:", error);
this.$message.error(this.$t("chat.pictureFailed")||"发送图片失败,请重试");
this.$message.error(
this.$t("chat.pictureFailed") || "发送图片失败,请重试"
);
}
},
@@ -1522,9 +1611,14 @@ export default {
console.error("重试连接失败:", error);
this.connectionStatus = "error";
this.isReconnecting = false;
this.connectionError = error.message || this.$t("chat.retryFailed")||"重试连接失败,请刷新页面";
this.connectionError =
error.message ||
this.$t("chat.retryFailed") ||
"重试连接失败,请刷新页面";
this.showRefreshButton = true;
this.$message.error(this.$t("chat.retryFailed")||"重试连接失败,请刷新页面重试");
this.$message.error(
this.$t("chat.retryFailed") || "重试连接失败,请刷新页面重试"
);
}
},
@@ -1581,26 +1675,88 @@ export default {
*/
handleConnectionError(errorObj) {
if (!errorObj) return false;
// 1. 优先判断 error/code 字段
if (errorObj.error === 'MAX_CONNECTIONS' || errorObj.code === 429) {
this.connectionError = this.$t('chat.maxConnectionsError') || '连接数已达上限,请刷新页面重试';
this.isReconnecting = false;
this.showRefreshButton = true;
return true;
// 获取错误码
const errorCode = errorObj.code;
const errorMessage = errorObj.message || "";
// 根据错误码处理不同情况
switch (errorCode) {
case 1020: // IP_LIMIT_CONNECT
this.connectionError =
this.$t("chat.ipLimitError") ||
"本机连接数已达上限,请刷新页面重试";
this.isReconnecting = false;
this.showRefreshButton = true;
this.$message.error(this.connectionError);
return true;
case 1021: // MAX_LIMIT_CONNECT
this.connectionError =
this.$t("chat.serverLimitError") ||
"服务器连接数已达上限,请稍后刷新重试";
this.isReconnecting = false;
this.showRefreshButton = true;
this.$message.error(this.connectionError);
return true;
case 1022: // SET_PRINCIPAL_FAIL
this.connectionError =
this.$t("chat.identityError") || "用户身份设置失败,请刷新页面重试";
this.isReconnecting = false;
this.showRefreshButton = true;
this.$message.error(this.connectionError);
return true;
case 1023: // GET_PRINCIPAL_FAIL
this.connectionError =
this.$t("chat.emailError") || "用户信息获取失败,请刷新页面重试";
this.isReconnecting = false;
this.showRefreshButton = true;
this.$message.error(this.connectionError);
return true;
default:
// 处理其他可能的错误情况
if (
errorMessage.includes("连接数已达上限") ||
errorMessage.toLowerCase().includes("maximum connections")
) {
this.connectionError =
this.$t("chat.maxConnectionsError") ||
"连接数已达上限,请刷新页面重试";
this.isReconnecting = false;
this.showRefreshButton = true;
return true;
}
return false;
}
// 2. 兜底判断 message 关键词(兼容历史/异常情况)
const msg = errorObj.message || '';
if (msg.includes('连接数已达上限') || msg.toLowerCase().includes('maximum connections')) {
this.connectionError = this.$t('chat.maxConnectionsError') || '连接数已达上限,请刷新页面重试';
this.isReconnecting = false;
this.showRefreshButton = true;
return true;
}
return false;
},
// 处理退出登录
handleLogout() {
// 断开 WebSocket 连接
this.disconnectWebSocket();
// 重置状态
this.isChatOpen = false;
this.isMinimized = true;
this.messages = [];
this.unreadMessages = 0;
this.connectionStatus = "disconnected";
this.isWebSocketConnected = false;
this.userType = 0;
this.userEmail = "";
this.roomId = "";
},
},
beforeDestroy() {
// 移除退出登录事件监听
this.$bus.$off("user-logged-out", this.handleLogout);
// 调用退出登录处理方法
this.handleLogout();
// 移除滚动监听
if (this.$refs.chatBody) {
this.$refs.chatBody.removeEventListener("scroll", this.handleChatScroll);
@@ -1837,9 +1993,24 @@ export default {
}
.message-content {
position: relative;
max-width: 70%;
padding: 10px 15px;
padding: 18px 15px 10px 15px; // 上方多留空间给时间
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
.message-time {
position: absolute;
top: 6px;
right: 15px;
font-size: 11px;
color: #bbb;
pointer-events: none;
user-select: none;
}
// 用户消息气泡内时间颜色适配
.chat-message-user & .message-time {
color: rgba(255, 255, 255, 0.7);
}
}
.message-text {
@@ -2185,34 +2356,18 @@ export default {
.message-content {
.chat-time-divider {
text-align: center;
margin: 16px 0;
font-size: 12px;
color: #fff;
background: rgba(180, 180, 180, 0.6);
display: inline-block;
padding: 2px 12px;
border-radius: 10px;
box-shadow: 0 1px 2px rgba(0,0,0,0.04);
left: 50%;
transform: translateX(-50%);
position: relative;
max-width: 70%;
padding: 18px 15px 10px 15px; // 上方多留空间给时间
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
.message-time {
position: absolute;
top: 6px;
right: 15px;
font-size: 11px;
color: #bbb;
pointer-events: none;
user-select: none;
}
// 用户消息气泡内时间颜色适配
.chat-message-user & .message-time {
color: rgba(255,255,255,0.7);
}
}
</style>

View File

@@ -382,10 +382,15 @@ export default {
document.addEventListener("click", function () {
const dropdown = document.querySelector(".dropdown");
const arrow = document.querySelector(".arrow");
if (dropdown.classList.contains("show")) {
try {
if (dropdown.classList.contains("show")) {
dropdown.classList.remove("show");
arrow.classList.remove("up");
}
} catch (error) {
console.log(error)
}
});
@@ -445,6 +450,8 @@ export default {
async fetchSignOut() {
const data = await getLogout();
if (data && data.code == 200) {
// 调用 Vuex 的 logout action 清除前端状态
await this.$store.dispatch('logout')
const lang = this.$i18n.locale;
this.$router.push(`/${lang}`);
}

View File

@@ -85,6 +85,10 @@ export const ChatWidget_zh = {
attemptToReconnect:"连接已断开,正在尝试重连...",
retryFailed:"重试连接失败,请刷新页面",
maxConnectionsError:"连接数已达上限,请刷新页面重试",
ipLimitError:"本机连接数已达上限,请刷新页面重试",
serverLimitError:"服务器连接数已达上限,请稍后刷新重试",
identityError:"用户身份设置失败,请刷新页面重试",
emailError:"用户信息获取失败,请刷新页面重试",
},
@@ -178,7 +182,10 @@ export const ChatWidget_en = {
attemptToReconnect:"Connection lost, attempting to reconnect...",
retryFailed:"Retry connection failed, please refresh page",
maxConnectionsError:"Connection limit reached, please refresh page",
ipLimitError:"Connection limit reached, please refresh page",
serverLimitError:"Connection limit reached, please try again later",
identityError:"Failed to set user identity, please refresh page",
emailError:"Failed to get user information, please refresh page",
}

File diff suppressed because one or more lines are too long

View File

@@ -15,6 +15,9 @@ import networkRecoveryMixin from './mixins/networkRecoveryMixin';
import './utils/loadingRecovery';
import errorNotificationManager from '../src/utils/errorNotificationManager';
// 创建事件总线 用于组件通信
Vue.prototype.$bus = new Vue();
Vue.use(MetaInfo)
Vue.prototype.$addStorageEvent = $addStorageEvent // 添加storage事件
Vue.config.productionTip = false

View File

@@ -32,7 +32,7 @@ const childrenRoutes = [
component: () => import('../views/miningAccount/index.vue'),
meta: {title: '挖矿账户页面',
description:i18n.t(`seo.miningAccount`),
allAuthority:[`admin`,`registered`],
allAuthority:[`admin`,`registered`,`customer_service`],
// keywords: 'M2Pool mining account, crypto mining stats, mining rewards, hashrate monitor, 矿池账户, 挖矿收益, 算力监控'
keywords:{
en: 'M2Pool mining account, crypto mining stats, mining rewards, hashrate monitor, 矿池账户, 挖矿收益, 算力监控',
@@ -63,7 +63,7 @@ const childrenRoutes = [
component: () => import('../views/reportBlock/index.vue'),
meta: {title: '报块页面',
description:i18n.t(`seo.reportBlock`),
allAuthority:[`admin`,`registered`],
allAuthority:[`admin`,`registered`,`customer_service`],
// keywords: 'M2Pool 矿池,报块页面,幸运值,区块高度,Block page,Lucky Value,block height,Mining Pool'
keywords:{
en: 'Block page,Lucky Value,block height,Mining Pool',
@@ -308,7 +308,7 @@ const childrenRoutes = [
component: () => import('../views/submitWorkOrder/index.vue'),
meta: {title: '提交工单页面',
description:i18n.t(`seo.submitWorkOrder`),
allAuthority:[`admin`,`registered`],
allAuthority:[`admin`,`registered`,`customer_service`],
// keywords: 'M2Pool 矿池,提交工单,技术支持,问题处理,Mining Pool,Work Order Submission, Technical Support, Troubleshooting'
keywords:{
en: 'Mining Pool,Work Order Submission, Technical Support, Troubleshooting',
@@ -322,7 +322,7 @@ const childrenRoutes = [
component: () => import('../views/workOrderRecords/index.vue'),
meta: {title: '工单记录页面(用户)',
description:i18n.t(`seo.workOrderRecords`),
allAuthority:[`admin`,`registered`],
allAuthority:[`admin`,`registered`,`customer_service`],
// keywords: 'M2Pool 矿池,用户工单记录,处理状态,问题进度,User Work Order Records, Processing Status, Issue Progress'
keywords:{
en: 'User Work Order Records, Processing Status, Issue Progress',
@@ -336,7 +336,7 @@ const childrenRoutes = [
component: () => import('../views/userWorkDetails/index.vue'),
meta: {title: '工单详情页面(用户)',
description:i18n.t(`seo.userWorkDetails`),
allAuthority:[`admin`,`registered`],
allAuthority:[`admin`,`registered`,`customer_service`],
// keywords: 'M2Pool 矿池,用户工单详情,问题描述,补充提交,User Work Order Details, Problem Description, Additional Submissions'
keywords:{
en: 'User Work Order Details, Problem Description, Additional Submissions',
@@ -408,7 +408,7 @@ const childrenRoutes = [
component: () => import('../views/personalCenter/index.vue'),
meta: {title: '个人中心页面',
description:i18n.t(`seo.personalCenter`),
allAuthority:[`admin`,`registered`],
allAuthority:[`admin`,`registered`,`customer_service`],
// keywords: 'M2Pool 矿池,个人中心,挖矿账户,只读页面设置安全设置API密钥生成,Personal Center,Mining Account,Read-Only Page Setup,Security Settings,API Key Generation'
keywords:{
en: 'Personal Center,Mining Account,Read-Only Page Setup,Security Settings,API Key Generation',
@@ -422,7 +422,7 @@ const childrenRoutes = [
component: () => import('../views/personalCenter/personalMining/index.vue'),
meta: {title: '挖矿账户设置页面',
description:i18n.t(`seo.personalMining`),
allAuthority:[`admin`,`registered`],
allAuthority:[`admin`,`registered`,`customer_service`],
// keywords: 'M2Pool 矿池,个人中心,挖矿账户设置,币种账户,Personal Center,Mining Account Settings,Coin Accounts'
keywords:{
en: 'Personal Center,Mining Account Settings,Coin Accounts',
@@ -436,7 +436,7 @@ const childrenRoutes = [
component: () => import('../views/personalCenter/readOnly/index.vue'),
meta: {title: '只读页面设置',
description:i18n.t(`seo.readOnly`),
allAuthority:[`admin`,`registered`],
allAuthority:[`admin`,`registered`,`customer_service`],
// keywords: 'M2Pool 矿池,个人中心,只读页面设置,矿池分享,Personal Center,Read-Only Page Setting,Mining Pool Sharing'
keywords:{
en: 'Personal Center,Read-Only Page Setting,Mining Pool Sharing',
@@ -450,7 +450,7 @@ const childrenRoutes = [
component: () => import('../views/personalCenter/securitySetting/index.vue'),
meta: {title: '安全设置页面',
description:i18n.t(`seo.securitySetting`),
allAuthority:[`admin`,`registered`],
allAuthority:[`admin`,`registered`,`customer_service`],
// keywords: 'M2Pool 矿池,安全设置,密码修改,Security settings, password change'
keywords:{
en: 'Security settings, password change',
@@ -464,7 +464,7 @@ const childrenRoutes = [
component: () => import('../views/personalCenter/personal/index.vue'),
meta: {title: '个人信息页面',
description:i18n.t(`seo.personal`),
allAuthority:[`admin`,`registered`],
allAuthority:[`admin`,`registered`,`customer_service`],
// keywords: 'M2Pool 矿池,个人信息,登录历史,Personal Information, Login History'
keywords:{
en: 'Personal Information, Login History',
@@ -478,7 +478,7 @@ const childrenRoutes = [
component: () => import('../views/personalCenter/miningReport/index.vue'),
meta: {title: '挖矿报告页面',
description:i18n.t(`seo.miningReport`),
allAuthority:[`admin`,`registered`],
allAuthority:[`admin`,`registered`,`customer_service`],
// keywords: 'M2Pool 矿池,个人中心,挖矿报告,订阅服务,Mining Report, Subscription Service'
keywords:{
en: 'Mining Report, Subscription Service',
@@ -493,7 +493,7 @@ const childrenRoutes = [
component: () => import('../views/personalCenter/personalAPI/index.vue'),
meta: {title: 'API页面',
description:i18n.t(`seo.personalAPI`),
allAuthority:[`admin`,`registered`],
allAuthority:[`admin`,`registered`,`customer_service`],
// keywords: 'M2Pool 矿池,个人中心,API 页面,API密钥生成,API Page,API Key Generation'
keywords:{
en: 'API Page,API Key Generation',
@@ -634,87 +634,88 @@ const router = new VueRouter({
// router.beforeEach((to, from, next) => {
// // 检查语言参数
// const lang = to.params.lang;
// const supportedLanguages = ['zh', 'en'];
router.beforeEach((to, from, next) => {
// 检查语言参数
const lang = to.params.lang;
const supportedLanguages = ['zh', 'en'];
// // 如果路径以斜杠结尾且不是根路径,则重定向
// if (to.path.endsWith('/') && to.path.length > 1) {
// const path = to.path.slice(0, -1);
// return next({
// path,
// query: to.query,
// hash: to.hash,
// params: to.params
// });
// }
// 如果路径以斜杠结尾且不是根路径,则重定向
if (to.path.endsWith('/') && to.path.length > 1) {
const path = to.path.slice(0, -1);
return next({
path,
query: to.query,
hash: to.hash,
params: to.params
});
}
// if (!lang && to.path !== '/') {
// const defaultLang = localStorage.getItem('lang') || 'en';
// return next(`/${defaultLang}${to.path}`);
// }
if (!lang && to.path !== '/') {
const defaultLang = localStorage.getItem('lang') || 'en';
return next(`/${defaultLang}${to.path}`);
}
// let data = localStorage.getItem("jurisdiction");
// let jurisdiction =JSON.parse(data);
let data = localStorage.getItem("jurisdiction");
let jurisdiction =JSON.parse(data);
console.log(jurisdiction,"权限");
// localStorage.setItem('superReportError',"")
// let element = document.getElementsByClassName('el-main')[0];
// if(element){
// element.scrollTop = 0
// }
localStorage.setItem('superReportError',"")
let element = document.getElementsByClassName('el-main')[0];
if(element){
element.scrollTop = 0
}
// let token
// try{
// token =JSON.parse(localStorage.getItem('token'))
// }catch(e){
// console.log(e);
// }
let token
try{
token =JSON.parse(localStorage.getItem('token'))
}catch(e){
console.log(e);
}
// if (token) {
if (token) {
// if (to.path === `/${lang}/login`|| to.path === `/${lang}/register`) {
// next({ path: `/${lang}` })
// }else if(to.meta.allAuthority && to.meta.allAuthority[0] ==`all`){
// next()
// }else if(jurisdiction.roleKey && to.meta.allAuthority&&to.meta.allAuthority.some(item=>item == jurisdiction.roleKey )){
// next()
// }else{
// console.log(to.meta.allAuthority,to.path,"权限");
if (to.path === `/${lang}/login`|| to.path === `/${lang}/register`) {
next({ path: `/${lang}` })
}else if(to.meta.allAuthority && to.meta.allAuthority[0] ==`all`){
next()
}else if(jurisdiction.roleKey && to.meta.allAuthority&&to.meta.allAuthority.some(item=>item == jurisdiction.roleKey )){
next()
}else{
console.log(to.meta.allAuthority,to.path,"权限");
// Message({//权限不足
// showClose: true,
// message:i18n.t(`mining.jurisdiction`),
// type: 'error'
// });
Message({//权限不足
showClose: true,
message:i18n.t(`mining.jurisdiction`),
type: 'error'
});
// next({ path: `/${lang}` }) // 添加这行,重定向到首页
// }
next({ path: `/${lang}` }) // 添加这行,重定向到首页
}
// }else{
}else{
// let paths = [`/${lang}/miningAccount`,`/${lang}/workOrderRecords`,`/${lang}/userWorkDetails`,`/${lang}/submitWorkOrder`,`/${lang}/workOrderBackend`,`/${lang}/BKWorkDetails`]
// if (paths.includes(to.path) || to.path.includes(`personalCenter`) ) {
let paths = [`/${lang}/miningAccount`,`/${lang}/workOrderRecords`,`/${lang}/userWorkDetails`,`/${lang}/submitWorkOrder`,`/${lang}/workOrderBackend`,`/${lang}/BKWorkDetails`]
if (paths.includes(to.path) || to.path.includes(`personalCenter`) ) {
// Message({//权限不足
// showClose: true,
// message:i18n.t(`mining.logInFirst`),
// type: 'error'
// });
Message({//权限不足
showClose: true,
message:i18n.t(`mining.logInFirst`),
type: 'error'
});
// next({ path: `/${lang}/login` })
// } else {
next({ path: `/${lang}/login` })
} else {
// next()
// }
// }
next()
}
}
// })
})

View File

@@ -5,12 +5,48 @@ Vue.use(Vuex)
export default new Vuex.Store({
state: {
isLoggedIn: false,
userInfo: null
},
getters: {
isLoggedIn: state => state.isLoggedIn,
userInfo: state => state.userInfo
},
mutations: {
SET_LOGIN_STATE(state, isLoggedIn) {
state.isLoggedIn = isLoggedIn
},
SET_USER_INFO(state, userInfo) {
state.userInfo = userInfo
},
CLEAR_USER_DATA(state) {
state.isLoggedIn = false
state.userInfo = null
}
},
actions: {
// 退出登录
async logout({ commit }) {
try {
// 清除本地存储
localStorage.removeItem('token')
localStorage.removeItem('userEmail')
localStorage.removeItem('jurisdiction')
// 清除 Vuex 状态
commit('CLEAR_USER_DATA')
// 触发全局事件,通知其他组件用户已退出
if (Vue.prototype.$bus) {
Vue.prototype.$bus.$emit('user-logged-out')
}
return true
} catch (error) {
console.error('退出登录失败:', error)
return false
}
}
},
modules: {
}

View File

@@ -18,7 +18,8 @@
<div class="textBox">
<p>{{ $t(`ServiceTerms.clauseService1`) }}</p>
<p>{{ $t(`ServiceTerms.clauseService2`) }}</p>
<p><span style="font-weight: 600;">{{ $t(`ServiceTerms.clauseService3`) }} </span>{{ $t(`ServiceTerms.clauseService4`) }}</p>
<p style="text-align: justify;"><span style="font-weight: 600 ;text-align: justify;">{{ $t(`ServiceTerms.clauseService3`) }} </span>{{ $t(`ServiceTerms.clauseService4`) }}</p>
</div>
</section>
@@ -117,7 +118,8 @@
<div class="textBox">
<p>{{ $t(`ServiceTerms.clauseService1`) }}</p>
<p>{{ $t(`ServiceTerms.clauseService2`) }}</p>
<p><span style="font-weight: 600;">{{ $t(`ServiceTerms.clauseService3`) }} </span>{{ $t(`ServiceTerms.clauseService4`) }}</p>
<p style="text-align: justify;"><span style="font-weight: 600;text-align: justify;">{{ $t(`ServiceTerms.clauseService3`) }} </span>{{ $t(`ServiceTerms.clauseService4`) }}</p>
</div>
</section>

View File

@@ -320,7 +320,7 @@ export default {
loadingRooms: true,
stompClient: null,
wsConnected: false,
userEmail: "497681109@qq.com", // 当前客服邮箱
userEmail: "", // 当前客服邮箱
userType: 1, // 0或者1 游客或者登录用户
loadingHistory: false, // 是否正在加载历史消息
@@ -331,7 +331,7 @@ export default {
id: "", //最后一条消息id
roomId: "", //聊天室id
userType: 2, //用户类型
email: "497681109@qq.com", //客服邮箱
email: "", //客服邮箱
},
historyAllParams: {
//7天以前的历史消息
@@ -380,8 +380,12 @@ export default {
async created() {
try {
// 初始化用户信息
// this.determineUserType();
let userEmail = localStorage.getItem("userEmail");
this.userEmail = JSON.parse(userEmail);
window.addEventListener("setItem", () => {
let userEmail = localStorage.getItem("userEmail");
this.userEmail = JSON.parse(userEmail);
});
// 获取聊天室列表
await this.fetchRoomList();
@@ -391,12 +395,8 @@ export default {
}
// 在组件创建时加载手动创建的聊天室
this.loadManualCreatedRooms();
let userEmail = localStorage.getItem("userEmail");
this.userEmail = JSON.parse(userEmail);
window.addEventListener("setItem", () => {
let userEmail = localStorage.getItem("userEmail");
this.userEmail = JSON.parse(userEmail);
});
console.log(this.userEmail,"初始化的时候")
// 初始化 WebSocket 连接
this.initWebSocket();
} catch (error) {
@@ -418,6 +418,8 @@ export default {
let userEmail = localStorage.getItem("userEmail");
this.userEmail = JSON.parse(userEmail);
});
console.log(this.userEmail,"mounted")
// 确保初始加载后滚动到底部
this.$nextTick(() => {
this.scrollToBottom();
@@ -480,7 +482,8 @@ export default {
if (this.isWebSocketConnected) return;
try {
const wsUrl = `${process.env.VUE_APP_BASE_API}chat/ws`;
const baseUrl = process.env.VUE_APP_BASE_API.replace('https', 'wss');
const wsUrl = `${baseUrl}chat/ws`;
this.stompClient = Stomp.client(wsUrl);
// 配置 STOMP 客户端参数
@@ -1267,18 +1270,17 @@ export default {
// 加载更多历史消息
async loadMoreHistory() {
if (!this.currentContactId) return;
// 获取当前已加载的消息列表
const currentMsgs = this.messages[this.currentContactId] || [];
// 取最后一条消息的id
const lastMsg =
currentMsgs.length > 0 ? currentMsgs[currentMsgs.length - 1] : null;
this.history7Params.id = lastMsg ? lastMsg.id : "";
// const lastMsg =
// currentMsgs.length > 0 ? currentMsgs[currentMsgs.length - 1] : null;
this.history7Params.id = currentMsgs[0].id ||"",
// this.history7Params.pageNum += 1; // 递增页码
this.history7Params.roomId = this.currentContactId;
this.history7Params.email = this.userEmail;
try {
this.messagesLoading = true;
const response = await getHistory7(this.history7Params);
@@ -1375,6 +1377,7 @@ export default {
if (!roomId) return;
try {
console.log(this.userEmail,"加载聊天消息")
this.history7Params.email = this.userEmail;
this.history7Params.roomId = roomId;
const response = await getHistory7(this.history7Params);
@@ -1903,7 +1906,6 @@ export default {
async loadHistory() {
this.loadingHistory = true;
this.userViewHistory = true; // 用户主动查看历史
console.log("哇哈哈哈哈哈哈", this.currentContactId);
if (!this.currentContactId) return;
try {
@@ -1912,14 +1914,15 @@ export default {
const currentMsgs = this.messages[this.currentContactId] || [];
// 取最后一条消息的id
const lastMsg =
currentMsgs.length > 0 ? currentMsgs[currentMsgs.length - 1] : null;
this.history7Params.id = lastMsg ? lastMsg.id : "";
// const lastMsg =
// currentMsgs.length > 0 ? currentMsgs[currentMsgs.length - 1] : null;
this.history7Params.id =currentMsgs[0].id ||"";
// this.history7Params.pageNum += 1; // 递增页码
this.history7Params.roomId = this.currentContactId;
this.history7Params.email = this.userEmail;
const response = await getHistory7(this.history7Params);
console.log(response,"respons及附加覅封建时代反间谍法附件覅发e");
if (response && response.code === 200 && response.data) {
let historyMessages = response.data
.filter((msg) => msg.roomId === this.currentContactId)