diff --git a/app.json b/app.json index 19a3480..915dcf3 100644 --- a/app.json +++ b/app.json @@ -23,7 +23,8 @@ "pages/noticeListDetails/noticeListDetails", "pages/todayInquiry/todayInquiry", "pages/carouselDetail/carouselDetail", - "pages/releaseSuffer/releaseSuffer" + "pages/releaseSuffer/releaseSuffer", + "pages/chatting/chatting" ] }, { diff --git a/pages/personal/personal.js b/pages/personal/personal.js index e2c4256..1604b89 100644 --- a/pages/personal/personal.js +++ b/pages/personal/personal.js @@ -342,7 +342,7 @@ Page({ // 查看问答消息 goToQA() { wx.navigateTo({ - url: '' // 请填写实际的问答消息页面路径 + url: '/pagesA/pages/chatting/chatting' }) }, diff --git a/pagesA/pages/chatting/chatting.js b/pagesA/pages/chatting/chatting.js new file mode 100644 index 0000000..0622692 --- /dev/null +++ b/pagesA/pages/chatting/chatting.js @@ -0,0 +1,115 @@ +import http from '../../../utils/api'; +const baseUrl = require('../../../utils/baseUrl'); + +Page({ + /** + * 页面的初始数据 + */ + data: { + baseUrl: baseUrl, + chatSessions: [] // 存储聊天列表数据 + }, + + /** + * 生命周期函数--监听页面加载 + */ + onLoad(options) { + this.getsessions(); + }, + + // 获取聊天列表 + getsessions() { + http.sessions({ + data: {}, + success: (res) => { + console.log('接口返回数据:', res); + if (res.code === 200 && Array.isArray(res.data)) { + // 预处理数据:为每条数据添加格式化后的时间字段 + const formattedSessions = res.data.map(item => { + return { + ...item, + formattedTime: this.formatTime(item.lastMessageTime) // 新增字段,在JS里直接格式化好 + }; + }); + + this.setData({ + chatSessions: formattedSessions + }); + } else { + console.error('数据格式错误或接口异常', res); + } + }, + fail: (err) => { + console.error('请求失败', err); + } + }); + }, + + // 格式化时间显示 - 兼容多种格式 + formatTime(timeValue) { + console.log('时间原始值:', timeValue); + + if (!timeValue) return ''; + + let date; + + // 判断是否是时间戳(数字或数字字符串) + if (typeof timeValue === 'number' || (typeof timeValue === 'string' && /^\d+$/.test(timeValue))) { + // 如果是时间戳,直接使用 + date = new Date(Number(timeValue)); + } + // 判断是否是日期字符串 + else if (typeof timeValue === 'string') { + // 尝试解析字符串日期 + // 将 '2026-03-06 15:50:11' 替换为 '2026/03/06 15:50:11' (兼容 iOS) + const formattedStr = timeValue.replace(/-/g, '/'); + date = new Date(formattedStr); + } + else { + console.log('未知的时间格式:', timeValue); + return ''; + } + + // 检查日期是否有效 + if (isNaN(date.getTime())) { + console.log('无效的日期:', timeValue); + return ''; + } + + const now = new Date(); + const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime(); + const yesterday = today - 24 * 60 * 60 * 1000; + const targetDate = new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime(); + + if (targetDate === today) { + // 今天: 显示 时:分 + const hours = date.getHours().toString().padStart(2, '0'); + const minutes = date.getMinutes().toString().padStart(2, '0'); + return `${hours}:${minutes}`; + } else if (targetDate === yesterday) { + return '昨天'; + } else { + // 更早: 显示 月-日 + const month = (date.getMonth() + 1).toString().padStart(2, '0'); + const day = date.getDate().toString().padStart(2, '0'); + return `${month}-${day}`; + } + }, + + // 跳转到聊天详情页面 + goToChat(e) { + console.log(e); + const id = e.currentTarget.dataset.id + wx.navigateTo({ + url: `/pagesA/pages/expertChat/expertChat?id=${id}` + }); + }, + + /** + * 页面相关事件处理函数--监听用户下拉动作 + */ + onPullDownRefresh() { + this.getsessions(); + wx.stopPullDownRefresh(); + } +}); \ No newline at end of file diff --git a/pagesA/pages/chatting/chatting.json b/pagesA/pages/chatting/chatting.json new file mode 100644 index 0000000..aabdd9c --- /dev/null +++ b/pagesA/pages/chatting/chatting.json @@ -0,0 +1,4 @@ +{ + "navigationBarTitleText":"聊天列表", + "usingComponents": {} +} \ No newline at end of file diff --git a/pagesA/pages/chatting/chatting.wxml b/pagesA/pages/chatting/chatting.wxml new file mode 100644 index 0000000..8a5c81b --- /dev/null +++ b/pagesA/pages/chatting/chatting.wxml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + {{item.otherUserName || '未知用户'}} + {{item.formattedTime}} + + + + {{item.lastMessage || '[暂无消息]'}} + + + {{item.lastMessage}} + + + + + + + + + 暂无聊天记录 + + \ No newline at end of file diff --git a/pagesA/pages/chatting/chatting.wxss b/pagesA/pages/chatting/chatting.wxss new file mode 100644 index 0000000..39474b6 --- /dev/null +++ b/pagesA/pages/chatting/chatting.wxss @@ -0,0 +1,111 @@ +/* 页面背景 - 微信灰 */ +.chat-list { + background-color: #ededed; + min-height: 100vh; + } + + /* 列表项 - 微信白色卡片 */ + .list-item { + display: flex; + padding: 10px 15px; + background-color: #ffffff; + position: relative; + align-items: center; /* 关键:垂直居中对齐 */ + } + + /* 微信没有点击变色效果,如果要加可以保留,不加可以删除 */ + .list-item:active { + background-color: #f4f4f4; + } + + /* 头像区域 - 微信45px,固定大小不变 */ + .avatar { + width: 45px; + height: 45px; + margin-right: 12px; + flex-shrink: 0; + } + + .avatar image { + width: 100%; + height: 100%; + border-radius: 6px; + background-color: #f0f0f0; + } + + /* 内容区域 - 占据剩余空间,垂直居中 */ + .content { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + justify-content: center; /* 内容垂直居中 */ + height: 100%; /* 占满父容器高度 */ + } + + /* 第一行:用户名和时间 */ + .info-row { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + line-height: 1.2; /* 控制行高 */ + } + + /* 用户名 - 微信字体16px,粗体,不换行 */ + .username { + font-size: 16px; + font-weight: bold; + color: #333333; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + flex: 1; /* 自动伸缩 */ + min-width: 0; /* 防止溢出 */ + } + + /* 时间 - 微信浅灰色小字,固定宽度不换行 */ + .time { + font-size: 12px; + color: #b3b3b3; + white-space: nowrap; + margin-left: 8px; + } + + /* 第二行:最后一条消息 */ + .message-row { + display: flex; + align-items: center; + width: 100%; + margin-top: 16rpx; + } + + /* 消息基础样式 - 微信14px灰色 */ + .last-message { + font-size: 14px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + flex: 1; /* 自动伸缩 */ + min-width: 0; /* 防止溢出 */ + } + + /* 正常消息 - 微信灰色 #8a8a8a */ + .last-message.normal { + color: #9E9E9E; + } + + /* 空消息 - 微信红色 */ + .last-message.empty { + color: #fa5151; + } + + /* 空状态页面 */ + .empty-state { + display: flex; + justify-content: center; + align-items: center; + height: 80vh; + color: #b3b3b3; + font-size: 15px; + } \ No newline at end of file diff --git a/pagesA/pages/expert/expert.js b/pagesA/pages/expert/expert.js index b47fba4..c612560 100644 --- a/pagesA/pages/expert/expert.js +++ b/pagesA/pages/expert/expert.js @@ -9,151 +9,7 @@ Page({ currentFilter: '全部', // 所有专家数据 - allExperts: [{ - id: 1, - name: '张建国', - title: '畜牧学博士', - expertise: '牛类养殖与疾病防治', - experience: 15, - online: true, - senior: true, - phone: '138-0013-8001', - email: 'zhangjg@agri-expert.com', - institution: '国家畜牧业研究所', - address: '北京市朝阳区农业科技大厦', - bio: '15年牛类养殖研究经验,发表SCI论文20余篇,擅长规模化养殖场管理与疾病防控。', - tags: ['肉牛养殖', '疾病预防', '饲料配方', '规模化养殖'], - successRate: 98, - responseTime: '15分钟内响应', - avatar: '/pagesA/images/1.png' - }, - { - id: 2, - name: '李秀英', - title: '兽医学硕士', - expertise: '猪病诊断与治疗', - experience: 12, - online: true, - senior: true, - phone: '139-0013-9002', - email: 'lixy@vet-hospital.cn', - institution: '农业大学动物医学院', - address: '上海市徐汇区农业路58号', - bio: '资深猪病防治专家,参与多项国家级科研项目,成功解决多个大规模猪场疫情。', - tags: ['猪病诊疗', '疫苗管理', '养殖场规划', '传染病防治'], - successRate: 96, - responseTime: '20分钟内响应', - avatar: '/pagesA/images/2.png' - }, - { - id: 3, - name: '王伟民', - title: '高级畜牧师', - expertise: '羊类繁殖与饲养管理', - experience: 20, - online: false, - senior: true, - phone: '137-0013-7003', - email: 'wangwm@livestock.cn', - institution: '草原畜牧业研究中心', - address: '内蒙古呼和浩特市牧业科技园', - bio: '羊类养殖领域权威专家,拥有20年草原牧区养殖经验,精通各类羊只繁殖技术。', - tags: ['绵羊养殖', '山羊繁殖', '牧草种植', '草原管理'], - successRate: 95, - responseTime: '1小时内响应', - avatar: '/pagesA/images/3.png' - }, - { - id: 4, - name: '陈秀兰', - title: '家禽养殖专家', - expertise: '鸡鸭养殖技术', - experience: 18, - online: true, - senior: false, - phone: '136-0013-6004', - email: 'chenxl@poultry-expert.com', - institution: '家禽养殖技术中心', - address: '广东省广州市禽业科技园', - bio: '家禽养殖技术推广专家,帮助300+养殖户实现科学化、标准化养殖管理。', - tags: ['蛋鸡养殖', '肉鸭饲养', '疾病防控', '禽舍设计'], - successRate: 94, - responseTime: '30分钟内响应', - avatar: '/pagesA/images/1.png' - }, - { - id: 5, - name: '刘志强', - title: '特种养殖专家', - expertise: '鹿、兔等特种动物养殖', - experience: 10, - online: false, - senior: false, - phone: '135-0013-5005', - email: 'liuzq@special-livestock.cn', - institution: '特种动物养殖研究所', - address: '四川省成都市农业科技园', - bio: '特种动物养殖新兴力量,专注于梅花鹿、肉兔等特种动物的现代化养殖技术。', - tags: ['梅花鹿养殖', '肉兔饲养', '市场分析', '特色养殖'], - successRate: 92, - responseTime: '2小时内响应', - avatar: '/pagesA/images/2.png' - }, - { - id: 6, - name: '赵雪梅', - title: '畜牧营养学博士', - expertise: '牲畜饲料配方与营养', - experience: 14, - online: true, - senior: true, - phone: '134-0013-4006', - email: 'zhaoxm@feed-research.cn', - institution: '农业科学院饲料研究所', - address: '江苏省南京市科研路88号', - bio: '饲料营养学专家,研发多个高效饲料配方,帮助养殖户降低30%饲料成本。', - tags: ['饲料配方', '营养管理', '成本控制', '饲料安全'], - successRate: 97, - responseTime: '25分钟内响应', - avatar: '/pagesA/images/3.png' - }, - { - id: 7, - name: '孙建国', - title: '养殖场管理专家', - expertise: '现代化养殖场设计与运营', - experience: 22, - online: true, - senior: true, - phone: '133-0013-3007', - email: 'sunjg@farm-management.com', - institution: '现代农业发展中心', - address: '浙江省杭州市创新产业园', - bio: '养殖场规划设计专家,参与设计200+现代化养殖场,精通自动化养殖设备应用。', - tags: ['场舍设计', '设备管理', '成本核算', '环保处理'], - successRate: 99, - responseTime: '10分钟内响应', - avatar: '/pagesA/images/2.png' - }, - { - id: 8, - name: '周小芳', - title: '兽医病理学硕士', - expertise: '牲畜常见病与传染病防治', - experience: 11, - online: false, - senior: false, - phone: '132-0013-2008', - email: 'zhouxf@vet-pathology.cn', - institution: '动物疫病防控中心', - address: '湖北省武汉市动物防疫站', - bio: '专攻牲畜病理诊断与传染病防控,在快速诊断和精准治疗方面有丰富经验。', - tags: ['传染病防治', '病理诊断', '用药指导', '疫情处理'], - successRate: 93, - responseTime: '1.5小时内响应', - avatar: '/pagesA/images/3.png' - } - ], + allExperts: [], baseUr: baseUr, formzj: { diff --git a/pagesA/pages/expertChat/expertChat.js b/pagesA/pages/expertChat/expertChat.js index ec2bc04..e884140 100644 --- a/pagesA/pages/expertChat/expertChat.js +++ b/pagesA/pages/expertChat/expertChat.js @@ -55,6 +55,8 @@ Page({ pageSize: 20, hasMore: true, total: 0, + // 记录最早消息的时间戳,用于加载更早的消息 + earliestMsgTime: 0, // 时间显示间隔 timeInterval: 5, @@ -66,7 +68,18 @@ Page({ // WebSocket状态 socketConnected: false, reconnectCount: 0, - maxReconnectCount: 5 + maxReconnectCount: 5, + + // 第一次加载完成标记 + firstLoadComplete: false, + // 是否有新消息 + hasNewMessage: false, + + // 消息ID集合,用于去重 + messageIds: new Set(), + + // 正在发送中的消息ID集合 + sendingMessages: new Set() }, onLoad: function(options) { @@ -82,7 +95,15 @@ Page({ if (options.id) { this.setData({ otherUserId: options.id, - currentExpertId: options.id + currentExpertId: options.id, + // 初始页码设置为1 + page: 1, + messageList: [], + hasMore: true, + earliestMsgTime: 0, + firstLoadComplete: false, + messageIds: new Set(), + sendingMessages: new Set() }); // 先创建对话,然后获取聊天记录 @@ -164,14 +185,21 @@ Page({ this.setData({ conversationId: conversationData.id || conversationData.conversationId || id, conversation: conversationData, - otherUserId: id // 确保对方用户ID正确 + otherUserId: id, + // 重置分页参数 + page: 1, + hasMore: true, + messageList: [], + earliestMsgTime: 0, + firstLoadComplete: false, + messageIds: new Set(), + sendingMessages: new Set() }); + // 获取聊天记录(从第一页开始) + this.getChatHistory(id, false); - // 获取聊天记录 - this.getChatHistory(id); - - // 连接WebSocket(确保用户ID已加载) + // 连接WebSocket const userId = this.getCurrentUserId(); if (userId) { console.log('用户ID已存在,立即连接WebSocket'); @@ -199,50 +227,83 @@ Page({ }, // 获取聊天记录 - getChatHistory(id) { + 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: { - otherUserId: id, - page: this.data.page, - pageSize: this.data.pageSize - }, + data: params, success: res => { console.log('获取聊天记录响应:', res); - wx.hideLoading(); + + if (!isLoadMore) { + wx.hideLoading(); + } if (res && res.code === 200) { // 处理返回的消息数据 - this.processChatHistory(res); + this.processChatHistory(res, isLoadMore); } else { // 如果没有聊天记录,显示空状态 this.setData({ messageList: [], loading: false, - hasMore: false + loadingMore: false, + hasMore: false, + firstLoadComplete: true }); + + if (!isLoadMore) { + wx.showToast({ + title: res?.msg || '获取聊天记录失败', + icon: 'none' + }); + } } }, fail: err => { console.error('获取聊天记录失败:', err); - wx.hideLoading(); + + if (!isLoadMore) { + wx.hideLoading(); + } + wx.showToast({ - title: '获取聊天记录失败', + title: '网络错误', icon: 'none' }); this.setData({ messageList: [], - loading: false + loading: false, + loadingMore: false, + firstLoadComplete: true }); } }); }, // 处理聊天历史数据 - processChatHistory(response) { + processChatHistory(response, isLoadMore = false) { let messages = []; let total = 0; - let hasMore = false; // 根据实际返回的数据结构调整这里 if (response.rows && Array.isArray(response.rows)) { @@ -256,26 +317,79 @@ Page({ total = messages.length; } - // 格式化消息数据,适配页面渲染 - const formattedMessages = this.formatMessages(messages); + console.log('处理消息数据:', { + messageCount: messages.length, + total, + isLoadMore, + currentPage: this.data.page + }); - // 处理消息时间显示 - const processedMessages = this.processMessageTimes(formattedMessages); + // 格式化消息数据 + const formattedMessages = this.formatMessages(messages); // 判断是否还有更多数据 - hasMore = this.data.page * this.data.pageSize < total; + 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 + total: total, + earliestMsgTime: earliestTime, + firstLoadComplete: true }, () => { - // 滚动到底部 - setTimeout(() => { - this.scrollToBottom(true); - }, 100); + if (isLoadMore) { + // 加载更多后,调整滚动位置以保持在当前查看的位置 + setTimeout(() => { + this.setData({ + scrollAnimate: false, + scrollTop: targetScrollTop + }, () => { + setTimeout(() => { + this.setData({ scrollAnimate: true }); + }, 100); + }); + }, 50); + } else { + // 首次加载,滚动到底部(显示最新消息) + setTimeout(() => { + this.scrollToBottom(false); + }, 100); + } }); }, @@ -313,7 +427,43 @@ Page({ }); }, - + // 加载更多消息 + 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() { @@ -365,18 +515,6 @@ Page({ } }, - // 加载更多消息 - loadMoreMessages: function() { - if (this.data.loadingMore || !this.data.hasMore || !this.data.otherUserId) return; - - this.setData({ - loadingMore: true, - page: this.data.page + 1 - }, () => { - this.getChatHistory(this.data.otherUserId); - }); - }, - // ========== WebSocket相关方法 ========== // 连接WebSocket @@ -507,7 +645,7 @@ Page({ reconnectCount: this.data.reconnectCount + 1 }); this.connectWebSocket(); - }, 3000); // 3秒后重连 + }, 3000); }, // 开始心跳 @@ -525,7 +663,7 @@ Page({ timestamp: Date.now() })); } - }, 30000); // 30秒发送一次心跳 + }, 30000); }, // 发送WebSocket消息 @@ -538,16 +676,13 @@ Page({ }, fail: (err) => { console.error('WebSocket消息发送失败:', err); - // 加入队列,等待重发 socketMsgQueue.push(data); } }); } else { console.log('WebSocket未连接,消息加入队列'); - // 队列存储 socketMsgQueue.push(data); - // 尝试重连 if (!isManualClose) { this.connectWebSocket(); } @@ -559,29 +694,24 @@ Page({ console.log('处理消息类型:', data.type); switch (data.type) { - case 'chat': // 聊天消息 - case 'message': // 兼容其他类型 - // 新消息 + 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; @@ -590,101 +720,129 @@ Page({ } }, - // 处理新消息 + // 处理新消息 handleNewMessage(data) { - console.log('处理新消息:', data); + console.log('处理新消息 - 完整数据:', data); - // 获取当前用户ID - const currentUserId = this.getCurrentUserId(); + // 提取消息内容 + let messageData = data.data || data; - // 检查消息是否属于当前对话 - const conversationId = data.conversationId || data.chatId || data.roomId; - const senderId = data.senderId || data.fromUserId || data.userId; - const receiverId = data.receiverId || data.toUserId; - - console.log('消息归属检查:', { - currentConversationId: this.data.conversationId, - messageConversationId: conversationId, - currentUserId: currentUserId, - senderId: senderId, - receiverId: receiverId, - otherUserId: this.data.otherUserId - }); + // 获取消息ID + const messageId = messageData.id || messageData.messageId || 'msg_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); - // 判断是否属于当前对话 - let isCurrentConversation = false; + console.log('消息ID:', messageId); + console.log('当前发送中消息集合:', this.data.sendingMessages); - if (conversationId && conversationId === this.data.conversationId) { - isCurrentConversation = true; - } else if (!conversationId) { - // 如果没有对话ID,则根据发送者和接收者判断 - // 消息发送者是我,接收者是对方 - if (senderId === currentUserId && receiverId === this.data.otherUserId) { - isCurrentConversation = true; - } - // 消息发送者是对方,接收者是我 - else if (senderId === this.data.otherUserId && receiverId === currentUserId) { - isCurrentConversation = true; - } + // 检查是否是自己发送的消息(通过sendingMessages集合判断) + if (this.data.sendingMessages.has(messageId)) { + console.log('收到自己发送的消息回执,跳过处理:', messageId); + + // 从发送中集合移除 + const sendingMessages = new Set(this.data.sendingMessages); + sendingMessages.delete(messageId); + this.setData({ sendingMessages: sendingMessages }); + + // 更新本地消息状态为成功 + this.updateMessageStatus(messageId, 'success'); + return; } - if (!isCurrentConversation) { - console.log('消息不属于当前对话,忽略'); + // 检查消息是否已存在(去重) + if (this.data.messageIds.has(messageId)) { + console.log('消息已存在,跳过处理:', messageId); return; } - // 判断发送者是否是当前用户 - const isMe = senderId === currentUserId; + const userId = this.getCurrentUserId(); + const senderId = messageData.senderId || messageData.fromUserId || messageData.userId; + + // 判断是否是自己发送的消息 + const isMe = senderId === userId; - console.log('消息归属判断结果:', { isMe, isCurrentConversation }); + console.log('消息归属判断:', { + senderId: senderId, + userId: userId, + isMe: isMe, + messageId: messageId + }); - // 如果是自己发送的消息,检查是否已存在 + // 如果是自己发送的消息但不在sendingMessages中,说明是通过其他方式发送的 + // 也跳过处理,避免重复 if (isMe) { - // 检查是否已存在相同ID的消息 - const exists = this.data.messageList.some(msg => - msg.id === data.messageId || msg.id === data.id - ); - if (exists) { - console.log('消息已存在,忽略重复添加'); - return; - } + console.log('是自己发送的消息,但不在发送中集合,可能已存在,跳过处理'); + return; } - + + // 创建新消息对象 const newMessage = { - id: data.messageId || data.id || 'msg_' + Date.now() + Math.random(), + id: messageId, isMe: isMe, sender: isMe ? 'user' : 'expert', senderId: senderId, - contentType: data.contentType || 'text', - type: data.contentType || 'text', - content: data.content, - timestamp: data.timestamp || Date.now(), - status: 'success' + contentType: messageData.contentType || messageData.type || 'text', + type: messageData.type || messageData.contentType || 'text', + content: messageData.content || '', + timestamp: messageData.timestamp || Date.now(), + status: 'success', + progress: 100 }; - - console.log('添加新消息到列表:', newMessage); - - // 处理消息时间显示 - const { messageList } = this.data; - const processedMessage = this.processSingleMessageTime(newMessage, messageList); - // 添加新消息 - messageList.push(processedMessage); + console.log('创建的新消息对象:', newMessage); - this.setData({ messageList }, () => { - this.scrollToBottom(); + // 获取当前消息列表 + 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); - // 如果不是自己发的消息,发送已读回执 - if (!newMessage.isMe) { - this.sendReadReceipt(newMessage.id); - - // 播放提示音 - this.playNotificationSound(); - - // 震动提示 - wx.vibrateShort({ - type: 'light' - }); + // 判断是否需要滚动到底部 + 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) { + // wx.vibrateShort({ type: 'light' }); + // 发送已读回执 + this.sendReadReceipt(messageId); } }); }, @@ -699,21 +857,24 @@ Page({ messageList[index].status = data.status || 'success'; this.setData({ messageList }); } + + // 如果收到成功状态,从发送中集合移除 + if (data.status === 'success') { + const sendingMessages = new Set(this.data.sendingMessages); + sendingMessages.delete(messageId); + this.setData({ sendingMessages: sendingMessages }); + } }, // 处理正在输入状态 handleTypingStatus(data) { const userId = data.userId || data.senderId; if (userId === this.data.otherUserId) { - this.setData({ - 'expertInfo.typing': true - }); + this.setData({ 'expertInfo.typing': true }); clearTimeout(this.typingTimer); this.typingTimer = setTimeout(() => { - this.setData({ - 'expertInfo.typing': false - }); + this.setData({ 'expertInfo.typing': false }); }, 3000); } }, @@ -728,7 +889,7 @@ Page({ } }, - // 发送消息 - 修复用户ID获取 + // 发送消息到服务器 sendMessageToServer: function(content, type, messageId) { const receiverId = this.data.otherUserId; const senderId = this.getCurrentUserId(); @@ -747,16 +908,25 @@ Page({ title: '发送失败,用户信息错误', icon: 'none' }); + + // 发送失败,从发送中集合移除 + const sendingMessages = new Set(this.data.sendingMessages); + sendingMessages.delete(messageId); + this.setData({ sendingMessages: sendingMessages }); + + // 更新消息状态为失败 + this.updateMessageStatus(messageId, 'failed'); return; } const message = { - type: 'chat', // 指定为chat类型 - receiverId: receiverId, // 接收者ID,就是对方的ID - senderId: senderId, // 发送者ID - content: content, // 消息内容 - contentType: type || 'text', // 内容类型 - timestamp: Date.now() + type: 'chat', + receiverId: receiverId, + senderId: senderId, + content: content, + contentType: type || 'text', + timestamp: Date.now(), + messageId: messageId }; const messageStr = JSON.stringify(message); @@ -769,10 +939,7 @@ Page({ icon: 'none' }); - // 尝试重连 this.connectWebSocket(); - - // 加入队列 socketMsgQueue.push(messageStr); return; } @@ -810,7 +977,7 @@ Page({ // ========== 消息发送相关方法 ========== - // 发送文本消息 + // 发送文本消息 sendTextMessage: function() { const content = this.data.inputValue.trim(); if (!content) return; @@ -819,6 +986,10 @@ Page({ console.log('发送文本消息,ID:', messageId); + // 将消息ID添加到发送中集合 + const sendingMessages = new Set(this.data.sendingMessages); + sendingMessages.add(messageId); + // 创建本地消息 const newMessage = { id: messageId, @@ -835,18 +1006,32 @@ Page({ // 添加到列表 this.addMessageToList(newMessage); + // 更新发送中集合 + this.setData({ sendingMessages: sendingMessages }); + // 清空输入框 - this.setData({ - inputValue: '' - }); + this.setData({ inputValue: '' }); // 通过WebSocket发送到服务器 this.sendMessageToServer(content, 'text', messageId); - // 模拟发送成功回调(实际应该通过WebSocket接收消息状态) + // 设置超时,如果5秒后还没有收到回执,认为发送失败 setTimeout(() => { - this.updateMessageStatus(messageId, 'success'); - }, 500); + 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 }); + + wx.showToast({ + title: '发送超时', + icon: 'none' + }); + } + }, 5000); }, // 上传图片后发送 @@ -866,6 +1051,11 @@ Page({ // 通用文件上传 uploadFile: function(tempFilePath, fileName, fileSize = 0, type = '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, @@ -883,10 +1073,11 @@ Page({ }; this.addMessageToList(message); + this.setData({ sendingMessages: sendingMessages }); this.simulateUpload(messageId, type, tempFilePath); }, - // 上传 + // 模拟上传 simulateUpload: function(messageId, type, tempFilePath) { let progress = 0; const uploadInterval = setInterval(() => { @@ -897,7 +1088,6 @@ Page({ clearInterval(uploadInterval); setTimeout(() => { - // 上传完成,更新消息状态 this.updateMessageStatus(messageId, 'success'); const { messageList } = this.data; @@ -906,9 +1096,7 @@ Page({ delete messageList[index].progress; this.setData({ messageList }); - // 上传完成后,通过WebSocket发送消息 - - const fileUrl = tempFilePath; // 上传后的URL + const fileUrl = tempFilePath; this.sendMessageToServer(fileUrl, type, messageId); } }, 200); @@ -925,12 +1113,27 @@ Page({ // 添加消息到列表 addMessageToList: function(message) { - const { messageList } = this.data; - const processedMessage = this.processSingleMessageTime(message, messageList); + const { messageList, messageIds } = this.data; + + // 检查是否已存在 + if (messageIds.has(message.id)) { + console.log('消息已存在,不重复添加:', message.id); + return; + } - messageList.push(processedMessage); + // 新消息添加到末尾 + messageList.push(message); - this.setData({ messageList }, () => { + // 更新消息ID集合 + messageIds.add(message.id); + + // 重新处理时间显示 + const processedMessages = this.processMessageTimes(messageList); + + this.setData({ + messageList: processedMessages, + messageIds: messageIds + }, () => { this.scrollToBottom(); }); }, @@ -946,15 +1149,10 @@ Page({ } }, - - - // 输入处理(带防抖) + // 输入处理 onInput: function(e) { - this.setData({ - inputValue: e.detail.value - }); + this.setData({ inputValue: e.detail.value }); - // 发送正在输入状态(防抖) clearTimeout(this.typingDebounce); this.typingDebounce = setTimeout(() => { this.sendTypingStatus(); @@ -963,9 +1161,7 @@ Page({ // 输入框获得焦点 onInputFocus: function() { - this.setData({ - inputFocus: true - }, () => { + this.setData({ inputFocus: true }, () => { setTimeout(() => { this.scrollToBottom(); }, 200); @@ -974,9 +1170,7 @@ Page({ // 输入框失去焦点 onInputBlur: function() { - this.setData({ - inputFocus: false - }); + this.setData({ inputFocus: false }); }, // 清除输入 @@ -1000,36 +1194,47 @@ Page({ this.setData({ isScrolling: false }); }, 200); - // 加载更多 - if (scrollTop <= 100 && !this.data.loadingMore && this.data.hasMore) { + // 当滚动到顶部附近时加载更多 + 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 + this.setData({ + scrollAnimate: animate, + hasNewMessage: false }, () => { setTimeout(() => { - this.setData({ - scrollTop: 999999 - }); + this.setData({ scrollTop: 999999 }); }, 50); }); }, // 键盘高度变化 onKeyboardHeightChange: function(res) { - this.setData({ - keyboardHeight: res.height - }); + this.setData({ keyboardHeight: res.height }); if (res.height > 0) { this.setData({ showMediaSheet: false }); - setTimeout(() => { this.scrollToBottom(); }, 100); @@ -1046,9 +1251,7 @@ Page({ // 隐藏多媒体选择面板 hideMediaActionSheet: function() { - this.setData({ - showMediaSheet: false - }); + this.setData({ showMediaSheet: false }); }, // 选择图片 @@ -1115,12 +1318,11 @@ Page({ processMessageTimes: function(messages) { if (!messages || messages.length === 0) return []; - const sortedMessages = [...messages].sort((a, b) => a.timestamp - b.timestamp); const processedMessages = []; let lastShowTime = null; - for (let i = 0; i < sortedMessages.length; i++) { - const msg = { ...sortedMessages[i] }; + for (let i = 0; i < messages.length; i++) { + const msg = { ...messages[i] }; if (i === 0) { msg.showTime = true; @@ -1141,27 +1343,6 @@ Page({ return processedMessages; }, - // 处理单条消息时间 - processSingleMessageTime: function(message, messageList) { - const msg = { ...message }; - - if (messageList.length === 0) { - msg.showTime = true; - this.setData({ lastShowTimeStamp: msg.timestamp }); - return msg; - } - - const lastShowTime = this.data.lastShowTimeStamp; - const timeDiffMinutes = (msg.timestamp - lastShowTime) / (1000 * 60); - - msg.showTime = timeDiffMinutes >= this.data.timeInterval; - if (msg.showTime) { - this.setData({ lastShowTimeStamp: msg.timestamp }); - } - - return msg; - }, - // 格式化时间 formatTime: function(timestamp) { if (!timestamp) return ''; @@ -1188,21 +1369,6 @@ Page({ return size.toFixed(1) + units[unitIndex]; }, - // 播放提示音 - playNotificationSound() { - try { - const innerAudioContext = wx.createInnerAudioContext(); - innerAudioContext.src = '/assets/sounds/notification.mp3'; - innerAudioContext.play(); - - innerAudioContext.onEnded(() => { - innerAudioContext.destroy(); - }); - } catch (e) { - console.log('播放提示音失败:', e); - } - }, - // 阻止事件冒泡 stopPropagation: function() {} }); \ No newline at end of file diff --git a/pagesA/pages/expertChat/expertChat.wxml b/pagesA/pages/expertChat/expertChat.wxml index 15ec09f..680be95 100644 --- a/pagesA/pages/expertChat/expertChat.wxml +++ b/pagesA/pages/expertChat/expertChat.wxml @@ -6,7 +6,7 @@ {{conversation.otherUserName}} - + 在线 @@ -31,7 +31,7 @@ - + diff --git a/utils/api.js b/utils/api.js index 057a5b7..5770054 100644 --- a/utils/api.js +++ b/utils/api.js @@ -255,7 +255,6 @@ function fkfw(params) { http('/system/dict/data/list', 'get', params) } - // 获取聊天记录列表 function sessions(params) { http('/system/chat/sessions', 'get', params) @@ -280,4 +279,5 @@ export default { // 暴露接口 areaChildren,userCode,UserInfo,videoList,videoZd,videoDetails,forumList,forumAdd,forumDetails, forumReply,commentReply,experience,experiencezd,experienceDetails,realName,revise,feedback, warningType,disasterDetail,today,carouselDetail,planDetails,wzdDetails,create,direct,fkfw, + sessions } diff --git a/utils/tool.wxs b/utils/tool.wxs index 797bd6e..7534e98 100644 --- a/utils/tool.wxs +++ b/utils/tool.wxs @@ -34,12 +34,8 @@ function medicineType(type) { } - - - - module.exports = { type:type, ztLevel:ztLevel, - medicineType:medicineType + medicineType:medicineType, } \ No newline at end of file