// pages/messages/messages.js const app = getApp(); import { formatDate } from '../../static/lib/utils'; import { resolveAvatarUrl, getDefaultAvatarUrl, resolveAvatarFromStorage } from '../../utils/avatar.js'; import { getLocalImUserId, getLocalImIdentity, getImUserIdForRole, loadGroupMetaCache, getDefaultAvatar } from '../../utils/im-user.js'; import { enrichGroupConversation, isOrderGroupConversation, sortConversations, recordConversationOpen, getZhuangtaiText } from '../../utils/group-chat.js'; import request from '../../utils/request'; import { openCustomerServiceChat } from '../../utils/kefu-nav.js'; import { getPrimaryRole } from '../../utils/primary-role.js'; import { isStaffMode } from '../../utils/staff-api.js'; import { createPage } from '../../utils/base-page.js'; const { jianquanxian } = require('../../utils/imAuth/jianquanxian'); Page(createPage({ data: { allConversations: [], filteredConversations: [], activeTab: 0, tabUnread: [0, 0, 0], displayCount: 10, searchKeyword: '', actionPopup: { visible: false, conversation: null }, currentUser: null, notificationMuted: false, scrollHeight: 0, imStatus: 'connecting', imStatusText: '', defaultAvatarUrl: '', }, _permissionSeq: 0, _peerProfileCache: {}, _lastConvSignature: '', _lastRefreshTs: 0, _showRefreshTimer: null, _statusRefreshTimer: null, _chatAllowed: true, onLoad() { this.calculateScrollHeight(); this.setData({ defaultAvatarUrl: getDefaultAvatarUrl(app) }); const muted = wx.getStorageSync('notificationMuted') || false; this.setData({ notificationMuted: muted }); if (app.globalData.messageManager) { app.globalData.messageManager.notificationMuted = muted; } loadGroupMetaCache(app); this._onGlobalConvUpdated = (content) => { if (content && content.conversations) { this.renderConversations(content); } else if (app.loadConversations) { app.loadConversations(); } }; 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) : '', }); } }; this._onConnectionChanged = (data) => { const status = data?.status || 'disconnected'; const textMap = { connected: '', connecting: '正在连接消息服务…', reconnecting: '正在重新连接…', disconnected: '消息未连接,点击重试', disabled: '消息服务未配置', }; this.setData({ imStatus: status, imStatusText: textMap[status] || (status === 'connected' ? '' : '消息未连接,点击重试'), }); if (status === 'connected') { this.setupConversationListener(); this.loadConversations(); } }; app.on('conversationsUpdated', this._onGlobalConvUpdated); app.on('unreadCountChanged', this._onUnreadChanged); app.on('connectionChanged', this._onConnectionChanged); }, onUnload() { if (this._onGlobalConvUpdated) { app.off('conversationsUpdated', this._onGlobalConvUpdated); } if (this._onUnreadChanged) { app.off('unreadCountChanged', this._onUnreadChanged); } if (this._onConnectionChanged) { app.off('connectionChanged', this._onConnectionChanged); } if (this.conversationsUpdatedListener && wx.goEasy?.im) { wx.goEasy.im.off(wx.GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, this.conversationsUpdatedListener); this.conversationsUpdatedListener = null; } }, async onShow() { if (!this.checkLoginStatus()) return; this.setData({ defaultAvatarUrl: getDefaultAvatarUrl(app) }); loadGroupMetaCache(app); if (app.restoreTabBarBadge) app.restoreTabBarBadge(); this.registerNotificationComponent(); if (app.globalData.messageManager?.unreadTotal != null && app.emitEvent) { app.emitEvent('tabBarBadgeChanged', { badgeText: app.globalData.messageManager.unreadTotal > 0 ? String(app.globalData.messageManager.unreadTotal) : '', }); } this.patchLocalReadState(); await this.checkPermissionAndAutoConnect(); if (this._chatAllowed) { if (app.ensureConnection) app.ensureConnection(); this.setupConversationListener(); if (app.syncConnectionStatus) app.syncConnectionStatus(); this.scheduleSoftRefresh(); } }, scheduleSoftRefresh() { if (this._showRefreshTimer) clearTimeout(this._showRefreshTimer); if (this._statusRefreshTimer) clearTimeout(this._statusRefreshTimer); this._showRefreshTimer = setTimeout(() => { this._showRefreshTimer = null; const now = Date.now(); if (now - this._lastRefreshTs < 1800 && this.data.allConversations.length) { return; } this._lastRefreshTs = now; if (app.loadConversations) app.loadConversations(); else if (wx.goEasy?.im) this.loadConversations(); }, 350); }, patchLocalReadState() { const openId = app.globalData.pageState?.lastOpenedConvId; if (!openId || !this.data.allConversations.length) return; const list = this.data.allConversations.map((c) => { const id = c.groupId || c.userId; if (id === openId && (c.unread || 0) > 0) { return { ...c, unread: 0 }; } return c; }); const openTimes = wx.getStorageSync('conversationOpenTimes') || {}; sortConversations(list, openTimes); this.setData({ allConversations: list }, () => this.applyFilter()); }, retryImConnection() { if (!app.globalData.chatEnabled) { wx.showToast({ title: '消息服务未就绪', icon: 'none' }); return; } this.setData({ imStatus: 'connecting', imStatusText: '正在连接消息服务…' }); app.globalData.goEasyConnection.autoReconnect = true; app.globalData.goEasyConnection.reconnectAttempts = 0; if (app.ensureConnection) app.ensureConnection(); setTimeout(() => { if (app.syncConnectionStatus) app.syncConnectionStatus(); if (app.loadConversations) app.loadConversations(); }, 2000); }, onHide() {}, // ========== ==== ================ ==== ================ 鉴权检查 ========== async checkPermissionAndAutoConnect() { const seq = ++this._permissionSeq; let quanxian; try { quanxian = await jianquanxian(app, { silent: true }); } catch (e) { this._chatAllowed = true; return; } if (seq !== this._permissionSeq) return; if (!quanxian.allowed) { this._chatAllowed = false; const currentRole = app.globalData.currentRole || 'normal'; let hint = '消息连接暂不可用,列表可正常浏览'; if (currentRole === 'dashou') { hint = '开通会员后可收发消息(列表可正常浏览)'; } const prev = this.data.imStatusText; if (prev !== hint) { this.setData({ imStatusText: hint }); } return; } this._chatAllowed = true; if (this.data.imStatusText && this.data.imStatusText.includes('会员')) { this.setData({ imStatusText: '' }); } this.autoConnect(); }, // 登录状态检查 checkLoginStatus() { const uid = wx.getStorageSync('uid'); if (!uid) { wx.showToast({ title: '请先登录', icon: 'none' }); setTimeout(() => { wx.redirectTo({ url: '/pages/mine/mine' }); }, 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; }, autoConnect() { const role = isStaffMode() ? 'shangjia' : (getLocalImIdentity(app) || app.globalData.currentRole || 'normal'); const uid = wx.getStorageSync('uid'); const targetUserId = getImUserIdForRole(role, uid); if (!targetUserId) return; const onReady = () => { this.setupConversationListener(); if (app.loadConversations) app.loadConversations(); else this.loadConversations(); }; const status = wx.goEasy?.getConnectionStatus?.() || 'disconnected'; const currentUserId = wx.goEasy?.im?.userId; if ((status === 'connected' || status === 'reconnected') && currentUserId === targetUserId) { onReady(); return; } const tryConnect = async () => { try { if (app.ensureImForRole) { await app.ensureImForRole(role); } else if (app.connectWithIdentity) { await app.connectWithIdentity(role, targetUserId, true); } else if (app.ensureConnection) { app.ensureConnection(); } onReady(); } catch (e) { const s = wx.goEasy?.getConnectionStatus?.(); if (s === 'connected' || s === 'reconnected') onReady(); } }; tryConnect(); }, 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) { } }, 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) { if (!content || !content.conversations) { this.setData({ allConversations: [] }, () => this.applyFilter()); return; } let list = content.conversations; const defaultAvatar = getDefaultAvatar(app); const myGoEasyId = wx.goEasy?.im?.userId || getLocalImUserId(app); const ossBase = app.globalData.ossImageUrl || ''; const groupMeta = app.globalData.groupInfoMap || {}; list.forEach((item, idx) => { item.key = item.groupId || item.userId || item.teamId || `${item.type || 'c'}_${idx}`; if (item.type === 'group') { if (!item.data) item.data = {}; if (!item.data.orderId && item.groupId) { const m = /^group_(.+)$/.exec(String(item.groupId)); if (m) item.data.orderId = m[1]; } const meta = groupMeta[item.groupId]; if (meta) { if (!item.data) item.data = {}; Object.assign(item.data, meta); } if (item.data.orderJieshao && !item.data.orderDesc) { item.data.orderDesc = item.data.orderJieshao; } 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 = {}; item.data.avatar = resolveAvatarUrl(item.data.avatar, app) || defaultAvatar; } if (!item.lastMessage) { item.lastMessage = { type: 'text', payload: { text: '' }, timestamp: 0 }; } if (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); const signature = list.map((c) => { const id = c.groupId || c.userId || c.teamId || ''; const unread = c.unread || 0; const ts = c.lastMessage?.timestamp || 0; const preview = c.displayLastMsg || c.lastMessage?.payload?.text || ''; return `${id}:${unread}:${ts}:${preview.slice(0, 20)}`; }).join('|'); if (signature === this._lastConvSignature && this.data.allConversations.length) { if (app.globalData.messageManager) { app.globalData.messageManager.unreadTotal = unreadTotal; } if (app.updateUnreadCount) { app.updateUnreadCount(unreadTotal); } else if (app.emitEvent) { app.emitEvent('tabBarBadgeChanged', { badgeText: unreadTotal > 0 ? String(unreadTotal) : '' }); } this.applyFilter(); return; } this._lastConvSignature = signature; if (app.globalData.messageManager) { app.globalData.messageManager.unreadTotal = unreadTotal; } if (app.updateUnreadCount) { app.updateUnreadCount(unreadTotal); } else if (app.emitEvent) { app.emitEvent('tabBarBadgeChanged', { badgeText: unreadTotal > 0 ? String(unreadTotal) : '' }); } const prevDisplay = this.data.displayCount; this.setData({ allConversations: list, displayCount: prevDisplay > 10 ? prevDisplay : 10, }, () => { this.applyFilter(); }); this.subscribeGroupsIfNeeded(list); this.scheduleOrderStatusRefresh(list); }, scheduleOrderStatusRefresh(conversations) { if (this._statusRefreshTimer) clearTimeout(this._statusRefreshTimer); this._statusRefreshTimer = setTimeout(() => { this._statusRefreshTimer = null; this.refreshOrderStatuses(conversations); }, 400); }, async refreshOrderStatuses(conversations) { const orderIds = []; (conversations || this.data.allConversations).forEach((c) => { if (!isOrderGroupConversation(c)) return; const oid = c.data?.orderId; if (oid && !orderIds.includes(oid)) orderIds.push(oid); }); if (!orderIds.length) return; try { const res = await request({ url: '/dingdan/ltddztpl', method: 'POST', data: { order_ids: orderIds.slice(0, 20) }, }); const body = res?.data || {}; const map = body.data || {}; if (!Object.keys(map).length) return; let changed = false; const list = this.data.allConversations.map((c) => { const oid = c.data?.orderId; if (!oid || !map[oid]) return c; const st = map[oid]; changed = true; const data = { ...c.data, orderId: oid, orderDesc: st.jieshao || c.data.orderDesc, orderJine: st.jine != null ? st.jine : c.data.orderJine, orderZhuangtai: st.zhuangtai, orderZhuangtaiText: getZhuangtaiText(st.zhuangtai), }; if (c.groupId && app.globalData.groupInfoMap) { app.globalData.groupInfoMap[c.groupId] = { ...(app.globalData.groupInfoMap[c.groupId] || {}), ...data, }; } return { ...c, data }; }); if (changed) { this.setData({ allConversations: list }, () => this.applyFilter()); } } catch (e) { console.warn('刷新订单状态失败', e); } }, /** * 群组订阅,每次获取群组列表时直接订阅保证消息不丢失 */ 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: 10, searchKeyword: '' }, () => { this.applyFilter(); this.calculateScrollHeight(); }); }, 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 (this._chatAllowed === false) { wx.showToast({ title: '请先开通会员后再聊天', icon: 'none' }); return; } recordConversationOpen(conversation); const convId = conversation.groupId || conversation.userId; if (convId) { if (!app.globalData.pageState) app.globalData.pageState = {}; app.globalData.pageState.lastOpenedConvId = convId; } if (conversation.type === 'private') { const param = { toUserId: conversation.userId, toName: conversation.data.name || '用户', toAvatar: resolveAvatarUrl(conversation.data.avatar, app), }; wx.navigateTo({ url: '/pages/chat/chat?data=' + encodeURIComponent(JSON.stringify(param)) }); } else if (conversation.type === 'group' || (conversation.groupId && isOrderGroupConversation(conversation))) { const d = conversation.data || {}; const rawGid = conversation.groupId || ''; const oid = String(d.orderId || rawGid).replace(/^group_/, ''); const groupId = rawGid.startsWith('group_') ? rawGid : (oid ? `group_${oid}` : rawGid); const param = { groupId, orderId: oid, groupName: d.name || '订单群聊', groupAvatar: d.avatar, isCross: d.isCross || 0, orderZhuangtai: d.orderZhuangtai, orderJine: d.orderJine, orderDesc: d.orderDesc || d.orderJieshao || '', currentUserId: wx.goEasy?.im?.userId || getLocalImUserId(app), currentUserName: this.data.currentUser?.name || '', currentUserAvatar: this.data.currentUser?.avatar || '', }; wx.navigateTo({ url: '/pages/group-chat/group-chat?data=' + encodeURIComponent(JSON.stringify(param)) }); } else if (conversation.type === 'cs') { const param = { teamId: conversation.teamId || conversation.id }; wx.navigateTo({ url: '/pages/cs-chat/cs-chat?data=' + encodeURIComponent(JSON.stringify(param)) }); } }, chatKefu() { openCustomerServiceChat(); }, 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 kefuBarHeight = this.data.activeTab === 2 ? (112 / rpxRatio) : 0; const safeAreaBottom = systemInfo.safeArea ? (windowHeight - systemInfo.safeArea.bottom) : 0; this.setData({ scrollHeight: windowHeight - headerHeight - tabBarHeight - kefuBarHeight - safeAreaBottom }); }, onAvatarError(e) { const index = e.currentTarget.dataset.index; const def = getDefaultAvatarUrl(app); const key = `filteredConversations[${index}].data.avatar`; this.setData({ [key]: def }); } }))