From 7b3e7a39e952d2c0c6c2cb0bc1abc35c8c52a2c7 Mon Sep 17 00:00:00 2001 From: XingQue Date: Tue, 23 Jun 2026 23:30:14 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8C=E6=AD=A5=E8=81=8A=E5=A4=A9=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E5=88=B0=E6=A0=B9=E7=9B=AE=E5=BD=95pages/utils?= =?UTF-8?q?=EF=BC=88=E4=B8=8E=E5=89=AF=E6=9C=AC=E5=B7=A5=E7=A8=8B=E7=BB=93?= =?UTF-8?q?=E6=9E=84=E4=B8=80=E8=87=B4=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- pages/qunliaotian/qunliaotian.js | 20 ++---- pages/xiaoxi/xiaoxi.js | 65 ++++++++++++------ pages/xiaoxi/xiaoxi.wxml | 9 +++ pages/xiaoxi/xiaoxi.wxss | 3 + utils/chat-core.js | 13 +++- utils/group-chat.js | 114 +++++++++++++++++++++++++------ utils/im-user.js | 50 ++++++++++++++ utils/message-sender.js | 34 +++++++-- utils/xiaoxilj.js | 15 +++- 9 files changed, 258 insertions(+), 65 deletions(-) create mode 100644 utils/im-user.js diff --git a/pages/qunliaotian/qunliaotian.js b/pages/qunliaotian/qunliaotian.js index 01cf295..12a48f8 100644 --- a/pages/qunliaotian/qunliaotian.js +++ b/pages/qunliaotian/qunliaotian.js @@ -3,6 +3,7 @@ import { formatDate } from '../../static/lib/utils'; import { sendGroupMessage } from '../../utils/message-sender'; import { showConfirmByScene } from '../../utils/scriptService.js'; import { normalizeGroupMessage } from '../../utils/group-chat.js'; +import { getFreshImUser } from '../../utils/im-user.js'; Page({ data: { @@ -62,17 +63,6 @@ Page({ } }, - // 原有的 initCurrentUser 保留,仅当没有外部参数时使用 - initCurrentUser() { - const uid = wx.getStorageSync('uid'); - const role = app.globalData.currentRole || 'normal'; - const prefix = { normal:'Boss', dashou:'Ds', shangjia:'Sj', guanshi:'Gs', zuzhang:'Zz' }[role] || 'Boss'; - this.setData({ currentUser: { - id: prefix + uid, - name: app.globalData.currentUser?.name || `用户${uid}`, - avatar: this.fixAvatar(wx.getStorageSync('touxiang')) - }}); - }, /*onLoad(options) { if (options.data) { try { @@ -184,11 +174,11 @@ Page({ initCurrentUser() { const uid = wx.getStorageSync('uid'); const role = app.globalData.currentRole || 'normal'; - const prefix = { normal:'Boss', dashou:'Ds', shangjia:'Sj', guanshi:'Gs', zuzhang:'Zz' }[role] || 'Boss'; + const fresh = getFreshImUser(app, role, uid); this.setData({ currentUser: { - id: prefix + uid, - name: app.globalData.currentUser?.name || `用户${uid}`, - avatar: this.fixAvatar(wx.getStorageSync('touxiang')) + id: fresh.id, + name: fresh.name, + avatar: fresh.avatar, }}); }, diff --git a/pages/xiaoxi/xiaoxi.js b/pages/xiaoxi/xiaoxi.js index 2d70bf2..13a91bf 100644 --- a/pages/xiaoxi/xiaoxi.js +++ b/pages/xiaoxi/xiaoxi.js @@ -2,7 +2,8 @@ const app = getApp(); import { formatDate } from '../../static/lib/utils'; const { jianquanxian } = require('../../utils/imAuth/jianquanxian'); -import { enrichGroupConversation, isOrderGroupConversation } from '../../utils/group-chat.js'; +import { enrichGroupConversation, isOrderGroupConversation, sortConversations, recordConversationOpen } from '../../utils/group-chat.js'; +import { loadGroupMetaCache } from '../../utils/im-user.js'; Page({ data: { @@ -24,10 +25,33 @@ Page({ 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); + } + }; + app.on('conversationsUpdated', this._onGlobalConvUpdated); + app.on('unreadCountChanged', this._onGlobalConvUpdated); + }, + + onUnload() { + if (this._onGlobalConvUpdated) { + app.off('conversationsUpdated', this._onGlobalConvUpdated); + app.off('unreadCountChanged', this._onGlobalConvUpdated); + } }, 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(); }, @@ -194,11 +218,14 @@ Page({ const meta = groupMeta[item.groupId]; if (meta) { if (!item.data) item.data = {}; - if (meta.name) item.data.name = meta.name; - if (meta.avatar) item.data.avatar = meta.avatar; - if (meta.orderDesc) item.data.orderDesc = meta.orderDesc; - if (meta.orderId) item.data.orderId = meta.orderId; - if (meta.orderZhuangtai != null) item.data.orderZhuangtai = meta.orderZhuangtai; + 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) { @@ -213,24 +240,23 @@ Page({ } }); - 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; - return (b.lastMessage.timestamp || 0) - (a.lastMessage.timestamp || 0); - }); + 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(); }); - const unreadTotal = content.unreadTotal !== undefined ? content.unreadTotal : list.reduce((sum, c) => sum + (c.unread || 0), 0); - if (app && app.emitEvent) { - app.emitEvent('tabBarBadgeChanged', { badgeText: unreadTotal > 0 ? String(unreadTotal) : '' }); - } - - // ✅ 每次渲染会话后,无条件重新订阅所有群组,确保订阅关系始终有效 + // 群组订阅(不重复断开连接) this.subscribeGroupsIfNeeded(list); }, @@ -316,6 +342,7 @@ Page({ chat(e) { const conversation = e.currentTarget.dataset.conversation; if (!conversation) return; + recordConversationOpen(conversation); if (conversation.type === 'private') { const param = { diff --git a/pages/xiaoxi/xiaoxi.wxml b/pages/xiaoxi/xiaoxi.wxml index a69a9dd..46e169b 100644 --- a/pages/xiaoxi/xiaoxi.wxml +++ b/pages/xiaoxi/xiaoxi.wxml @@ -37,6 +37,15 @@ {{item.lastMessage.date}} + + 对方 {{item.data.counterpartYonghuid}} {{item.data.name}} + + + 打手 {{item.data.dashouYonghuid}} {{item.data.dashouNicheng}} + + + {{item.data.partnerRoleLabel || '商家'}} {{item.data.partnerYonghuid}} {{item.data.partnerNicheng}} + 单号 {{item.data.orderId}} {{item.data.orderDesc}} diff --git a/pages/xiaoxi/xiaoxi.wxss b/pages/xiaoxi/xiaoxi.wxss index 3d1ca42..c43c4f1 100644 --- a/pages/xiaoxi/xiaoxi.wxss +++ b/pages/xiaoxi/xiaoxi.wxss @@ -23,6 +23,9 @@ .top-line { display: flex; justify-content: space-between; margin-bottom: 8rpx; } .name { font-size: 30rpx; color: #1a1a1a; font-weight: 500; } .time { font-size: 22rpx; color: #999; flex-shrink: 0; margin-left: 12rpx; } +.user-meta-row { margin-bottom: 4rpx; } +.user-meta { font-size: 24rpx; color: #333; } +.user-meta.sub { color: #666; font-size: 22rpx; } .order-block { background: #f7f8fa; border-radius: 12rpx; padding: 16rpx 18rpx; margin-bottom: 10rpx; } .order-id { display: block; font-size: 24rpx; color: #576b95; margin-bottom: 6rpx; } .order-desc { display: block; font-size: 26rpx; color: #333; line-height: 1.5; word-break: break-all; } diff --git a/utils/chat-core.js b/utils/chat-core.js index d3bc751..20f5265 100644 --- a/utils/chat-core.js +++ b/utils/chat-core.js @@ -1,6 +1,7 @@ // utils/chat-core.js import GoEasy from '../static/lib/goeasy-2.13.24.esm.min'; import { jianquanxian } from './imAuth/jianquanxian'; +import { getFreshImUser } from './im-user.js'; let _globalPrivateHandler = null; let _globalGroupHandler = null; @@ -209,16 +210,22 @@ async function connectWithIdentity(app, identityType, userId, isAutoRestore = fa saveConnectionState(app); const uid = wx.getStorageSync('uid'); + const role = app.globalData.currentRole || identityType || 'normal'; + const freshUser = getFreshImUser(app, role, uid); const currentUser = app.globalData.currentUser || { id: uid, - name: '用户' + (uid ? uid.substring(0, 6) : ''), - avatar: app.globalData.ossImageUrl + app.globalData.morentouxiang, + name: freshUser.name, + avatar: freshUser.avatar, }; return new Promise((resolve, reject) => { wx.goEasy.connect({ id: userId, - data: { name: currentUser.name, avatar: currentUser.avatar, identityType }, + data: { + name: freshUser.name || currentUser.name, + avatar: freshUser.avatar || currentUser.avatar || (app.globalData.ossImageUrl + app.globalData.morentouxiang), + identityType, + }, onSuccess: () => { app.globalData.goEasyConnection.status = 'connected'; app.globalData.goEasyConnection.lastConnectTime = Date.now(); diff --git a/utils/group-chat.js b/utils/group-chat.js index 9410394..dc81e6b 100644 --- a/utils/group-chat.js +++ b/utils/group-chat.js @@ -1,5 +1,5 @@ /** - * 打手-商家/老板配对群:group_Ds{打手}_Sj{商家} 或 group_Ds{打手}_Boss{老板} + * 打手-商家/老板配对群:group_Ds{打手}_Sj{商家} */ export const ORDER_CARD_PREFIX = '[ORDER_CARD]'; @@ -29,6 +29,67 @@ export function getCounterpartGoEasyId(groupId, myGoEasyId) { return null; } +function fixAvatarUrl(avatar, defaultAvatar, ossBase) { + if (!avatar) return defaultAvatar; + if (avatar.startsWith('http')) return avatar; + return (ossBase || '') + avatar.replace(/^\//, ''); +} + +/** 根据群 to.data 与当前身份,解析对方/双方展示信息 */ +export function applyPairMetaToConversation(item, myGoEasyId, defaultAvatar, ossBase) { + if (!item || !item.data) return; + const d = item.data; + + if (d.dashouGoEasyId && d.partnerGoEasyId) { + const isDashou = myGoEasyId === d.dashouGoEasyId; + const isPartner = myGoEasyId === d.partnerGoEasyId; + d.dashouNicheng = d.dashouName || d.dashouNicheng || ''; + d.partnerNicheng = d.partnerName || d.partnerNicheng || ''; + d.dashouAvatar = fixAvatarUrl(d.dashouAvatar, defaultAvatar, ossBase); + d.partnerAvatar = fixAvatarUrl(d.partnerAvatar, defaultAvatar, ossBase); + + if (isDashou) { + d.counterpartId = d.partnerGoEasyId; + d.counterpartYonghuid = d.partnerYonghuid || d.partnerGoEasyId.replace(/^(Sj|Boss)/, ''); + d.partnerRoleLabel = d.partnerGoEasyId.startsWith('Boss') ? '老板' : '商家'; + d.name = d.partnerNicheng || `${d.partnerRoleLabel}${d.counterpartYonghuid}`; + d.avatar = d.partnerAvatar || defaultAvatar; + } else if (isPartner) { + d.counterpartId = d.dashouGoEasyId; + d.counterpartYonghuid = d.dashouYonghuid || d.dashouGoEasyId.replace(/^Ds/, ''); + d.name = d.dashouNicheng || `打手${d.counterpartYonghuid}`; + d.avatar = d.dashouAvatar || defaultAvatar; + } else { + const cpId = getCounterpartGoEasyId(item.groupId, myGoEasyId); + if (cpId === d.partnerGoEasyId) { + d.name = d.partnerNicheng; + d.avatar = d.partnerAvatar || defaultAvatar; + } else if (cpId === d.dashouGoEasyId) { + d.name = d.dashouNicheng; + d.avatar = d.dashouAvatar || defaultAvatar; + } + } + return; + } + + const counterpartId = getCounterpartGoEasyId(item.groupId, myGoEasyId); + if (counterpartId) { + d.counterpartId = counterpartId; + d.counterpartYonghuid = counterpartId.replace(/^(Ds|Sj|Boss)/, ''); + if (!d.name || d.name === item.groupId) { + d.name = `用户${d.counterpartYonghuid.slice(-6)}`; + } + } + + if (item.lastMessage && item.lastMessage.senderId && item.lastMessage.senderId !== myGoEasyId) { + const sd = item.lastMessage.senderData || {}; + if (sd.avatar) d.avatar = fixAvatarUrl(sd.avatar, defaultAvatar, ossBase); + if (sd.name) d.name = sd.name; + } + + d.avatar = fixAvatarUrl(d.avatar, defaultAvatar, ossBase); +} + export function parseOrderCardText(text) { if (!text || !text.startsWith(ORDER_CARD_PREFIX)) return null; try { @@ -77,36 +138,49 @@ export function enrichGroupConversation(item, myGoEasyId, defaultAvatar, ossBase if (!item || item.type !== 'group') return item; if (!item.data) item.data = {}; - const counterpartId = getCounterpartGoEasyId(item.groupId, myGoEasyId); - if (counterpartId) { - item.data.counterpartId = counterpartId; - if (!item.data.name || item.data.name === item.groupId) { - item.data.name = `用户${counterpartId.replace(/^(Ds|Sj|Boss)/, '').slice(-6)}`; - } - } - - if (item.lastMessage && item.lastMessage.senderId && item.lastMessage.senderId !== myGoEasyId) { - const sd = item.lastMessage.senderData || {}; - if (sd.avatar) item.data.avatar = sd.avatar; - if (sd.name) item.data.name = sd.name; - } - - let avatar = item.data.avatar || ''; - if (avatar && !avatar.startsWith('http')) { - avatar = (ossBase || '') + avatar.replace(/^\//, ''); - } - item.data.avatar = avatar || defaultAvatar; + applyPairMetaToConversation(item, myGoEasyId, defaultAvatar, ossBase); if (item.data.orderZhuangtai != null) { item.data.orderZhuangtaiText = ZHUANGTAI_MAP[item.data.orderZhuangtai] || ''; } item.displayLastMsg = formatLastMessagePreview(item.lastMessage); + if (!item.data.avatar) item.data.avatar = defaultAvatar; return item; } export function isOrderGroupConversation(c) { if (!c || c.type !== 'group') return false; if (c.data && c.data.orderId) return true; + if (c.data && c.data.dashouGoEasyId) return true; return isPairGroupId(c.groupId) || /^group_\w+$/.test(c.groupId || ''); } + +export function getConversationSortTime(conversation, openTimes) { + const id = conversation.groupId || conversation.userId || ''; + const msgTs = conversation.lastMessage?.timestamp || 0; + const openTs = (openTimes && id && openTimes[id]) ? openTimes[id] : 0; + return Math.max(msgTs, openTs); +} + +export function sortConversations(list, openTimes) { + return 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 = getConversationSortTime(a, openTimes); + const tb = getConversationSortTime(b, openTimes); + return tb - ta; + }); +} + +export function recordConversationOpen(conversation) { + const id = conversation?.groupId || conversation?.userId; + if (!id) return; + try { + const times = wx.getStorageSync('conversationOpenTimes') || {}; + times[id] = Date.now(); + wx.setStorageSync('conversationOpenTimes', times); + } catch (e) {} +} diff --git a/utils/im-user.js b/utils/im-user.js new file mode 100644 index 0000000..2ea6b63 --- /dev/null +++ b/utils/im-user.js @@ -0,0 +1,50 @@ +/** + * IM 用户展示:从本地缓存读取最新头像,空则用默认图 + */ +export function getDefaultAvatar(app) { + const oss = app.globalData.ossImageUrl || ''; + return oss + (app.globalData.morentouxiang || ''); +} + +export function resolveAvatarUrl(raw, app) { + const def = getDefaultAvatar(app); + if (!raw) return def; + if (raw.startsWith('http')) return raw; + const oss = app.globalData.ossImageUrl || ''; + return oss + raw.replace(/^\//, ''); +} + +export function getFreshImUser(app, role, uid) { + const prefixMap = { + normal: 'Boss', dashou: 'Ds', shangjia: 'Sj', + guanshi: 'Gs', zuzhang: 'Zz', kaoheguan: 'Kh', + }; + const prefix = prefixMap[role] || 'Boss'; + const touxiang = wx.getStorageSync('touxiang') || ''; + const avatar = resolveAvatarUrl(touxiang, app); + const name = app.globalData.currentUser?.name || `用户${(uid || '').slice(-6)}`; + return { + id: prefix + uid, + 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) {} +} diff --git a/utils/message-sender.js b/utils/message-sender.js index 879ee25..be84948 100644 --- a/utils/message-sender.js +++ b/utils/message-sender.js @@ -2,9 +2,19 @@ const app = getApp(); import { formatDate } from '../static/lib/utils'; import request from './request.js'; +import { getFreshImUser } from './im-user.js'; function sendGroupMessage(options) { const { msgData, onSuccess, onSendStart } = options; + const role = app.globalData.currentRole || 'normal'; + const uid = wx.getStorageSync('uid'); + const freshUser = getFreshImUser(app, role, uid); + msgData.currentUser = { + ...msgData.currentUser, + name: freshUser.name, + avatar: freshUser.avatar, + }; + const { type, currentUser, groupId, orderId, isCross, groupName, groupAvatar } = msgData; const localMsg = buildLocalMessage(msgData); @@ -41,12 +51,22 @@ function buildLocalMessage(data) { } function sendViaSDK(localMsg, currentUser, groupId, onSuccess, orderId, isCross, groupName, groupAvatar) { + const groupMeta = (app.globalData.groupInfoMap && app.globalData.groupInfoMap[groupId]) || {}; const toData = { - name: groupName || '订单群聊', - avatar: groupAvatar || currentUser.avatar + name: groupName || groupMeta.name || '订单群聊', + avatar: groupAvatar || groupMeta.avatar || currentUser.avatar, + orderId: orderId || groupMeta.orderId, + isCross: isCross || 0, + dashouGoEasyId: groupMeta.dashouGoEasyId, + partnerGoEasyId: groupMeta.partnerGoEasyId, + dashouYonghuid: groupMeta.dashouYonghuid, + partnerYonghuid: groupMeta.partnerYonghuid, + dashouName: groupMeta.dashouName, + partnerName: groupMeta.partnerName, + dashouAvatar: groupMeta.dashouAvatar, + partnerAvatar: groupMeta.partnerAvatar, + orderDesc: groupMeta.orderDesc, }; - if (orderId) toData.orderId = orderId; - toData.isCross = isCross || 0; const to = { type: wx.GoEasy.IM_SCENE.GROUP, @@ -80,14 +100,14 @@ function sendViaBackend(localMsg, currentUser, groupId, orderId, onSuccess) { const identityType = role === 'dashou' ? 'dashou' : (role === 'shangjia' ? 'shangjia' : 'boss'); const prefix = prefixMap[role] || 'Boss'; const senderId = prefix + uid; - + const freshUser = getFreshImUser(app, role, uid); const params = { orderId: orderId, groupId: groupId, identityType: identityType, // 告诉后端当前是什么身份 senderId: senderId, - senderName: currentUser.name || ('用户' + uid), - senderAvatar: currentUser.avatar || (app.globalData.ossImageUrl + app.globalData.morentouxiang), + senderName: freshUser.name || currentUser.name || ('用户' + uid), + senderAvatar: freshUser.avatar || currentUser.avatar || (app.globalData.ossImageUrl + app.globalData.morentouxiang), messageType: localMsg.type, messageId: localMsg.messageId, timestamp: localMsg.timestamp diff --git a/utils/xiaoxilj.js b/utils/xiaoxilj.js index 72cff09..2e65348 100644 --- a/utils/xiaoxilj.js +++ b/utils/xiaoxilj.js @@ -2,6 +2,7 @@ * 连接管理模块 - 订单群聊跳转(后端准备群 + 稳定连接) */ import request from './request'; +import { persistGroupMeta } from './im-user.js'; const app = getApp(); function waitForConnection(expectedUserId, timeout = 15000) { @@ -127,13 +128,25 @@ class ConnectionManager { } if (!app.globalData.groupInfoMap) app.globalData.groupInfoMap = {}; - app.globalData.groupInfoMap[realGroupId] = { + const meta = { name: chatData.counterpartName || chatData.groupName || groupName, avatar, orderId: chatData.orderId || orderId, orderDesc: chatData.orderDesc || '', orderZhuangtai: chatData.orderZhuangtai, + dashouGoEasyId: chatData.dashouGoEasyId, + partnerGoEasyId: chatData.partnerGoEasyId, + dashouYonghuid: chatData.dashouYonghuid, + partnerYonghuid: chatData.partnerYonghuid, + dashouName: chatData.dashouName, + partnerName: chatData.partnerName, + dashouAvatar: chatData.dashouAvatar, + partnerAvatar: chatData.partnerAvatar, + counterpartId: chatData.counterpartId, + counterpartYonghuid: chatData.counterpartYonghuid, }; + app.globalData.groupInfoMap[realGroupId] = meta; + persistGroupMeta(app, realGroupId, meta); await new Promise((resolve, reject) => { wx.goEasy.im.subscribeGroup({