// pages/xiaoxi/xiaoxi.js const app = getApp(); import { formatDate } from '../../static/lib/utils'; import { resolveAvatarUrl, getDefaultAvatarUrl, resolveAvatarFromStorage } from '../../utils/avatar.js'; import { getLocalImUserId } from '../../utils/im-user.js'; import { normalizeConversationsList } from '../../utils/conversation-display.js'; import { reconnectForRole } from '../../utils/role-tab-bar.js'; import { getPrimaryRole } from '../../utils/primary-role.js'; const { jianquanxian } = require('../../utils/imAuth/jianquanxian'); Page({ data: { allConversations: [], filteredConversations: [], activeTab: 0, tabUnread: [0, 0, 0], displayCount: 10, searchKeyword: '', actionPopup: { visible: false, conversation: null }, currentUser: null, notificationMuted: false, scrollHeight: 0 }, _permissionSeq: 0, _peerProfileCache: {}, onLoad() { this.calculateScrollHeight(); const muted = wx.getStorageSync('notificationMuted') || false; this.setData({ notificationMuted: muted }); }, onShow() { if (!this.checkLoginStatus()) return; this.registerNotificationComponent(); this.checkPermissionAndAutoConnect(); }, onHide() {}, onUnload() {}, // ========== 鉴权检查 ========== 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: getLocalImUserId(app) || uid, name: wx.getStorageSync('nicheng') || app.globalData.dashouNicheng || app.globalData.nicheng || ('用户' + uid.substring(0, 6)), avatar: resolveAvatarFromStorage(app), }; app.globalData.currentUser = currentUser; } this.setData({ currentUser }); return true; }, registerNotificationComponent() { const notificationComp = this.selectComponent('#global-notification'); if (notificationComp && notificationComp.showNotification) { app.globalData.globalNotification = { show: (data) => notificationComp.showNotification(data), hide: () => notificationComp.hideNotification(), }; } }, autoConnect() { const targetUserId = getLocalImUserId(app); if (!targetUserId) return; if (app.ensureConnection) app.ensureConnection(); reconnectForRole(getPrimaryRole(app)); const status = wx.goEasy.getConnectionStatus ? wx.goEasy.getConnectionStatus() : 'disconnected'; if (status === 'connected' || status === 'reconnected') { const currentUserId = wx.goEasy.im ? wx.goEasy.im.userId : null; if (currentUserId === targetUserId) { this.setupConversationListener(); this.loadConversations(); return; } try { wx.goEasy.disconnect(); } catch (e) {} } this.connectGoEasy(targetUserId); }, connectGoEasy(userId) { const { currentUser } = this.data; if (!currentUser) return; wx.goEasy.connect({ id: userId, data: { name: currentUser.name, avatar: resolveAvatarUrl(currentUser.avatar, app), }, onSuccess: () => { try { wx.setStorageSync('savedGoEasyConnection', JSON.stringify({ userId, identityType: app.globalData.currentRole, lastConnectTime: Date.now(), status: 'connected', })); } catch (e) {} this.setupConversationListener(); this.loadConversations(); if (app.registerNotification) { // 全局通知在 chat-core 注册 } }, onFailed: (error) => { if (error.code === 408) { this.setupConversationListener(); this.loadConversations(); } else { wx.showToast({ title: '连接失败,请重试', icon: 'none' }); } }, }); }, setupConversationListener() { if (!wx.goEasy || !wx.goEasy.im) return; if (this.conversationsUpdatedListener) { wx.goEasy.im.off(wx.GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, this.conversationsUpdatedListener); } this.conversationsUpdatedListener = (result) => { const content = result?.content || result; this.renderConversations(content); }; wx.goEasy.im.on(wx.GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, this.conversationsUpdatedListener); }, loadConversations() { if (!wx.goEasy || !wx.goEasy.im) return; wx.goEasy.im.latestConversations({ onSuccess: (result) => { const content = result?.content || result; this.renderConversations(content); }, onFailed: () => { wx.showToast({ title: '获取会话失败', icon: 'none' }); }, }); }, // ========== 会话渲染与排序 ========== renderConversations(content) { const conversations = (content && content.conversations) ? content.conversations : []; if (!conversations.length) { this.setData({ allConversations: [] }, () => this.applyFilter()); return; } let list = normalizeConversationsList(conversations, app, this._peerProfileCache); list.forEach(item => { if (!item.lastMessage) { item.lastMessage = { type: 'text', payload: { text: '' }, timestamp: 0 }; } if (item.lastMessage.timestamp) { item.lastMessage.date = formatDate(item.lastMessage.timestamp); } }); list.sort((a, b) => { if (a.top && !b.top) return -1; if (!a.top && b.top) return 1; if (a.unread > 0 && b.unread <= 0) return -1; if (a.unread <= 0 && b.unread > 0) return 1; const ta = (a.lastMessage && a.lastMessage.timestamp) || 0; const tb = (b.lastMessage && b.lastMessage.timestamp) || 0; return tb - ta; }); this.setData({ allConversations: list, displayCount: 10 }, () => { this.applyFilter(); }); const unreadTotal = content.unreadTotal !== undefined ? content.unreadTotal : list.reduce((sum, c) => sum + (c.unread || 0), 0); if (app.updateUnreadCount) { app.updateUnreadCount(unreadTotal); } else if (app.emitEvent) { app.emitEvent('tabBarBadgeChanged', { badgeText: unreadTotal > 0 ? String(unreadTotal) : '' }); } // ✅ 每次渲染会话后,无条件重新订阅所有群组,确保订阅关系始终有效 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 => c.type === 'group' && c.data && c.data.orderId); } 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 (c.type === 'group' && c.data && c.data.orderId) 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: 10, searchKeyword: '' }, () => this.applyFilter()); }, onSearchInput(e) { this.setData({ searchKeyword: e.detail.value, displayCount: 10 }, () => this.applyFilter()); }, onScrollToLower() { const { displayCount, filteredConversations } = this.data; if (displayCount < filteredConversations.length) { this.setData({ displayCount: Math.min(displayCount + 10, filteredConversations.length) }); } }, onRefresh() { this.loadConversations(); }, chat(e) { const conversation = e.currentTarget.dataset.conversation; if (!conversation) return; if (conversation.type === 'private') { const param = { toUserId: conversation.userId, toName: conversation.data.name || '用户', toAvatar: resolveAvatarUrl(conversation.data.avatar, app), }; 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 }); }, onAvatarError(e) { const index = e.currentTarget.dataset.index; const def = getDefaultAvatarUrl(app); const key = `filteredConversations[${index}].data.avatar`; this.setData({ [key]: def }); } });