与牧同行-兽医端小程序
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

1503 lines
41 KiB

import http from '../../../utils/api'
const baseUrl = require('../../../utils/baseUrl')
// WebSocket实例
let socketOpen = false
let socketMsgQueue = []
let heartbeatInterval = null
let reconnectTimer = null
let isManualClose = false // 是否手动关闭
let heartBeatTimer = null // 心跳定时器
Page({
data: {
baseUrl: baseUrl,
// 专家信息
expertInfo: {},
// 用户信息
userInfo: {
user: {} // 完整用户信息
},
// 对话信息
conversation: {},
// 消息列表
messageList: [],
scrollTop: 0,
scrollAnimate: false,
// 输入相关
inputValue: '',
inputFocus: false,
// 多媒体
showMediaSheet: false,
// 页面状态
showDateDivider: true,
todayDate: '',
loading: false,
loadingMore: false,
// 滚动相关
isScrolling: false,
lastScrollTop: 0,
// 当前对话ID和对方用户ID
currentExpertId: '',
conversationId: '',
otherUserId: '',
// 分页相关
page: 1,
pageSize: 20,
hasMore: true,
total: 0,
// 记录最早消息的时间戳,用于加载更早的消息
earliestMsgTime: 0,
// 时间显示间隔
timeInterval: 5,
lastShowTimeStamp: 0,
// 键盘高度
keyboardHeight: 0,
// WebSocket状态
socketConnected: false,
reconnectCount: 0,
maxReconnectCount: 5,
// 第一次加载完成标记
firstLoadComplete: false,
// 是否有新消息
hasNewMessage: false,
// 消息ID集合,用于去重
messageIds: new Set(),
// 正在发送中的消息ID集合
sendingMessages: new Set(),
// 显示发送中提示
showSendingTip: false,
// 发送中的消息数量
sendingCount: 0,
// 上传状态
isUploading: false
},
onLoad: function(options) {
console.log('接收到的参数:', options);
// 设置今天日期
this.setTodayDate();
// 加载用户信息
this.loadUserInfo();
// 获取对方用户ID
if (options.id) {
this.setData({
otherUserId: options.id,
currentExpertId: options.id,
// 初始页码设置为1
page: 1,
messageList: [],
hasMore: true,
earliestMsgTime: 0,
firstLoadComplete: false,
messageIds: new Set(),
sendingMessages: new Set(),
showSendingTip: false,
sendingCount: 0
});
// 先创建对话,然后获取聊天记录
this.createConversation(options.id);
}
// 监听键盘高度变化
wx.onKeyboardHeightChange(this.onKeyboardHeightChange.bind(this));
},
onShow: function() {
// 页面显示时连接WebSocket
isManualClose = false;
// 确保用户ID和对话ID都存在再连接
const userId = this.getCurrentUserId();
if (userId && this.data.conversationId) {
console.log('页面显示,准备连接WebSocket,用户ID:', userId);
this.connectWebSocket();
} else {
console.log('用户ID或对话ID不存在,稍后再连接', {
userId: userId,
conversationId: this.data.conversationId
});
}
},
onHide: function() {
// 页面隐藏时暂停心跳检查但不关闭连接
console.log('页面隐藏,暂停心跳检查');
if (heartBeatTimer) {
clearInterval(heartBeatTimer);
heartBeatTimer = null;
}
},
onUnload: function() {
// 页面卸载时关闭WebSocket并清理资源
isManualClose = true;
this.closeWebSocket();
wx.offKeyboardHeightChange();
// 清理定时器
if (heartbeatInterval) {
clearInterval(heartbeatInterval);
heartbeatInterval = null;
}
if (reconnectTimer) {
clearTimeout(reconnectTimer);
reconnectTimer = null;
}
if (heartBeatTimer) {
clearInterval(heartBeatTimer);
heartBeatTimer = null;
}
},
// 获取当前用户ID的辅助方法
getCurrentUserId: function() {
const userId = this.data.userInfo?.user?.userId;
console.log('获取到的用户ID:', userId);
return userId;
},
// 创建一对一聊天
createConversation(id) {
wx.showLoading({ title: '加载中...' });
http.create({
data: {
otherUserId: id
},
success: res => {
console.log('创建对话响应:', res);
if (res && res.data) {
// 保存对话ID
const conversationData = res.data;
this.setData({
conversationId: conversationData.id || conversationData.conversationId || id,
conversation: conversationData,
otherUserId: id,
// 重置分页参数
page: 1,
hasMore: true,
messageList: [],
earliestMsgTime: 0,
firstLoadComplete: false,
messageIds: new Set(),
sendingMessages: new Set()
});
// 获取聊天记录(从第一页开始)
this.getChatHistory(id, false);
// 连接WebSocket
const userId = this.getCurrentUserId();
if (userId) {
console.log('用户ID已存在,立即连接WebSocket');
this.connectWebSocket();
} else {
console.log('用户ID尚未加载,等待用户信息加载完成');
}
} else {
wx.hideLoading();
wx.showToast({
title: '创建对话失败',
icon: 'none'
});
}
},
fail: err => {
console.error('创建对话失败:', err);
wx.hideLoading();
wx.showToast({
title: '网络错误',
icon: 'none'
});
}
});
},
// 获取聊天记录
getChatHistory(id, isLoadMore = false) {
// 如果是加载更多且没有更多数据,直接返回
if (isLoadMore && !this.data.hasMore) {
console.log('没有更多消息了');
this.setData({ loadingMore: false });
return;
}
// 如果不是加载更多,显示加载提示
if (!isLoadMore) {
wx.showLoading({ title: '加载中...' });
}
const params = {
otherUserId: id,
page: this.data.page,
pageSize: this.data.pageSize
};
console.log('请求聊天记录参数:', params);
http.direct({
data: params,
success: res => {
console.log('获取聊天记录响应:', res);
if (!isLoadMore) {
wx.hideLoading();
}
if (res && res.code === 200) {
// 处理返回的消息数据
this.processChatHistory(res, isLoadMore);
} else {
// 如果没有聊天记录,显示空状态
this.setData({
messageList: [],
loading: false,
loadingMore: false,
hasMore: false,
firstLoadComplete: true
});
if (!isLoadMore) {
wx.showToast({
title: res?.msg || '获取聊天记录失败',
icon: 'none'
});
}
}
},
fail: err => {
console.error('获取聊天记录失败:', err);
if (!isLoadMore) {
wx.hideLoading();
}
wx.showToast({
title: '网络错误',
icon: 'none'
});
this.setData({
messageList: [],
loading: false,
loadingMore: false,
firstLoadComplete: true
});
}
});
},
// 处理聊天历史数据
processChatHistory(response, isLoadMore = false) {
let messages = [];
let total = 0;
// 根据实际返回的数据结构调整这里
if (response.rows && Array.isArray(response.rows)) {
messages = response.rows;
total = response.total || messages.length;
} else if (response.data && Array.isArray(response.data)) {
messages = response.data;
total = response.total || messages.length;
} else if (Array.isArray(response)) {
messages = response;
total = messages.length;
}
console.log('处理消息数据:', {
messageCount: messages.length,
total,
isLoadMore,
currentPage: this.data.page
});
// 格式化消息数据
const formattedMessages = this.formatMessages(messages);
// 判断是否还有更多数据
const hasMore = this.data.page * this.data.pageSize < total;
// 记录最早消息的时间戳(用于加载更多)
let earliestTime = this.data.earliestMsgTime;
if (formattedMessages.length > 0) {
// 因为接口返回的是正序,第一条就是最早的
earliestTime = formattedMessages[0].timestamp;
}
let newMessageList;
let targetScrollTop = 0;
// 构建消息ID集合
const messageIds = new Set(this.data.messageIds);
formattedMessages.forEach(msg => {
if (msg.id) {
messageIds.add(msg.id);
}
});
if (isLoadMore) {
// 加载更多:新加载的消息(更早的)应该放在现有消息列表的前面
newMessageList = [...formattedMessages, ...this.data.messageList];
// 记录新消息的总高度,用于滚动定位
targetScrollTop = formattedMessages.length * 120; // 估算每条消息高度
} else {
// 首次加载
newMessageList = formattedMessages;
}
// 重新处理消息时间显示
const processedMessages = this.processMessageTimes(newMessageList);
this.setData({
messageList: processedMessages,
messageIds: messageIds,
loading: false,
loadingMore: false,
hasMore: hasMore,
total: total,
earliestMsgTime: earliestTime,
firstLoadComplete: true
}, () => {
if (isLoadMore) {
// 加载更多后,调整滚动位置以保持在当前查看的位置
setTimeout(() => {
this.setData({
scrollAnimate: false,
scrollTop: targetScrollTop
}, () => {
setTimeout(() => {
this.setData({ scrollAnimate: true });
}, 100);
});
}, 50);
} else {
// 首次加载,滚动到底部(显示最新消息)
setTimeout(() => {
this.scrollToBottom(false);
}, 100);
}
});
},
// 格式化消息数据
formatMessages(messages) {
if (!messages || messages.length === 0) return [];
const userId = this.getCurrentUserId();
console.log('格式化消息使用的用户ID:', userId);
return messages.map(msg => {
// 统一使用contentType字段
let contentType = msg.contentType || msg.type || 'text';
let content = msg.content || '';
// 判断发送者
const isMe = msg.senderId === userId ||
msg.fromUserId === userId ||
msg.userId === userId ||
msg.sendId === userId;
return {
id: msg.id || 'msg_' + Date.now() + Math.random(),
isMe: isMe,
sender: isMe ? 'user' : 'expert',
senderId: msg.senderId || msg.fromUserId || msg.userId,
contentType: contentType,
content: content,
timestamp: msg.createTime || msg.timestamp || Date.now(),
status: msg.status || 'success',
fileName: msg.fileName || '',
fileSize: msg.fileSize || 0,
thumb: msg.thumb || '',
progress: msg.progress || 100
};
});
},
// 加载更多消息
loadMoreMessages: function() {
// 防止重复加载
if (this.data.loadingMore) {
console.log('正在加载中,请稍后');
return;
}
// 检查是否有更多数据
if (!this.data.hasMore) {
console.log('没有更多消息了');
return;
}
// 检查对方用户ID是否存在
if (!this.data.otherUserId) {
console.error('对方用户ID不存在');
return;
}
console.log('开始加载更多消息,当前页码:', this.data.page);
// 设置加载状态
this.setData({
loadingMore: true
}, () => {
// 页码+1,加载更早的消息
const nextPage = this.data.page + 1;
this.setData({
page: nextPage
}, () => {
// 调用获取聊天记录方法,传入true表示是加载更多操作
this.getChatHistory(this.data.otherUserId, true);
});
});
},
// 加载用户信息
loadUserInfo: function() {
try {
const userInfo = wx.getStorageSync('userInfo');
console.log('从缓存加载的用户信息:', userInfo);
if (userInfo) {
// 处理用户信息,确保正确的结构
let processedUserInfo = { ...this.data.userInfo };
if (userInfo.user) {
// 如果已经有user结构
processedUserInfo = userInfo;
} else if (userInfo.userId) {
// 如果是直接返回的用户数据
processedUserInfo = {
user: userInfo
};
}
// 确保有user对象
if (!processedUserInfo.user) {
processedUserInfo.user = {};
}
// 确保userId存在
if (userInfo.userId && !processedUserInfo.user.userId) {
processedUserInfo.user.userId = userInfo.userId;
}
// 设置用户ID
const userId = processedUserInfo.user?.userId || userInfo.userId;
if (userId) {
processedUserInfo.id = userId;
}
console.log('处理后的用户信息:', processedUserInfo);
this.setData({ userInfo: processedUserInfo });
// 用户信息加载完成后,如果对话ID已存在,连接WebSocket
if (this.data.conversationId && !this.data.socketConnected) {
console.log('用户信息加载完成,连接WebSocket');
this.connectWebSocket();
}
}
} catch (e) {
console.error('加载用户信息失败:', e);
}
},
// ========== WebSocket相关方法 ==========
// 连接WebSocket
connectWebSocket() {
// 如果已有连接,先检查是否可用
if (socketOpen) {
console.log('WebSocket已连接,无需重复连接');
return;
}
const userId = this.getCurrentUserId();
console.log('准备连接WebSocket,用户ID:', userId);
if (!userId) {
console.error('用户ID不存在,无法连接WebSocket');
wx.showToast({
title: '用户信息加载中,请稍后',
icon: 'none'
});
return;
}
// WebSocket连接URL
const basurl = '192.168.101.109:8080'; // 从配置文件获取
const wsUrl = `ws://${basurl}/ws/mini/chat?userId=${userId}`;
console.log('开始连接WebSocket:', wsUrl);
wx.connectSocket({
url: wsUrl,
success: () => {
console.log('WebSocket连接初始化成功');
},
fail: (err) => {
console.error('WebSocket连接初始化失败:', err);
this.reconnectWebSocket();
}
});
// 监听连接打开
wx.onSocketOpen(() => {
console.log('WebSocket连接已打开');
socketOpen = true;
this.setData({ socketConnected: true, reconnectCount: 0 });
// 发送队列中的消息
while (socketMsgQueue.length > 0) {
const msg = socketMsgQueue.shift();
this.sendWebSocketMessage(msg);
}
// 开始心跳
this.startHeartbeat();
});
// 监听收到消息
wx.onSocketMessage((res) => {
try {
console.log('收到原始WebSocket消息:', res.data);
const data = JSON.parse(res.data);
console.log('解析后的消息:', data);
// 处理不同类型的消息
this.handleWebSocketMessage(data);
} catch (e) {
console.error('解析消息失败:', e, '原始数据:', res.data);
}
});
// 监听连接关闭
wx.onSocketClose(() => {
console.log('WebSocket连接已关闭');
socketOpen = false;
this.setData({ socketConnected: false });
// 停止心跳
if (heartBeatTimer) {
clearInterval(heartBeatTimer);
heartBeatTimer = null;
}
// 如果不是手动关闭,尝试重连
if (!isManualClose) {
this.reconnectWebSocket();
}
});
// 监听错误
wx.onSocketError((err) => {
console.error('WebSocket错误:', err);
socketOpen = false;
this.setData({ socketConnected: false });
});
},
// 关闭WebSocket
closeWebSocket() {
if (socketOpen) {
wx.closeSocket({
success: () => {
console.log('WebSocket连接已主动关闭');
}
});
socketOpen = false;
this.setData({ socketConnected: false });
}
if (heartBeatTimer) {
clearInterval(heartBeatTimer);
heartBeatTimer = null;
}
},
// 重连WebSocket
reconnectWebSocket() {
if (this.data.reconnectCount >= this.data.maxReconnectCount) {
console.log('达到最大重连次数,停止重连');
return;
}
if (reconnectTimer) {
clearTimeout(reconnectTimer);
}
reconnectTimer = setTimeout(() => {
console.log(`尝试第${this.data.reconnectCount + 1}次重连`);
this.setData({
reconnectCount: this.data.reconnectCount + 1
});
this.connectWebSocket();
}, 3000);
},
// 开始心跳
startHeartbeat() {
if (heartBeatTimer) {
clearInterval(heartBeatTimer);
}
heartBeatTimer = setInterval(() => {
if (socketOpen) {
console.log('发送心跳');
this.sendWebSocketMessage(JSON.stringify({
type: 'heartbeat',
userId: this.getCurrentUserId(),
timestamp: Date.now()
}));
}
}, 30000);
},
// 发送WebSocket消息
sendWebSocketMessage(data) {
if (socketOpen) {
wx.sendSocketMessage({
data: typeof data === 'string' ? data : JSON.stringify(data),
success: () => {
console.log('WebSocket消息发送成功:', data);
},
fail: (err) => {
console.error('WebSocket消息发送失败:', err);
socketMsgQueue.push(data);
}
});
} else {
console.log('WebSocket未连接,消息加入队列');
socketMsgQueue.push(data);
if (!isManualClose) {
this.connectWebSocket();
}
}
},
// 处理WebSocket消息
handleWebSocketMessage(data) {
console.log('处理消息类型:', data.type);
switch (data.type) {
case 'chat':
case 'message':
this.handleNewMessage(data);
break;
case 'message_status':
this.handleMessageStatus(data);
break;
case 'typing':
this.handleTypingStatus(data);
break;
case 'online_status':
this.handleOnlineStatus(data);
break;
case 'heartbeat_ack':
console.log('收到心跳响应');
break;
default:
console.log('未知消息类型:', data);
}
},
// 处理新消息
handleNewMessage(data) {
console.log('处理新消息 - 完整数据:', data);
// 提取消息内容
let messageData = data.data || data;
// 获取消息ID
const messageId = messageData.id || messageData.messageId || 'msg_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
console.log('消息ID:', messageId);
console.log('当前发送中消息集合:', this.data.sendingMessages);
// 检查是否是自己发送的消息(通过sendingMessages集合判断)
if (this.data.sendingMessages.has(messageId)) {
console.log('收到自己发送的消息回执,跳过处理:', messageId);
// 从发送中集合移除
const sendingMessages = new Set(this.data.sendingMessages);
sendingMessages.delete(messageId);
this.setData({
sendingMessages: sendingMessages,
sendingCount: sendingMessages.size,
showSendingTip: sendingMessages.size > 0
});
// 更新本地消息状态为成功
this.updateMessageStatus(messageId, 'success');
return;
}
// 检查消息是否已存在(去重)
if (this.data.messageIds.has(messageId)) {
console.log('消息已存在,跳过处理:', messageId);
return;
}
const userId = this.getCurrentUserId();
const senderId = messageData.senderId || messageData.fromUserId || messageData.userId;
// 判断是否是自己发送的消息
const isMe = senderId === userId;
console.log('消息归属判断:', {
senderId: senderId,
userId: userId,
isMe: isMe,
messageId: messageId
});
// 如果是自己发送的消息但不在sendingMessages中,说明是通过其他方式发送的
// 也跳过处理,避免重复
if (isMe) {
console.log('是自己发送的消息,但不在发送中集合,可能已存在,跳过处理');
return;
}
// 创建新消息对象 - 统一使用contentType
const newMessage = {
id: messageId,
isMe: isMe,
sender: isMe ? 'user' : 'expert',
senderId: senderId,
contentType: messageData.contentType || messageData.type || 'text',
content: messageData.content || '',
timestamp: messageData.timestamp || Date.now(),
status: 'success', // 对方消息直接显示成功
progress: 100,
fileName: messageData.fileName || '',
fileSize: messageData.fileSize || 0,
thumb: messageData.thumb || ''
};
console.log('创建的新消息对象:', newMessage);
// 获取当前消息列表
const currentList = [...this.data.messageList];
console.log('当前消息列表长度:', currentList.length);
// 添加新消息到列表末尾
currentList.push(newMessage);
// 更新消息ID集合
const messageIds = new Set(this.data.messageIds);
messageIds.add(messageId);
// 重新处理时间显示
const processedMessages = this.processMessageTimes(currentList);
console.log('处理后的消息列表长度:', processedMessages.length);
console.log('最后一条消息:', processedMessages[processedMessages.length - 1]);
// 更新数据
this.setData({
messageList: processedMessages,
messageIds: messageIds,
hasNewMessage: !isMe // 如果不是自己发的,标记有新消息
}, () => {
console.log('消息列表已更新,当前列表:', this.data.messageList);
// 判断是否需要滚动到底部
const query = wx.createSelectorQuery();
query.select('#chatScroll').boundingClientRect();
query.select('.chat-bottom-space').boundingClientRect();
query.exec((res) => {
if (res[0] && res[1]) {
const scrollHeight = res[0].height;
const bottomOffset = res[1].top - res[0].top;
const scrollTop = this.data.lastScrollTop;
// 如果在底部附近(距离底部小于200px)或是自己发的消息,则滚动到底部
const shouldScroll = isMe || (scrollTop + scrollHeight >= bottomOffset - 200);
if (shouldScroll) {
this.scrollToBottom(true);
}
} else {
// 如果获取不到位置信息,默认滚动到底部
if (isMe) {
this.scrollToBottom(true);
}
}
});
// 如果不是自己发的消息,发送已读回执
if (!isMe) {
this.sendReadReceipt(messageId);
}
});
},
// 处理消息状态
handleMessageStatus(data) {
const { messageList } = this.data;
const messageId = data.messageId || data.id;
const index = messageList.findIndex(msg => msg.id === messageId);
if (index !== -1) {
messageList[index].status = data.status || 'success';
this.setData({ messageList });
}
// 如果收到成功状态,从发送中集合移除
if (data.status === 'success' || data.status === 'read') {
const sendingMessages = new Set(this.data.sendingMessages);
sendingMessages.delete(messageId);
this.setData({
sendingMessages: sendingMessages,
sendingCount: sendingMessages.size,
showSendingTip: sendingMessages.size > 0
});
}
},
// 处理正在输入状态
handleTypingStatus(data) {
const userId = data.userId || data.senderId;
if (userId === this.data.otherUserId) {
this.setData({ 'expertInfo.typing': true });
clearTimeout(this.typingTimer);
this.typingTimer = setTimeout(() => {
this.setData({ 'expertInfo.typing': false });
}, 3000);
}
},
// 处理在线状态
handleOnlineStatus(data) {
const userId = data.userId || data.senderId;
if (userId === this.data.otherUserId) {
this.setData({
'expertInfo.online': data.online || data.status === 'online'
});
}
},
// 发送消息到服务器
sendMessageToServer: function(content, contentType, messageId) {
const receiverId = this.data.otherUserId;
const senderId = this.getCurrentUserId();
console.log('发送消息参数:', {
senderId: senderId,
receiverId: receiverId,
content: content,
contentType: contentType,
messageId: messageId
});
if (!receiverId || !senderId) {
console.error('发送者或接收者ID不存在', { senderId, receiverId });
wx.showToast({
title: '发送失败,用户信息错误',
icon: 'none'
});
// 发送失败,从发送中集合移除
const sendingMessages = new Set(this.data.sendingMessages);
sendingMessages.delete(messageId);
this.setData({
sendingMessages: sendingMessages,
sendingCount: sendingMessages.size,
showSendingTip: sendingMessages.size > 0
});
// 更新消息状态为失败
this.updateMessageStatus(messageId, 'failed');
return;
}
const message = {
type: 'chat',
receiverId: receiverId,
senderId: senderId,
content: content,
contentType: contentType,
timestamp: Date.now(),
messageId: messageId
};
const messageStr = JSON.stringify(message);
console.log('发送消息到服务器:', JSON.parse(messageStr));
if (!socketOpen) {
console.log('WebSocket未连接,尝试连接');
wx.showToast({
title: '连接中,请稍后',
icon: 'none'
});
this.connectWebSocket();
socketMsgQueue.push(messageStr);
return;
}
this.sendWebSocketMessage(messageStr);
},
// 发送已读回执
sendReadReceipt(messageId) {
if (!socketOpen) return;
this.sendWebSocketMessage(JSON.stringify({
type: 'message_status',
messageId: messageId,
status: 'read',
userId: this.getCurrentUserId(),
receiverId: this.data.otherUserId,
conversationId: this.data.conversationId,
timestamp: Date.now()
}));
},
// 发送正在输入状态
sendTypingStatus() {
if (!socketOpen || !this.data.inputValue) return;
this.sendWebSocketMessage(JSON.stringify({
type: 'typing',
userId: this.getCurrentUserId(),
receiverId: this.data.otherUserId,
conversationId: this.data.conversationId,
timestamp: Date.now()
}));
},
// ========== 消息发送相关方法 ==========
// 发送文本消息
sendTextMessage: function() {
const content = this.data.inputValue.trim();
if (!content) return;
const messageId = 'msg_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
console.log('发送文本消息,ID:', messageId);
// 将消息ID添加到发送中集合
const sendingMessages = new Set(this.data.sendingMessages);
sendingMessages.add(messageId);
// 创建本地消息 - 使用contentType
const newMessage = {
id: messageId,
isMe: true,
sender: 'user',
senderId: this.getCurrentUserId(),
contentType: 'text',
content: content,
timestamp: Date.now(),
status: 'sending', // 设置为发送中状态
progress: 100
};
// 添加到列表
this.addMessageToList(newMessage);
// 更新发送中集合和发送计数
this.setData({
sendingMessages: sendingMessages,
sendingCount: sendingMessages.size,
showSendingTip: sendingMessages.size > 0
});
// 清空输入框
this.setData({ inputValue: '' });
// 通过WebSocket发送到服务器
this.sendMessageToServer(content, 'text', messageId);
// 设置超时,如果5秒后还没有收到回执,认为发送失败
setTimeout(() => {
if (this.data.sendingMessages.has(messageId)) {
console.log('消息发送超时:', messageId);
this.updateMessageStatus(messageId, 'timeout');
// 从发送中集合移除
const sendingMessages = new Set(this.data.sendingMessages);
sendingMessages.delete(messageId);
this.setData({
sendingMessages: sendingMessages,
sendingCount: sendingMessages.size,
showSendingTip: sendingMessages.size > 0
});
wx.showToast({
title: '发送超时',
icon: 'none'
});
}
}, 5000);
},
// 上传图片后发送
uploadImages: function(tempFilePaths) {
// 改为依次上传,避免并发问题
this.uploadNextImage(tempFilePaths, 0);
},
// 递归上传下一张图片
uploadNextImage: function(tempFilePaths, index) {
if (index >= tempFilePaths.length) return;
const tempFilePath = tempFilePaths[index];
const fileName = `image_${Date.now()}_${index}.jpg`;
this.uploadImage(tempFilePath, fileName, () => {
// 上传完成后,继续上传下一张
this.uploadNextImage(tempFilePaths, index + 1);
});
},
// 上传单张图片 - 修复版
uploadImage: function(tempPath, fileName, callback) {
if (this.data.isUploading) {
wx.showToast({
title: '已有上传任务,请稍后',
icon: 'none'
});
return;
}
this.setData({ isUploading: true });
// 显示上传中提示
wx.showLoading({
title: '上传中...',
mask: true
});
const messageId = 'img_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
// 将消息ID添加到发送中集合
const sendingMessages = new Set(this.data.sendingMessages);
sendingMessages.add(messageId);
// 先显示本地图片(临时路径)
const tempMessage = {
id: messageId,
isMe: true,
sender: 'user',
senderId: this.getCurrentUserId(),
contentType: 'image',
content: tempPath, // 先用临时路径显示
timestamp: Date.now(),
status: 'uploading',
progress: 0,
fileName: fileName
};
this.addMessageToList(tempMessage);
this.setData({
sendingMessages: sendingMessages,
sendingCount: sendingMessages.size,
showSendingTip: sendingMessages.size > 0
});
// 开始上传
wx.uploadFile({
url: baseUrl + '/common/upload',
header: {
'Authorization': 'Bearer ' + wx.getStorageSync('token')
},
filePath: tempPath,
name: 'file',
success: (uploadRes) => {
try {
console.log('上传响应:', uploadRes);
const result = JSON.parse(uploadRes.data);
if (result.code === 200 || result.fileName) {
// 获取服务器返回的文件路径
const serverPath = result.fileName || result.url;
console.log('服务器返回路径:', serverPath);
// 更新消息内容为服务器路径
this.updateMessageContent(messageId, serverPath);
// 通过WebSocket发送消息
this.sendMessageToServer(serverPath, 'image', messageId);
wx.hideLoading();
wx.showToast({
title: '上传成功',
icon: 'success'
});
} else {
throw new Error(result.msg || '上传失败');
}
} catch (error) {
console.error('上传失败:', error);
wx.hideLoading();
// 更新消息状态为失败
this.updateMessageStatus(messageId, 'failed');
// 从发送中集合移除
const sendingMessages = new Set(this.data.sendingMessages);
sendingMessages.delete(messageId);
this.setData({
sendingMessages: sendingMessages,
sendingCount: sendingMessages.size,
showSendingTip: sendingMessages.size > 0
});
wx.showToast({
title: error.message || '上传失败',
icon: 'none'
});
}
},
fail: (error) => {
console.error('网络请求失败:', error);
wx.hideLoading();
// 更新消息状态为失败
this.updateMessageStatus(messageId, 'failed');
// 从发送中集合移除
const sendingMessages = new Set(this.data.sendingMessages);
sendingMessages.delete(messageId);
this.setData({
sendingMessages: sendingMessages,
sendingCount: sendingMessages.size,
showSendingTip: sendingMessages.size > 0
});
wx.showToast({
title: '网络请求失败',
icon: 'none'
});
},
complete: () => {
this.setData({ isUploading: false });
if (callback) callback();
}
});
// 模拟上传进度(因为wx.uploadFile不支持进度回调)
let progress = 0;
const progressInterval = setInterval(() => {
progress += Math.random() * 20 + 5;
if (progress >= 95) {
progress = 95;
clearInterval(progressInterval);
}
this.updateMessageProgress(messageId, Math.min(Math.floor(progress), 95));
}, 200);
},
// 更新消息内容
updateMessageContent: function(messageId, serverPath) {
const { messageList } = this.data;
const index = messageList.findIndex(msg => msg.id === messageId);
if (index !== -1) {
messageList[index].content = serverPath;
messageList[index].status = 'success';
messageList[index].progress = 100;
this.setData({ messageList });
}
},
// 更新消息进度
updateMessageProgress: function(messageId, progress) {
const { messageList } = this.data;
const index = messageList.findIndex(msg => msg.id === messageId);
if (index !== -1) {
messageList[index].progress = progress;
this.setData({ messageList });
}
},
// 添加消息到列表
addMessageToList: function(message) {
const { messageList, messageIds } = this.data;
// 检查是否已存在
if (messageIds.has(message.id)) {
console.log('消息已存在,不重复添加:', message.id);
return;
}
// 新消息添加到末尾
messageList.push(message);
// 更新消息ID集合
messageIds.add(message.id);
// 重新处理时间显示
const processedMessages = this.processMessageTimes(messageList);
this.setData({
messageList: processedMessages,
messageIds: messageIds
}, () => {
this.scrollToBottom();
});
},
// 更新消息状态
updateMessageStatus: function(messageId, status) {
const { messageList } = this.data;
const index = messageList.findIndex(msg => msg.id === messageId);
if (index !== -1) {
messageList[index].status = status;
this.setData({ messageList });
// 如果是失败状态,从发送中集合移除
if (status === 'failed' || status === 'timeout') {
const sendingMessages = new Set(this.data.sendingMessages);
sendingMessages.delete(messageId);
this.setData({
sendingMessages: sendingMessages,
sendingCount: sendingMessages.size,
showSendingTip: sendingMessages.size > 0
});
}
}
},
// 输入处理
onInput: function(e) {
this.setData({ inputValue: e.detail.value });
clearTimeout(this.typingDebounce);
this.typingDebounce = setTimeout(() => {
this.sendTypingStatus();
}, 500);
},
// 输入框获得焦点
onInputFocus: function() {
this.setData({ inputFocus: true }, () => {
setTimeout(() => {
this.scrollToBottom();
}, 200);
});
},
// 输入框失去焦点
onInputBlur: function() {
this.setData({ inputFocus: false });
},
// 清除输入
clearInput: function() {
this.setData({
inputValue: '',
inputFocus: true
});
},
// 滚动事件
onScroll: function(e) {
const scrollTop = e.detail.scrollTop;
this.setData({
lastScrollTop: scrollTop,
isScrolling: true
});
clearTimeout(this.scrollTimer);
this.scrollTimer = setTimeout(() => {
this.setData({ isScrolling: false });
}, 200);
// 当滚动到顶部附近时加载更多
if (scrollTop <= 50 && !this.data.loadingMore && this.data.hasMore && this.data.firstLoadComplete) {
console.log('触发加载更多,当前滚动位置:', scrollTop);
this.loadMoreMessages();
}
// 当滚动到底部时,清除新消息标记
const query = wx.createSelectorQuery();
query.select('#chatScroll').boundingClientRect();
query.select('.chat-bottom-space').boundingClientRect();
query.exec((res) => {
if (res[0] && res[1]) {
const scrollHeight = res[0].height;
const bottomOffset = res[1].top - res[0].top;
if (scrollTop + scrollHeight >= bottomOffset - 50) {
this.setData({ hasNewMessage: false });
}
}
});
},
// 滚动到底部
scrollToBottom: function(animate = true) {
if (this.data.isScrolling) return;
this.setData({
scrollAnimate: animate,
hasNewMessage: false
}, () => {
setTimeout(() => {
this.setData({ scrollTop: 999999 });
}, 50);
});
},
// 键盘高度变化
onKeyboardHeightChange: function(res) {
this.setData({ keyboardHeight: res.height });
if (res.height > 0) {
this.setData({ showMediaSheet: false });
setTimeout(() => {
this.scrollToBottom();
}, 100);
}
},
// 显示多媒体选择面板
showMediaActionSheet: function() {
this.setData({
showMediaSheet: true,
inputFocus: false
});
},
// 隐藏多媒体选择面板
hideMediaActionSheet: function() {
this.setData({ showMediaSheet: false });
},
// 选择图片
chooseImage: function() {
this.hideMediaActionSheet();
wx.chooseImage({
count: 9,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
console.log('选择的图片:', res.tempFilePaths);
this.uploadImages(res.tempFilePaths);
},
fail: (err) => {
console.error('选择图片失败:', err);
wx.showToast({
title: '选择图片失败',
icon: 'none'
});
}
});
},
// 预览图片
previewImage: function(e) {
const url = e.currentTarget.dataset.url;
wx.previewImage({
current: url,
urls: [url]
});
},
// 设置今天日期
setTodayDate: function() {
const now = new Date();
const month = now.getMonth() + 1;
const date = now.getDate();
const week = ['日', '一', '二', '三', '四', '五', '六'][now.getDay()];
this.setData({
todayDate: `${month}${date}日 星期${week}`
});
},
// 处理消息时间显示
processMessageTimes: function(messages) {
if (!messages || messages.length === 0) return [];
const processedMessages = [];
let lastShowTime = null;
for (let i = 0; i < messages.length; i++) {
const msg = { ...messages[i] };
if (i === 0) {
msg.showTime = true;
lastShowTime = msg.timestamp;
} else {
const timeDiffMinutes = (msg.timestamp - lastShowTime) / (1000 * 60);
msg.showTime = timeDiffMinutes >= this.data.timeInterval;
if (msg.showTime) lastShowTime = msg.timestamp;
}
processedMessages.push(msg);
}
if (lastShowTime) {
this.setData({ lastShowTimeStamp: lastShowTime });
}
return processedMessages;
},
// 格式化时间
formatTime: function(timestamp) {
if (!timestamp) return '';
const date = new Date(Number(timestamp));
const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0');
return `${hours}:${minutes}`;
},
// 格式化文件大小
formatFileSize: function(bytes) {
if (!bytes || bytes === 0) return '0B';
const units = ['B', 'KB', 'MB', 'GB'];
let size = bytes;
let unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return size.toFixed(1) + units[unitIndex];
},
// 阻止事件冒泡
stopPropagation: function() {}
});