From 6e2f7bc39f5bb4783d30b08461cfb463577c266f Mon Sep 17 00:00:00 2001 From: XingQue Date: Mon, 29 Jun 2026 04:20:39 +0800 Subject: [PATCH] =?UTF-8?q?refactor(im):=20=E5=AF=B9=E9=BD=90=E6=96=87?= =?UTF-8?q?=E8=B5=AB=E7=94=B5=E7=AB=9E=E6=B6=88=E6=81=AF=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=EF=BC=88=E9=85=8D=E5=AF=B9=E7=BE=A4ID+=E7=A8=B3=E5=AE=9A?= =?UTF-8?q?=E7=9B=91=E5=90=AC=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.js | 35 +++-- .../fighter-order-detail.js | 9 +- .../merchant-order-detail.js | 2 + pages/messages/messages.js | 133 +++++++++++++----- pages/order-detail/order-detail.js | 2 + utils/chat-core.js | 6 +- utils/im-user.js | 55 +++++++- 7 files changed, 191 insertions(+), 51 deletions(-) diff --git a/app.js b/app.js index e8e1994..18fdaef 100644 --- a/app.js +++ b/app.js @@ -81,6 +81,8 @@ App({ xshenfen: 1, goEasyConfig: null, + chatEnabled: false, + groupInfoMap: {}, kefuConfig: { link: '', enterpriseId: '' @@ -152,6 +154,7 @@ App({ pageState: { currentPage: '', isInChatPage: false, + isInGroupChat: false, currentChatId: '', lastPageUpdate: 0 }, @@ -222,13 +225,11 @@ App({ this.emitEvent('staffContextChanged', {}); } - // ⑤ 建立连接 - const saved = this.getSavedConnection(); - console.log('【启动】缓存身份:', saved ? saved.identityType : '无', - 'userId:', saved ? saved.userId : '无'); + // ⑤ 建立连接(文赫式:全局监听,不反复 disconnect) setTimeout(() => { - if (this.startImWhenReady) this.startImWhenReady(); - else if (this.ensureConnection) this.ensureConnection(); + if (this.globalData.chatEnabled && this.ensureConnection) { + this.ensureConnection(); + } }, 300); // ⑥ 获取远程配置 @@ -243,8 +244,9 @@ App({ } catch (e) {} this.initGoEasyWithConfig(); this.initCurrentUser(); - if (this.startImWhenReady) this.startImWhenReady(); - else if (this.connectForCurrentRole) this.connectForCurrentRole(); + if (this.globalData.chatEnabled && this.ensureConnection) { + this.ensureConnection(); + } console.log('远程配置更新完成'); }) .catch(err => { @@ -252,10 +254,20 @@ App({ }); }, - /** 从后台切回前台时再次向后端校验 */ + /** 从后台切回前台时再次向后端校验,并维持 IM 连接 */ async onShow() { if (!wx.getStorageSync('token')) return; await ensurePhoneAuth({ redirect: true }); + if (this.globalData.chatEnabled && typeof this.ensureConnection === 'function') { + this.globalData.goEasyConnection.autoReconnect = true; + this.ensureConnection(); + if (typeof this.syncConnectionStatus === 'function') { + setTimeout(() => this.syncConnectionStatus(), 800); + } + if (typeof this.loadConversations === 'function') { + setTimeout(() => this.loadConversations(), 300); + } + } }, // 连接管理方法 @@ -397,9 +409,10 @@ App({ modules: ['im', 'pubsub'] }); wx.GoEasy = GoEasy; + this.globalData.chatEnabled = true; console.log('GoEasy 初始化成功'); - if (this.startImWhenReady && wx.getStorageSync('uid')) { - setTimeout(() => this.startImWhenReady(), 100); + if (this.ensureConnection && wx.getStorageSync('uid')) { + setTimeout(() => this.ensureConnection(), 100); } } catch (error) { console.error('GoEasy 初始化失败:', error); diff --git a/pages/fighter-order-detail/fighter-order-detail.js b/pages/fighter-order-detail/fighter-order-detail.js index d4bb52a..7e8951a 100644 --- a/pages/fighter-order-detail/fighter-order-detail.js +++ b/pages/fighter-order-detail/fighter-order-detail.js @@ -285,7 +285,7 @@ Page({ }); }, - // ===== 联系老板(订单群 groupId = 订单号) ===== + // ===== 联系老板/商家(配对群 group_Ds{打手}_Sj/Boss{对方}) ===== goToChatWithBoss() { const uid = wx.getStorageSync('uid') if (!uid) { @@ -299,10 +299,17 @@ Page({ return } + const fadanPingtai = this.data.jibenShuju.fadanpingtai || 0 + const partnerUid = fadanPingtai === 2 + ? (this.data.xiangxiShuju.shangjia_id || '') + : (this.data.xiangxiShuju.laoban_id || '') + connectionManager.connectToGroupChat({ identityType: 'dashou', userId: 'Ds' + uid, orderId: orderId, + partnerUid, + fadanPingtai, groupName: (this.data.jibenShuju.nicheng || '订单') + '的订单群', isCross: this.data.jibenShuju.fadanpingtai || 0, }).catch((err) => { diff --git a/pages/merchant-order-detail/merchant-order-detail.js b/pages/merchant-order-detail/merchant-order-detail.js index f40d0ff..f9b2d64 100644 --- a/pages/merchant-order-detail/merchant-order-detail.js +++ b/pages/merchant-order-detail/merchant-order-detail.js @@ -350,6 +350,8 @@ Page({ identityType: 'shangjia', userId: 'Sj' + uid, orderId: orderId, + partnerUid: dashouUid, + fadanPingtai: this.data.jibenShuju.fadanpingtai || 0, groupName, isCross, }).catch((err) => { diff --git a/pages/messages/messages.js b/pages/messages/messages.js index 0bac1cb..b9db0ba 100644 --- a/pages/messages/messages.js +++ b/pages/messages/messages.js @@ -2,10 +2,11 @@ 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 { getLocalImUserId, getLocalImIdentity, getImUserIdForRole, loadGroupMetaCache, getDefaultAvatar } from '../../utils/im-user.js'; +import { enrichGroupConversation, isOrderGroupConversation, sortConversations, recordConversationOpen } from '../../utils/group-chat.js'; import { reconnectForRole } from '../../utils/role-tab-bar.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'); @@ -30,18 +31,51 @@ Page(createPage({ this.calculateScrollHeight(); 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(); + } + }; + app.on('conversationsUpdated', this._onGlobalConvUpdated); + }, + + onUnload() { + if (this._onGlobalConvUpdated) { + app.off('conversationsUpdated', this._onGlobalConvUpdated); + } + 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); 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.checkPermissionAndAutoConnect(); + if (app.ensureConnection) app.ensureConnection(); + this.setupConversationListener(); + setTimeout(() => { + if (app.loadConversations) app.loadConversations(); + else if (wx.goEasy?.im) this.loadConversations(); + }, 200); }, onHide() {}, - onUnload() {}, - // ========== ==== ================ ==== ================ 鉴权检查 ========== async checkPermissionAndAutoConnect() { const seq = ++this._permissionSeq; @@ -90,26 +124,40 @@ Page(createPage({ }, autoConnect() { - const targetUserId = getLocalImUserId(app); + const role = isStaffMode() ? 'shangjia' : (getLocalImIdentity(app) || app.globalData.currentRole || 'normal'); + const uid = wx.getStorageSync('uid'); + const targetUserId = getImUserIdForRole(role, uid); if (!targetUserId) return; - if (app.ensureConnection) app.ensureConnection(); - reconnectForRole(getPrimaryRole(app)); + const onReady = () => { + this.setupConversationListener(); + if (app.loadConversations) app.loadConversations(); + else this.loadConversations(); + }; - 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) {} + const status = wx.goEasy?.getConnectionStatus?.() || 'disconnected'; + const currentUserId = wx.goEasy?.im?.userId; + if ((status === 'connected' || status === 'reconnected') && currentUserId === targetUserId) { + onReady(); + return; } - this.connectGoEasy(targetUserId); + 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) { @@ -178,7 +226,21 @@ Page(createPage({ this.setData({ allConversations: [] }, () => this.applyFilter()); return; } - let list = normalizeConversationsList(conversations, app, this._peerProfileCache); + const myGoEasyId = wx.goEasy?.im?.userId || getLocalImUserId(app); + const defaultAvatar = getDefaultAvatar(app); + const ossBase = app.globalData.ossImageUrl || ''; + + let list = conversations.map((item) => { + if (item.type === 'group') { + return enrichGroupConversation(item, myGoEasyId, defaultAvatar, ossBase); + } + if (!item.data) item.data = {}; + if (item.data.avatar) { + item.data.avatar = resolveAvatarUrl(item.data.avatar, app); + } + return item; + }); + list.forEach(item => { if (!item.lastMessage) { item.lastMessage = { type: 'text', payload: { text: '' }, timestamp: 0 }; @@ -186,17 +248,13 @@ Page(createPage({ if (item.lastMessage.timestamp) { item.lastMessage.date = formatDate(item.lastMessage.timestamp); } + if (item.type === 'group' && item.displayLastMsg) { + item.lastMessage.preview = item.displayLastMsg; + } }); - 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; - }); + const openTimes = wx.getStorageSync('conversationOpenTimes') || {}; + list = sortConversations(list, openTimes); this.setData({ allConversations: list, displayCount: 10 }, () => { this.applyFilter(); @@ -244,7 +302,7 @@ Page(createPage({ const { allConversations, activeTab, searchKeyword } = this.data; let filtered = []; if (activeTab === 0) { - filtered = allConversations.filter(c => c.type === 'group' && c.data && c.data.orderId); + filtered = allConversations.filter(c => isOrderGroupConversation(c)); } else if (activeTab === 1) { filtered = allConversations.filter(c => c.type === 'private'); } else if (activeTab === 2) { @@ -263,7 +321,7 @@ Page(createPage({ const tabUnread = [0, 0, 0]; allConversations.forEach(c => { - if (c.type === 'group' && c.data && c.data.orderId) tabUnread[0] += c.unread || 0; + 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; }); @@ -294,6 +352,7 @@ Page(createPage({ chat(e) { const conversation = e.currentTarget.dataset.conversation; if (!conversation) return; + recordConversationOpen(conversation); if (conversation.type === 'private') { const param = { @@ -303,12 +362,16 @@ Page(createPage({ }; wx.navigateTo({ url: '/pages/chat/chat?data=' + encodeURIComponent(JSON.stringify(param)) }); } else if (conversation.type === 'group') { + const d = conversation.data || {}; const param = { groupId: conversation.groupId, - orderId: conversation.data.orderId || '', - groupName: conversation.data.name, - groupAvatar: conversation.data.avatar, - isCross: conversation.data.isCross || 0 + orderId: d.orderId || '', + groupName: d.name || '订单群聊', + groupAvatar: d.avatar, + isCross: d.isCross || 0, + orderZhuangtai: d.orderZhuangtai, + orderJine: d.orderJine, + orderDesc: d.orderDesc || d.orderJieshao || '', }; wx.navigateTo({ url: '/pages/group-chat/group-chat?data=' + encodeURIComponent(JSON.stringify(param)) }); } else if (conversation.type === 'cs') { diff --git a/pages/order-detail/order-detail.js b/pages/order-detail/order-detail.js index d04a153..3e985fb 100644 --- a/pages/order-detail/order-detail.js +++ b/pages/order-detail/order-detail.js @@ -211,6 +211,8 @@ Page({ identityType: 'normal', userId: 'Boss' + uid, orderId: dingdanId, + partnerUid: this.data.xiangxiShuju.dashou || '', + fadanPingtai: this.data.jibenShuju.fadanpingtai || 0, groupName: this.data.jibenShuju.nicheng + '的订单群', isCross: 0 }).catch(err => { diff --git a/utils/chat-core.js b/utils/chat-core.js index 48c51ce..fd0192d 100644 --- a/utils/chat-core.js +++ b/utils/chat-core.js @@ -2,7 +2,7 @@ import GoEasy from '../static/lib/goeasy-2.13.24.esm.min'; import { jianquanxian } from './imAuth/jianquanxian'; import { parseOrderCardText } from './group-chat.js'; -import { persistGroupMeta, getFreshImUser, getImUserIdForRole } from './im-user.js'; +import { persistGroupMeta, getFreshImUser, getImUserIdForRole, getLocalImUserId, getLocalImIdentity } from './im-user.js'; let _globalPrivateHandler = null; let _globalGroupHandler = null; @@ -82,9 +82,11 @@ function getCurrentGoEasyUserId() { } function getExpectedImUserId(app) { + const fromLocal = getLocalImUserId(app); + if (fromLocal) return fromLocal; const uid = wx.getStorageSync('uid'); if (!uid) return ''; - const role = app.globalData.currentRole || 'normal'; + const role = app.globalData.currentRole || getLocalImIdentity(app) || 'normal'; return getImUserIdForRole(role, uid); } diff --git a/utils/im-user.js b/utils/im-user.js index 49f2b39..29a2c56 100644 --- a/utils/im-user.js +++ b/utils/im-user.js @@ -2,6 +2,7 @@ import { getPrimaryRole } from './primary-role.js'; import { resolveAvatarUrl } from './avatar.js'; import { ensureRoleOnCenterPage } from './role-tab-bar.js'; +import { isStaffMode } from './staff-api.js'; /** 联系对象前缀:管事/组长/考核官已废弃独立前缀,统一 Ds */ const PEER_PREFIX = { @@ -14,15 +15,37 @@ const PEER_PREFIX = { kaoheguan: 'Ds', }; +export const IM_ROLE_PREFIX = { + normal: 'Boss', + dashou: 'Ds', + shangjia: 'Sj', + guanshi: 'Ds', + zuzhang: 'Ds', + kaoheguan: 'Ds', +}; + +export function getDefaultAvatar(app) { + const oss = app.globalData.ossImageUrl || ''; + return oss + (app.globalData.morentouxiang || ''); +} + export function getImPrefixForRole(role) { + if (isStaffMode()) return 'Sj'; return PEER_PREFIX[role] || 'Ds'; } -/** 当前端连接 GoEasy 用的 userId */ +export function getImUserIdForRole(role, uid) { + if (!uid) return ''; + const prefix = getImPrefixForRole(role); + return prefix + uid; +} + +/** 当前端连接 GoEasy 用的 userId(子客服仍用 Sj) */ export function getLocalImUserId(app) { app = app || getApp(); const uid = wx.getStorageSync('uid') || ''; if (!uid) return ''; + if (isStaffMode()) return 'Sj' + uid; const primary = getPrimaryRole(app); if (primary === 'dashou') return 'Ds' + uid; if (primary === 'shangjia') return 'Sj' + uid; @@ -31,15 +54,43 @@ export function getLocalImUserId(app) { export function getLocalImIdentity(app) { app = app || getApp(); + if (isStaffMode()) return 'shangjia'; return getPrimaryRole(app) || 'normal'; } +export function getFreshImUser(app, role, uid) { + const effectiveRole = isStaffMode() ? 'shangjia' : role; + const id = getImUserIdForRole(effectiveRole, uid); + const touxiang = wx.getStorageSync('touxiang') || ''; + const avatar = resolveAvatarUrl(touxiang, app); + const name = app.globalData.currentUser?.name || `用户${(uid || '').slice(-6)}`; + return { id, name, avatar }; +} + +export function persistGroupMeta(app, groupId, meta) { + if (!groupId || !meta) return; + if (!app.globalData.groupInfoMap) app.globalData.groupInfoMap = {}; + app.globalData.groupInfoMap[groupId] = { ...app.globalData.groupInfoMap[groupId], ...meta }; + try { + const cache = wx.getStorageSync('pairGroupMetaCache') || {}; + cache[groupId] = app.globalData.groupInfoMap[groupId]; + wx.setStorageSync('pairGroupMetaCache', cache); + } catch (e) {} +} + +export function loadGroupMetaCache(app) { + try { + const cache = wx.getStorageSync('pairGroupMetaCache') || {}; + if (!app.globalData.groupInfoMap) app.globalData.groupInfoMap = {}; + Object.assign(app.globalData.groupInfoMap, cache); + } catch (e) {} +} + export function buildPeerImUserId(uid, role = 'dashou') { if (!uid) return ''; return getImPrefixForRole(role) + uid; } -/** 联系邀请人/管事/组长:统一 Ds 前缀 */ export function buildGuanshiPeerId(uid) { return buildDashouPeerId(uid); }