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.
1398 lines
38 KiB
1398 lines
38 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
|
|
},
|
|
|
|
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) {
|
|
tempFilePaths.forEach((tempFilePath, index) => {
|
|
const fileName = `image_${Date.now()}_${index}.jpg`;
|
|
this.uploadFile(tempFilePath, fileName, 0, 'image');
|
|
});
|
|
},
|
|
|
|
// 上传视频后发送
|
|
uploadVideo: function(tempFilePath, thumbPath) {
|
|
const fileName = `video_${Date.now()}.mp4`;
|
|
this.uploadFile(tempFilePath, fileName, 0, 'video', thumbPath);
|
|
},
|
|
|
|
// 通用文件上传
|
|
uploadFile: function(tempFilePath, fileName, fileSize = 0, contentType = 'file', thumbPath = '') {
|
|
const messageId = 'file_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
|
|
|
// 将消息ID添加到发送中集合
|
|
const sendingMessages = new Set(this.data.sendingMessages);
|
|
sendingMessages.add(messageId);
|
|
|
|
const message = {
|
|
id: messageId,
|
|
isMe: true,
|
|
sender: 'user',
|
|
contentType: contentType,
|
|
content: tempFilePath,
|
|
thumb: thumbPath,
|
|
fileName: fileName,
|
|
fileSize: fileSize,
|
|
extension: fileName.split('.').pop().toLowerCase(),
|
|
timestamp: Date.now(),
|
|
status: 'uploading',
|
|
progress: 0
|
|
};
|
|
|
|
this.addMessageToList(message);
|
|
this.setData({
|
|
sendingMessages: sendingMessages,
|
|
sendingCount: sendingMessages.size,
|
|
showSendingTip: sendingMessages.size > 0
|
|
});
|
|
this.simulateUpload(messageId, contentType, tempFilePath);
|
|
},
|
|
|
|
// 模拟上传
|
|
simulateUpload: function(messageId, contentType, tempFilePath) {
|
|
let progress = 0;
|
|
const uploadInterval = setInterval(() => {
|
|
progress += Math.random() * 20 + 10;
|
|
|
|
if (progress >= 100) {
|
|
progress = 100;
|
|
clearInterval(uploadInterval);
|
|
|
|
setTimeout(() => {
|
|
this.updateMessageStatus(messageId, 'success');
|
|
|
|
const { messageList } = this.data;
|
|
const index = messageList.findIndex(msg => msg.id === messageId);
|
|
if (index !== -1) {
|
|
delete messageList[index].progress;
|
|
this.setData({ messageList });
|
|
|
|
const fileUrl = tempFilePath;
|
|
this.sendMessageToServer(fileUrl, contentType, messageId);
|
|
}
|
|
}, 200);
|
|
}
|
|
|
|
const { messageList } = this.data;
|
|
const index = messageList.findIndex(msg => msg.id === messageId);
|
|
if (index !== -1) {
|
|
messageList[index].progress = Math.min(Math.floor(progress), 100);
|
|
this.setData({ messageList });
|
|
}
|
|
}, 100);
|
|
},
|
|
|
|
// 添加消息到列表
|
|
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'],
|
|
success: (res) => {
|
|
this.uploadImages(res.tempFilePaths);
|
|
},
|
|
fail: () => {
|
|
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() {}
|
|
});
|