更新服务条款页面内容

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

@@ -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>