// pages/xiaoxi/xiaoxi.js const app = getApp(); import { formatDate } from '../../static/lib/utils'; const { jianquanxian } = require('../../utils/imAuth/jianquanxian'); import { enrichGroupConversation, isOrderGroupConversation, sortConversations, recordConversationOpen } from '../../utils/group-chat.js'; import { loadGroupMetaCache } from '../../utils/im-user.js'; Page({ data: { allConversations: [], filteredConversations: [], activeTab: 0, tabUnread: [0, 0, 0], displayCount: 4, searchKeyword: '', actionPopup: { visible: false, conversation: null }, currentUser: null, notificationMuted: false, scrollHeight: 0 }, _permissionSeq: 0, onLoad() { this.calculateScrollHeight(); const muted = wx.getStorageSync('notificationMuted') || false; this.setData({ notificationMuted: muted }); loadGroupMetaCache(app); this._onGlobalConvUpdated = (result) => { const content = result?.content || result; if (content && content.conversations) { this.renderConversations(content); } }; this._onUnreadChanged = (data) => { const unreadTotal = data?.unreadTotal ?? app.globalData.messageManager?.unreadTotal ?? 0; if (app.globalData.messageManager) { app.globalData.messageManager.unreadTotal = unreadTotal; } if (app.emitEvent) { app.emitEvent('tabBarBadgeChanged', { badgeText: unreadTotal > 0 ? String(unreadTotal) : '', }); } if (this.data.currentUser) { this.loadConversations(); } }; app.on('conversationsUpdated', this._onGlobalConvUpdated); app.on('unreadCountChanged', this._onUnreadChanged); }, onUnload() { if (this._onGlobalConvUpdated) { app.off('conversationsUpdated', this._onGlobalConvUpdated); } if (this._onUnreadChanged) { app.off('unreadCountChanged', this._onUnreadChanged); } if (this.conversationsUpdatedListener && wx.goEasy?.im) { wx.goEasy.im.off(wx.GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, this.conversationsUpdatedListener); this.conversationsUpdatedListener = null; } }, onShow() { if (!this.checkLoginStatus()) return; loadGroupMetaCache(app); if (app.globalData.messageManager?.unreadTotal != null) { app.emitEvent('tabBarBadgeChanged', { badgeText: app.globalData.messageManager.unreadTotal > 0 ? String(app.globalData.messageManager.unreadTotal) : '' }); } this.checkPermissionAndAutoConnect(); }, onHide() {}, async checkPermissionAndAutoConnect() { const seq = ++this._permissionSeq; let quanxian; try { quanxian = await jianquanxian(app); } catch (e) { return; } if (seq !== this._permissionSeq) return; if (!quanxian.allowed) { const currentRole = app.globalData.currentRole || 'normal'; let content = '您没有权限使用消息功能'; if (currentRole === 'dashou') content = '您需要先充值会员才能使用消息功能'; wx.showModal({ title: '权限不足', content, showCancel: false, confirmText: '我知道了' }); return; } this.autoConnect(); }, // ========== 登录状态检查 ========== checkLoginStatus() { const uid = wx.getStorageSync('uid'); if (!uid) { wx.showToast({ title: '请先登录', icon: 'none' }); setTimeout(() => { wx.redirectTo({ url: '/pages/wode/wode' }); }, 1500); return false; } let currentUser = app.globalData.currentUser; if (!currentUser) { currentUser = { id: uid, name: '用户' + uid.substring(0, 6), avatar: app.globalData.ossImageUrl + app.globalData.morentouxiang }; app.globalData.currentUser = currentUser; } this.setData({ currentUser }); return true; }, // ========== 自动连接(复用全局连接,避免反复 disconnect) ========== autoConnect() { const role = app.globalData.currentRole || 'normal'; const uid = wx.getStorageSync('uid'); const prefixMap = { normal: 'Boss', dashou: 'Ds', shangjia: 'Sj', guanshi: 'Gs', zuzhang: 'Zz' }; const targetUserId = (prefixMap[role] || 'Boss') + uid; const onReady = () => { this.setupConversationListener(); this.loadConversations(); }; const status = wx.goEasy?.getConnectionStatus?.() || 'disconnected'; const currentUserId = wx.goEasy?.im?.userId; if ((status === 'connected' || status === 'reconnected') && currentUserId === targetUserId) { onReady(); return; } if (currentUserId && currentUserId !== targetUserId && app.disconnectGoEasy) { app.disconnectGoEasy().then(() => { app.connectWithIdentity(role, targetUserId, true).then(onReady).catch(() => {}); }); return; } if (!app.connectWithIdentity) { this.connectGoEasy(targetUserId); return; } app.connectWithIdentity(role, targetUserId, true).then(onReady).catch(() => { const s = wx.goEasy?.getConnectionStatus?.(); if (s === 'connected' || s === 'reconnected') onReady(); }); }, connectGoEasy(userId) { const { currentUser } = this.data; if (!currentUser) return; wx.goEasy.connect({ id: userId, data: { name: currentUser.name, avatar: currentUser.avatar || (app.globalData.ossImageUrl + app.globalData.morentouxiang) }, onSuccess: () => { try { const connection = { userId: userId, identityType: app.globalData.currentRole, lastConnectTime: Date.now(), autoReconnect: true, status: 'connected' }; wx.setStorageSync('savedGoEasyConnection', JSON.stringify(connection)); wx.setStorageSync('goEasyUserId', userId); } catch (e) {} this.setupConversationListener(); this.loadConversations(); }, onFailed: (error) => { if (error.code === 408) { this.setupConversationListener(); this.loadConversations(); } else { wx.showToast({ title: '连接失败,请重试', icon: 'none' }); } } }); }, // ========== 会话监听(永不清理) ========== setupConversationListener() { if (this.conversationsUpdatedListener) { wx.goEasy.im.off(wx.GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, this.conversationsUpdatedListener); } this.conversationsUpdatedListener = (result) => { this.renderConversations(result); }; wx.goEasy.im.on(wx.GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, this.conversationsUpdatedListener); }, loadConversations() { wx.goEasy.im.latestConversations({ onSuccess: (result) => { this.renderConversations(result.content); }, onFailed: () => { wx.showToast({ title: '获取会话失败', icon: 'none' }); } }); }, // ========== 会话渲染与排序 ========== renderConversations(content) { if (!content || !content.conversations) { this.setData({ allConversations: [] }, () => this.applyFilter()); return; } let list = content.conversations; const defaultAvatar = app.globalData.ossImageUrl + app.globalData.morentouxiang; const myGoEasyId = wx.goEasy?.im?.userId || ''; const ossBase = app.globalData.ossImageUrl || ''; const groupMeta = app.globalData.groupInfoMap || {}; list.forEach(item => { if (item.type === 'group') { const meta = groupMeta[item.groupId]; if (meta) { if (!item.data) item.data = {}; Object.assign(item.data, meta); } if (item.data && item.data.dashouGoEasyId) { if (!app.globalData.groupInfoMap) app.globalData.groupInfoMap = {}; app.globalData.groupInfoMap[item.groupId] = { ...app.globalData.groupInfoMap[item.groupId], ...item.data, }; } enrichGroupConversation(item, myGoEasyId, defaultAvatar, ossBase); } else if (item.data && item.data.avatar) { if (!item.data.avatar.startsWith('http')) { item.data.avatar = ossBase + item.data.avatar; } } else if (item.data) { item.data.avatar = defaultAvatar; } if (item.lastMessage && item.lastMessage.timestamp) { item.lastMessage.date = formatDate(item.lastMessage.timestamp); } }); const openTimes = wx.getStorageSync('conversationOpenTimes') || {}; sortConversations(list, openTimes); const unreadTotal = content.unreadTotal !== undefined ? content.unreadTotal : list.reduce((sum, c) => sum + (c.unread || 0), 0); if (app.globalData.messageManager) { app.globalData.messageManager.unreadTotal = unreadTotal; } if (app && app.emitEvent) { app.emitEvent('tabBarBadgeChanged', { badgeText: unreadTotal > 0 ? String(unreadTotal) : '' }); app.emitEvent('unreadCountChanged', { unreadTotal }); } this.setData({ allConversations: list, displayCount: 4 }, () => { this.applyFilter(); }); // 群组订阅(不重复断开连接) this.subscribeGroupsIfNeeded(list); }, /** * 群组订阅(依赖:GoEasy IM) * 根据官方文档,需要主动 subscribeGroup 才能接收群聊消息。 * 为了应对切后台、网络波动等导致的订阅丢失,这里不再缓存订阅状态, * 每次获取到群组列表时都直接订阅,保证消息不丢失。 */ subscribeGroupsIfNeeded(conversations) { if (!wx.goEasy?.im || typeof wx.goEasy.im.subscribeGroup !== 'function') return; const groupIds = conversations .filter(c => c.type === 'group' && c.groupId) .map(c => c.groupId); if (groupIds.length === 0) return; // 不再判断缓存,直接订阅当前所有群组 wx.goEasy.im.subscribeGroup({ groupIds: groupIds, onSuccess: () => { // 可选的日志输出,不影响功能 // console.log('群组订阅成功', groupIds); }, onFailed: (error) => { console.error('群组订阅失败:', error); } }); }, applyFilter() { const { allConversations, activeTab, searchKeyword } = this.data; let filtered = []; if (activeTab === 0) { filtered = allConversations.filter(c => isOrderGroupConversation(c)); } else if (activeTab === 1) { filtered = allConversations.filter(c => c.type === 'private'); } else if (activeTab === 2) { filtered = allConversations.filter(c => c.type === 'cs'); } if (searchKeyword.trim()) { const kw = searchKeyword.trim().toLowerCase(); filtered = filtered.filter(c => { const name = (c.data && c.data.name || '').toLowerCase(); const orderId = (c.data && c.data.orderId || '').toLowerCase(); const id = (c.userId || c.groupId || '').toLowerCase(); return name.includes(kw) || id.includes(kw) || orderId.includes(kw); }); } const tabUnread = [0, 0, 0]; allConversations.forEach(c => { if (isOrderGroupConversation(c)) tabUnread[0] += c.unread || 0; else if (c.type === 'private') tabUnread[1] += c.unread || 0; else if (c.type === 'cs') tabUnread[2] += c.unread || 0; }); this.setData({ filteredConversations: filtered, tabUnread }); }, switchTab(e) { const index = parseInt(e.currentTarget.dataset.index); this.setData({ activeTab: index, displayCount: 4, searchKeyword: '' }, () => this.applyFilter()); }, onSearchInput(e) { this.setData({ searchKeyword: e.detail.value, displayCount: 4 }, () => this.applyFilter()); }, onScrollToLower() { const { displayCount, filteredConversations } = this.data; if (displayCount < filteredConversations.length) { this.setData({ displayCount: Math.min(displayCount + 4, filteredConversations.length) }); } }, onRefresh() { this.loadConversations(); }, chat(e) { const conversation = e.currentTarget.dataset.conversation; if (!conversation) return; recordConversationOpen(conversation); if (conversation.type === 'private') { const param = { toUserId: conversation.userId, toName: conversation.data.name, toAvatar: conversation.data.avatar }; wx.navigateTo({ url: '/pages/liaotian/liaotian?data=' + encodeURIComponent(JSON.stringify(param)) }); } else if (conversation.type === 'group') { const param = { groupId: conversation.groupId, orderId: conversation.data.orderId || '', groupName: conversation.data.name, groupAvatar: conversation.data.avatar, isCross: conversation.data.isCross || 0 }; wx.navigateTo({ url: '/pages/qunliaotian/qunliaotian?data=' + encodeURIComponent(JSON.stringify(param)) }); } else if (conversation.type === 'cs') { const param = { teamId: conversation.teamId || conversation.id }; wx.navigateTo({ url: '/pages/kefuliaotian/kefuliaotian?data=' + encodeURIComponent(JSON.stringify(param)) }); } }, chatKefu() { const param = { teamId: 'support_team' }; wx.navigateTo({ url: '/pages/kefuliaotian/kefuliaotian?data=' + encodeURIComponent(JSON.stringify(param)) }); }, showAction(e) { const conversation = e.currentTarget.dataset.conversation; this.setData({ ['actionPopup.conversation']: conversation, ['actionPopup.visible']: true }); }, closeMask() { this.setData({ ['actionPopup.visible']: false }); }, topConversation() { let conversation = this.data.actionPopup.conversation; if (!conversation) return; const top = !conversation.top; wx.showLoading({ title: '处理中...' }); if (conversation.type === 'private') { wx.goEasy.im.topPrivateConversation({ userId: conversation.userId, top, onSuccess: () => { wx.hideLoading(); this.loadConversations(); wx.showToast({ title: top ? '已置顶' : '已取消置顶' }); }, onFailed: () => { wx.hideLoading(); wx.showToast({ title: '操作失败', icon: 'none' }); } }); } else { wx.goEasy.im.topGroupConversation({ groupId: conversation.groupId, top, onSuccess: () => { wx.hideLoading(); this.loadConversations(); wx.showToast({ title: top ? '已置顶' : '已取消置顶' }); }, onFailed: () => { wx.hideLoading(); wx.showToast({ title: '操作失败', icon: 'none' }); } }); } this.closeMask(); }, removeConversation() { let conversation = this.data.actionPopup.conversation; if (!conversation) return; wx.showLoading({ title: '删除中...' }); wx.goEasy.im.removeConversation({ conversation: conversation, onSuccess: () => { wx.hideLoading(); this.loadConversations(); wx.showToast({ title: '已删除' }); }, onFailed: () => { wx.hideLoading(); wx.showToast({ title: '删除失败', icon: 'none' }); } }); this.closeMask(); }, toggleNotification() { const newMuted = !this.data.notificationMuted; this.setData({ notificationMuted: newMuted }); wx.setStorageSync('notificationMuted', newMuted); wx.showToast({ title: newMuted ? '已静音' : '已开启通知', icon: 'none' }); }, calculateScrollHeight() { const systemInfo = wx.getSystemInfoSync(); const windowHeight = systemInfo.windowHeight; const windowWidth = systemInfo.windowWidth; const rpxRatio = 750 / windowWidth; const headerHeight = 150 / rpxRatio; const tabBarHeight = 130 / rpxRatio; const safeAreaBottom = systemInfo.safeArea ? (windowHeight - systemInfo.safeArea.bottom) : 0; this.setData({ scrollHeight: windowHeight - headerHeight - tabBarHeight - safeAreaBottom }); } });