// pages/chat-detail/index.js import socketManager from '../../utils/websocket'; Page({ /** * 页面的初始数据 */ data: { userId: '', userName: '', avatar: '', messages: [], inputValue: '', goodsInfo: null, // 商品信息 connectionStatus: 'disconnected', // 连接状态: disconnected, connecting, connected, error connectionMessage: '', // 连接状态提示消息 isMockMode: false, // 默认为真实WebSocket通信模式 isManager: false // 是否是与客服的聊天 }, /** * 从本地存储加载历史消息 */ loadMessagesFromStorage: function() { try { const storedMessages = wx.getStorageSync(`chat_messages_${this.data.userId}`); if (storedMessages && storedMessages.length > 0) { console.log('从本地存储加载了', storedMessages.length, '条历史消息'); // 处理消息数据,确保包含必要的字段 const messagesWithRequiredFields = storedMessages.map(msg => ({ ...msg, // 重新生成shortTime字段 shortTime: msg.time ? this.extractShortTime(msg.time) : this.getShortTime() })); // 重新处理时间显示逻辑 const processedMessages = this.processMessageTimes(messagesWithRequiredFields); this.setData({ messages: processedMessages }); return true; } } catch (e) { console.error('从本地存储加载消息失败:', e); } return false; }, /** * 根据用户ID获取用户名 */ getUserNameById: function(userId) { try { // 参数有效性检查 if (!userId || typeof userId === 'undefined') { return '未知用户'; } // 确保userId是字符串类型 const safeUserId = String(userId); // 首先从客服列表缓存中查找 const app = getApp(); if (app.globalData.customerServiceList) { const service = app.globalData.customerServiceList.find(item => item.id === safeUserId || item.managerId === safeUserId || String(item.id) === safeUserId || String(item.managerId) === safeUserId ); if (service) { return service.alias || service.name || service.id || '未知客服'; } } // 尝试从本地存储获取客服列表 const cachedCustomerServices = wx.getStorageSync('cached_customer_services') || []; const service = cachedCustomerServices.find(item => item.id === safeUserId || item.managerId === safeUserId || String(item.id) === safeUserId || String(item.managerId) === safeUserId ); if (service) { return service.alias || service.name || service.id || '未知客服'; } // 从固定映射中查找 const userNameMap = { '1001': '刘海', '1002': '李明', '1003': '王刚', '1004': '张琳' }; if (userNameMap[safeUserId]) { return userNameMap[safeUserId]; } // 对于manager_开头的ID,显示为客服 if (safeUserId.startsWith('manager_') || safeUserId.includes('manager')) { return '客服-' + (safeUserId.length >= 4 ? safeUserId.slice(-4) : safeUserId); } // 如果都没有找到,返回默认名称,避免出现undefined if (safeUserId.length >= 4) { return '用户' + safeUserId.slice(-4); } else { return '用户' + safeUserId; } } catch (e) { console.error('获取用户名出错:', e); return '未知用户'; } }, /** * 生命周期函数--监听页面加载 */ onLoad: function (options) { // 接收从上一个页面传递的参数 if (options) { const userId = options.userId || ''; // 优先使用传递的userName参数,并确保正确解码 let userName = options.userName ? decodeURIComponent(options.userName) : ''; // 如果没有传递userName或userName为空,则使用getUserNameById获取默认名称 if (!userName || userName.trim() === '') { userName = this.getUserNameById(userId); } // 检查是否传递了测试模式参数和是否是客服 const isTestMode = options.isTestMode === 'true'; const isManager = options.isManager === 'true'; this.setData({ userId: userId, userName: userName, avatar: options.avatar || '', isMockMode: isTestMode, // 默认为false,只有明确指定才启用测试模式 isManager: isManager // 设置是否是与客服的聊天 }); // 更新导航栏标题 wx.setNavigationBarTitle({ title: userName || '在线联系' }); // 首先尝试从本地存储加载历史消息 const hasLoadedStoredMessages = this.loadMessagesFromStorage(); // 如果没有历史消息,初始化模拟消息数据 if (!hasLoadedStoredMessages) { this.initMockMessages(); } // 初始化WebSocket连接(无论是否模拟模式都初始化,但模拟模式下不发送真实消息) this.initWebSocket(); // 显示当前模式提示 wx.showToast({ title: isTestMode ? '开发测试模式' : '真实通信模式', icon: 'none', duration: 2000 }); } }, /** * 初始化WebSocket连接 */ initWebSocket: function() { // 获取当前用户ID(实际项目中应该从全局或本地存储获取) const currentUserId = wx.getStorageSync('userId') || 'user_' + Date.now(); // 保存当前用户ID到本地存储 wx.setStorageSync('userId', currentUserId); // 构建WebSocket连接地址 // 注意:在实际部署时,需要替换为真实的WebSocket服务地址 // 这里使用本地服务器地址作为示例 const app = getApp(); const globalUserInfo = app.globalData.userInfo || {}; const isManager = this.data.isManager || (globalUserInfo.userType === 'manager' || globalUserInfo.type === 'manager'); const wsUrl = `ws://localhost:3003/ws?userId=${currentUserId}&targetId=${this.data.userId}&type=chat&userType=${isManager ? 'manager' : 'user'}`; this.setData({ connectionStatus: 'connecting', connectionMessage: '正在连接服务器...' }); // 设置WebSocket事件监听 this.setupWebSocketListeners(); // 连接WebSocket服务器 socketManager.connect(wsUrl, { maxReconnectAttempts: 5, reconnectInterval: 3000, heartbeatTime: 30000 }); // 监听网络状态变化 this.startNetworkListener(); }, /** * 手动重新连接 */ reconnect: function() { wx.showLoading({ title: '正在重连...', }); // 重置重连计数 socketManager.reconnectAttempts = 0; // 重新初始化WebSocket连接 this.initWebSocket(); setTimeout(() => { wx.hideLoading(); }, 1000); }, /** * 开始网络状态监听 */ startNetworkListener: function() { // 监听网络状态变化 wx.onNetworkStatusChange((res) => { console.log('网络状态变化:', res); if (!res.isConnected) { // 网络断开 this.setData({ connectionStatus: 'error', connectionMessage: '网络已断开' }); // 显示网络断开提示 wx.showToast({ title: '网络连接已断开', icon: 'none', duration: 2000 }); } else if (res.isConnected && this.data.connectionStatus === 'error') { // 网络恢复且之前连接错误,尝试重连 wx.showToast({ title: '网络已恢复,正在重连', icon: 'none', duration: 2000 }); this.reconnect(); } }); }, /** * 设置WebSocket事件监听器 */ setupWebSocketListeners: function() { // 连接成功 socketManager.on('open', () => { const app = getApp(); const isManager = this.data.isManager || (app.globalData.userInfo?.userType === 'manager' || app.globalData.userInfo?.type === 'manager'); // 如果是客服,更新全局用户信息 if (isManager && app.globalData.userInfo) { app.globalData.userInfo.userType = 'manager'; app.globalData.userInfo.type = 'manager'; } this.setData({ connectionStatus: 'connected', connectionMessage: '已连接' }); console.log('WebSocket连接成功', { isManager }); // 发送认证消息 this.sendAuthMessage(); wx.showToast({ title: '连接成功', icon: 'success', duration: 1500 }); }); // 接收消息 socketManager.on('message', (data) => { console.log('收到WebSocket消息:', data); this.handleReceivedMessage(data); }); // 连接关闭 socketManager.on('close', (res) => { if (this.data.connectionStatus !== 'disconnected') { this.setData({ connectionStatus: 'disconnected', connectionMessage: `连接已断开(${res.code || ''})` }); } }); // 连接错误 socketManager.on('error', (error) => { console.error('WebSocket错误:', error); this.setData({ connectionStatus: 'error', connectionMessage: '连接错误' }); }); // 重连中 socketManager.on('reconnecting', (attempts) => { this.setData({ connectionStatus: 'connecting', connectionMessage: `重连中(${attempts}/5)...` }); }); // 重连失败 socketManager.on('reconnectFailed', () => { this.setData({ connectionStatus: 'error', connectionMessage: '重连失败,请检查网络' }); wx.showModal({ title: '连接失败', content: '无法连接到服务器,请检查网络设置后重试。', showCancel: false, confirmText: '确定' }); }); // 监听WebSocket状态更新事件 socketManager.on('status', (status) => { console.log('WebSocket状态更新:', status); this.setData({ connectionMessage: status.message || '状态更新' }); // 根据状态类型更新连接状态 switch(status.type) { case 'connecting': this.setData({ connectionStatus: 'connecting' }); break; case 'connected': this.setData({ connectionStatus: 'connected' }); break; case 'disconnected': this.setData({ connectionStatus: 'disconnected' }); break; case 'error': this.setData({ connectionStatus: 'error' }); if (status.isWarning) { wx.showToast({ title: status.message, icon: 'none', duration: 2000 }); } break; case 'reconnecting': this.setData({ connectionStatus: 'reconnecting' }); break; } }); // 监听消息发送成功事件 socketManager.on('sendSuccess', (data) => { console.log('消息发送成功:', data); // 可以在这里更新UI状态,如显示已发送标记等 }); // 监听消息发送失败事件 socketManager.on('sendError', (error) => { console.error('消息发送失败:', error); wx.showToast({ title: '消息发送失败,请重试', icon: 'none', duration: 2000 }); }); }, /** * 发送认证消息 */ sendAuthMessage: function() { const currentUserId = wx.getStorageSync('userId') || 'user_' + Date.now(); const app = getApp(); const globalUserInfo = app.globalData.userInfo || {}; // 确定用户类型:优先使用页面参数,然后是全局用户信息,最后默认为普通用户 const isManager = this.data.isManager || false; const userType = isManager ? 'manager' : (globalUserInfo.userType || globalUserInfo.type || 'user'); console.log('认证信息:', { userId: currentUserId, userType, isManager: this.data.isManager }); const authMessage = { type: 'auth', data: { userId: currentUserId, type: userType, // 用户类型:普通用户或客服 name: isManager ? '客服' + currentUserId.slice(-4) : '用户' + currentUserId.slice(-4) }, timestamp: Date.now() }; console.log('发送认证消息:', authMessage); socketManager.send(authMessage); }, /** * 处理接收到的消息 */ handleReceivedMessage: function(data) { console.log('收到消息:', data); // 确保数据格式正确 if (!data || typeof data !== 'object') { console.error('收到无效消息:', data); return; } // 添加消息数据格式验证 if (!data.type) { console.error('消息缺少必要字段:', data); return; } // 根据消息类型处理 if (data.type === 'new_message' && data.payload) { // 处理服务器推送的新消息 this.processServerMessage(data.payload); } else if (data.type === 'message') { // 处理旧格式的普通消息(向后兼容) this.processChatMessage(data); } else if (data.type === 'system') { // 处理系统消息 const systemMessage = { type: 'system', content: data.content || '', time: this.getFormattedTime(), showTime: true }; // 如果是警告消息,添加警告标记 if (data.isWarning) { systemMessage.isWarning = true; } const messages = [...this.data.messages]; messages.push(systemMessage); this.setData({ messages }); // 保存消息到本地存储 this.saveMessagesToStorage(messages); this.scrollToBottom(); } else if (data.type === 'status') { // 处理状态更新消息 this.setData({ connectionMessage: data.message || '连接状态更新' }); wx.showToast({ title: data.message || '状态更新', icon: 'none' }); } else if (data.type === 'message_sent') { // 处理服务器确认消息 console.log('服务器确认消息已送达:', data.payload); } else if (data.type === 'error') { // 处理错误消息 console.error('收到服务器错误:', data.message); wx.showToast({ title: '消息发送失败: ' + data.message, icon: 'none', duration: 2000 }); } }, // 处理聊天消息(旧格式,向后兼容) processChatMessage: function(message) { // 确保消息对象包含必要的字段 const content = message.content || ''; const isImage = message.isImage || false; const app = getApp(); const currentUser = wx.getStorageSync('userId') || (app.globalData.userInfo?.userId || ''); const currentUserType = app.globalData.userType || wx.getStorageSync('userType') || 'customer'; console.log('处理聊天消息 - 用户类型:', currentUserType, '当前用户ID:', currentUser, '消息来源:', message.from || message.senderId); // 确定消息发送方(me或other) let sender = 'other'; if (message.from === currentUser || message.senderId === currentUser) { sender = 'me'; } else if (currentUserType === 'customer_service' && message.receiverId && message.receiverId === this.data.userId) { // 客服接收到的发送给自己的消息 sender = 'other'; } // 创建新消息对象 const newMessage = { id: message.id || 'msg_' + Date.now(), type: 'message', content: content, isImage: isImage, sender: sender, time: message.timestamp ? this.formatTimestampToTime(message.timestamp) : this.getFormattedTime(), showTime: this.shouldShowTime(), shortTime: this.getShortTime(), status: 'sent', // 接收的消息默认已发送 senderType: message.senderType || 'unknown', // 保存发送方类型 serverData: message // 保存完整的服务器数据 }; this.addReceivedMessage(newMessage); }, // 处理服务器推送的新消息 processServerMessage: function(messageData) { console.log('处理服务器推送的新消息:', messageData); // 获取当前用户信息 const app = getApp(); const currentUser = wx.getStorageSync('userId') || (app.globalData.userInfo?.userId || ''); const currentUserType = app.globalData.userType || wx.getStorageSync('userType') || 'customer'; console.log('处理服务器消息 - 用户类型:', currentUserType, '当前用户ID:', currentUser, '消息数据:', messageData); // 确定消息发送方(me或other) let sender = 'other'; // 检查是否是当前用户发送的消息 if (messageData.senderId === currentUser) { sender = 'me'; } else if (currentUserType === 'customer_service') { // 对于客服,还需要考虑其他情况 if (messageData.direction === 'customer_to_service' && messageData.receiverId === currentUser) { // 客户发送给当前客服的消息 sender = 'other'; } } // 处理消息内容,支持嵌套的data字段 const messageContent = messageData.content || (messageData.data && messageData.data.content) || ''; const contentType = messageData.contentType || (messageData.data && messageData.data.contentType) || 1; const isImage = contentType === 2 || (messageData.fileUrl && contentType !== 1); // 创建符合前端UI要求的消息对象 const newMessage = { id: messageData.messageId || messageData.id || 'msg_' + Date.now(), type: 'message', content: isImage && messageData.fileUrl ? messageData.fileUrl : messageContent, isImage: isImage, sender: sender, senderType: messageData.senderType || 'unknown', // 保存发送方类型 direction: messageData.direction || 'unknown', // 保存消息方向 time: messageData.createdAt ? this.formatServerTime(messageData.createdAt) : this.getFormattedTime(), status: 'sent', serverData: messageData // 保存完整的服务器数据 }; // 特殊处理客服消息显示逻辑 if (currentUserType === 'customer_service' && sender === 'other') { console.log('客服收到客户消息:', messageContent); // 确保客服消息可见性 this.ensureManagerMessageVisibility(newMessage); } this.addReceivedMessage(newMessage); }, // 通用的添加接收消息方法 addReceivedMessage: function(newMessage) { // 检查是否已经存在相同的消息,避免重复添加 const existingMessage = this.data.messages.find(msg => msg.id === newMessage.id || (newMessage.serverData && msg.serverData && msg.serverData.messageId === newMessage.serverData.messageId) ); if (existingMessage) { console.log('消息已存在,跳过添加:', newMessage); return; } const messages = [...this.data.messages]; // 确定是否显示时间 let showTime = false; if (messages.length === 0) { showTime = true; } else { const lastMessage = messages[messages.length - 1]; const currentTime = this.parseMessageTime(newMessage.time); const lastTime = this.parseMessageTime(lastMessage.time); // 如果时间相差超过5分钟,则显示时间 if (Math.abs(currentTime - lastTime) > 5 * 60 * 1000) { showTime = true; } } newMessage.showTime = showTime; newMessage.shortTime = this.extractShortTime(newMessage.time); // 添加到消息列表 messages.push(newMessage); // 保存到本地存储 this.saveMessagesToStorage(messages); // 更新页面数据 this.setData({ messages }); // 滚动到底部 this.scrollToBottom(); // 如果是新消息,触发通知更新消息列表 if (newMessage.sender === 'other') { try { wx.vibrateShort(); // 轻微震动提示 const app = getApp(); if (app.globalData.onNewMessage) { app.globalData.onNewMessage(newMessage); } } catch (e) { console.error('触发新消息事件失败:', e); } } // 强制更新消息列表,确保聊天记录能在消息中心显示 this.updateMessageListGlobal(); }, /** * 强制更新全局消息列表 */ updateMessageListGlobal: function() { try { console.log('强制更新全局消息列表'); // 触发全局事件,通知消息列表页面更新 const app = getApp(); if (app.globalData.onNewMessage) { app.globalData.onNewMessage({ userId: this.data.userId, userName: this.data.userName }); } // 直接调用消息列表页面的loadChatList方法(如果能访问到) // 这里我们通过重新加载存储来确保数据同步 console.log('消息已保存到存储,消息列表页面下次显示时会自动刷新'); } catch (e) { console.error('更新全局消息列表失败:', e); } }, // 格式化服务器时间 formatServerTime: function(serverTime) { const date = new Date(serverTime); const month = date.getMonth() + 1; const day = date.getDate(); const hours = date.getHours().toString().padStart(2, '0'); const minutes = date.getMinutes().toString().padStart(2, '0'); return `${month}-${day} ${hours}:${minutes}`; }, // 解析消息时间字符串为时间戳 parseMessageTime: function(timeStr) { if (!timeStr) return Date.now(); return new Date(timeStr).getTime(); }, // 提取短时间格式 (HH:MM) extractShortTime: function(timeStr) { if (!timeStr) return this.getShortTime(); // 如果时间格式包含小时分钟,直接提取 const timeMatch = timeStr.match(/(\d{1,2}):(\d{2})/); if (timeMatch) { return `${timeMatch[1].padStart(2, '0')}:${timeMatch[2]}`; } return this.getShortTime(); }, /** * 将时间戳格式化为时间字符串 */ formatTimestampToTime: function(timestamp) { const date = new Date(timestamp); const month = date.getMonth() + 1; const day = date.getDate(); const hours = date.getHours().toString().padStart(2, '0'); const minutes = date.getMinutes().toString().padStart(2, '0'); return `${month}-${day} ${hours}:${minutes}`; }, /** * 判断是否应该显示消息时间 */ shouldShowTime: function() { const messages = this.data.messages; if (messages.length === 0) { return true; } const lastMessage = messages[messages.length - 1]; const currentTime = this.parseMessageTime(this.getFormattedTime()); const lastTime = this.parseMessageTime(lastMessage.time); // 如果时间差超过5分钟,显示时间 return currentTime - lastTime > 5 * 60 * 1000; }, // 初始化模拟消息数据 initMockMessages: function() { // 只有从客服列表页面进入(isManager=true)且没有历史消息时,才显示开场白 if (this.data.isManager) { // 获取当前时间作为消息时间 const currentTime = this.getFormattedTime(); // 模拟历史消息,适用于鸡蛋交易平台 const mockMessages = [ { type: 'message', sender: 'other', content: '您好!欢迎来到鸡蛋交易平台,我是' + this.data.userName + ',请问有什么可以帮助您的吗?', time: currentTime }, ]; // 处理消息时间显示逻辑 const processedMessages = this.processMessageTimes(mockMessages); this.setData({ messages: processedMessages }); // 滚动到底部 this.scrollToBottom(); } else { // 从消息中心进入时,不显示开场白,设置空消息列表 this.setData({ messages: [] }); } }, // 输入框内容变化 onInput: function(e) { this.setData({ inputValue: e.detail.value }); }, /** * 优化消息数据结构,移除不必要的字段 */ optimizeMessagesForStorage: function(messages) { // 限制每条聊天最多保存1000条消息 const MAX_MESSAGES_PER_CHAT = 1000; let optimizedMessages = messages.slice(-MAX_MESSAGES_PER_CHAT); // 移除不必要的字段,只保留核心数据 return optimizedMessages.map(msg => ({ type: msg.type, sender: msg.sender, content: msg.content, time: msg.time, showTime: msg.showTime // 移除shortTime字段,因为可以从time字段中提取 })); }, /** * 保存消息到本地存储 */ saveMessagesToStorage: function(messages) { try { // 优化消息数据结构 const optimizedMessages = this.optimizeMessagesForStorage(messages); // 获取当前聊天对象的标识 const chatIdentifier = this.data.userId; const chatKey = `chat_messages_${chatIdentifier}`; // 保存消息到当前聊天的存储 wx.setStorageSync(chatKey, optimizedMessages); console.log('消息已优化并保存到本地存储,当前消息数量:', optimizedMessages.length); console.log('存储键名:', chatKey); // 获取应用实例和用户信息 const app = getApp(); const currentUserId = wx.getStorageSync('userId') || ''; const isCurrentUserManager = app.globalData.userInfo?.userType === 'manager' || app.globalData.userInfo?.type === 'manager'; const currentManagerId = app.globalData.userInfo?.managerId || ''; // 确保双向消息同步 // 1. 对于客服发送的消息,确保创建反向记录 if (isCurrentUserManager && currentManagerId && currentManagerId !== chatIdentifier) { this.ensureManagerMessageVisibility(chatIdentifier, currentManagerId, messages); } // 2. 对于普通用户发送给客服的消息,创建特殊的反向记录 if (!isCurrentUserManager && currentUserId && currentUserId !== chatIdentifier) { // 创建反向消息记录(客服视角) const reversedKey = `chat_messages_${currentUserId}`; const reversedMessages = wx.getStorageSync(reversedKey) || []; // 获取用户发送的最新消息 const userMessages = messages.filter(msg => msg.sender === 'me'); if (userMessages.length > 0) { const latestUserMessage = userMessages[userMessages.length - 1]; // 检查消息是否已存在 const isMessageExists = reversedMessages.some(msg => msg.time === latestUserMessage.time && msg.sender === 'other' ); if (!isMessageExists) { // 创建反向消息(从客服视角看是对方发送的) const reversedMessage = { type: 'message', sender: 'other', content: latestUserMessage.content, time: latestUserMessage.time, showTime: reversedMessages.length === 0, shortTime: latestUserMessage.shortTime }; reversedMessages.push(reversedMessage); const optimizedReversed = this.optimizeMessagesForStorage(reversedMessages); wx.setStorageSync(reversedKey, optimizedReversed); console.log('已为用户创建反向消息记录,确保客服能看到'); } } } // 强制更新全局消息列表 this.updateMessageListGlobal(); } catch (e) { console.error('保存消息到本地存储失败:', e); } }, /** * 确保客服能看到自己发送的消息 * 当客服向其他用户发送消息时,确保消息也能在自己的消息列表中显示 */ ensureManagerMessageVisibility: function(targetId, currentManagerId, messages) { try { // 从消息中提取该客服发送的最新消息 const managerMessages = messages.filter(msg => msg.sender === 'me'); if (managerMessages.length > 0) { // 获取最新的客服发送消息 const latestMessage = managerMessages[managerMessages.length - 1]; // 创建反向的消息记录(客服视角) const reversedKey = `chat_messages_${targetId}`; let reversedMessages = wx.getStorageSync(reversedKey) || []; // 检查这条消息是否已经存在,避免重复添加 const isMessageExists = reversedMessages.some(msg => msg.time === latestMessage.time && msg.sender === 'me' ); if (!isMessageExists) { // 添加新消息到反向消息列表 const newReversedMessage = { ...latestMessage }; // 设置是否显示时间 newReversedMessage.showTime = reversedMessages.length === 0; reversedMessages.push(newReversedMessage); // 保存反向消息记录 const optimizedReversedMessages = this.optimizeMessagesForStorage(reversedMessages); wx.setStorageSync(reversedKey, optimizedReversedMessages); console.log('已为客服创建反向消息记录,确保消息可见性'); } // 更新消息列表 this.updateMessageListGlobal(); } } catch (e) { console.error('确保客服消息可见性失败:', e); } }, /** * 清理指定聊天的历史消息 */ clearChatHistory: function() { try { wx.removeStorageSync(`chat_messages_${this.data.userId}`); this.setData({ messages: [] }); console.log('聊天历史已清空'); wx.showToast({ title: '聊天历史已清空', icon: 'success' }); } catch (e) { console.error('清空聊天历史失败:', e); wx.showToast({ title: '清空失败', icon: 'none' }); } }, // 发送消息 sendMessage: function() { if (!this.data.inputValue.trim()) return; // 创建消息对象 const newMessage = { type: 'message', sender: 'me', content: this.data.inputValue.trim(), time: this.getFormattedTime(), status: 'sending' // 初始状态为发送中 }; // 添加新消息到消息列表 const messages = [...this.data.messages]; // 设置是否显示新消息的时间 let showTime = false; if (messages.length === 0) { showTime = true; } else { const lastMessage = messages[messages.length - 1]; const currentTime = this.parseMessageTime(this.getFormattedTime()); const lastTime = this.parseMessageTime(lastMessage.time); // 如果时间差超过5分钟,显示时间 if (currentTime - lastTime > 5 * 60 * 1000) { showTime = true; } } newMessage.showTime = showTime; newMessage.shortTime = this.getShortTime(); messages.push(newMessage); // 立即更新UI this.setData({ messages: messages, inputValue: '' }); // 保存消息到本地存储 this.saveMessagesToStorage(messages); this.scrollToBottom(); // 根据模式决定发送方式 if (this.data.isMockMode) { // 模拟模式:仅用于开发测试,消息不会真正发送 console.log('模拟模式:消息未通过WebSocket发送', newMessage); // 更新消息状态为已发送(模拟) setTimeout(() => { this.updateMessageStatus(messages.length - 1, 'sent'); }, 500); wx.showToast({ title: '测试模式:消息仅在本地显示', icon: 'none', duration: 1500 }); } else { // 真实模式:通过WebSocket发送消息 const sent = this.sendWebSocketMessage(newMessage); // 更新消息状态为已发送 setTimeout(() => { this.updateMessageStatus(messages.length - 1, sent ? 'sent' : 'failed'); }, 500); } }, /** * 更新消息状态 * @param {number} index - 消息索引 * @param {string} status - 状态值(sending/sent/failed) */ updateMessageStatus: function(index, status) { const messages = [...this.data.messages]; if (messages[index]) { messages[index].status = status; this.setData({ messages }); this.saveMessagesToStorage(messages); } }, /** * 通过WebSocket发送消息 */ sendWebSocketMessage: function(message) { const app = getApp(); // 获取当前用户信息 const currentUserType = app.globalData.userType || wx.getStorageSync('userType') || 'customer'; const currentUserId = wx.getStorageSync('userId') || (app.globalData.userInfo?.userId || 'user_' + Date.now()); const chatTargetId = this.data.userId; // 聊天对象ID const isCurrentUserService = currentUserType === 'customer_service' || app.globalData.userInfo?.userType === 'customer_service' || app.globalData.userInfo?.type === 'customer_service'; console.log('发送消息 - 用户类型:', currentUserType, '是否客服:', isCurrentUserService, '聊天对象ID:', chatTargetId); if (socketManager.getConnectionStatus()) { // 构建符合服务器要求的消息格式 const wsMessage = { type: 'chat_message', // 服务器期望的类型 direction: isCurrentUserService ? 'service_to_customer' : 'customer_to_service', // 消息方向 data: { // 根据用户类型设置正确的接收方ID receiverId: chatTargetId, senderId: currentUserId, senderType: isCurrentUserService ? 'customer_service' : 'customer', content: message.content, contentType: message.isImage ? 2 : 1, // 1: 文本消息, 2: 图片消息 timestamp: Date.now() } }; // 如果是图片消息,添加URL if (message.isImage && message.content.startsWith('http')) { wsMessage.data.fileUrl = message.content; } console.log('通过WebSocket发送符合服务器格式的消息:', wsMessage); const sent = socketManager.send(wsMessage); if (sent) { // 添加发送成功的提示 wx.showToast({ title: '消息已发送', icon: 'success', duration: 1000 }); return true; } else { console.error('消息发送失败,可能是队列已满或连接断开'); return false; } } else { console.error('WebSocket未连接,无法发送消息'); wx.showToast({ title: '连接未建立,发送失败', icon: 'none', duration: 1500 }); // 尝试重新连接 this.initWebSocket(); return false; } }, /** * 模拟对方回复(用于开发测试) */ simulateReply: function() { setTimeout(() => { const replyMessage = { type: 'message', sender: 'other', content: this.getRandomReply(), time: this.getFormattedTime(), showTime: false, // 回复消息通常不显示时间 shortTime: this.getShortTime() }; const messages = [...this.data.messages]; messages.push(replyMessage); this.setData({ messages: messages }); // 保存消息到本地存储 this.saveMessagesToStorage(messages); this.scrollToBottom(); }, 1000); }, // 获取格式化时间(带日期) getFormattedTime: function() { const now = new Date(); const month = now.getMonth() + 1; const date = now.getDate(); const hours = now.getHours().toString().padStart(2, '0'); const minutes = now.getMinutes().toString().padStart(2, '0'); return `${month}-${date} ${hours}:${minutes}`; }, // 获取仅包含时分的时间格式 getShortTime: function() { const now = new Date(); const hours = now.getHours().toString().padStart(2, '0'); const minutes = now.getMinutes().toString().padStart(2, '0'); return `${hours}:${minutes}`; }, // 处理消息时间显示逻辑 processMessageTimes: function(messages) { return messages.map((message, index) => { // 复制消息对象,避免直接修改原数据 const processedMessage = { ...message }; // 设置是否显示消息时间 processedMessage.showTime = false; // 第一条消息总是显示时间 if (index === 0) { processedMessage.showTime = true; } else { // 与上一条消息比较时间差 const currentTime = this.parseMessageTime(message.time); const previousTime = this.parseMessageTime(messages[index - 1].time); // 如果时间差超过5分钟,显示时间 if (currentTime - previousTime > 5 * 60 * 1000) { processedMessage.showTime = true; } } // 为每条消息添加短时间格式(用于消息气泡旁显示) processedMessage.shortTime = this.extractShortTime(message.time); return processedMessage; }); }, // 获取随机回复 getRandomReply: function() { const replies = [ '好的,了解了', '稍等,我马上回复', '这个没问题', '您还有其他问题吗?', '好的,我知道了' ]; return replies[Math.floor(Math.random() * replies.length)]; }, // 预览图片 - 使用微信插件进行预览 previewImage: function(e) { let imageUrl = e.currentTarget.dataset.src; // 处理URL格式,确保能正确预览 if (typeof imageUrl === 'string') { // 移除可能的引号 imageUrl = imageUrl.replace(/^["']|["']$/g, ''); // 检查URL协议 const isHttpProtocol = imageUrl.startsWith('http://') || imageUrl.startsWith('https://'); const isWxfileProtocol = imageUrl.startsWith('wxfile://'); // 如果URL缺少协议且不是wxfile协议,添加https前缀 if (!isHttpProtocol && !isWxfileProtocol) { imageUrl = 'https://' + imageUrl; } } // 使用微信原生预览功能 wx.previewImage({ current: imageUrl, // 当前显示图片的链接 urls: [imageUrl], // 需要预览的图片链接列表 showmenu: true, // 显示菜单,允许用户保存图片等操作 success: function() { console.log('图片预览成功'); }, fail: function(error) { console.error('图片预览失败:', error); wx.showToast({ title: '图片预览失败', icon: 'none' }); } }); }, // 滚动到底部 scrollToBottom: function() { setTimeout(() => { wx.createSelectorQuery().select('#message-container').boundingClientRect(res => { if (res) { wx.createSelectorQuery().select('#message-list').scrollOffset(res => { if (res) { wx.createSelectorQuery().select('#message-list').context(context => { context.scrollTo({ scrollTop: res.scrollHeight, animated: true }); }).exec(); } }).exec(); } }).exec(); }, 100); }, // 显示表情 showEmoji: function() { // 表情功能占位符 wx.showToast({ title: '表情功能开发中', icon: 'none' }); }, // 切换语音模式 toggleVoiceMode: function() { // 语音模式切换功能占位符 wx.showToast({ title: '语音功能开发中', icon: 'none' }); }, // 显示更多操作 showMoreOptions: function() { wx.showActionSheet({ itemList: ['发送图片', '发送位置'], success: (res) => { if (!res.cancel) { if (res.tapIndex === 0) { // 发送图片 wx.chooseImage({ count: 1, success: (res) => { console.log('选择的图片路径:', res.tempFilePaths[0]); this.sendImageMessage(res.tempFilePaths[0]); } }); } else if (res.tapIndex === 1) { // 发送位置 wx.chooseLocation({ success: function(res) { console.log('选择的位置:', res); // 这里可以添加发送位置的逻辑 } }); } } } }); }, // 发送图片消息 sendImageMessage: function(imagePath) { const newMessage = { type: 'message', sender: 'me', content: imagePath, time: this.getFormattedTime(), isImage: true // 标记为图片消息 }; // 添加新消息到消息列表 const messages = [...this.data.messages]; // 设置是否显示新消息的时间 let showTime = false; if (messages.length === 0) { showTime = true; } else { const lastMessage = messages[messages.length - 1]; const currentTime = this.parseMessageTime(this.getFormattedTime()); const lastTime = this.parseMessageTime(lastMessage.time); // 如果时间差超过5分钟,显示时间 if (currentTime - lastTime > 5 * 60 * 1000) { showTime = true; } } newMessage.showTime = showTime; newMessage.shortTime = this.getShortTime(); messages.push(newMessage); // 立即更新UI this.setData({ messages: messages }); // 保存消息到本地存储 this.saveMessagesToStorage(messages); this.scrollToBottom(); // 根据模式决定发送方式 if (this.data.isMockMode) { // 模拟模式:不再自动回复,让用户和客服可以真正沟通 // 移除了 this.simulateReply() 调用 } else { // WebSocket模式:通过WebSocket发送消息(实际项目中需要先上传图片获取URL) this.sendWebSocketMessage(newMessage); } }, /** * 生命周期函数--监听页面初次渲染完成 */ onReady: function () { }, /** * 生命周期函数--监听页面显示 */ onShow: function () { }, /** * 生命周期函数--监听页面隐藏 */ onHide: function () { }, /** * 切换模拟模式 */ toggleMockMode: function(e) { const checked = e.detail.value; wx.showLoading({ title: checked ? '切换到WebSocket模式...' : '切换到模拟模式...', }); // 如果是切换到模拟模式 if (!checked) { // 清理WebSocket连接和监听器 socketManager.off('open'); socketManager.off('message'); socketManager.off('close'); socketManager.off('error'); socketManager.off('reconnecting'); socketManager.off('reconnectFailed'); // 移除网络状态监听 wx.offNetworkStatusChange(); // 可选:关闭连接 socketManager.close(); this.setData({ isMockMode: true, connectionStatus: '', connectionMessage: '' }); } else { // 切换到WebSocket模式 this.setData({ isMockMode: false }); // 初始化WebSocket连接 this.initWebSocket(); } setTimeout(() => { wx.hideLoading(); wx.showToast({ title: checked ? '已切换到WebSocket模式' : '已切换到模拟模式', icon: 'none', duration: 1500 }); }, 1000); }, /** * 生命周期函数--监听页面卸载 */ onUnload: function () { // 清理WebSocket连接和监听器 if (!this.data.isMockMode) { socketManager.off('open'); socketManager.off('message'); socketManager.off('close'); socketManager.off('error'); socketManager.off('reconnecting'); socketManager.off('reconnectFailed'); // 移除网络状态监听 wx.offNetworkStatusChange(); // 关闭WebSocket连接 // 注意:这里可以根据实际需求决定是否关闭连接 // 如果是多页面共用一个连接,可以不在这里关闭 // socketManager.close(); } }, /** * 页面相关事件处理函数--监听用户下拉动作 */ onPullDownRefresh: function () { }, /** * 页面上拉触底事件的处理函数 */ onReachBottom: function () { this.loadMoreMessages(); }, /** * 加载更多消息 */ loadMoreMessages: function() { // 加载历史消息功能占位符 console.log('加载更多历史消息'); // 实际项目中,这里应该从服务器加载更多历史消息 wx.showToast({ title: '已加载全部历史消息', icon: 'none' }); }, /** * 用户点击右上角分享 */ onShareAppMessage: function () { } });