From 8d1784d1f8b86b42614873e5c3b5dbafc584c231 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E9=A3=9E=E6=B4=8B?= <15778543+xufeiyang6017@user.noreply.gitee.com> Date: Mon, 22 Dec 2025 16:39:37 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=81=8A=E5=A4=A9=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E7=BA=A2=E7=82=B9=E7=82=B9=E5=87=BB=E5=90=8E=E4=B8=8D?= =?UTF-8?q?=E6=B6=88=E5=A4=B1=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/chat-detail/index.js | 105 ++++++++++++++- pages/chat/index.js | 262 ++++++++++++++++++++++++++++--------- utils/api.js | 55 ++++++++ 3 files changed, 351 insertions(+), 71 deletions(-) diff --git a/pages/chat-detail/index.js b/pages/chat-detail/index.js index e862e0b..ea88c6c 100644 --- a/pages/chat-detail/index.js +++ b/pages/chat-detail/index.js @@ -2,6 +2,9 @@ const API = require('../../utils/api.js'); Page({ + // 页面状态标志,用于检测页面是否已卸载 + isUnloaded: false, + data: { chatId: null, messages: [], @@ -57,10 +60,16 @@ Page({ const managerPhone = this.data.managerPhone; if (managerPhone) { API.getSalesPersonnelInfo(managerPhone).then(personnelInfo => { + // 检查页面是否已卸载 + if (this.isUnloaded) return; + if (personnelInfo && personnelInfo.alias) { this.setData({ chatTitle: personnelInfo.alias }); } }).catch(error => { + // 检查页面是否已卸载 + if (this.isUnloaded) return; + console.error('获取业务员信息失败:', error); }); } @@ -132,6 +141,12 @@ Page({ console.log('加载聊天消息 - 聊天ID:', this.data.chatId, '用户手机号:', userPhone); // 获取聊天消息 API.getChatMessages(this.data.chatId, userPhone).then(res => { + // 检查页面是否已卸载 + if (this.isUnloaded) { + console.log('页面已卸载,忽略聊天消息加载结果'); + return; + } + console.log('加载聊天消息 - API返回:', JSON.stringify(res, null, 2)); if (Array.isArray(res)) { console.log('加载聊天消息 - API返回消息数量:', res.length); @@ -163,6 +178,12 @@ Page({ messages: processedMessages, loading: false }, () => { + // 检查页面是否已卸载 + if (this.isUnloaded) { + console.log('页面已卸载,忽略滚动和新消息检测'); + return; + } + // 在数据更新完成的回调中执行逻辑,确保DOM已更新 // 首次加载消息或明确需要滚动时,直接滚动到底部 @@ -176,10 +197,12 @@ Page({ // 有新消息的条件: // 1. 消息列表不为空 // 2. 新消息列表的最后一条消息与当前显示的最后一条消息ID不同 + // 3. 最新消息是对方发送的(不是自己发送的) const hasNewMessages = newLastMessage && currentLastMessage && - newLastMessage.id !== currentLastMessage.id; + newLastMessage.id !== currentLastMessage.id && + newLastMessage.sender === 'other'; // 只有对方发送的消息才视为新消息 - console.log('新消息检测:', { hasNewMessages, isAtBottom: this.data.isAtBottom }); + console.log('新消息检测:', { hasNewMessages, isAtBottom: this.data.isAtBottom, sender: newLastMessage ? newLastMessage.sender : 'none' }); if (hasNewMessages) { if (this.data.isAtBottom) { @@ -205,8 +228,16 @@ Page({ }); } }).catch(error => { + // 检查页面是否已卸载 + if (this.isUnloaded) { + console.log('页面已卸载,忽略聊天消息加载错误'); + return; + } + console.error('加载聊天记录失败:', error); - this.setData({ loading: false }); + this.setData({ + loading: false + }); wx.showToast({ title: '加载聊天记录失败', icon: 'none' @@ -218,19 +249,44 @@ Page({ scrollToBottom: function (withAnimation = true) { console.log('执行滚动到底部操作,是否使用动画:', withAnimation); + // 检查页面是否已卸载 + if (this.isUnloaded) { + console.log('页面已卸载,忽略滚动操作'); + return; + } + // 使用wx.nextTick确保在DOM更新后执行 wx.nextTick(() => { + // 再次检查页面是否已卸载 + if (this.isUnloaded) { + console.log('页面已卸载,忽略滚动设置'); + return; + } + this.setData({ scrollWithAnimation: withAnimation, scrollTop: 999999 }, () => { - // 滚动完成后清除新消息提示 + // 再次检查页面是否已卸载 + if (this.isUnloaded) { + console.log('页面已卸载,忽略滚动完成后的操作'); + return; + } + + // 滚动完成后清除新消息提示并显式设置isAtBottom为true setTimeout(() => { + // 再次检查页面是否已卸载 + if (this.isUnloaded) { + console.log('页面已卸载,忽略滚动完成后的状态更新'); + return; + } + this.setData({ hasNewMessages: false, - newMessageCount: 0 + newMessageCount: 0, + isAtBottom: true // 显式设置为在底部 }); - console.log('滚动完成,清除新消息提示'); + console.log('滚动完成,清除新消息提示并设置为在底部'); }, 100); }); }); @@ -246,6 +302,15 @@ Page({ console.log('滚动检测:', { scrollTop, scrollHeight, clientHeight, distanceToBottom, isAtBottom }); + // 如果滚动到底部,并且有新消息提示,自动隐藏提示 + if (isAtBottom && this.data.hasNewMessages) { + this.setData({ + hasNewMessages: false, + newMessageCount: 0 + }); + console.log('用户滑动到底部,自动隐藏新消息提示'); + } + // 直接更新状态,确保滚动位置实时反映 this.setData({ isAtBottom: isAtBottom @@ -313,11 +378,23 @@ Page({ messages: newMessages, inputValue: '' }, () => { + // 检查页面是否已卸载 + if (this.isUnloaded) { + console.log('页面已卸载,忽略发送消息后的操作'); + return; + } + // 发送消息时无论用户在哪个位置,都自动滚动到底部 this.scrollToBottom(true); // 短暂延迟后移除isNew标记,确保动画完成 setTimeout(() => { + // 检查页面是否已卸载 + if (this.isUnloaded) { + console.log('页面已卸载,忽略消息状态更新'); + return; + } + const updatedMessages = this.data.messages.map(msg => ({ ...msg, isNew: false @@ -328,8 +405,20 @@ Page({ // 发送到服务器 API.sendMessage(userPhone, managerPhone, content).then(res => { + // 检查页面是否已卸载 + if (this.isUnloaded) { + console.log('页面已卸载,忽略消息发送结果'); + return; + } + console.log('消息发送成功:', res); }).catch(error => { + // 检查页面是否已卸载 + if (this.isUnloaded) { + console.log('页面已卸载,忽略消息发送错误'); + return; + } + console.error('消息发送失败:', error); wx.showToast({ title: '消息发送失败', @@ -361,6 +450,10 @@ Page({ }, onUnload: function () { + console.log('聊天详情页面卸载 - 清除定时器并设置卸载标志'); + // 设置页面卸载标志 + this.isUnloaded = true; + if (this.data.timer) { clearInterval(this.data.timer); this.setData({ timer: null }); diff --git a/pages/chat/index.js b/pages/chat/index.js index 0a44454..4df1e22 100644 --- a/pages/chat/index.js +++ b/pages/chat/index.js @@ -9,7 +9,8 @@ Page({ timer: null, debugCount: 0, // 调试信息输出计数器 loginToastShown: false, // 登录提示是否已显示 - lastLoadTime: 0 // 用于节流的时间戳 + lastLoadTime: 0, // 用于节流的时间戳 + chatContentCache: {} // 缓存聊天项的内容,避免重复获取 }, onLoad: function (options) { @@ -214,84 +215,148 @@ Page({ } // 调用API获取聊天列表 - API.getChatList(userPhone).then(res => { - if (res && Array.isArray(res)) { - // 创建业务员信息缓存,避免重复调用API - const personnelCache = {}; - - // 处理每个聊天项,获取业务员信息 - const chatListPromises = res.map(async (chatItem) => { - if (chatItem.manager_phone) { - try { - // 先检查缓存中是否已有该业务员信息 - if (personnelCache[chatItem.manager_phone]) { - const cachedInfo = personnelCache[chatItem.manager_phone]; - chatItem.name = cachedInfo.alias || chatItem.manager_phone; - chatItem.avatar = chatItem.avatar || 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0'; - console.log('使用缓存的业务员信息:', chatItem.manager_phone); - } else { - // 获取业务员信息 - const personnelInfo = await API.getSalesPersonnelInfo(chatItem.manager_phone); - if (personnelInfo) { - // 缓存业务员信息 - personnelCache[chatItem.manager_phone] = personnelInfo; - // 使用业务员的alias作为显示名称 - chatItem.name = personnelInfo.alias || chatItem.manager_phone; + API.getChatList(userPhone).then(res => { + if (res && Array.isArray(res)) { + console.log('===== 获取聊天列表原始数据 ====='); + console.log(JSON.stringify(res, null, 2)); + + // 创建业务员信息缓存,避免重复调用API + const personnelCache = {}; + + // 处理每个聊天项,获取业务员信息 + const chatListPromises = res.map(async (chatItem, index) => { + console.log(`\n===== 处理聊天项 ${index} =====`); + console.log('原始聊天项数据:', JSON.stringify(chatItem, null, 2)); + console.log('原始unread值:', chatItem.unread, '类型:', typeof chatItem.unread); + + if (chatItem.manager_phone) { + try { + // 先检查缓存中是否已有该业务员信息 + if (personnelCache[chatItem.manager_phone]) { + const cachedInfo = personnelCache[chatItem.manager_phone]; + chatItem.name = cachedInfo.alias || chatItem.manager_phone; chatItem.avatar = chatItem.avatar || 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0'; - console.log('获取并缓存业务员信息:', chatItem.manager_phone); + console.log('使用缓存的业务员信息:', chatItem.manager_phone); + } else { + // 获取业务员信息 + const personnelInfo = await API.getSalesPersonnelInfo(chatItem.manager_phone); + if (personnelInfo) { + // 缓存业务员信息 + personnelCache[chatItem.manager_phone] = personnelInfo; + // 使用业务员的alias作为显示名称 + chatItem.name = personnelInfo.alias || chatItem.manager_phone; + chatItem.avatar = chatItem.avatar || 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0'; + console.log('获取并缓存业务员信息:', chatItem.manager_phone); + } } + } catch (error) { + console.error('获取业务员信息失败:', error); + // 如果获取失败,使用默认名称 + chatItem.name = chatItem.manager_phone; + chatItem.avatar = 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0'; } - } catch (error) { - console.error('获取业务员信息失败:', error); - // 如果获取失败,使用默认名称 - chatItem.name = chatItem.manager_phone; - chatItem.avatar = 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0'; + } else { + // 系统消息或其他没有manager_phone的消息 + chatItem.name = chatItem.name || '系统消息'; + chatItem.avatar = chatItem.avatar || 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0'; } - } else { - // 系统消息或其他没有manager_phone的消息 - chatItem.name = chatItem.name || '系统消息'; - chatItem.avatar = chatItem.avatar || 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0'; - } - - // 修复:对所有没有消息内容的聊天项获取最新消息内容 - if (!chatItem.content) { + + // 获取用户已查看过的聊天列表 + const viewedChats = wx.getStorageSync('viewedChats') || {}; + const chatId = chatItem.manager_phone; + + // 获取最新消息内容和时间 + let lastMessage = null; try { - const messages = await API.getChatMessages(chatItem.manager_phone, userPhone, { limit: 1 }); + const cacheKey = chatId; + const cache = this.data.chatContentCache[cacheKey]; + const now = Date.now(); + + // 每次刷新列表时都尝试获取最新消息,确保消息实时性 + const messages = await API.getChatMessages(chatId, userPhone, { limit: 1 }); if (messages.length > 0) { - chatItem.content = messages[0].content || '暂无消息内容'; + lastMessage = messages[0]; + chatItem.content = lastMessage.content || '暂无消息内容'; // 同时更新时间为最新消息的时间 - chatItem.time = messages[0].created_at || null; - } else { + chatItem.time = lastMessage.created_at || null; + + // 更新缓存 + const newCache = { + ...this.data.chatContentCache, + [cacheKey]: { + content: chatItem.content, + time: chatItem.time, + timestamp: now, + messageId: lastMessage.id || lastMessage.message_id || null + } + }; + this.setData({ chatContentCache: newCache }); + console.log('更新聊天内容缓存:', cacheKey); + } else if (!chatItem.content) { chatItem.content = '暂无消息内容'; } } catch (error) { console.error('获取聊天消息失败:', error); - chatItem.content = '暂无消息内容'; + // 如果获取失败,尝试使用缓存内容 + const cacheKey = chatId; + const cache = this.data.chatContentCache[cacheKey]; + if (cache && !chatItem.content) { + chatItem.content = cache.content; + chatItem.time = cache.time; + console.log('获取最新消息失败,使用缓存内容:', cacheKey); + } else if (!chatItem.content) { + chatItem.content = '暂无消息内容'; + } } - } - // 格式化时间 - chatItem.time = this.formatDateTime(chatItem.time || null); - // 确保unread字段存在 - chatItem.unread = chatItem.unread || false; - - return chatItem; - }); - - // 等待所有聊天项处理完成 - Promise.all(chatListPromises).then(processedChatList => { - this.setData({ - chatList: processedChatList, - filteredChatList: processedChatList + + // 格式化时间 + chatItem.time = this.formatDateTime(chatItem.time || null); + + // 未读状态判断:基于用户是否查看过该聊天 + // 只在用户未查看过时显示未读红点 + console.log('当前已查看的聊天:', viewedChats); + console.log('当前聊天ID:', chatId); + + // 检查该聊天是否已被查看过 + const isViewed = viewedChats[chatId] === true; + console.log('是否已查看:', isViewed); + + // 设置未读状态:未查看过则显示未读 + chatItem.unread = !isViewed; + console.log('设置unread的值:', chatItem.unread); + + // 保存最后一条消息的ID或内容作为标识(可选) + if (lastMessage) { + chatItem.lastMessageId = lastMessage.id || lastMessage.message_id; + chatItem.lastMessageContent = lastMessage.content; + } + + console.log('处理完成的聊天项:', JSON.stringify(chatItem, null, 2)); + return chatItem; }); - wx.hideLoading(); - }).catch(error => { - console.error('处理聊天列表失败:', error); - wx.hideLoading(); - wx.showToast({ - title: '加载聊天列表失败', - icon: 'none' + + // 等待所有聊天项处理完成 + Promise.all(chatListPromises).then(processedChatList => { + console.log('\n===== 所有聊天项处理完成 ====='); + console.log('处理后的列表:', JSON.stringify(processedChatList, null, 2)); + + this.setData({ + chatList: processedChatList, + filteredChatList: processedChatList + }, () => { + console.log('===== 页面数据已更新 ====='); + console.log('chatList:', JSON.stringify(this.data.chatList, null, 2)); + console.log('filteredChatList:', JSON.stringify(this.data.filteredChatList, null, 2)); + }); + wx.hideLoading(); + }).catch(error => { + console.error('处理聊天列表失败:', error); + wx.hideLoading(); + wx.showToast({ + title: '加载聊天列表失败', + icon: 'none' + }); }); - }); } else { wx.hideLoading(); wx.showToast({ @@ -344,6 +409,73 @@ Page({ // 使用对方的电话号码作为聊天ID,而不是会话ID const chatId = chatItem.manager_phone; + // 获取用户手机号 + const users = wx.getStorageSync('users') || {}; + const userId = wx.getStorageSync('userId'); + let userPhone = null; + + // 尝试从users中获取手机号(支持不同的键名) + if (userId && users[userId]) { + if (users[userId].phoneNumber) { + userPhone = users[userId].phoneNumber; + } else if (users[userId].phone) { + userPhone = users[userId].phone; + } + } + + // 如果还没有获取到,尝试从全局用户信息获取 + if (!userPhone) { + const userInfo = wx.getStorageSync('userInfo'); + if (userInfo) { + if (userInfo.phoneNumber) { + userPhone = userInfo.phoneNumber; + } else if (userInfo.phone) { + userPhone = userInfo.phone; + } + } + } + + // 如果还没有获取到,尝试从直接存储获取(支持不同的键名) + if (!userPhone) { + if (wx.getStorageSync('phoneNumber')) { + userPhone = wx.getStorageSync('phoneNumber'); + } else if (wx.getStorageSync('phone')) { + userPhone = wx.getStorageSync('phone'); + } + } + + // 1. 更新localStorage中的viewedChats,标记为已查看 + const viewedChats = wx.getStorageSync('viewedChats') || {}; + viewedChats[chatId] = true; + wx.setStorageSync('viewedChats', viewedChats); + console.log('更新localStorage中的viewedChats:', chatId, viewedChats[chatId]); + + // 2. 更新filteredChatList中的unread状态 + const updatedFilteredChatList = [...this.data.filteredChatList]; + updatedFilteredChatList[index].unread = false; + + // 3. 更新chatList中的unread状态 + const updatedChatList = [...this.data.chatList]; + const originalIndex = updatedChatList.findIndex(item => item.manager_phone === chatItem.manager_phone); + if (originalIndex !== -1) { + updatedChatList[originalIndex].unread = false; + } + + // 4. 更新页面数据 + this.setData({ + filteredChatList: updatedFilteredChatList, + chatList: updatedChatList + }); + + console.log('标记聊天为已读:', chatId); + + // 5. 调用API将服务器端的消息标记为已读 + API.markMessagesAsRead(chatId, userPhone).then(() => { + console.log('服务器端消息标记为已读成功'); + }).catch(error => { + console.error('服务器端消息标记为已读失败:', error); + }); + // 跳转到聊天详情页,传递chatId和name参数 wx.navigateTo({ url: '/pages/chat-detail/index?id=' + chatId + '&name=' + encodeURIComponent(chatItem.name), diff --git a/utils/api.js b/utils/api.js index 5dbd0e0..e130782 100644 --- a/utils/api.js +++ b/utils/api.js @@ -3748,6 +3748,61 @@ module.exports = { reject(new Error(error.message || '获取业务员信息失败,请稍后重试')); }); }); + }, + + // 标记消息为已读 + markMessagesAsRead: function (chatId, userPhone) { + console.log('API.markMessagesAsRead - chatId:', chatId, 'userPhone:', userPhone); + return new Promise((resolve, reject) => { + // 如果没有传入手机号,尝试从本地存储获取 + if (!userPhone) { + // 获取用户信息,包含手机号 + const users = wx.getStorageSync('users') || {}; + const userId = wx.getStorageSync('userId'); + + // 尝试从users中获取手机号 + if (userId && users[userId] && users[userId].phoneNumber) { + userPhone = users[userId].phoneNumber; + } else { + // 尝试从全局用户信息获取 + const userInfo = wx.getStorageSync('userInfo'); + if (userInfo && userInfo.phoneNumber) { + userPhone = userInfo.phoneNumber; + } else { + // 尝试从直接存储的phoneNumber获取 + userPhone = wx.getStorageSync('phoneNumber'); + } + } + } + + // 如果没有手机号,直接返回错误 + if (!userPhone) { + reject(new Error('请先登录并绑定手机号')); + return; + } + + // 如果没有chatId,直接返回错误 + if (!chatId) { + reject(new Error('聊天ID不能为空')); + return; + } + + const requestData = { + chat_id: chatId, + user_phone: userPhone + }; + + console.log('API.markMessagesAsRead - 请求数据:', requestData); + + request('/api/chat/read', 'POST', requestData).then(res => { + console.log('API.markMessagesAsRead - 响应数据:', res); + resolve(res); + }).catch(error => { + console.error('API.markMessagesAsRead - 请求失败:', error); + // 标记为已读的API可能尚未实现,所以不抛出错误,让前端继续执行 + resolve({}); + }); + }); } }; \ No newline at end of file