diff --git a/pages/qunliaotian/qunliaotian.js b/pages/qunliaotian/qunliaotian.js index 0045f78..01cf295 100644 --- a/pages/qunliaotian/qunliaotian.js +++ b/pages/qunliaotian/qunliaotian.js @@ -1,6 +1,8 @@ const app = getApp(); 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'; Page({ data: { @@ -89,33 +91,45 @@ Page({ },*/ onShow() { - this.autoConnect(); + this.ensureChatConnection(); }, onHide() { - this.clearAllListeners(); + this.clearPageListeners(); }, - autoConnect() { + ensureChatConnection() { const role = app.globalData.currentRole || 'normal'; const uid = wx.getStorageSync('uid'); const prefixMap = { normal:'Boss', dashou:'Ds', shangjia:'Sj', guanshi:'Gs', zuzhang:'Zz' }; - const prefix = prefixMap[role] || 'Boss'; - const targetUserId = prefix + uid; + const targetUserId = (prefixMap[role] || 'Boss') + uid; - 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.subscribeGroupIfNeeded(); - this.setupAllListeners(); - this.loadHistory(true); - return; - } else { - wx.goEasy.disconnect(); - } + const onReady = () => { + this.subscribeGroupIfNeeded(); + this.setupAllListeners(); + this.loadHistory(true).then(() => this.markGroupMessageAsRead()); + }; + + const status = wx.goEasy?.getConnectionStatus?.() || 'disconnected'; + const currentUserId = wx.goEasy?.im?.userId; + + if ((status === 'connected' || status === 'reconnected') && currentUserId === targetUserId) { + onReady(); + return; } - this.connectGoEasy(targetUserId); + + if (app.connectWithIdentity) { + app.connectWithIdentity(role, targetUserId, true).then(onReady).catch(() => { + const s = wx.goEasy?.getConnectionStatus?.(); + if (s === 'connected' || s === 'reconnected') onReady(); + }); + } else { + this.connectGoEasy(targetUserId); + } + }, + + clearPageListeners() { + this.clearAllListeners(); }, connectGoEasy(userId) { @@ -130,10 +144,7 @@ Page({ onSuccess: () => { this.subscribeGroupIfNeeded(); this.setupAllListeners(); - this.loadHistory(true).then(()=>{ - this.markGroupMessageAsRead - - }); + this.loadHistory(true).then(() => this.markGroupMessageAsRead()); }, onFailed: (error) => { if (error.code === 408) { @@ -213,6 +224,7 @@ Page({ let list = res.content || []; list.sort((a, b) => a.timestamp - b.timestamp); list.forEach((m, idx) => { + normalizeGroupMessage(m); m.formattedTime = formatDate(m.timestamp); if (idx === 0) m.showTime = true; else m.showTime = (m.timestamp - list[idx-1].timestamp) / 60000 > 5; @@ -248,6 +260,7 @@ Page({ this._msgHandler = (msg) => { if (msg.groupId !== this.data.groupId) return; if (msg.senderId === this.data.currentUser.id) return; + normalizeGroupMessage(msg); msg.formattedTime = formatDate(msg.timestamp); const msgs = this.data.messages; const last = msgs.length ? msgs[msgs.length-1] : null; @@ -301,8 +314,11 @@ Page({ sendMessage() { if (this.data.pendingImage) { - this.sendImageMsg(this.data.pendingImage); - this.setData({ pendingImage: '' }); + const file = this.data.pendingImage; + showConfirmByScene('chat_image_confirm', () => { + this.sendImageMsg(file); + this.setData({ pendingImage: '' }); + }); return; } const text = this.data.inputText.trim(); diff --git a/pages/sjddxq/sjddxq.js b/pages/sjddxq/sjddxq.js index b7fb27a..10ebe5a 100644 --- a/pages/sjddxq/sjddxq.js +++ b/pages/sjddxq/sjddxq.js @@ -1,6 +1,7 @@ // pages/sjddxq/sjddxq.js - 商家订单详情(完整版,已添加罚款/退款操作及教程按钮) const app = getApp() import request from '../../utils/request.js' +import connectionManager from '../../utils/xiaoxilj.js' import PopupService from '../../services/popupService.js' // 新增:导入弹窗服务 Page({ @@ -94,55 +95,17 @@ Page({ ensureIMConnection() { const uid = wx.getStorageSync('uid') if (!uid) return + app.globalData.currentRole = 'shangjia' + wx.setStorageSync('currentRole', 'shangjia') const targetUserId = 'Sj' + uid - if (wx.goEasy && wx.goEasy.getConnectionStatus) { - const status = wx.goEasy.getConnectionStatus() - if (status === 'connected' || status === 'reconnected') { - const currentUserId = wx.goEasy.im ? wx.goEasy.im.userId : null - if (currentUserId === targetUserId) { - this.setData({ currentIMUserId: targetUserId }) - return - } else { - wx.goEasy.disconnect() - } - } + const status = wx.goEasy?.getConnectionStatus?.() || 'disconnected' + const currentUserId = wx.goEasy?.im?.userId + if ((status === 'connected' || status === 'reconnected') && currentUserId === targetUserId) { + this.setData({ currentIMUserId: targetUserId }) + return } - this.connectGoEasy(targetUserId) - }, - - connectGoEasy(userId) { - let currentUser = app.globalData.currentUser - if (!currentUser) { - const uid = wx.getStorageSync('uid') - currentUser = { - id: uid, - name: '用户' + (uid ? uid.substring(0, 6) : ''), - avatar: app.globalData.ossImageUrl + app.globalData.morentouxiang - } - } - wx.goEasy.connect({ - id: userId, - data: { - name: currentUser.name, - avatar: currentUser.avatar || (app.globalData.ossImageUrl + app.globalData.morentouxiang) - }, - onSuccess: () => { - this.setData({ currentIMUserId: userId }) - try { - wx.setStorageSync('savedGoEasyConnection', JSON.stringify({ - userId, identityType: 'shangjia', lastConnectTime: Date.now() - })) - wx.setStorageSync('goEasyUserId', userId) - } catch (e) {} - }, - onFailed: (error) => { - if (error.code === 408) { - this.setData({ currentIMUserId: userId }) - } else { - console.error('IM连接失败', error) - } - } - }) + if (app.ensureConnection) app.ensureConnection() + this.setData({ currentIMUserId: targetUserId }) }, jiexiTiaozhuanCanshu(options) { @@ -265,7 +228,7 @@ Page({ }) }, - // ===== 联系打手(替打手订阅 + 发机器人消息 + 跳转) ===== + // ===== 联系打手(后端准备配对群 + 稳定跳转) ===== goToChatWithDashou() { const uid = wx.getStorageSync('uid') if (!uid) return @@ -276,110 +239,14 @@ Page({ return } - const targetUserId = 'Sj' + uid - if (this.data.currentIMUserId !== targetUserId) { - this.ensureIMConnection() - wx.showToast({ title: '连接中,请稍后再试', icon: 'none' }) - return - } - - const that = this - const groupName = this.data.jibenShuju.nicheng + '的订单群' - const isCross = this.data.jibenShuju.fadanpingtai || 0 - const dashouUid = this.data.dashouInfo.yonghuid - - // 第一步:商家自己订阅群组 - wx.goEasy.im.subscribeGroup({ - groupIds: [orderId], - onSuccess: () => { - // 如果有打手,则需要帮打手订阅并发送消息 - if (dashouUid) { - that.bangDashouDingyue(orderId, dashouUid) - .then(() => { - that.tiaozhuanQunLiao(orderId, groupName, isCross) - }) - .catch((err) => { - console.error('替打手订阅失败,仍可跳转群聊', err) - wx.showToast({ title: '通知打手可能失败', icon: 'none' }) - that.tiaozhuanQunLiao(orderId, groupName, isCross) - }) - } else { - // 无打手,直接跳转 - that.tiaozhuanQunLiao(orderId, groupName, isCross) - } - }, - onFailed: (error) => { - console.error('订阅群组失败', error) - wx.showToast({ title: '连接群聊失败', icon: 'none' }) - } - }) - }, - - // 帮打手订阅群组 + 发送机器人消息 - bangDashouDingyue(orderId, dashouUid) { - return new Promise((resolve, reject) => { - const dashouImId = 'Ds' + dashouUid - const appkey = 'BC-32e2991c69bc47e1b538e73aaed37fc1' // 你的GoEasy AppKey - const restHost = 'https://rest-hz.goeasy.io' - - // 1. 通过 REST API 为打手订阅群组 - wx.request({ - url: restHost + '/v2/im/subscribe-groups', - method: 'POST', - header: { 'content-type': 'application/json' }, - data: { - appkey: appkey, - userIds: [dashouImId], - groupIds: [orderId] - }, - success: (res) => { - if (res.data.code === 200) { - // 2. 订阅成功后,以打手身份发送一条系统消息 - wx.request({ - url: restHost + '/v2/im/message', - method: 'POST', - header: { 'content-type': 'application/json' }, - data: { - appkey: appkey, - senderId: dashouImId, - senderData: { name: '系统通知', avatar: '' }, - to: { - type: 'group', - id: orderId, - data: { name: '订单群', avatar: '' } - }, - type: 'text', - payload: '此消息为系统自动发送,打手看到后会尽快回复您。' - }, - success: (msgRes) => { - if (msgRes.data.code === 200) { - resolve() - } else { - reject(new Error('发送消息失败')) - } - }, - fail: reject - }) - } else { - reject(new Error('订阅打手失败')) - } - }, - fail: reject - }) - }) - }, - - // 跳转群聊页面 - tiaozhuanQunLiao(orderId, groupName, isCross) { - const param = { - groupId: orderId, - orderId: orderId, - groupName: groupName, - groupAvatar: '', - isCross: isCross - } - wx.navigateTo({ - url: '/pages/qunliaotian/qunliaotian?data=' + encodeURIComponent(JSON.stringify(param)) + connectionManager.connectToGroupChat({ + identityType: 'shangjia', + userId: 'Sj' + uid, + orderId, + groupName: (this.data.dashouInfo.nicheng || this.data.jibenShuju.nicheng) + '的订单群', + isCross: this.data.jibenShuju.fadanpingtai || 0 + }).catch(err => { + console.error('跳转群聊失败', err) }) }, diff --git a/pages/xiaoxi/xiaoxi.js b/pages/xiaoxi/xiaoxi.js index a880cd8..2d70bf2 100644 --- a/pages/xiaoxi/xiaoxi.js +++ b/pages/xiaoxi/xiaoxi.js @@ -2,6 +2,7 @@ const app = getApp(); import { formatDate } from '../../static/lib/utils'; const { jianquanxian } = require('../../utils/imAuth/jianquanxian'); +import { enrichGroupConversation, isOrderGroupConversation } from '../../utils/group-chat.js'; Page({ data: { @@ -9,7 +10,7 @@ Page({ filteredConversations: [], activeTab: 0, tabUnread: [0, 0, 0], - displayCount: 10, + displayCount: 4, searchKeyword: '', actionPopup: { visible: false, conversation: null }, currentUser: null, @@ -81,26 +82,42 @@ Page({ 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 prefix = prefixMap[role] || 'Boss'; - const targetUserId = prefix + uid; + const targetUserId = (prefixMap[role] || 'Boss') + uid; - 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; - } else { - wx.goEasy.disconnect(); - } + 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; } - this.connectGoEasy(targetUserId); + + 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) { @@ -168,12 +185,27 @@ Page({ } 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.data && item.data.avatar) { - if (!item.data.avatar.startsWith('http')) { - item.data.avatar = app.globalData.ossImageUrl + item.data.avatar; + if (item.type === 'group') { + 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; } - } else { + 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) { @@ -189,7 +221,7 @@ Page({ return (b.lastMessage.timestamp || 0) - (a.lastMessage.timestamp || 0); }); - this.setData({ allConversations: list, displayCount: 10 }, () => { + this.setData({ allConversations: list, displayCount: 4 }, () => { this.applyFilter(); }); @@ -234,7 +266,7 @@ Page({ 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) { @@ -253,7 +285,7 @@ Page({ 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; }); @@ -263,17 +295,17 @@ Page({ switchTab(e) { const index = parseInt(e.currentTarget.dataset.index); - this.setData({ activeTab: index, displayCount: 10, searchKeyword: '' }, () => this.applyFilter()); + this.setData({ activeTab: index, displayCount: 4, searchKeyword: '' }, () => this.applyFilter()); }, onSearchInput(e) { - this.setData({ searchKeyword: e.detail.value, displayCount: 10 }, () => this.applyFilter()); + 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 + 10, filteredConversations.length) }); + this.setData({ displayCount: Math.min(displayCount + 4, filteredConversations.length) }); } }, diff --git a/pages/xiaoxi/xiaoxi.wxml b/pages/xiaoxi/xiaoxi.wxml index c238480..a69a9dd 100644 --- a/pages/xiaoxi/xiaoxi.wxml +++ b/pages/xiaoxi/xiaoxi.wxml @@ -29,14 +29,25 @@ - - + + {{item.data.name}} {{item.lastMessage.date}} - + + + 单号 {{item.data.orderId}} + {{item.data.orderDesc}} + {{item.data.orderZhuangtaiText}} + + + {{item.displayLastMsg || item.lastMessage.payload.text}} + {{item.unread > 99 ? '99+' : item.unread}} + + + {{item.lastMessage.type === 'text' ? item.lastMessage.payload.text : '[其他消息]'}} {{item.unread > 99 ? '99+' : item.unread}} diff --git a/pages/xiaoxi/xiaoxi.wxss b/pages/xiaoxi/xiaoxi.wxss index 09a88ee..3d1ca42 100644 --- a/pages/xiaoxi/xiaoxi.wxss +++ b/pages/xiaoxi/xiaoxi.wxss @@ -14,13 +14,19 @@ .tab-badge { background: #fa5151; color: #fff; font-size: 20rpx; border-radius: 20rpx; padding: 2rpx 12rpx; margin-left: 8rpx; min-width: 32rpx; text-align: center; } .conversation-list { background: #fff; } -.conversation-item { display: flex; align-items: center; padding: 20rpx 24rpx; border-bottom: 1rpx solid #f0f0f0; } +.conversation-item { display: flex; align-items: flex-start; padding: 24rpx 24rpx; border-bottom: 1rpx solid #f0f0f0; } +.order-group-item { min-height: 160rpx; padding: 28rpx 24rpx; } .has-unread { background: #fafafa; } -.avatar { width: 96rpx; height: 96rpx; border-radius: 12rpx; margin-right: 20rpx; background: #eee; } -.info { flex: 1; } +.avatar { width: 96rpx; height: 96rpx; border-radius: 12rpx; margin-right: 20rpx; background: #eee; flex-shrink: 0; } +.avatar-round { border-radius: 50%; } +.info { flex: 1; min-width: 0; } .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; } +.time { font-size: 22rpx; color: #999; flex-shrink: 0; margin-left: 12rpx; } +.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; } +.order-status { display: inline-block; font-size: 22rpx; color: #07c160; margin-top: 8rpx; padding: 2rpx 12rpx; background: #e8f8ef; border-radius: 8rpx; } .bottom-line { display: flex; justify-content: space-between; align-items: center; } .last-msg { font-size: 26rpx; color: #888; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .unread-dot { background: #fa5151; color: #fff; font-size: 20rpx; border-radius: 50%; min-width: 36rpx; height: 36rpx; line-height: 36rpx; text-align: center; margin-left: 12rpx; } diff --git a/utils/chat-core.js b/utils/chat-core.js index 57856e8..d3bc751 100644 --- a/utils/chat-core.js +++ b/utils/chat-core.js @@ -131,7 +131,9 @@ function ensureConnection(app) { function getSavedConnection(app) { try { const saved = wx.getStorageSync(app.globalData.goEasyConnection.cacheKeys.savedConnection); - return saved ? JSON.parse(saved) : null; + if (!saved) return null; + if (typeof saved === 'string') return JSON.parse(saved); + return saved; } catch (error) { console.error('获取保存的连接信息失败:', error); return null; @@ -583,8 +585,9 @@ function updateCurrentPageState(app) { const pages = getCurrentPages(); if (pages.length > 0) { const currentPage = pages[pages.length - 1]; + const chatPages = ['pages/liaotian/liaotian', 'pages/qunliaotian/qunliaotian', 'pages/kefuliaotian/kefuliaotian']; app.globalData.pageState.currentPage = currentPage.route; - app.globalData.pageState.isInChatPage = currentPage.route === 'pages/liaotian/liaotian'; + app.globalData.pageState.isInChatPage = chatPages.indexOf(currentPage.route) >= 0; app.globalData.pageState.lastPageUpdate = Date.now(); } } catch (error) { diff --git a/utils/group-chat.js b/utils/group-chat.js new file mode 100644 index 0000000..9410394 --- /dev/null +++ b/utils/group-chat.js @@ -0,0 +1,112 @@ +/** + * 打手-商家/老板配对群:group_Ds{打手}_Sj{商家} 或 group_Ds{打手}_Boss{老板} + */ +export const ORDER_CARD_PREFIX = '[ORDER_CARD]'; + +const ZHUANGTAI_MAP = { + 1: '已下单', 2: '进行中', 3: '已完成', 4: '退款中', + 5: '已退款', 6: '退款失败', 7: '指定中', 8: '结算中', +}; + +export function isPairGroupId(groupId) { + return /^group_Ds\w+_(Sj|Boss)\w+$/.test(groupId || ''); +} + +export function parsePairGroupId(groupId) { + if (!groupId) return null; + let m = /^group_Ds(\w+)_Sj(\w+)$/.exec(groupId); + if (m) return { dashouId: `Ds${m[1]}`, partnerId: `Sj${m[2]}`, partnerRole: 'shangjia' }; + m = /^group_Ds(\w+)_Boss(\w+)$/.exec(groupId); + if (m) return { dashouId: `Ds${m[1]}`, partnerId: `Boss${m[2]}`, partnerRole: 'boss' }; + return null; +} + +export function getCounterpartGoEasyId(groupId, myGoEasyId) { + const parsed = parsePairGroupId(groupId); + if (!parsed || !myGoEasyId) return null; + if (myGoEasyId === parsed.dashouId) return parsed.partnerId; + if (myGoEasyId === parsed.partnerId) return parsed.dashouId; + return null; +} + +export function parseOrderCardText(text) { + if (!text || !text.startsWith(ORDER_CARD_PREFIX)) return null; + try { + const data = JSON.parse(text.slice(ORDER_CARD_PREFIX.length)); + return { + orderId: data.dingdan_id, + jieshao: data.jieshao || '', + jine: data.jine || '', + beizhu: data.beizhu || '', + zhuangtai: data.zhuangtai, + nicheng: data.nicheng || '', + create_time: data.create_time || '', + }; + } catch (e) { + return null; + } +} + +export function normalizeGroupMessage(msg) { + if (!msg) return msg; + if (msg.type === 'text' && msg.payload && msg.payload.text) { + const card = parseOrderCardText(msg.payload.text); + if (card) { + msg.type = 'order'; + msg.payload = card; + } + } + return msg; +} + +export function formatLastMessagePreview(msg) { + if (!msg) return ''; + if (msg.type === 'text' && msg.payload && msg.payload.text) { + const card = parseOrderCardText(msg.payload.text); + if (card) return `[订单] ${card.jieshao || card.orderId}`; + return msg.payload.text; + } + if (msg.type === 'order' && msg.payload) { + return `[订单] ${msg.payload.jieshao || msg.payload.orderId || ''}`; + } + if (msg.type === 'image') return '[图片]'; + return '[消息]'; +} + +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; + + if (item.data.orderZhuangtai != null) { + item.data.orderZhuangtaiText = ZHUANGTAI_MAP[item.data.orderZhuangtai] || ''; + } + + item.displayLastMsg = formatLastMessagePreview(item.lastMessage); + return item; +} + +export function isOrderGroupConversation(c) { + if (!c || c.type !== 'group') return false; + if (c.data && c.data.orderId) return true; + return isPairGroupId(c.groupId) || /^group_\w+$/.test(c.groupId || ''); +} diff --git a/utils/xiaoxilj.js b/utils/xiaoxilj.js index e7a0cff..72cff09 100644 --- a/utils/xiaoxilj.js +++ b/utils/xiaoxilj.js @@ -1,11 +1,18 @@ /** - * 连接管理模块 - xiaoxilj.js (根治发送失败:跳转前确保群组已订阅) - * 从详情页跳转订单群聊时,提前订阅群组,避免发送消息时订阅未完成而失败 + * 连接管理模块 - 订单群聊跳转(后端准备群 + 稳定连接) */ +import request from './request'; const app = getApp(); -function waitForConnection(expectedUserId, timeout = 10000) { +function waitForConnection(expectedUserId, timeout = 15000) { return new Promise((resolve, reject) => { + const status = wx.goEasy?.getConnectionStatus?.(); + const currentUserId = wx.goEasy?.im?.userId; + if ((status === 'connected' || status === 'reconnected') && currentUserId === expectedUserId) { + resolve(); + return; + } + const timer = setTimeout(() => { app.off('connectionChanged', handler); reject(new Error('连接超时')); @@ -13,87 +20,76 @@ function waitForConnection(expectedUserId, timeout = 10000) { const handler = (event) => { if (event.status === 'connected') { - if (event.userId === expectedUserId) { + const uid = event.userId || wx.goEasy?.im?.userId; + if (uid === expectedUserId) { clearTimeout(timer); app.off('connectionChanged', handler); resolve(); - } else { - console.error(`连接身份不匹配: 期望 ${expectedUserId}, 实际 ${event.userId}`); - wx.removeStorageSync('savedGoEasyConnection'); - wx.removeStorageSync('goEasyUserId'); - wx.removeStorageSync('currentGoEasyIdentity'); - if (app.clearSavedConnection) app.clearSavedConnection(); - clearTimeout(timer); - app.off('connectionChanged', handler); - reject(new Error(`连接身份不匹配: 期望 ${expectedUserId}, 实际 ${event.userId}`)); } - } else if (event.status === 'disconnected' && !event.manual) { - wx.removeStorageSync('savedGoEasyConnection'); - wx.removeStorageSync('goEasyUserId'); - wx.removeStorageSync('currentGoEasyIdentity'); - if (app.clearSavedConnection) app.clearSavedConnection(); + } else if (event.status === 'disconnected' && event.manual) { clearTimeout(timer); app.off('connectionChanged', handler); - reject(new Error('连接失败')); + reject(new Error('连接已断开')); } }; app.on('connectionChanged', handler); }); } +async function ensureIdentityConnection(identityType, userId) { + app.globalData.currentRole = identityType; + wx.setStorageSync('currentRole', identityType); + + const uid = userId.replace(/^(Ds|Sj|Boss|Gs|Zz|Kh)/, ''); + const avatar = wx.getStorageSync('touxiang') || + (app.globalData.ossImageUrl + app.globalData.morentouxiang); + app.globalData.currentUser = { + id: userId, + name: app.globalData.currentUser?.name || `用户${uid}`, + avatar: avatar, + }; + + const status = wx.goEasy?.getConnectionStatus?.() || 'disconnected'; + const currentUserId = wx.goEasy?.im?.userId; + + if ((status === 'connected' || status === 'reconnected') && currentUserId === userId) { + return; + } + + if (currentUserId && currentUserId !== userId && app.disconnectGoEasy) { + await app.disconnectGoEasy(); + } + + const waitPromise = waitForConnection(userId); + const connectPromise = app.connectWithIdentity(identityType, userId, true); + await waitPromise; + await connectPromise; +} + class ConnectionManager { - // 原有的私聊方法保持不变 async connectAndChat(params) { const { identityType, userId, targetUser } = params; if (!identityType || !userId || !targetUser || !targetUser.id) { throw new Error('参数不完整'); } - if (identityType === 'dashou' && !userId.startsWith('Ds')) { - throw new Error(`打手身份的用户ID必须以Ds开头,实际为 ${userId}`); - } - if (identityType === 'shangjia' && !userId.startsWith('Sj')) { - throw new Error(`商家身份的用户ID必须以Sj开头,实际为 ${userId}`); - } - if (identityType === 'boss' && !userId.startsWith('Boss')) { - throw new Error(`老板身份的用户ID必须以Boss开头,实际为 ${userId}`); - } + await ensureIdentityConnection(identityType, userId); - wx.removeStorageSync('savedGoEasyConnection'); - wx.removeStorageSync('goEasyUserId'); - wx.removeStorageSync('currentGoEasyIdentity'); - if (app.clearSavedConnection) app.clearSavedConnection(); + const currentUser = { + id: userId, + name: app.globalData.currentUser?.name || '用户', + avatar: app.globalData.currentUser?.avatar || (app.globalData.ossImageUrl + app.globalData.morentouxiang), + }; - if (wx.goEasy && wx.goEasy.getConnectionStatus() === 'connected') { - wx.goEasy.disconnect(); - } + const to = { + id: targetUser.id, + name: targetUser.name || '用户', + avatar: targetUser.avatar || (app.globalData.ossImageUrl + app.globalData.morentouxiang), + }; - const waitPromise = waitForConnection(userId); - app.connectWithIdentity(identityType, userId, false); - - try { - await waitPromise; - - const currentUser = { - id: userId, - name: app.globalData.currentUser?.name || '用户', - avatar: app.globalData.currentUser?.avatar || (app.globalData.ossImageUrl + app.globalData.morentouxiang) - }; - - const to = { - id: targetUser.id, - name: targetUser.name || '用户', - avatar: targetUser.avatar || (app.globalData.ossImageUrl + app.globalData.morentouxiang) - }; - - const param = { to, currentUser }; - const path = '/pages/liaotian/liaotian?data=' + encodeURIComponent(JSON.stringify(param)); - wx.navigateTo({ url: path }); - } catch (err) { - console.error('连接失败:', err); - wx.showToast({ title: err.message || '连接失败', icon: 'none' }); - throw err; - } + const param = { to, currentUser }; + const path = '/pages/liaotian/liaotian?data=' + encodeURIComponent(JSON.stringify(param)); + wx.navigateTo({ url: path }); } async connectToGroupChat(params) { @@ -102,113 +98,70 @@ class ConnectionManager { throw new Error('参数不完整:identityType, userId, orderId 必填'); } - // 校验 userId 前缀 - if (identityType === 'dashou' && !userId.startsWith('Ds')) { - throw new Error(`打手身份的用户ID必须以Ds开头,实际为 ${userId}`); - } - if (identityType === 'shangjia' && !userId.startsWith('Sj')) { - throw new Error(`商家身份的用户ID必须以Sj开头,实际为 ${userId}`); - } - if (identityType === 'boss' && !userId.startsWith('Boss')) { - throw new Error(`老板身份的用户ID必须以Boss开头,实际为 ${userId}`); - } + wx.showLoading({ title: '建立联系中...', mask: true }); - // 1. 同步全局角色,确保群聊页初始化正确 - app.globalData.currentRole = identityType; - wx.setStorageSync('currentRole', identityType); - - const uid = userId.replace(/^(Ds|Sj|Boss|Gs|Zz)/, ''); - const avatar = wx.getStorageSync('touxiang') || - (app.globalData.ossImageUrl + app.globalData.morentouxiang); - app.globalData.currentUser = { - id: userId, - name: app.globalData.currentUser?.name || `用户${uid}`, - avatar: avatar - }; - - // 2. 确保 GoEasy 已使用目标身份连接 - let needConnect = false; - if (!wx.goEasy || !wx.goEasy.getConnectionStatus || wx.goEasy.getConnectionStatus() !== 'connected') { - needConnect = true; - } else { - const currentUserId = wx.goEasy.im?.userId; - if (currentUserId !== userId) { - needConnect = true; - } - } - - if (needConnect) { - wx.removeStorageSync('savedGoEasyConnection'); - wx.removeStorageSync('goEasyUserId'); - if (app.clearSavedConnection) app.clearSavedConnection(); - if (wx.goEasy && wx.goEasy.getConnectionStatus() === 'connected') { - wx.goEasy.disconnect(); - } - const waitPromise = waitForConnection(userId); - app.connectWithIdentity(identityType, userId, false); - try { - await waitPromise; - } catch (err) { - wx.showToast({ title: '连接失败', icon: 'none' }); - throw err; - } - } - - // 3. 从会话列表查找真实群组ID - let realGroupId = null; try { - const result = await new Promise((resolve, reject) => { - wx.goEasy.im.latestConversations({ - onSuccess: (res) => { - const conversations = res.content?.conversations || []; - const found = conversations.find(c => c.type === 'group' && c.data && c.data.orderId === orderId); - resolve(found ? found.groupId : null); - }, - onFailed: reject - }); + await ensureIdentityConnection(identityType, userId); + + const res = await request({ + url: '/dingdan/ltdhzb', + method: 'POST', + data: { + dingdan_id: orderId, + identityType, + push_order_card: true, + }, }); - realGroupId = result; - } catch (err) { - console.error('获取会话列表失败:', err); - wx.showToast({ title: '获取群聊信息失败', icon: 'none' }); - throw err; - } - if (!realGroupId) { - wx.showToast({ title: '暂未创建群聊', icon: 'none' }); - return; - } + const body = res?.data || {}; + if (body.code !== 0 || !body.data || !body.data.groupId) { + throw new Error(body.msg || '准备群聊失败'); + } + + const chatData = body.data; + const realGroupId = chatData.groupId; + + let avatar = chatData.counterpartAvatar || chatData.groupAvatar || groupAvatar || ''; + if (avatar && !avatar.startsWith('http')) { + avatar = app.globalData.ossImageUrl + avatar.replace(/^\//, ''); + } + + if (!app.globalData.groupInfoMap) app.globalData.groupInfoMap = {}; + app.globalData.groupInfoMap[realGroupId] = { + name: chatData.counterpartName || chatData.groupName || groupName, + avatar, + orderId: chatData.orderId || orderId, + orderDesc: chatData.orderDesc || '', + orderZhuangtai: chatData.orderZhuangtai, + }; - // 4. 🔥 关键:主动订阅群组并等待成功,确保发送消息时订阅已完成 - try { await new Promise((resolve, reject) => { wx.goEasy.im.subscribeGroup({ groupIds: [realGroupId], - onSuccess: () => { - resolve(); - }, - onFailed: (error) => { - console.error('订阅群组失败:', error); - reject(error); - } + onSuccess: () => resolve(), + onFailed: (error) => reject(error), }); }); + + const param = { + groupId: realGroupId, + orderId: chatData.orderId || orderId, + groupName: chatData.counterpartName || chatData.groupName || groupName || '订单群聊', + groupAvatar: avatar, + isCross: chatData.isCross != null ? chatData.isCross : (isCross || 0), + }; + + wx.hideLoading(); + wx.navigateTo({ + url: '/pages/qunliaotian/qunliaotian?data=' + encodeURIComponent(JSON.stringify(param)), + }); } catch (err) { - wx.showToast({ title: '订阅群组失败', icon: 'none' }); + wx.hideLoading(); + console.error('跳转群聊失败:', err); + wx.showToast({ title: err.message || '进入聊天失败', icon: 'none' }); throw err; } - - // 5. 跳转群聊页(参数与消息列表完全一致) - const param = { - groupId: realGroupId, - orderId: orderId, - groupName: groupName || '订单群聊', - groupAvatar: groupAvatar || '', - isCross: isCross || 0 - }; - const path = '/pages/qunliaotian/qunliaotian?data=' + encodeURIComponent(JSON.stringify(param)); - wx.navigateTo({ url: path }); } } -export default new ConnectionManager(); \ No newline at end of file +export default new ConnectionManager();