| | |
| | | </view> |
| | | <view class="user-details" v-else> |
| | | <text class="user-name">{{ card.name}}</text> |
| | | <text class="user-status" > |
| | | <text class="user-status"> |
| | | {{ card.unit[0]+' . '+card.position[0]}} |
| | | </text> |
| | | </view> |
| | |
| | | <text class="message-text">{{ message.content }}</text> |
| | | </view> |
| | | </view> |
| | | <view class="message-status" :class="message.is_read ? 'read-read' : 'read-sent'"> |
| | | <!-- <view class="message-status" :class="message.is_read ? 'read-read' : 'read-sent'"> |
| | | <text class="icon iconfont icon-eye "></text> |
| | | </view> |
| | | </view> --> |
| | | </view> |
| | | <view class="message-info"> |
| | | <text class="message-time">{{ formatTime(message.create_time) }}</text> |
| | | <text class="message-time">{{ formatTime(message.send_time||message.create_time) }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | |
| | | <view class="message-mine" v-else> |
| | | <view class="message-content"> |
| | | <view class="d-s-e"> |
| | | <view class="message-status" :class="message.is_read ? 'read-read' : 'read-sent'"> |
| | | <view class="message-status" :class="message.is_read ? 'read-read' : 'read-sent'"> |
| | | </view> |
| | | <view class="d-f"> |
| | | <view class="message-bubble"> |
| | |
| | | |
| | | </view> |
| | | <view class="message-info"> |
| | | <text class="message-time">{{ formatTime(message.create_time) }}</text> |
| | | <text class="message-time">{{ formatTime(message.send_time||message.create_time) }}</text> |
| | | </view> |
| | | |
| | | </view> |
| | |
| | | nickName: '', |
| | | avatarUrl: '' |
| | | }, |
| | | card:{}, |
| | | card: {}, |
| | | wsUrl:"", |
| | | |
| | | // 聊天数据 |
| | | messages: [], |
| | |
| | | |
| | | // 定时器 |
| | | typingTimer: null, |
| | | readTimer: null |
| | | readTimer: null, |
| | | reconnectTimer: null, |
| | | reconnectAttempts: 0, |
| | | maxReconnectAttempts: 5, |
| | | heartbeatTimer: null, |
| | | is_exit: false //是否退出 |
| | | }; |
| | | }, |
| | | onLoad(options) { |
| | |
| | | this.businessCardId = parseInt(options.business_card_id) || 0; |
| | | this.conversationId = parseInt(options.conversation_id) || 0; |
| | | this.targetUser.nickName = options.nickName || '用户'; |
| | | |
| | | // 获取当前用户信息 |
| | | this.getCurrentUser(); |
| | | if (!this.conversationId) { |
| | | // 初始化会话 |
| | | this.initConversation(); |
| | | }else{ |
| | | // 加载消息 |
| | | this.loadMessages(); |
| | | this.initWebSocket(); |
| | | } |
| | | |
| | | |
| | | // 加载消息 |
| | | this.loadMessages(); |
| | | |
| | | // 初始化WebSocket连接 |
| | | this.initWebSocket(); |
| | | |
| | | }, |
| | | onUnload() { |
| | | this.is_exit = true; |
| | | // 清理定时器 |
| | | if (this.typingTimer) { |
| | | clearTimeout(this.typingTimer); |
| | |
| | | if (this.readTimer) { |
| | | clearTimeout(this.readTimer); |
| | | } |
| | | |
| | | if (this.reconnectTimer) { |
| | | clearTimeout(this.reconnectTimer); |
| | | } |
| | | // 关闭WebSocket |
| | | this.closeWebSocket(); |
| | | }, |
| | |
| | | business_card_id: this.businessCardId |
| | | }, res => { |
| | | this.conversationId = res.data.conversation.conversation_id; |
| | | this.loadMessages(); |
| | | this.wsUrl = res.data.wsUrl; |
| | | // 初始化WebSocket连接 |
| | | this.initWebSocket(); |
| | | }); |
| | | }, |
| | | |
| | |
| | | if (self.page === 1) { |
| | | // First page - sort messages by create_time ascending, newest at bottom |
| | | self.messages = newMessages.sort((a, b) => { |
| | | return new Date(a.create_time) - new Date(b.create_time); |
| | | return a.send_time - b.send_time; |
| | | }); |
| | | } else { |
| | | // For pagination, prepend older messages |
| | | const sortedMessages = newMessages.sort((a, b) => { |
| | | return new Date(a.create_time) - new Date(b.create_time); |
| | | return a.send_time - b.send_time; |
| | | }); |
| | | self.messages = [...sortedMessages, ...self.messages]; |
| | | } |
| | |
| | | self.loadMore = false; |
| | | self.page++; |
| | | if (self.page == 2) { |
| | | console.log(res.data.heUser); |
| | | // 获取目标用户信息 |
| | | this.targetUser = res.data.heUser; |
| | | this.card = res.data.card; |
| | | // 滚动到底部 |
| | | self.$nextTick(() => { |
| | | self.scrollToBottom(); |
| | | }); |
| | | // 标记整个会话为已读 |
| | | if (self.conversationId) { |
| | | self._post('plus.business.chat.chat/markAsRead', { |
| | |
| | | if (msg.sender_id !== self.currentUser.user_id) { |
| | | msg.is_read = true; |
| | | } |
| | | }); |
| | | |
| | | // 滚动到底部 |
| | | self.$nextTick(() => { |
| | | self.scrollToBottom(); |
| | | }); |
| | | } |
| | | }); |
| | |
| | | async sendMessage() { |
| | | let self = this; |
| | | if (!this.inputMessage.trim()) return; |
| | | |
| | | const content = self.inputMessage.trim(); |
| | | |
| | | self._post('plus.business.chat.chat/sendMessage', { |
| | | conversation_id: self.conversationId, |
| | | business_card_id: self.businessCardId, |
| | | content: content |
| | | }, res => { |
| | | if (res.code === 1) { |
| | | // 添加到消息列表 |
| | | self.messages.push(res.data); |
| | | self.inputMessage = ''; |
| | | // 滚动到底部 |
| | | self.$nextTick(() => { |
| | | self.scrollToBottom(); |
| | | }); |
| | | // 发送WebSocket消息 |
| | | self.sendWebSocketMessage(res.data); |
| | | } else { |
| | | self.showError(res.msg || '发送失败'); |
| | | } |
| | | }); |
| | | console.log(content); |
| | | self.sendWebSocketMessage(content); |
| | | }, |
| | | |
| | | // WebSocket相关方法 |
| | |
| | | console.log('WebSocket已连接'); |
| | | return; |
| | | } |
| | | console.log(currentUserId); |
| | | // 获取当前用户ID |
| | | const currentUserId = this.currentUser.user_id; |
| | | |
| | | // 构建WebSocket连接URL |
| | | const wsUrl = `ws://localhost:8282?user_id=${currentUserId}`; |
| | | |
| | | // 构建WebSocket连接URL - 使用名片聊天专用端口2349 |
| | | const wsUrl = `${this.wsUrl}:2349?user_id=${currentUserId}&usertype=supplier `; |
| | | try { |
| | | // 创建WebSocket连接 |
| | | this.socketTask = uni.connectSocket({ |
| | | url: wsUrl, |
| | | success: () => { |
| | | console.log('WebSocket连接成功'); |
| | | }, |
| | | success: () => {}, |
| | | fail: (err) => { |
| | | console.error('WebSocket连接失败:', err); |
| | | // 尝试重连 |
| | | this.reconnectWebSocket(); |
| | | } |
| | | }); |
| | | |
| | | // 监听WebSocket打开事件 |
| | | this.socketTask.onOpen(() => { |
| | | console.log('WebSocket已打开'); |
| | | this.isOnline = true; |
| | | |
| | | this.reconnectAttempts = 0; // 重置重连计数 |
| | | // 发送心跳包 |
| | | this.startHeartbeat(); |
| | | // 绑定用户ID |
| | | this.bindUserId(); |
| | | }); |
| | | |
| | | // 监听WebSocket消息事件 |
| | | this.socketTask.onMessage((res) => { |
| | | console.log('收到WebSocket消息:', res.data); |
| | | |
| | | try { |
| | | const data = JSON.parse(res.data); |
| | | if (data.Online == 'on') { |
| | | this.isOnline = false; |
| | | } else { |
| | | this.isOnline = true; |
| | | } |
| | | this.handleWebSocketMessage(data); |
| | | } catch (error) { |
| | | console.error('解析WebSocket消息失败:', error); |
| | |
| | | this.socketTask.onError((err) => { |
| | | console.error('WebSocket错误:', err); |
| | | this.isOnline = false; |
| | | // 尝试重连 |
| | | this.reconnectWebSocket(); |
| | | }); |
| | | |
| | | // 监听WebSocket关闭事件 |
| | |
| | | clearInterval(this.heartbeatTimer); |
| | | this.heartbeatTimer = null; |
| | | } |
| | | |
| | | // 尝试重连 |
| | | this.reconnectWebSocket(); |
| | | }); |
| | | |
| | | } catch (error) { |
| | | console.error('初始化WebSocket失败:', error); |
| | | // 尝试重连 |
| | | this.reconnectWebSocket(); |
| | | } |
| | | }, |
| | | |
| | | // 绑定用户ID |
| | | bindUserId() { |
| | | if (this.socketTask && this.currentUser.user_id) { |
| | | const bindMsg = { |
| | | type: 'bind', |
| | | user_id: this.currentUser.user_id |
| | | }; |
| | | try { |
| | | this.socketTask.send({ |
| | | data: JSON.stringify(bindMsg) |
| | | }); |
| | | console.log('用户ID绑定消息已发送'); |
| | | } catch (error) { |
| | | console.log('发送用户ID绑定消息失败:', error); |
| | | } |
| | | } |
| | | }, |
| | | |
| | | // WebSocket重连机制 |
| | | reconnectWebSocket() { |
| | | if (this.is_exit) { |
| | | console.log(this.is_exit); |
| | | return false; |
| | | } |
| | | this.reconnectAttempts++; |
| | | console.log(`尝试第${this.reconnectAttempts}次重连WebSocket`); |
| | | |
| | | // 清除之前的重连定时器 |
| | | if (this.reconnectTimer) { |
| | | clearTimeout(this.reconnectTimer); |
| | | } |
| | | |
| | | // 设置重连延迟,指数退避策略 |
| | | const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 10000); |
| | | this.reconnectTimer = setTimeout(() => { |
| | | this.initWebSocket(); |
| | | }, delay); |
| | | }, |
| | | |
| | | // 处理WebSocket消息 |
| | |
| | | case 'message': |
| | | // 收到新消息 |
| | | this.handleNewMessage(data); |
| | | break; |
| | | case 'read': |
| | | // 收到已读确认 |
| | | this.handleReadMessage(data); |
| | | break; |
| | | case 'ping': |
| | | // 心跳响应 |
| | |
| | | // 处理新消息 |
| | | handleNewMessage(data) { |
| | | // 检查消息是否属于当前会话 |
| | | if (data.business_card_id === this.businessCardId || |
| | | if ((data.business_card_id && data.business_card_id === this.businessCardId) || |
| | | (data.conversation_id && data.conversation_id === this.conversationId)) { |
| | | |
| | | // 添加消息到列表 |
| | |
| | | conversation_id: data.conversation_id, |
| | | sender_id: data.user_id, |
| | | content: data.content, |
| | | create_time: Math.floor(Date.now() / 1000), |
| | | send_time: data.send_time, |
| | | create_time: data.create_time || Math.floor(Date.now() / 1000), |
| | | is_read: false |
| | | }; |
| | | |
| | |
| | | setTimeout(() => { |
| | | // 通过WebSocket发送已读标记 |
| | | this.markMessageAsRead(newMessage.chat_id); |
| | | |
| | | // 调用后端API标记消息为已读 |
| | | this._post('plus.business.chat.chat/markAsRead', { |
| | | chat_id: newMessage.chat_id, |
| | |
| | | } |
| | | }, |
| | | |
| | | // 处理已读消息 |
| | | handleReadMessage(data) { |
| | | // 筛选出当前用户发送的、尚未标记为已读的消息 |
| | | const unreadMessages = this.messages.filter(msg => { |
| | | return msg.sender_id === this.currentUser.user_id && !msg.is_read; |
| | | }); |
| | | if (unreadMessages.length === 0) { |
| | | console.log('没有需要标记已读的消息'); |
| | | return; |
| | | } |
| | | console.log(`找到 ${unreadMessages.length} 条未读消息,开始批量标记`); |
| | | // 对每条未读消息调用markMessageAsRead方法 |
| | | unreadMessages.forEach(msg => { |
| | | this.markMessageAsRead(msg.chat_id); |
| | | // 同时更新本地消息状态 |
| | | msg.is_read = true; |
| | | }); |
| | | console.log('所有未读消息已标记为已读'); |
| | | }, |
| | | sendMessages(message){ |
| | | if(message==''){ |
| | | return |
| | | } |
| | | this._post('plus.business.chat.chat/sendMessage',{ |
| | | content:this.inputMessage, |
| | | conversation_id: this.conversationId, |
| | | business_card_id: this.businessCardId, |
| | | },res=>{ |
| | | // 添加消息到本地列表 |
| | | const localMessage = { |
| | | chat_id: Date.now(), |
| | | conversation_id: this.conversationId, |
| | | sender_id: this.currentUser.user_id, |
| | | content: this.inputMessage, |
| | | create_time: Math.floor(Date.now() / 1000), |
| | | send_time: (Date.now() / 1000), |
| | | is_read: false |
| | | }; |
| | | this.messages.push(localMessage); |
| | | this.inputMessage = ''; |
| | | }) |
| | | }, |
| | | // 发送WebSocket消息 |
| | | sendWebSocketMessage(message) { |
| | | if (!this.socketTask || !this.isOnline) { |
| | | console.warn('WebSocket未连接,无法发送消息'); |
| | | let self = this; |
| | | if (!this.socketTask) { |
| | | self.initWebSocket(); |
| | | self.sendMessages(message); |
| | | // 等待连接建立后再发送消息 |
| | | setTimeout(() => { |
| | | self.sendWebSocketMessage(''); |
| | | }, 1000); |
| | | return; |
| | | } |
| | | |
| | |
| | | type: 'message', |
| | | user_id: this.currentUser.user_id, |
| | | to_user_id: this.targetUserId, |
| | | content: message.content, |
| | | conversation_id: this.conversationId, |
| | | business_card_id: this.businessCardId, |
| | | chat_id: message.chat_id |
| | | content: message, |
| | | app_id: this.currentUser.grade.app_id |
| | | }; |
| | | |
| | | // 检查连接状态 |
| | | if (!this.isOnline) { |
| | | self.initWebSocket(); |
| | | // 等待连接建立后再发送消息 |
| | | self.sendMessages(message); |
| | | setTimeout(() => { |
| | | self.sendWebSocketMessage(''); |
| | | }, 1000); |
| | | return; |
| | | } |
| | | if(message==''){ |
| | | return |
| | | } |
| | | try { |
| | | this.socketTask.send({ |
| | | self.socketTask.send({ |
| | | data: JSON.stringify(wsMessage) |
| | | }); |
| | | console.log('WebSocket消息发送成功:', wsMessage); |
| | | // 添加消息到本地列表 |
| | | const localMessage = { |
| | | chat_id: Date.now(), |
| | | conversation_id: this.conversationId, |
| | | sender_id: this.currentUser.user_id, |
| | | content: message, |
| | | create_time: Math.floor(Date.now() / 1000), |
| | | send_time: (Date.now() / 1000), |
| | | is_read: false |
| | | }; |
| | | self.messages.push(localMessage); |
| | | self.inputMessage = ''; |
| | | |
| | | // 滚动到底部 - 使用更强的保证方式 |
| | | this.$nextTick(() => { |
| | | // 延迟一小段时间确保DOM更新完成 |
| | | setTimeout(() => { |
| | | this.scrollToBottom(); |
| | | }, 50); |
| | | }); |
| | | } catch (error) { |
| | | console.error('发送WebSocket消息失败:', error); |
| | | console.log('发送WebSocket消息失败:', error); |
| | | // 尝试重连 |
| | | this.reconnectWebSocket(); |
| | | } |
| | | }, |
| | | |
| | |
| | | user_id: this.currentUser.user_id, |
| | | to_user_id: this.targetUserId |
| | | }; |
| | | |
| | | try { |
| | | this.socketTask.send({ |
| | | data: JSON.stringify(heartbeatMsg) |
| | | }); |
| | | } catch (error) { |
| | | console.error('发送心跳失败:', error); |
| | | console.log('发送心跳失败:', error); |
| | | } |
| | | } |
| | | }, 30000); // 30秒 |
| | |
| | | chat_id: chatId, |
| | | conversation_id: this.conversationId |
| | | }; |
| | | |
| | | console.log(readMsg); |
| | | try { |
| | | this.socketTask.send({ |
| | | data: JSON.stringify(readMsg) |
| | | }); |
| | | } catch (error) { |
| | | console.error('发送已读标记失败:', error); |
| | | console.log('发送已读标记失败:', error); |
| | | } |
| | | }, |
| | | |
| | |
| | | // 发送关闭消息 |
| | | const closeMsg = { |
| | | type: 'close', |
| | | user_id: this.currentUser.user_id |
| | | user_id: this.currentUser.user_id, |
| | | to_user_id: this.targetUserId |
| | | }; |
| | | |
| | | try { |
| | |
| | | data: JSON.stringify(closeMsg) |
| | | }); |
| | | } catch (error) { |
| | | console.error('发送关闭消息失败:', error); |
| | | console.log('发送关闭消息失败:', error); |
| | | } |
| | | |
| | | // 关闭WebSocket连接 |
| | |
| | | } |
| | | |
| | | this.isOnline = false; |
| | | console.log('WebSocket连接已关闭'); |
| | | }, |
| | | |
| | | // 滚动相关方法 |
| | | scrollToBottom() { |
| | | this.$nextTick(() => { |
| | | this.scrollTop = this.oldScrollTop + 1; |
| | | }); |
| | | console.log(this.scrollTop); |
| | | // 增加滚动距离确保到达底部 |
| | | this.scrollTop = this.oldScrollTop + 10000; |
| | | }, |
| | | |
| | | onScroll(e) { |
| | |
| | | if (!timeStr) return ''; |
| | | |
| | | // 处理日期字符串,将yyyy-MM-dd转换为yyyy/MM/dd以兼容iOS |
| | | const formattedTimeStr = typeof timeStr === 'string' ? timeStr.replace(/\-/g, '/') : timeStr; |
| | | const formattedTimeStr = typeof timeStr === 'string' ? timeStr.replace(/\-/g, '/') : timeStr * 1000; |
| | | const date = new Date(formattedTimeStr); |
| | | const now = new Date(); |
| | | |