.m2pool 客服系统对接 1.订阅接口区分用户和客服 避免消息混乱 已处理 2.历史消息渲染isSelf返回值有问题 已处理 3.图片发送方式修改为用base64Image发送后soket自动断开 已处理 4.优化心跳机制 断线重连 最多自动重连5次 已处理 5.游客功能添加、删除列表离线游客 处理中

6.客服给游客发送消息 游客收不到推送消息 已处理
This commit is contained in:
2025-05-16 14:01:38 +08:00
parent 2e56d71b0c
commit a326f62f81
6 changed files with 1203 additions and 769 deletions

View File

@@ -5,8 +5,8 @@ VUE_APP_TITLE = m2pool
ENV = 'development' ENV = 'development'
#开发环境 #开发环境
VUE_APP_BASE_API = 'https://test.m2pool.com/api/' # VUE_APP_BASE_API = 'https://test.m2pool.com/api/'
# VUE_APP_BASE_API = 'http://10.168.2.150:8101/' VUE_APP_BASE_API = 'http://10.168.2.150:8101/'
VUE_APP_BASE_URL = 'https://test.m2pool.com/' VUE_APP_BASE_URL = 'https://test.m2pool.com/'
# 路由懒加载 # 路由懒加载
VUE_CLI_BABEL_TRANSPILE_MODULES = true VUE_CLI_BABEL_TRANSPILE_MODULES = true

View File

@@ -58,11 +58,11 @@ export function getUpdateRoom(data) {
//图根据当前用户邮箱查询聊天室id //图根据当前用户邮箱查询聊天室id
export function getUserid() { export function getUserid(data) {
return request({ return request({
url: `chat/rooms/find/room/by/userid`, url: `chat/rooms/find/room/by/userid`,
method: 'get', method: 'post',
data
}) })
} }

View File

@@ -220,10 +220,16 @@ export default {
cachedMessages: {}, // 缓存各聊天室的消息 cachedMessages: {}, // 缓存各聊天室的消息
isMinimized: false, // 区分最小化和关闭状态 isMinimized: false, // 区分最小化和关闭状态
reconnectAttempts: 0,
maxReconnectAttempts: 5,
reconnectInterval: 5000, // 5秒
isReconnecting: false,
}; };
}, },
async created() { async created() {
this.determineUserType();
// 页面加载时立即获取用户信息 // 页面加载时立即获取用户信息
await this.initChatSystem(); await this.initChatSystem();
}, },
@@ -247,7 +253,7 @@ export default {
async initChatSystem() { async initChatSystem() {
try { try {
// 获取用户ID和未读消息数 // 获取用户ID和未读消息数
const userData = await this.fetchUserid(); const userData = await this.fetchUserid({ email: this.userEmail });
if (userData) { if (userData) {
this.roomId = userData.id; this.roomId = userData.id;
this.receivingEmail = userData.userEmail; this.receivingEmail = userData.userEmail;
@@ -273,7 +279,7 @@ export default {
determineUserType() { determineUserType() {
try { try {
const token = localStorage.getItem("token"); const token = localStorage.getItem("token");
console.log("token", token);
if (!token) { if (!token) {
// 游客身份 // 游客身份
this.userType = 0; this.userType = 0;
@@ -293,11 +299,13 @@ export default {
if (userInfo.roleKey === "customer_service") { if (userInfo.roleKey === "customer_service") {
// 客服用户 // 客服用户
this.userType = 2; this.userType = 2;
this.userEmail = "";
} else { } else {
// 登录用户 // 登录用户
this.userType = 1; this.userType = 1;
this.userEmail = email;
} }
this.userEmail = email;
} catch (parseError) { } catch (parseError) {
console.error("解析用户信息失败:", parseError); console.error("解析用户信息失败:", parseError);
// 解析失败时默认为游客 // 解析失败时默认为游客
@@ -317,68 +325,124 @@ export default {
}, },
// 添加订阅消息的方法 // 添加订阅消息的方法
subscribeToPersonalMessages() { subscribeToPersonalMessages() {
if (!this.stompClient || !this.isWebSocketConnected) return; if (!this.stompClient || !this.isWebSocketConnected) return;
try { try {
// 订阅个人消息频道 // 订阅个人消息频道
this.stompClient.subscribe( this.stompClient.subscribe(
`/user/queue/${this.userEmail}`, `/sub/queue/user/${this.userEmail}`,
this.onMessageReceived, this.onMessageReceived,
{ // {
id: `chat_${this.userEmail}`, // id: `chat_${this.userEmail}`,
} // }
); );
console.log("成功订阅消息频道:", `/user/queue/${this.userEmail}`);
} catch (error) {
console.error("订阅消息失败:", error);
this.$message.error("消息订阅失败,可能无法接收新消息");
}
},
console.log("成功订阅消息频道:", `/sub/queue/user/${this.userEmail}`);
} catch (error) {
console.error("订阅消息失败:", error);
this.$message.error("消息订阅失败,可能无法接收新消息");
}
},
// 连接 WebSocket // 连接 WebSocket
connectWebSocket() { connectWebSocket() {
if (this.isWebSocketConnected) return; if (this.isWebSocketConnected || this.isReconnecting) return;
this.connectionStatus = "connecting"; this.connectionStatus = "connecting";
try { this.isReconnecting = true;
const wsUrl = `${process.env.VUE_APP_BASE_API}chat/ws`;
this.stompClient = Stomp.client(wsUrl);
const headers = { try {
email: this.userEmail, const wsUrl = `${process.env.VUE_APP_BASE_API}chat/ws`;
type: this.userType, this.stompClient = Stomp.client(wsUrl);
}; // 配置 STOMP 客户端参数
this.stompClient.splitLargeFrames = true; // 启用大型消息帧分割
// 添加详细的调试日志
// this.stompClient.debug = (str) => {
// console.log("STOMP Debug:", str);
// // 记录连接状态变化
// if (str.includes("CONNECTED")) {
// console.log("WebSocket 连接成功");
// this.isWebSocketConnected = true;
// this.connectionStatus = "connected";
// this.reconnectAttempts = 0;
// this.isReconnecting = false;
// } else if (
// str.includes("DISCONNECTED") ||
// str.includes("Connection closed")
// ) {
// console.log("WebSocket 连接断开");
// this.handleDisconnect();
// }
// };
this.stompClient.connect( const headers = {
headers, email: this.userEmail,
(frame) => { type: this.userType,
console.log("WebSocket Connected:", frame); };
this.isWebSocketConnected = true;
this.connectionStatus = "connected";
// 连接成功后立即订阅消息
this.subscribeToPersonalMessages();
},
(error) => {
console.error("WebSocket Error:", error);
this.isWebSocketConnected = false;
this.connectionStatus = "error";
// 添加重连逻辑
setTimeout(() => this.connectWebSocket(), 5000);
}
);
// 配置心跳 // 添加连接状态监听
this.stompClient.heartbeat.outgoing = 20000; this.stompClient.onStompError = (frame) => {
this.stompClient.heartbeat.incoming = 20000; console.error("STOMP 错误:", frame);
} catch (error) { this.handleDisconnect();
console.error("初始化 WebSocket 失败:", error); };
this.stompClient.connect(
headers,
(frame) => {
console.log("WebSocket Connected:", frame);
this.isWebSocketConnected = true;
this.connectionStatus = "connected";
this.reconnectAttempts = 0;
this.isReconnecting = false;
// 连接成功后立即订阅消息
this.subscribeToPersonalMessages();
// 显示连接成功提示
this.$message.success("连接成功");
},
(error) => {
console.error("WebSocket Error:", error);
this.handleDisconnect();
}
);
// 配置心跳
this.stompClient.heartbeat.outgoing = 20000;
this.stompClient.heartbeat.incoming = 20000;
} catch (error) {
console.error("初始化 WebSocket 失败:", error);
this.handleDisconnect();
}
},
// 添加新重连最多重连5次
handleDisconnect() {
this.isWebSocketConnected = false;
this.connectionStatus = "error"; this.connectionStatus = "error";
} this.isReconnecting = false;
},
// 如果重连次数未超过最大尝试次数,则尝试重连
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
console.log(
`尝试重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts})...`
);
this.$message.warning(
`连接断开,${this.reconnectInterval / 1000}秒后重试...`
);
setTimeout(() => {
if (!this.isWebSocketConnected) {
this.connectWebSocket();
}
}, this.reconnectInterval);
} else {
console.log("达到最大重连次数,停止重连");
this.$message.error("连接失败,请刷新页面重试");
}
},
// 新增:页面卸载时的处理 // 新增:页面卸载时的处理
handleBeforeUnload() { handleBeforeUnload() {
@@ -387,73 +451,93 @@ export default {
// 发送消息 // 发送消息
sendMessage() { sendMessage() {
if (!this.inputMessage.trim() || this.connectionStatus !== "connected") if (!this.inputMessage.trim()) return;
return;
// 检查 WebSocket 连接状态
if (!this.stompClient || !this.stompClient.connected) {
console.log('发送消息时连接已断开,尝试重连...');
this.$message.warning('连接已断开,正在重新连接...');
this.handleDisconnect();
return;
}
const messageText = this.inputMessage.trim(); const messageText = this.inputMessage.trim();
// 添加用户消息到界面 try {
this.messages.push({ // 添加用户消息到界面
type: "user", this.messages.push({
text: messageText, type: "user",
time: new Date(), text: messageText,
email: this.receivingEmail, time: new Date(),
receiveUserType: 2, //接收消息用户类型 email: this.receivingEmail,
roomId: this.roomId, receiveUserType: 2, //接收消息用户类型
isRead: false, // 新发送的消息默认未读 roomId: this.roomId,
}); isRead: false, // 新发送的消息默认未读
const message = { });
content: messageText, const message = {
type: 1, // 1 表示文字消息 content: messageText,
email: this.receivingEmail, type: 1, // 1 表示文字消息
receiveUserType: 2, email: this.receivingEmail,
roomId: this.roomId, receiveUserType: 2,
}; roomId: this.roomId,
// 发送消息 };
this.stompClient.send("/point/send/message", {}, JSON.stringify(message)); // 发送消息
// 通过 WebSocket 发送消息 this.stompClient.send(
// if (this.stompClient && this.stompClient.connected) { "/point/send/message/to/customer",
// this.stompClient.send({ {},
// destination: "/point/send/message", JSON.stringify(message)
// body: JSON.stringify({ );
// content: messageText,
// type: 1,
// email: this.receivingEmail,
// receiveUserType:2,//当前用户类型
// roomId: this.roomId,
// }),
// });
// } else {
// this.handleAutoResponse(messageText);
// }
this.inputMessage = ""; // 通过 WebSocket 发送消息
// if (this.stompClient && this.stompClient.connected) {
// this.stompClient.send({
// destination: "/point/send/message",
// body: JSON.stringify({
// content: messageText,
// type: 1,
// email: this.receivingEmail,
// receiveUserType:2,//当前用户类型
// roomId: this.roomId,
// }),
// });
// } else {
// this.handleAutoResponse(messageText);
// }
this.$nextTick(() => { this.inputMessage = "";
this.scrollToBottom();
}); this.$nextTick(() => {
this.scrollToBottom();
});
} catch (error) {
console.error("发送消息失败:", error);
this.$message.error("发送消息失败,请重试");
this.handleDisconnect();
}
}, },
// 断开 WebSocket 连接 // 断开 WebSocket 连接
disconnectWebSocket() { disconnectWebSocket() {
if (this.stompClient) { if (this.stompClient) {
try { try {
// 取消所有订阅 // 取消所有订阅
if (this.stompClient.subscriptions) { if (this.stompClient.subscriptions) {
Object.keys(this.stompClient.subscriptions).forEach(id => { Object.keys(this.stompClient.subscriptions).forEach((id) => {
this.stompClient.unsubscribe(id); this.stompClient.unsubscribe(id);
}); });
}
// 断开连接
this.stompClient.deactivate();
this.isWebSocketConnected = false;
this.connectionStatus = "disconnected";
this.reconnectAttempts = 0;
this.isReconnecting = false;
} catch (error) {
console.error("断开 WebSocket 连接失败:", error);
} }
// 断开连接
this.stompClient.deactivate();
this.isWebSocketConnected = false;
this.connectionStatus = "disconnected";
} catch (error) {
console.error("断开 WebSocket 连接失败:", error);
} }
} },
},
// 处理页面可见性变化 // 处理页面可见性变化
handleVisibilityChange() { handleVisibilityChange() {
// 当页面变为可见且聊天窗口已打开时,标记消息为已读 // 当页面变为可见且聊天窗口已打开时,标记消息为已读
@@ -496,53 +580,61 @@ export default {
this.isLoadingHistory = true; this.isLoadingHistory = true;
try { try {
const response = await getHistory7({ roomId: this.roomId, userType: this.userType }); const response = await getHistory7({
roomId: this.roomId,
userType: this.userType,
email: this.userEmail,
});
console.log("历史消息数据:", response); console.log("历史消息数据:", response);
if (response?.code === 200 && Array.isArray(response.data)) { if (response?.code === 200 && Array.isArray(response.data)) {
// 处理历史消息 // 处理历史消息
const historyMessages = response.data.map(msg => ({ const historyMessages = response.data.map((msg) => ({
type: msg.isSelf === 1 ? "user" : "system", // 根据 isSelf 判断消息类型 type: msg.isSelf === 1 ? "user" : "system",
text: msg.content, text: msg.content,
isImage: msg.type === 2, isImage: msg.type === 2,
imageUrl: msg.type === 2 ? msg.content : null, imageUrl: msg.type === 2 ? msg.content : null,
time: new Date(msg.createTime), // 使用 createTime 字段 time: new Date(msg.createTime),
id: msg.id, id: msg.id,
roomId: msg.roomId, roomId: msg.roomId,
sender: msg.sendEmail, sender: msg.sendEmail,
isHistory: true, isHistory: true,
isRead: true // 历史消息默认已读 isRead: true,
})); }));
// 按时间顺序排序 // 按时间顺序排序
this.messages = historyMessages.sort((a, b) => this.messages = historyMessages.sort(
new Date(a.time) - new Date(b.time) (a, b) => new Date(a.time) - new Date(b.time)
); );
// 滚动到底部 // 等待 DOM 更新和图片加载完成后再滚动
this.$nextTick(() => { await this.$nextTick();
this.scrollToBottom(); // 添加一个小延时确保所有内容都渲染完成
}); setTimeout(() => {
this.scrollToBottom(true); // 传入 true 表示强制滚动
}, 100);
} else { } else {
// 没有历史消息时显示提示 this.messages = [
this.messages = [{ {
type: "system", type: "system",
text: "暂无历史消息", text: "暂无历史消息",
isSystemHint: true, isSystemHint: true,
time: new Date() time: new Date(),
}]; },
];
} }
} catch (error) { } catch (error) {
console.error("加载历史消息失败:", error); console.error("加载历史消息失败:", error);
this.$message.error("加载历史消息失败"); this.$message.error("加载历史消息失败");
// 显示错误提示 this.messages = [
this.messages = [{ {
type: "system", type: "system",
text: "加载历史消息失败,请重试", text: "加载历史消息失败,请重试",
isSystemHint: true, isSystemHint: true,
time: new Date(), time: new Date(),
isError: true isError: true,
}]; },
];
} finally { } finally {
this.isLoadingHistory = false; this.isLoadingHistory = false;
} }
@@ -550,105 +642,118 @@ export default {
// 加载更多历史消息超过7天的 // 加载更多历史消息超过7天的
async loadMoreHistory() { async loadMoreHistory() {
if (this.isLoadingHistory || !this.roomId) return; if (this.isLoadingHistory || !this.roomId) return;
this.isLoadingHistory = true; this.isLoadingHistory = true;
try { try {
// 显示加载中提示 // 获取当前消息列表中最旧消息的 ID
const loadingMsg = { const oldestMessage = this.messages.find(msg => !msg.isSystemHint && !msg.isLoading);
type: "system", if (!oldestMessage || !oldestMessage.id) {
text: "正在加载更多历史消息...", console.warn('没有找到有效的消息ID');
isLoading: true, this.hasMoreHistory = false;
time: new Date(), return;
}; }
this.messages.unshift(loadingMsg);
// 获取更早的聊天记录 // 显示加载中提示
const response = await getHistory({ roomId: this.roomId }); const loadingMsg = {
type: "system",
text: "正在加载更多历史消息...",
isLoading: true,
time: new Date(),
};
this.messages.unshift(loadingMsg);
// 移除加载中提示 // 获取更早的聊天记录,添加 id 参数
this.messages = this.messages.filter((msg) => !msg.isLoading); const response = await getHistory7({
roomId: this.roomId,
userType: this.userType,
email: this.userEmail,
id: oldestMessage.id // 添加最旧消息的 ID
});
if ( // 移除加载中提示
response && this.messages = this.messages.filter((msg) => !msg.isLoading);
response.code === 200 &&
response.data &&
response.data.length > 0
) {
// 处理并添加历史消息
const historyMessages = this.formatHistoryMessages(response.data);
// 将历史消息添加到消息列表的前面 if (
this.messages = [...historyMessages, ...this.messages]; response &&
response.code === 200 &&
response.data &&
response.data.length > 0
) {
// 处理并添加历史消息
const historyMessages = this.formatHistoryMessages(response.data);
// 如果没有数据返回,表示没有更多历史记录 // 将历史消息添加到消息列表的前面
this.hasMoreHistory = historyMessages.length > 0; this.messages = [...historyMessages, ...this.messages];
if (historyMessages.length === 0) { // 如果没有数据返回,表示没有更多历史记录
this.messages.unshift({ this.hasMoreHistory = historyMessages.length > 0;
type: "system",
text: "没有更多历史消息了", if (historyMessages.length === 0) {
isSystemHint: true,
time: new Date(),
});
}
} else {
this.hasMoreHistory = false;
this.messages.unshift({
type: "system",
text: "没有更多历史消息了",
isSystemHint: true,
time: new Date(),
});
}
} catch (error) {
console.error("加载更多历史消息失败:", error);
this.messages.unshift({ this.messages.unshift({
type: "system", type: "system",
text: "加载更多历史消息失败", text: "没有更多历史消息",
isError: true, isSystemHint: true,
time: new Date(), time: new Date(),
}); });
} finally {
this.isLoadingHistory = false;
} }
}, } else {
this.hasMoreHistory = false;
this.messages.unshift({
type: "system",
text: "没有更多历史消息了",
isSystemHint: true,
time: new Date(),
});
}
} catch (error) {
console.error("加载更多历史消息失败:", error);
this.messages.unshift({
type: "system",
text: "加载更多历史消息失败",
isError: true,
time: new Date(),
});
} finally {
this.isLoadingHistory = false;
}
},
// 格式化历史消息数据 // 格式化历史消息数据
formatHistoryMessages(messagesData) { formatHistoryMessages(messagesData) {
if (!messagesData || !Array.isArray(messagesData)) return []; if (!messagesData || !Array.isArray(messagesData)) return [];
return messagesData return messagesData
.map((msg) => ({ .map((msg) => ({
type: msg.isSelf === 1 ? "user" : "system", // 根据 isSelf 判断消息类型 type: msg.isSelf === 1 ? "user" : "system", // 根据 isSelf 判断消息类型
text: msg.content || "", text: msg.content || "",
isImage: msg.type === 2, isImage: msg.type === 2,
imageUrl: msg.type === 2 ? msg.content : null, imageUrl: msg.type === 2 ? msg.content : null,
time: new Date(msg.createTime), time: new Date(msg.createTime),
id: msg.id, id: msg.id,
roomId: msg.roomId, roomId: msg.roomId,
sender: msg.sendEmail, sender: msg.sendEmail,
isHistory: true, isHistory: true,
isRead: true isRead: true,
})) }))
.sort((a, b) => new Date(a.time) - new Date(b.time)); .sort((a, b) => new Date(a.time) - new Date(b.time));
}, },
// 修改 fetchUserid 方法,添加 token 检查 // 修改 fetchUserid 方法,添加 token 检查
async fetchUserid() { async fetchUserid(params) {
try { try {
// 先检查是否有 token // 先检查是否有 token
const token = localStorage.getItem("token"); // const token = localStorage.getItem("token");
if (!token) { // if (!token) {
console.log("用户未登录,不发起 getUserid 请求"); // console.log("用户未登录,不发起 getUserid 请求");
// 对于未登录用户,可以生成一个临时 ID // // 对于未登录用户,可以生成一个临时 ID
this.roomId = `guest_${Date.now()}`; // this.roomId = `guest_${Date.now()}`;
this.receivingEmail = "customer_service@example.com"; // 或默认客服邮箱 // this.receivingEmail = "customer_service@example.com"; // 或默认客服邮箱
return null; // return null;
} // }
const res = await getUserid(); const res = await getUserid(params);
if (res && res.code == 200) { if (res && res.code == 200) {
console.log("获取用户ID成功:", res); console.log("获取用户ID成功:", res);
this.receivingEmail = res.data.userEmail; this.receivingEmail = res.data.userEmail;
@@ -685,50 +790,49 @@ export default {
// 接收消息处理 // 接收消息处理
onMessageReceived(message) { onMessageReceived(message) {
try { try {
const data = JSON.parse(message.body); const data = JSON.parse(message.body);
console.log("收到新消息:", data); console.log("收到新消息:", data);
// 构造消息对象
const messageObj = {
type: data.sendUserType === this.userType ? "user" : "system", // 用户类型判断
text: data.content,
isImage: data.type === 2,
imageUrl: data.type === 2 ? data.content : null,
time: new Date(data.sendTime),
id: data.id,
roomId: data.roomId,
sender: data.sendEmail,
isRead: false
};
// 直接添加到消息列表 // 构造消息对象
this.messages.push(messageObj); const messageObj = {
type: data.sendUserType === this.userType ? "user" : "system", // 用户类型判断
text: data.content,
isImage: data.type === 2,
imageUrl: data.type === 2 ? data.content : null,
time: new Date(data.sendTime),
id: data.id,
roomId: data.roomId,
sender: data.sendEmail,
isRead: false,
};
// 如果聊天窗口未打开,增加未读消息数 // 直接添加到消息列表
if (!this.isChatOpen) { this.messages.push(messageObj);
// 使用服务器返回的未读数如果没有则增加1
if (data.clientReadNum !== undefined) { // 如果聊天窗口未打开,增加未读消息数
this.unreadMessages = data.clientReadNum; if (!this.isChatOpen) {
} else { // 使用服务器返回的未读数如果没有则增加1
this.unreadMessages++; if (data.clientReadNum !== undefined) {
this.unreadMessages = data.clientReadNum;
} else {
this.unreadMessages++;
}
// 显示消息通知
this.showNotification(messageObj);
} else {
// 如果聊天窗口已打开,立即标记为已读
this.markMessagesAsRead();
}
// 滚动到底部
this.$nextTick(() => {
this.scrollToBottom();
});
} catch (error) {
console.error("处理消息失败:", error);
} }
// 显示消息通知 },
this.showNotification(messageObj);
} else {
// 如果聊天窗口已打开,立即标记为已读
this.markMessagesAsRead();
}
// 滚动到底部
this.$nextTick(() => {
this.scrollToBottom();
});
} catch (error) {
console.error("处理消息失败:", error);
}
},
// 显示消息通知 // 显示消息通知
showNotification(message) { showNotification(message) {
@@ -778,35 +882,46 @@ export default {
} }
}, },
// 切换聊天窗口 // 打开聊天框
async toggleChat() { async toggleChat() {
this.isChatOpen = !this.isChatOpen; this.isChatOpen = !this.isChatOpen;
// 1. 判别身份
this.determineUserType();
// 2. 如果是客服,跳转到客服页面
if (this.userType === 2) {
const lang = this.$i18n.locale;
this.$router.push(`/${lang}/customerService`);
return;
}
if (this.isChatOpen) { if (this.isChatOpen) {
// 打开聊天窗口时
try { try {
// 确定用户类型 // 确定用户类型
this.determineUserType(); this.determineUserType();
// 如果未连接,则初始化 WebSocket // 如果未连接或连接断开,则重新初始化 WebSocket
if (this.connectionStatus === "disconnected") { if (!this.isWebSocketConnected || this.connectionStatus === "disconnected") {
await this.initWebSocket(); await this.connectWebSocket();
} }
// 如果消息列表为空,加载历史消息 // 如果消息列表为空,加载历史消息
if (this.messages.length === 0) { if (this.messages.length === 0) {
await this.loadHistoryMessages(); await this.loadHistoryMessages();
} else {
// 如果已有消息,确保滚动到底部
await this.$nextTick();
setTimeout(() => {
this.scrollToBottom(true);
}, 100);
} }
// 标记消息为已读 // 标记消息为已读
await this.markMessagesAsRead(); await this.markMessagesAsRead();
// 滚动到底部
this.$nextTick(() => {
this.scrollToBottom();
});
} catch (error) { } catch (error) {
console.error("初始化聊天失败:", error); console.error("初始化聊天失败:", error);
this.$message.error("初始化聊天失败,请重试");
} }
} }
}, },
@@ -879,22 +994,22 @@ export default {
this.loadMoreHistory(); this.loadMoreHistory();
} }
}, },
//滚动到底部
scrollToBottom(force = false) {
if (!this.$refs.chatBody) return;
scrollToBottom() { const scrollOptions = {
if (this.$refs.chatBody) { top: this.$refs.chatBody.scrollHeight,
const scrollOptions = { behavior: force ? 'auto' : 'smooth' // 强制滚动时使用 'auto'
top: this.$refs.chatBody.scrollHeight, };
behavior: "smooth",
};
try { try {
this.$refs.chatBody.scrollTo(scrollOptions); this.$refs.chatBody.scrollTo(scrollOptions);
} catch (error) { } catch (error) {
// 如果平滑滚动不支持,则直接设置 // 如果平滑滚动不支持,则直接设置
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())) {
@@ -950,7 +1065,10 @@ export default {
// 处理图片上传 // 处理图片上传
async handleImageUpload(event) { async handleImageUpload(event) {
if (this.connectionStatus !== "connected") return; if (this.connectionStatus !== "connected") {
console.log("当前连接状态:", this.connectionStatus);
return;
}
const file = event.target.files[0]; const file = event.target.files[0];
if (!file) return; if (!file) return;
@@ -981,62 +1099,71 @@ export default {
type: "info", type: "info",
}); });
// 准备FormData // 将文件转换为 base64
const formData = new FormData(); const reader = new FileReader();
formData.append("file", file); reader.onload = (e) => {
const base64Image = e.target.result;
// 上传文件到后端 // 检查连接状态
const response = await getFileUpdate(formData); if (!this.stompClient || !this.stompClient.connected) {
console.log("文件上传返回:", response); console.error("发送图片时连接已断开");
this.$message.error("连接已断开,正在重新连接...");
// 检查上传结果 this.connectWebSocket();
if (response && response.code === 200 && response.data) { return;
// 从后端响应中获取图片信息 }
const imageData = response.data;
// 使用后端返回的URL
const imageUrl = this.formatImageUrl(imageData.url);
console.log("图片URL:", imageUrl); // 调试用打印URL
// 添加用户图片消息到界面(本地显示) // 添加用户图片消息到界面(本地显示)
this.messages.push({ this.messages.push({
type: "user", type: "user",
text: "", // 保留空字符串 text: "",
isImage: true, isImage: true,
imageUrl: imageUrl, // 确保URL正确 imageUrl: base64Image,
time: new Date(), time: new Date(),
email: this.receivingEmail, email: this.receivingEmail,
sendUserType: this.userType, sendUserType: this.userType,
roomId: this.roomId, roomId: this.roomId,
isRead: false, isRead: false,
}); });
// 通过WebSocket发送图片消息
if (this.stompClient && this.stompClient.connected) { try {
// 通过 WebSocket 发送图片消息
const message = { const message = {
content: imageUrl, // URL作为消息内容 content: base64Image,
type: 2, // 使用数字类型2表示图片消息 type: 2,
email: this.receivingEmail, email: this.receivingEmail,
receiveUserType: 2, receiveUserType: 2,
roomId: this.roomId, roomId: this.roomId,
}; };
// 使用WebSocket发送消息 console.log(
"准备发送图片消息,当前连接状态:",
this.stompClient.connected
);
this.stompClient.send( this.stompClient.send(
"/point/send/message", "/point/send/message",
{}, {},
JSON.stringify(message) JSON.stringify(message)
); );
} console.log("图片消息发送完成");
this.$nextTick(() => { this.$nextTick(() => {
this.scrollToBottom(); this.scrollToBottom();
}); });
} else { } catch (sendError) {
this.$message.error("图片上传失败: " + (response?.msg || "未知错误")); console.error("发送图片消息失败:", sendError);
} this.$message.error("发送图片失败,请重试");
}
};
reader.onerror = (error) => {
console.error("读取文件失败:", error);
this.$message.error("读取图片失败,请重试");
};
reader.readAsDataURL(file);
} catch (error) { } catch (error) {
console.error("图片上传异常:", error); console.error("图片处理失败:", error);
this.$message.error("图片上传失败,请重试"); this.$message.error("图片处理失败,请重试");
} finally { } finally {
// 清空input允许重复选择同一文件 // 清空input允许重复选择同一文件
this.$refs.imageUpload.value = ""; this.$refs.imageUpload.value = "";
@@ -1070,7 +1197,7 @@ export default {
}, },
beforeDestroy() { beforeDestroy() {
this.disconnectWebSocket();
// 移除滚动监听 // 移除滚动监听
if (this.$refs.chatBody) { if (this.$refs.chatBody) {
@@ -1081,7 +1208,11 @@ export default {
"visibilitychange", "visibilitychange",
this.handleVisibilityChange this.handleVisibilityChange
); );
// 确保在销毁时断开连接
if (this.stompClient) {
this.stompClient.disconnect();
this.stompClient = null;
}
// 断开 WebSocket 连接 // 断开 WebSocket 连接
this.disconnectWebSocket(); this.disconnectWebSocket();
}, },

File diff suppressed because it is too large Load Diff

View File

@@ -76,22 +76,22 @@ export default {
nameTextStyle: { nameTextStyle: {
padding: [0, 0, 0, -40], padding: [0, 0, 0, -40],
} },
// min: `dataMin`, // min: `dataMin`,
// max: `dataMax`, // max: `dataMax`,
// axisLabel: { axisLabel: {
// formatter: function (value) { formatter: function (value) {
// let data // let data
// if (value > 10000000) { // if (value > 10000000) {
// data = `${(value / 10000000)} KW` // data = `${(value / 10000000)} KW`
// } else if (value > 1000000) { // } else if (value > 1000000) {
// data = `${(value / 1000000)} M` // data = `${(value / 1000000)} M`
// } else if (value / 10000) { // } else if (value / 10000) {
// data = `${(value / 10000)} W` // data = `${(value / 10000)} W`
// } // }
// return data return value
// } }
// } }
}, },
{ {
@@ -506,7 +506,8 @@ export default {
show: true, show: true,
amount: 1, amount: 1,
} },
// { //告知已删除此币种 Radiant // { //告知已删除此币种 Radiant
// value: "dgb2_odo", // value: "dgb2_odo",
// label: "dgb-odocrypt-pool2", // label: "dgb-odocrypt-pool2",
@@ -825,8 +826,37 @@ export default {
} }
}else{ //动态计算图表的grid.left 让左侧的Y轴标签显示完全
const yAxis = this.option.yAxis[0]; // 第一个 Y 轴
const maxValue = Math.max(...this.option.series[0].data); // 获取数据最大值
const formatter = yAxis.axisLabel.formatter;
const formattedValue = formatter(maxValue); // 格式化最大值
// 创建一个临时 DOM 元素计算宽度
const tempDiv = document.createElement('div');
tempDiv.style.position = 'absolute';
tempDiv.style.visibility = 'hidden';
tempDiv.style.fontSize = '12px'; // 与 axisLabel.fontSize 一致
tempDiv.innerText = formattedValue;
document.body.appendChild(tempDiv);
const labelWidth = tempDiv.offsetWidth;
document.body.removeChild(tempDiv);
// 动态设置 grid.left加上安全边距
const safeMargin = 20;
this.option.grid.left = labelWidth + safeMargin + 'px';
// this.$nextTick(
// // 更新图表
// this.inCharts()
// )
} }
this.getBlockInfoData(this.BlockInfoParams) this.getBlockInfoData(this.BlockInfoParams)
this.getCoinInfoData(this.params) this.getCoinInfoData(this.params)
@@ -1395,52 +1425,111 @@ export default {
handelCalculation() { handelCalculation() {
this.calculateIncome() this.calculateIncome()
}, },
// 获取单个币种项目的完整宽度包含margin
// 左滑动逻辑 getItemFullWidth() {
scrollLeft() { const listEl = this.$refs.currencyList;
const allLength = this.currencyList.length * 120 if (!listEl) return 120;
const boxLength = document.getElementById('list-box').clientWidth
if (allLength < boxLength) return const firstItem = listEl.querySelector('.list-item');
const listEl = document.getElementById('list') if (!firstItem) return 120;
const leftMove = Math.abs(parseInt(window.getComputedStyle(listEl, null)?.left))
if (leftMove + boxLength - 360 < boxLength) { const style = window.getComputedStyle(firstItem);
// 到最开始的时候 const width = firstItem.offsetWidth;
listEl.style.left = '0PX' const marginLeft = parseInt(style.marginLeft) || 0;
} else { const marginRight = parseInt(style.marginRight) || 0;
listEl.style.left = '-' + (leftMove - 360) + 'PX'
} return width + marginLeft + marginRight;
}, },
// 右滑动逻辑
scrollRight() { // 左滑动逻辑
const allLength = this.currencyList.length * 120 scrollLeft() {
const boxLength = document.getElementById('list-box').clientWidth const listEl = this.$refs.currencyList;
if (allLength < boxLength) return const listBox = this.$refs.listBox;
const listEl = document.getElementById('list') if (!listEl || !listBox) return;
const leftMove = Math.abs(parseInt(window.getComputedStyle(listEl, null)?.left))
if (leftMove + boxLength + 360 > allLength) { const itemFullWidth = this.getItemFullWidth();
listEl.style.left = '-' + (allLength - boxLength) + 'PX' const step = 2 * itemFullWidth; // 每次滑动2个币种
} else { const allLength = this.currencyList.length * itemFullWidth;
listEl.style.left = '-' + (leftMove + 360) + 'PX' const boxLength = listBox.clientWidth;
}
}, if (allLength <= boxLength) return;
// clickCurrency: throttle(function(item) { let currentLeft = Math.abs(parseInt(listEl.style.transform.replace('translateX(', '').replace('px)', '')) || 0);
// this.currency = item.label let newLeft = currentLeft - step;
// this.currencyPath = item.imgUrl
// this.params.coin = item.value listEl.classList.add('scrolling');
// this.BlockInfoParams.coin = item.value
// this.itemActive = item.value if (newLeft <= 0) {
// this.PowerParams.coin = item.value listEl.style.transform = 'translateX(0)';
// this.getCoinInfoData(this.params) } else {
// this.getBlockInfoData(this.BlockInfoParams) listEl.style.transform = `translateX(-${newLeft}px)`;
// // this.getPoolPowerData(this.PowerParams) }
// // this.getMinerCountData(this.params)
// if (this.powerActive) { // 增加动画时间到 500ms
// this.handelPower() setTimeout(() => {
// } else if (!this.powerActive) { listEl.classList.remove('scrolling');
// this.handelMiner() }, 500);
},
// 右滑动逻辑
scrollRight() {
const listEl = this.$refs.currencyList;
const listBox = this.$refs.listBox;
if (!listEl || !listBox) return;
const itemFullWidth = this.getItemFullWidth();
const step = 2 * itemFullWidth; // 每次滑动2个币种
const allLength = this.currencyList.length * itemFullWidth;
const boxLength = listBox.clientWidth;
if (allLength <= boxLength) return;
let currentLeft = Math.abs(parseInt(listEl.style.transform.replace('translateX(', '').replace('px)', '')) || 0);
let newLeft = currentLeft + step;
const maxLeft = allLength - boxLength;
listEl.classList.add('scrolling');
if (newLeft >= maxLeft) {
listEl.style.transform = `translateX(-${maxLeft}px)`;
} else {
listEl.style.transform = `translateX(-${newLeft}px)`;
}
// 增加动画时间到 500ms
setTimeout(() => {
listEl.classList.remove('scrolling');
}, 500);
},
// // 左滑动逻辑
// scrollLeft() {
// const allLength = this.currencyList.length * 120
// const boxLength = document.getElementById('list-box').clientWidth
// if (allLength < boxLength) return
// const listEl = document.getElementById('list')
// const leftMove = Math.abs(parseInt(window.getComputedStyle(listEl, null)?.left))
// if (leftMove + boxLength - 360 < boxLength) {
// // 到最开始的时候
// listEl.style.left = '0PX'
// } else {
// listEl.style.left = '-' + (leftMove - 360) + 'PX'
// } // }
// }, 1000), // },
// // 右滑动逻辑
// scrollRight() {
// const allLength = this.currencyList.length * 120
// const boxLength = document.getElementById('list-box').clientWidth
// if (allLength < boxLength) return
// const listEl = document.getElementById('list')
// const leftMove = Math.abs(parseInt(window.getComputedStyle(listEl, null)?.left))
// if (leftMove + boxLength + 360 > allLength) {
// listEl.style.left = '-' + (allLength - boxLength) + 'PX'
// } else {
// listEl.style.left = '-' + (leftMove + 360) + 'PX'
// }
// },
handleActiveItemChange(item) { handleActiveItemChange(item) {
if (!item) return; if (!item) return;

View File

@@ -340,6 +340,29 @@
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24"> <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<el-card> <el-card>
<div class="monitor-list"> <div class="monitor-list">
<div class="btn left" @click="scrollLeft">
<i class="iconfont icon-icon-prev" />
</div>
<div ref="listBox" class="list-box">
<div ref="currencyList" class="list">
<div
v-for="item in currencyList"
:key="item.value"
@click="clickCurrency(item)"
class="list-item"
>
<img :src="item.img" alt="coin" />
<span :class="{ active: itemActive === item.value }">
{{ item.label }}
</span>
</div>
</div>
</div>
<div class="btn right" @click="scrollRight">
<i class="iconfont icon-zuoyoujiantou1" />
</div>
</div>
<!-- <div class="monitor-list">
<div class="btn left" @click="scrollLeft"> <div class="btn left" @click="scrollLeft">
<i class="iconfont icon-icon-prev" /> <i class="iconfont icon-icon-prev" />
</div> </div>
@@ -361,7 +384,7 @@
<div class="btn right" @click="scrollRight"> <div class="btn right" @click="scrollRight">
<i class="iconfont icon-zuoyoujiantou1" /> <i class="iconfont icon-zuoyoujiantou1" />
</div> </div>
</div> </div> -->
</el-card> </el-card>
</el-col> </el-col>
</el-row> </el-row>
@@ -405,7 +428,6 @@
>{{ $t(item.label) }}</span >{{ $t(item.label) }}</span
> >
</div> </div>
<div <div
id="chart" id="chart"
@@ -3119,6 +3141,63 @@ export default {
transition: left 1s; transition: left 1s;
} }
} }
.list-box {//加的
width: calc(100% - 100px);
overflow: hidden;
position: relative;
}
.list {
display: flex;
will-change: transform;
padding-left: 2%;
&.scrolling {
// 增加动画时长到 0.5s,使用更平滑的缓动函数
transition: transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.list-item {
flex-shrink: 0;
width: 120px;
height: 95%;
margin-left: 18px;
// 增加过渡时间,使悬停效果更平滑
transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
&:hover {
transform: translateY(-3px);
box-shadow: 0 6px 16px rgba(110, 62, 219, 0.2);
}
img {
transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}
span {
transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}
}
}
.btn {
// 增加按钮过渡时间
transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
&:hover {
background-color: #6e3edb;
color: #fff;
transform: scale(1.05);
}
&:active {
transform: scale(0.95);
}
}
} }
// ----------------------- // -----------------------