diff --git a/pages/accept-order/accept-order.js b/pages/accept-order/accept-order.js index c23fe70..7126cd5 100644 --- a/pages/accept-order/accept-order.js +++ b/pages/accept-order/accept-order.js @@ -9,6 +9,7 @@ import PopupService from '../../services/popupService.js'; import { reconnectForRole } from '../../utils/role-tab-bar.js'; import { ensurePhoneAuth } from '../../utils/phone-auth.js'; import { fetchGonggaoLunbo, isGonggaoCacheValid } from '../../utils/display-config.js'; +import { checkPenaltyForGrab } from '../../utils/grab-order-gate.js'; Page({ data: { @@ -61,8 +62,18 @@ Page({ async onLoad() { if (app.globalData._acceptOrderSessionReady && app.globalData._acceptOrderPageCache) { - const cache = app.globalData._acceptOrderPageCache; - this.setData({ ...cache }); + const cache = { ...app.globalData._acceptOrderPageCache }; + this.data.shangpinleixing = cache.shangpinleixing || []; + const dingdanList = Array.isArray(cache.dingdanList) + ? cache.dingdanList.map((item) => this.processDingdanItem(item)) + : []; + this.setData({ + ...cache, + shangpinleixing: cache.shangpinleixing || [], + dingdanList, + bankuaiBiaoqian: cache.bankuaiBiaoqian || [], + lunboList: cache.lunboList || [], + }); if ((!cache.lunboList || cache.lunboList.length === 0) && !cache.gonggao && isGonggaoCacheValid(app)) { const lunboList = this.processLunboUrls(app.globalData.shangpinlunbo); const gonggao = app.globalData.shangpingonggao || ''; @@ -72,6 +83,12 @@ Page({ this.loadGlobalStatus(); this.registerNotificationComponent(); await this.syncDashouProfileFromServer(); + if (!this.data.shangpinleixing || this.data.shangpinleixing.length === 0) { + await this.loadShangpinLeixing(true, false, false); + } else if (this.data.xuanzhongLeixingId) { + await this.loadDingdanList(true, true); + } + this.persistPageCache(); this._skipShowRefresh = true; return; } @@ -546,6 +563,19 @@ Page({ const dingdanItem = e.currentTarget.dataset.item; if (!dingdanItem) return; + const penalty = await checkPenaltyForGrab(); + if (penalty.blocked) { + wx.showToast({ + title: penalty.msg, + icon: 'none', + duration: 2000, + }); + setTimeout(() => { + wx.navigateTo({ url: penalty.url }); + }, 500); + return; + } + await this.checkExamStatus(); if (this.data.examRequired && !this.data.examPassed) { diff --git a/pages/accept-order/accept-order.json b/pages/accept-order/accept-order.json index d496762..024a2e6 100644 --- a/pages/accept-order/accept-order.json +++ b/pages/accept-order/accept-order.json @@ -1,12 +1,12 @@ { "navigationBarTitleText": "抢单大厅", - "navigationBarBackgroundColor": "#f7dc51", - "backgroundColor": "#f7dc51", - "backgroundColorTop": "#f7dc51", + "navigationBarBackgroundColor": "#c4b5fd", + "backgroundColor": "#c4b5fd", + "backgroundColorTop": "#c4b5fd", "navigationBarTextStyle": "black", "enablePullDownRefresh": false, "backgroundTextStyle": "dark", - "backgroundColor": "#fff8e1", + "backgroundColor": "#f5f3ff", "usingComponents": { "chenghao-tag": "/components/chenghao-tag/chenghao-tag", "global-notification": "/components/global-notification/global-notification" diff --git a/pages/accept-order/accept-order.wxml b/pages/accept-order/accept-order.wxml index 4088337..d0a2cc6 100644 --- a/pages/accept-order/accept-order.wxml +++ b/pages/accept-order/accept-order.wxml @@ -1,4 +1,4 @@ - + @@ -18,7 +18,7 @@ refresher-default-style="black" - refresher-background="#fff8e1" + refresher-background="#f5f3ff" refresher-triggered="{{scrollViewRefreshing}}" @@ -56,7 +56,7 @@ indicator-color="rgba(255,255,255,0.6)" - indicator-active-color="#ffd061" + indicator-active-color="#9333ea" autoplay="{{lunboList.length > 1}}" diff --git a/pages/accept-order/accept-order.wxss b/pages/accept-order/accept-order.wxss index 394b5ff..c31f877 100644 --- a/pages/accept-order/accept-order.wxss +++ b/pages/accept-order/accept-order.wxss @@ -3,7 +3,7 @@ @import '../../styles/dashou-xym-order-card.wxss'; page { - background-color: #f7dc51; + background-color: #c4b5fd; } .qiangdan-page { diff --git a/pages/dashou-exam/dashou-exam.json b/pages/dashou-exam/dashou-exam.json index 34f1557..26876a8 100644 --- a/pages/dashou-exam/dashou-exam.json +++ b/pages/dashou-exam/dashou-exam.json @@ -1,6 +1,6 @@ { "usingComponents": {}, "navigationBarTitleText": "接单考试", - "navigationBarBackgroundColor": "#f7dc51", + "navigationBarBackgroundColor": "#c4b5fd", "navigationBarTextStyle": "black" } diff --git a/pages/dashou-exam/dashou-exam.wxss b/pages/dashou-exam/dashou-exam.wxss index a7f56b3..8227bc8 100644 --- a/pages/dashou-exam/dashou-exam.wxss +++ b/pages/dashou-exam/dashou-exam.wxss @@ -1,6 +1,6 @@ .exam-page { min-height: 100vh; - background: linear-gradient(180deg, #fff8e1 0%, #fff3d6 100%); + background: linear-gradient(180deg, #f5f3ff 0%, #fff3d6 100%); padding: 24rpx; padding-bottom: calc(24rpx + env(safe-area-inset-bottom)); box-sizing: border-box; @@ -29,12 +29,12 @@ } .btn-primary { - background: linear-gradient(90deg, #f7dc51, #ffc0a3); + background: linear-gradient(90deg, #c4b5fd, #a78bfa); padding: 22rpx 48rpx; border-radius: 48rpx; font-weight: 600; font-size: 30rpx; - color: #492f00; + color: #6d28d9; text-align: center; } @@ -83,7 +83,7 @@ .progress { font-size: 28rpx; - color: #492f00; + color: #6d28d9; font-weight: 600; } @@ -98,8 +98,8 @@ display: inline-block; font-size: 22rpx; color: #b8860b; - background: #fff8e1; - border: 1rpx solid #f7dc51; + background: #f5f3ff; + border: 1rpx solid #c4b5fd; border-radius: 8rpx; padding: 4rpx 16rpx; margin-bottom: 16rpx; @@ -139,7 +139,7 @@ } .opt-active { - border-color: #f7dc51; + border-color: #c4b5fd; background: #fffdf0; } @@ -157,15 +157,15 @@ } .opt-radio-on { - border-color: #f7dc51; - background: #fff8e1; + border-color: #c4b5fd; + background: #f5f3ff; } .opt-radio-dot { width: 18rpx; height: 18rpx; border-radius: 50%; - background: #f7dc51; + background: #c4b5fd; } .opt-body { @@ -177,7 +177,7 @@ .opt-label { font-weight: 700; - color: #492f00; + color: #6d28d9; min-width: 40rpx; font-size: 28rpx; } diff --git a/pages/express-order/express-order.json b/pages/express-order/express-order.json index 7682a2b..ab822a7 100644 --- a/pages/express-order/express-order.json +++ b/pages/express-order/express-order.json @@ -4,7 +4,7 @@ "chenghao-tag": "/components/chenghao-tag/chenghao-tag" }, "backgroundTextStyle": "dark", - "navigationBarBackgroundColor": "#fff8e1", + "navigationBarBackgroundColor": "#f5f3ff", "navigationBarTitleText": "常规发单", "navigationBarTextStyle": "black", "backgroundColor": "#f5f5f5", diff --git a/pages/express-order/express-order.wxss b/pages/express-order/express-order.wxss index 4584d49..4616d7b 100644 --- a/pages/express-order/express-order.wxss +++ b/pages/express-order/express-order.wxss @@ -290,7 +290,7 @@ page { .filter-switch.active { background: linear-gradient(180deg, #fae04d, #ffc0a3); - color: #492f00; + color: #6d28d9; } .link-list { @@ -324,7 +324,7 @@ page { .copy-link-btn { padding: 8rpx 20rpx; background: linear-gradient(180deg, #fae04d, #ffc0a3); - color: #492f00; + color: #6d28d9; border-radius: 15rpx; font-size: 22rpx; } @@ -339,7 +339,7 @@ page { padding: 15rpx 40rpx; background: linear-gradient(180deg, #fae04d, #ffc0a3); border-radius: 30rpx; - color: #492f00; + color: #6d28d9; font-size: 26rpx; } @@ -374,7 +374,7 @@ page { .copy-modal-grid-btn, .edit-modal-grid-btn { background: linear-gradient(180deg, #fae04d, #ffc0a3); - color: #492f00; + color: #6d28d9; } .delete-modal-grid-btn { @@ -485,7 +485,7 @@ page { .confirm-add-grid-btn { background: linear-gradient(180deg, #fae04d, #ffc0a3); - color: #492f00; + color: #6d28d9; } .confirm-content { @@ -539,7 +539,7 @@ page { .confirm-new-link-grid-btn { background: linear-gradient(180deg, #fae04d, #ffc0a3); - color: #492f00; + color: #6d28d9; } .loading-mask { @@ -569,7 +569,7 @@ page { width: 90rpx; height: 90rpx; border: 8rpx solid #eee; - border-top: 8rpx solid #ffd061; + border-top: 8rpx solid #9333ea; border-radius: 50%; animation: spin 1.2s linear infinite; margin-bottom: 35rpx; diff --git a/pages/fighter/fighter.json b/pages/fighter/fighter.json index a31615c..0f3a586 100644 --- a/pages/fighter/fighter.json +++ b/pages/fighter/fighter.json @@ -6,7 +6,7 @@ "pindao-modal": "/components/pindao-modal/pindao-modal" }, "navigationStyle": "custom", - "backgroundColor": "#fff8e1", + "backgroundColor": "#f5f3ff", "backgroundTextStyle": "dark", "enablePullDownRefresh": false, "onReachBottomDistance": 50 diff --git a/pages/fighter/fighter.wxml b/pages/fighter/fighter.wxml index b9f6e40..198e4e6 100644 --- a/pages/fighter/fighter.wxml +++ b/pages/fighter/fighter.wxml @@ -39,7 +39,7 @@ refresher-enabled="{{true}}" refresher-threshold="80" refresher-default-style="black" - refresher-background="#fff8e1" + refresher-background="#f5f3ff" refresher-triggered="{{scrollViewRefreshing}}" bindrefresherrefresh="onPullDownRefresh" > diff --git a/pages/fighter/fighter.wxss b/pages/fighter/fighter.wxss index 120d290..b70fa4f 100644 --- a/pages/fighter/fighter.wxss +++ b/pages/fighter/fighter.wxss @@ -1,7 +1,7 @@ /* 打手端个人中心 - 逍遥梦 UI(逻辑不变) */ page { - background: #fff8e1; + background: #f5f3ff; color: #333; } @@ -16,7 +16,7 @@ page { flex-direction: column; overflow: hidden; box-sizing: border-box; - background: linear-gradient(180deg, #f7dc51 0%, #fff 28%, #fff8e1 100%); + background: linear-gradient(180deg, #c4b5fd 0%, #fff 28%, #f5f3ff 100%); position: relative; } @@ -126,7 +126,7 @@ page { padding: 2rpx 12rpx; border-radius: 8rpx; background: linear-gradient(180deg, #fae04d, #ffc0a3); - color: #492f00; + color: #6d28d9; } .sn-row { @@ -238,7 +238,7 @@ page { border: 2rpx solid #fff; border-radius: 60rpx; background: linear-gradient(180deg, #fae04d, #ffc0a3); - color: #492f00; + color: #6d28d9; white-space: nowrap; } @@ -272,8 +272,8 @@ page { border-radius: 16rpx; font-size: 28rpx; font-weight: 600; - color: #492f00; - background: linear-gradient(180deg, #fff8e1, #ffe8b8); + color: #6d28d9; + background: linear-gradient(180deg, #f5f3ff, #ffe8b8); border: 1rpx solid rgba(201, 169, 98, 0.45); } @@ -305,7 +305,7 @@ page { .btn-bg { background: linear-gradient(180deg, #fae04d, #ffc0a3); border-radius: 30px; - color: #492f00; + color: #6d28d9; } .withdraw { @@ -334,7 +334,7 @@ page { .vip-strip-sub { display: block; font-size: 22rpx; - color: rgba(252, 210, 112, 0.65); + color: rgba(196, 181, 253, 0.65); margin-top: 4rpx; } @@ -552,7 +552,7 @@ page { } .rank-theme-gold { - background: linear-gradient(135deg, #a8842a 0%, #f5d563 100%); + background: linear-gradient(135deg, #a8842a 0%, #c4b5fd 100%); } .rank-theme-gold .rank-tile-name, @@ -608,7 +608,7 @@ page { margin-bottom: 16rpx; } -.unreg-btn-text { color: #492f00; } +.unreg-btn-text { color: #6d28d9; } .unreg-btn-secondary { text-align: center; @@ -644,7 +644,7 @@ page { width: 60rpx; height: 60rpx; border: 4rpx solid rgba(0, 0, 0, 0.08); - border-top-color: #ffd061; + border-top-color: #9333ea; border-radius: 50%; animation: fighterSpin 0.8s linear infinite; } diff --git a/pages/group-chat/group-chat.js b/pages/group-chat/group-chat.js index 9e1aee1..348cae3 100644 --- a/pages/group-chat/group-chat.js +++ b/pages/group-chat/group-chat.js @@ -1,8 +1,10 @@ const app = getApp(); import { formatDate } from '../../static/lib/utils'; import { sendGroupMessage } from '../../utils/message-sender'; -import { getLocalImUserId } from '../../utils/im-user.js'; -import { resolveAvatarUrl } from '../../utils/avatar.js'; +import { showConfirmByScene } from '../../utils/scriptService.js'; +import { normalizeGroupMessage, getZhuangtaiText } from '../../utils/group-chat.js'; +import { persistGroupMeta } from '../../utils/im-user.js'; +import { getFreshImUser } from '../../utils/im-user.js'; Page({ data: { @@ -11,6 +13,10 @@ Page({ groupAvatar: '', orderId: '', isCross: 0, + orderZhuangtai: null, + orderZhuangtaiText: '', + orderJine: '', + orderJieshao: '', currentUser: null, messages: [], inputText: '', @@ -28,7 +34,6 @@ Page({ lastTapMsgId: '', emojiList: ['😀','😁','😂','🤣','😃','😄','😅','😆','😉','😊','😋','😎','😍','😘','😗','😙','😚','🙂','🤗','🤩','🤔','🤨','😐','😑','😶','🙄','😏','😣','😥','😮','🤐','😯','😪','😫','😴','😌','😛','😜','😝','🤤','😒','😓','😔','😕','🙃','🤑','😲','☹','🙁','😖','😞','😟','😤','😢','😭','😦','😧','😨','😩','🤯','😬','😰','😱','🥵','🥶','😳','🤪','😵','😡','😠','🤬','😷','🤒','🤕','🤢','🤮','🤧','😇','🤠','🤡','🤥','🤫','🤭','🧐','🤓','😈','👿','👹','👺','💀','👻','👽','🤖','💩','😺','😸','😹','😻','😼','😽','🙀','😿','😾'], pendingImage: '', - pendingImageFile: null, keyboardHeight: 0, bottomSafeHeight: 10 }, @@ -41,111 +46,164 @@ Page({ p = { ...p, ...d }; } catch (e) {} } - const orderId = String(p.orderId || p.groupId || ''); - const groupId = String(p.groupId || p.orderId || ''); this.setData({ - groupId, + groupId: p.groupId, groupName: p.groupName, groupAvatar: p.groupAvatar, - orderId, - isCross: p.isCross, + orderId: p.orderId, + isCross: p.isCross }); wx.setNavigationBarTitle({ title: this.data.groupName }); - const localImId = getLocalImUserId(app); - const userId = localImId || p.currentUserId; - if (userId) { + // 如果有外部传入的 currentUser 信息,直接用,不再依赖全局状态 + if (p.currentUserId) { this.setData({ currentUser: { - id: userId, - name: p.currentUserName || app.globalData.currentUser?.name || `用户${wx.getStorageSync('uid') || ''}`, - avatar: resolveAvatarUrl(p.currentUserAvatar || wx.getStorageSync('touxiang'), app), - }, + id: p.currentUserId, + name: p.currentUserName || `用户${p.currentUserId.replace(/^[A-Za-z]+/,'')}`, + avatar: p.currentUserAvatar || (app.globalData.ossImageUrl + app.globalData.morentouxiang) + } }); } else { this.initCurrentUser(); } - }, - - initCurrentUser() { - const imId = getLocalImUserId(app); - const uid = wx.getStorageSync('uid'); - if (!imId) return; - this.setData({ - currentUser: { - id: imId, - name: app.globalData.currentUser?.name || wx.getStorageSync('nicheng') || `用户${uid}`, - avatar: resolveAvatarUrl(wx.getStorageSync('touxiang'), app), - }, - }); - }, - buildImageFile(tempPath, size) { - return { - path: tempPath, - tempFilePath: tempPath, - size: size || 0, - }; - }, - - normalizeMessage(msg) { - if (!msg) return msg; - if (!msg.payload) msg.payload = {}; - if (msg.type === 'image') { - const p = msg.payload; - if (!p.url) p.url = p.thumbnail || p.thumb || p.imageUrl || ''; + const meta = (app.globalData.groupInfoMap && app.globalData.groupInfoMap[p.groupId]) || {}; + const initZt = p.orderZhuangtai != null ? p.orderZhuangtai : meta.orderZhuangtai; + if (initZt != null) { + this.applyOrderStatus(initZt, { + orderId: p.orderId || meta.orderId, + jine: p.orderJine || meta.orderJine, + jieshao: p.orderDesc || meta.orderDesc, + }); } - return msg; }, - mergeSentMessage(localId, sent) { - if (!sent) { - this.updateMsg(localId, 'success'); + onShow() { + app.globalData.pageState.isInChatPage = true; + app.globalData.pageState.currentChatId = this.data.groupId; + app.globalData.pageState.isInGroupChat = true; + this.ensureChatConnection(); + }, + + onHide() { + app.globalData.pageState.isInChatPage = false; + app.globalData.pageState.currentChatId = ''; + app.globalData.pageState.isInGroupChat = false; + this.clearPageListeners(); + }, + + applyOrderStatus(zhuangtai, extra) { + if (zhuangtai == null && zhuangtai !== 0) return; + const patch = { + orderZhuangtai: zhuangtai, + orderZhuangtaiText: getZhuangtaiText(zhuangtai), + }; + if (extra) { + if (extra.jine) patch.orderJine = extra.jine; + if (extra.jieshao) patch.orderJieshao = extra.jieshao; + if (extra.orderId) patch.orderId = extra.orderId; + } + this.setData(patch); + + const groupId = this.data.groupId; + if (!groupId) return; + if (!app.globalData.groupInfoMap) app.globalData.groupInfoMap = {}; + const meta = { ...(app.globalData.groupInfoMap[groupId] || {}), orderZhuangtai: zhuangtai }; + if (extra && extra.orderId) meta.orderId = extra.orderId; + app.globalData.groupInfoMap[groupId] = meta; + persistGroupMeta(app, groupId, meta); + }, + + applyOrderCardPayload(payload) { + if (!payload) return; + const cardOrderId = payload.orderId || payload.dingdan_id; + if (cardOrderId && this.data.orderId && cardOrderId !== this.data.orderId) return; + const zt = payload.zhuangtai != null ? payload.zhuangtai : this.data.orderZhuangtai; + payload.zhuangtaiText = getZhuangtaiText(zt); + this.applyOrderStatus(zt, { + orderId: payload.orderId || payload.dingdan_id || this.data.orderId, + jine: payload.jine, + jieshao: payload.jieshao, + }); + this.updateOrderBubblesInList(payload); + }, + + updateOrderBubblesInList(payload) { + if (!payload) return; + const orderId = payload.orderId || payload.dingdan_id; + if (!orderId) return; + const messages = this.data.messages.map((m) => { + if (m.type !== 'order' || !m.payload) return m; + const oid = m.payload.orderId || m.payload.dingdan_id; + if (oid !== orderId) return m; + return { ...m, payload: { ...m.payload, ...payload, orderId: oid } }; + }); + this.setData({ messages }); + }, + + mergeServerMessage(localId, status, serverMsg) { + if (!serverMsg) { + this.updateMsg(localId, status); return; } + normalizeGroupMessage(serverMsg); + serverMsg.formattedTime = formatDate(serverMsg.timestamp); + if (serverMsg.type === 'order') { + this.applyOrderCardPayload(serverMsg.payload); + } const msgs = this.data.messages.map((m) => { if (m.messageId !== localId) return m; - return this.normalizeMessage({ - ...m, - ...sent, - status: 'success', - messageId: sent.messageId || m.messageId, - payload: sent.payload || m.payload, - senderData: m.senderData || sent.senderData, - }); + return { + ...serverMsg, + showTime: m.showTime, + status: status || 'success', + formattedTime: serverMsg.formattedTime || m.formattedTime, + }; }); this.setData({ messages: msgs }); }, - onShow() { - this.autoConnect(); - }, + async ensureChatConnection() { + const role = app.globalData.currentRole || 'normal'; + const uid = wx.getStorageSync('uid'); + const prefixMap = { normal:'Boss', dashou:'Ds', shangjia:'Sj', guanshi:'Gs', zuzhang:'Zz', kaoheguan:'Kh' }; + const pageUserId = this.data.currentUser?.id; + const targetUserId = pageUserId || (prefixMap[role] || 'Boss') + uid; - onHide() { - this.clearAllListeners(); - }, - - autoConnect() { - const targetUserId = this.data.currentUser?.id || getLocalImUserId(app); - if (!targetUserId) return; - - const afterReady = () => { - this.subscribeGroupIfNeeded().then(() => { - this.setupAllListeners(); - this.loadHistory(true); - this.markGroupMessageAsRead(); - }); + const onReady = () => { + this.subscribeGroupIfNeeded() + .then(() => { + this.setupAllListeners(); + return this.loadHistory(true); + }) + .then(() => this.markGroupMessageAsRead()); }; - 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) { - afterReady(); - return; - } - wx.goEasy.disconnect(); + const connectedId = app.globalData?.goEasyConnection?.userId || wx.goEasy?.im?.userId; + const status = wx.goEasy?.getConnectionStatus?.() || 'disconnected'; + const imOk = status === 'connected' || status === 'reconnected'; + + if (imOk && connectedId === targetUserId) { + onReady(); + return; } - this.connectGoEasy(targetUserId); + + try { + if (app.ensureImForRole) { + await app.ensureImForRole(role); + } else if (app.connectWithIdentity) { + await app.connectWithIdentity(role, targetUserId, true); + } + onReady(); + } catch (e) { + const s = wx.goEasy?.getConnectionStatus?.(); + if (s === 'connected' || s === 'reconnected') onReady(); + else wx.showToast({ title: '连接失败,请重试', icon: 'none' }); + } + }, + + clearPageListeners() { + this.clearAllListeners(); }, connectGoEasy(userId) { @@ -155,14 +213,15 @@ Page({ id: userId, data: { name: currentUser.name, - avatar: resolveAvatarUrl(currentUser.avatar, app), + avatar: currentUser.avatar || (app.globalData.ossImageUrl + app.globalData.morentouxiang) }, onSuccess: () => { - this.subscribeGroupIfNeeded().then(() => { - this.setupAllListeners(); - this.loadHistory(true); - this.markGroupMessageAsRead(); - }); + this.subscribeGroupIfNeeded() + .then(() => { + this.setupAllListeners(); + return this.loadHistory(true); + }) + .then(() => this.markGroupMessageAsRead()); }, onFailed: (error) => { if (error.code === 408) { @@ -179,14 +238,17 @@ Page({ subscribeGroupIfNeeded() { const { groupId } = this.data; - if (!groupId || !wx.goEasy?.im?.subscribeGroup) return Promise.resolve(); + if (!groupId || !wx.goEasy?.im) return Promise.resolve(); return new Promise((resolve) => { wx.goEasy.im.subscribeGroup({ - groupIds: [String(groupId)], - onSuccess: () => resolve(true), - onFailed: (error) => { - console.warn('subscribeGroup failed', error); - resolve(false); + groupIds: [groupId], + onSuccess: () => { + console.log(`[群聊页] 订阅成功: ${groupId}`); + resolve(); + }, + onFailed: (err) => { + console.error(`[群聊页] 订阅失败: ${groupId}`, err); + resolve(); }, }); }); @@ -205,6 +267,23 @@ Page({ if (this._deleteHandler) { wx.goEasy.im.off(wx.GoEasy.IM_EVENT.MESSAGE_DELETED, this._deleteHandler); this._deleteHandler = null; } }, + initCurrentUser() { + const uid = wx.getStorageSync('uid'); + const role = app.globalData.currentRole || 'normal'; + const fresh = getFreshImUser(app, role, uid); + this.setData({ currentUser: { + id: fresh.id, + name: fresh.name, + avatar: fresh.avatar, + }}); + }, + + fixAvatar(url) { + if (!url) return app.globalData.ossImageUrl + app.globalData.morentouxiang; + if (url.startsWith('http')) return url; + return app.globalData.ossImageUrl + url; + }, + isConnected() { const s = wx.goEasy.getConnectionStatus ? wx.goEasy.getConnectionStatus() : 'disconnected'; return s === 'connected' || s === 'reconnected'; @@ -231,12 +310,14 @@ Page({ let list = res.content || []; list.sort((a, b) => a.timestamp - b.timestamp); list.forEach((m, idx) => { - m = this.normalizeMessage(m); - list[idx] = m; + normalizeGroupMessage(m); m.formattedTime = formatDate(m.timestamp); if (idx === 0) m.showTime = true; else m.showTime = (m.timestamp - list[idx-1].timestamp) / 60000 > 5; if (!m.senderData) m.senderData = { name: '未知用户', avatar: '' }; + if (m.type === 'order' && m.payload) { + this.applyOrderCardPayload(m.payload); + } }); let final = refresh ? list : [...list, ...this.data.messages]; if (!refresh) { @@ -267,17 +348,21 @@ Page({ if (this._msgHandler) wx.goEasy.im.off(wx.GoEasy.IM_EVENT.GROUP_MESSAGE_RECEIVED, this._msgHandler); this._msgHandler = (msg) => { if (msg.groupId !== this.data.groupId) return; - if (msg.senderId === this.data.currentUser.id) return; - msg = this.normalizeMessage(msg); + normalizeGroupMessage(msg); + if (msg.type === 'order' && msg.payload) { + this.applyOrderCardPayload(msg.payload); + } + if (msg.senderId === this.data.currentUser.id && msg.type !== 'order') return; msg.formattedTime = formatDate(msg.timestamp); const msgs = this.data.messages; + if (msgs.some((m) => m.messageId === msg.messageId)) return; const last = msgs.length ? msgs[msgs.length-1] : null; msg.showTime = last ? (msg.timestamp - last.timestamp) / 60000 > 5 : true; this.setData({ messages: [...msgs, msg], scrollToView: 'msg-bottom' }); this.markGroupMessageAsRead(); }; wx.goEasy.im.on(wx.GoEasy.IM_EVENT.GROUP_MESSAGE_RECEIVED, this._msgHandler); - this.markGroupMessageAsRead(); + this.markGroupMessageAsRead(); // ← 收到新消息就标记已读 }, markGroupMessageAsRead() { @@ -312,31 +397,21 @@ Page({ chooseImage() { const that = this; - wx.chooseMedia({ - count: 1, - mediaType: ['image'], - sizeType: ['compressed'], - sourceType: ['album', 'camera'], - success(res) { - const file = (res.tempFiles && res.tempFiles[0]) || null; - if (!file || !file.tempFilePath) return; - that.setData({ - pendingImage: file.tempFilePath, - pendingImageFile: that.buildImageFile(file.tempFilePath, file.size), - }); - }, - fail() { - wx.showToast({ title: '选择图片失败', icon: 'none' }); - }, + wx.chooseImage({ + count: 1, sizeType: ['compressed'], sourceType: ['album', 'camera'], + success(res) { that.setData({ pendingImage: res.tempFilePaths[0] }); } }); }, - clearPendingImage() { this.setData({ pendingImage: '', pendingImageFile: null }); }, + clearPendingImage() { this.setData({ pendingImage: '' }); }, sendMessage() { if (this.data.pendingImage) { - this.sendImageMsg(this.data.pendingImage, this.data.pendingImageFile); - this.setData({ pendingImage: '', pendingImageFile: null }); + const file = this.data.pendingImage; + showConfirmByScene('chat_image_confirm', () => { + this.sendImageMsg(file); + this.setData({ pendingImage: '' }); + }); return; } const text = this.data.inputText.trim(); @@ -367,61 +442,18 @@ Page({ } that.setData({ messages: [...msgs, localMsg], scrollToView: 'msg-bottom' }); }, - onSuccess: (messageId, status) => { - that.updateMsg(messageId, status); + onSuccess: (messageId, status, serverMsg) => { + that.mergeServerMessage(messageId, status, serverMsg); } }); }, - sendImageMsg(filePath, fileObj) { + sendImageMsg(file) { const that = this; - const { currentUser, groupId, orderId, isCross, groupName, groupAvatar } = that.data; - if (!filePath || !currentUser || !groupId) return; - - if (isCross === 0) { - if (!wx.goEasy || !wx.goEasy.im) { - wx.showToast({ title: '聊天未连接', icon: 'none' }); - return; - } - const avatar = resolveAvatarUrl(currentUser.avatar, app); - const name = currentUser.name || '用户'; - const localId = `image-${Date.now()}`; - const imageFile = fileObj || this.buildImageFile(filePath, 0); - const localMsg = { - messageId: localId, - type: 'image', - timestamp: Date.now(), - senderId: currentUser.id, - groupId, - senderData: { name, avatar }, - payload: { url: filePath }, - status: 'sending', - formattedTime: formatDate(Date.now()), - showTime: this.needShow(Date.now()), - }; - that.setData({ messages: [...that.data.messages, localMsg], scrollToView: 'msg-bottom' }); - const toData = { name: groupName || '订单群聊', avatar: groupAvatar || avatar, isCross: 0 }; - if (orderId) toData.orderId = orderId; - const imgMsg = wx.goEasy.im.createImageMessage({ - file: imageFile, - to: { type: wx.GoEasy.IM_SCENE.GROUP, id: groupId, data: toData }, - }); - wx.goEasy.im.sendMessage({ - message: imgMsg, - onSuccess: (sent) => that.mergeSentMessage(localId, sent), - onFailed: (error) => { - console.error('[群聊] 图片发送失败', error); - that.updateMsg(localId, 'failed'); - wx.showToast({ title: '图片发送失败', icon: 'none' }); - }, - }); - return; - } - sendGroupMessage({ msgData: { type: 'image', - filePath, + filePath: file, currentUser: that.data.currentUser, groupId: that.data.groupId, orderId: that.data.orderId, @@ -438,11 +470,8 @@ Page({ } that.setData({ messages: [...msgs, localMsg], scrollToView: 'msg-bottom' }); }, - onSuccess: (messageId, status) => { - that.updateMsg(messageId, status); - if (status === 'failed') { - wx.showToast({ title: '图片发送失败', icon: 'none' }); - } + onSuccess: (messageId, status, serverMsg) => { + that.mergeServerMessage(messageId, status, serverMsg); } }); }, @@ -477,7 +506,7 @@ Page({ const msgId = e.currentTarget.dataset.messageid; if (!msgId) return; const msg = this.data.messages.find(m => m.messageId === msgId); - if (!msg || msg.messageId.startsWith('local-')) return; + if (!msg || String(msg.messageId).startsWith('local-')) return; const itemList = ['复制']; if (msg.senderId === this.data.currentUser.id && (Date.now() - msg.timestamp < 120000) && !msg.recalled) { itemList.push('撤回'); diff --git a/pages/merchant-data-stats/merchant-data-stats.wxss b/pages/merchant-data-stats/merchant-data-stats.wxss index f9d7bdd..338894b 100644 --- a/pages/merchant-data-stats/merchant-data-stats.wxss +++ b/pages/merchant-data-stats/merchant-data-stats.wxss @@ -1,6 +1,6 @@ @import '../../styles/shangjia-xym-common.wxss'; -page { background: #fff8e1; } +page { background: #f5f3ff; } .sub-scroll { height: calc(100vh - 120rpx); } diff --git a/pages/merchant-dispatch/merchant-dispatch.json b/pages/merchant-dispatch/merchant-dispatch.json index c01f2ef..3278739 100644 --- a/pages/merchant-dispatch/merchant-dispatch.json +++ b/pages/merchant-dispatch/merchant-dispatch.json @@ -4,7 +4,7 @@ "popup-notice": "/components/popup-notice/popup-notice" }, "backgroundTextStyle": "dark", - "navigationBarBackgroundColor": "#fff8e1", + "navigationBarBackgroundColor": "#f5f3ff", "navigationBarTitleText": "自定义发单", "navigationBarTextStyle": "black", "backgroundColor": "#f5f5f5", diff --git a/pages/merchant-dispatch/merchant-dispatch.wxml b/pages/merchant-dispatch/merchant-dispatch.wxml index b535bc2..600f22e 100644 --- a/pages/merchant-dispatch/merchant-dispatch.wxml +++ b/pages/merchant-dispatch/merchant-dispatch.wxml @@ -159,7 +159,7 @@ 选填 - + {{commissionEnabled ? '已开启' : '未开启'}} NOTIFY_MAX_AGE_MS) return false; - _notifiedMessageIds.set(id, Date.now()); - if (_notifiedMessageIds.size > 400) { - const cutoff = Date.now() - 60000; - _notifiedMessageIds.forEach((t, k) => { - if (t < cutoff) _notifiedMessageIds.delete(k); - }); - } - return true; -} - -function bindGlobalListenersIfNeeded(app, userId) { - if (_listenersBoundForUser === userId && _convListener) return; - registerGlobalListeners(app); - registerNotification(app); - _listenersBoundForUser = userId; -} - -function initGlobalMessageSystem(app) { - app.ensureConnection = () => ensureConnection(app); - app.connectForCurrentRole = () => ensureConnection(app); - app.loadConversations = () => loadConversations(app); - app.disconnectGoEasy = () => disconnectGoEasy(app); - app.switchRoleAndReconnect = (newRole) => switchRoleAndReconnect(app, newRole); - app.updateUnreadCount = (count) => updateTabBarBadge(app, count || 0); - app.reregisterNotification = () => registerNotification(app); - app.startImWhenReady = () => startImWhenReady(app); - - if (!app.globalData.messageManager) { - app.globalData.messageManager = {}; - } - try { - const muted = wx.getStorageSync('notificationMuted'); - if (muted !== undefined) app.globalData.messageManager.notificationMuted = muted; - } catch (e) {} - - initAppStateListener(app); - initNetworkListener(app); - startHeartbeat(app); - startImWhenReady(app); -} - -/** GoEasy 未就绪时轮询,登录后尽早建立监听 */ -function startImWhenReady(app, maxAttempts = 40) { - let attempts = 0; - const tick = () => { - if (!wx.getStorageSync('uid')) return; - attempts += 1; - if (!wx.goEasy) { - if (attempts < maxAttempts) setTimeout(tick, 250); - return; - } - ensureConnection(app); - }; - tick(); -} - -function getCurrentUserId() { - return (wx.goEasy && wx.goEasy.im && wx.goEasy.im.userId) || null; -} - -function getConnectionStatus() { - if (!wx.goEasy || !wx.goEasy.getConnectionStatus) return 'disconnected'; - return wx.goEasy.getConnectionStatus(); -} - -function isConnected() { - const s = getConnectionStatus(); - return s === 'connected' || s === 'reconnected'; -} - -/** 统一会话数据结构,避免不同回调格式导致列表空白 */ -function normalizeConversationsPayload(result) { - if (!result) return { conversations: [], unreadTotal: 0 }; - const content = result.content || result; - const conversations = content.conversations || result.conversations || []; - const unreadTotal = content.unreadTotal ?? result.unreadTotal ?? 0; - return { conversations, unreadTotal }; -} - -function emitConversationsUpdated(app, result) { - const payload = normalizeConversationsPayload(result); - updateTabBarBadge(app, payload.unreadTotal); - app.emitEvent('conversationsUpdated', { - content: payload, - unreadTotal: payload.unreadTotal, - raw: result, - }); -} - -async function ensureConnection(app) { - const uid = wx.getStorageSync('uid'); - if (!uid || !wx.goEasy) return; - - const targetUserId = getLocalImUserId(app); - const identityType = getLocalImIdentity(app); - if (!targetUserId) return; - - if (isConnected()) { - const currentUserId = getCurrentUserId(); - if (currentUserId === targetUserId) { - bindGlobalListenersIfNeeded(app, targetUserId); - loadConversations(app); - const now = Date.now(); - if (now - _lastGroupSubscribeAt > 60000) { - _lastGroupSubscribeAt = now; - await subscribeAllGroups(app); - } - return; - } - try { - wx.goEasy.disconnect(); - } catch (e) {} - } - - connectGoEasy(app, targetUserId, identityType); -} - -function connectGoEasy(app, userId, identityType) { - const defAvatar = resolveAvatarUrl('', app); - const tx = wx.getStorageSync('touxiang') || ''; - const uid = wx.getStorageSync('uid') || ''; - const nick = wx.getStorageSync('nicheng') || app.globalData.nicheng || app.globalData.dashouNicheng || ''; - const name = nick || ('用户' + (uid || '').substring(0, 6)); - const avatar = resolveAvatarUrl(tx, app) || defAvatar; - - wx.goEasy.connect({ - id: userId, - data: { - name, - avatar, - identityType: identityType === 'normal' ? 'boss' : identityType, - }, - onSuccess: async () => { - try { - wx.setStorageSync('savedGoEasyConnection', JSON.stringify({ - userId, - identityType, - lastConnectTime: Date.now(), - status: 'connected', - })); - } catch (e) {} - bindGlobalListenersIfNeeded(app, userId); - _lastGroupSubscribeAt = Date.now(); - await subscribeAllGroups(app); - loadConversations(app); - }, - onFailed: (error) => { - // 408 多为 GoEasy 节点超时,开发者工具常见,不影响订单等业务接口 - if (error && error.code === 408) { - console.warn('GoEasy 连接超时(408),将使用本地会话缓存'); - bindGlobalListenersIfNeeded(app, userId); - loadConversations(app); - return; - } - console.warn('GoEasy连接失败', error); - wx.showToast({ title: '消息服务连接失败', icon: 'none' }); - }, - }); -} - -function subscribeAllGroups(app) { - return new Promise((resolve) => { - if (!wx.goEasy || !wx.goEasy.im) return resolve(); - wx.goEasy.im.latestConversations({ - onSuccess: (result) => { - const payload = normalizeConversationsPayload(result); - const groupIds = payload.conversations - .filter((c) => c.type === 'group' && c.groupId) - .map((c) => c.groupId); - if (groupIds.length && wx.goEasy.im.subscribeGroup) { - wx.goEasy.im.subscribeGroup({ - groupIds, - onSuccess: () => resolve(), - onFailed: () => resolve(), - }); - } else { - resolve(); - } - }, - onFailed: () => resolve(), - }); - }); -} - -function registerGlobalListeners(app) { - if (!wx.goEasy || !wx.goEasy.im) return; - - if (_convListener) { - wx.goEasy.im.off(wx.GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, _convListener); - } - if (_privateListener) { - wx.goEasy.im.off(wx.GoEasy.IM_EVENT.PRIVATE_MESSAGE_RECEIVED, _privateListener); - } - if (_groupListener) { - wx.goEasy.im.off(wx.GoEasy.IM_EVENT.GROUP_MESSAGE_RECEIVED, _groupListener); - } - - _convListener = (result) => { - emitConversationsUpdated(app, result); - }; - wx.goEasy.im.on(wx.GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, _convListener); - - _privateListener = () => { - loadConversations(app); - }; - wx.goEasy.im.on(wx.GoEasy.IM_EVENT.PRIVATE_MESSAGE_RECEIVED, _privateListener); - - _groupListener = () => { - loadConversations(app); - }; - wx.goEasy.im.on(wx.GoEasy.IM_EVENT.GROUP_MESSAGE_RECEIVED, _groupListener); -} - -function loadConversations(app) { - if (!wx.goEasy || !wx.goEasy.im) return; - if (!getCurrentUserId()) return; - - wx.goEasy.im.latestConversations({ - onSuccess: (result) => { - emitConversationsUpdated(app, result); - subscribeAllGroups(app); - }, - onFailed: (err) => { - console.error('加载会话失败', err); - }, - }); -} - -function updateTabBarBadge(app, unreadCount) { - const badgeText = unreadCount > 0 ? (unreadCount > 99 ? '99+' : String(unreadCount)) : ''; - if (!app.globalData.messageManager) app.globalData.messageManager = {}; - app.globalData.messageManager.unreadTotal = unreadCount; - app.globalData.messageManager.tabBarBadgeText = badgeText; - app.emitEvent('tabBarBadgeChanged', { badgeText, showRedDot: unreadCount > 0 }); - - try { - const pages = getCurrentPages(); - if (pages.length) { - const page = pages[pages.length - 1]; - const tabBar = page.selectComponent('#tab-bar'); - if (tabBar && tabBar.setData) tabBar.setData({ badgeText }); - if (page.getTabBar) { - const tb = page.getTabBar(); - if (tb && tb.setData) tb.setData({ badgeText }); - } - } - } catch (e) {} - wx.setStorageSync('messageUnreadTotal', unreadCount); -} - -function registerNotification(app) { - if (!wx.goEasy || !wx.goEasy.im) return; - - if (_notificationHandler) { - wx.goEasy.im.off(wx.GoEasy.IM_EVENT.PRIVATE_MESSAGE_RECEIVED, _notificationHandler); - wx.goEasy.im.off(wx.GoEasy.IM_EVENT.GROUP_MESSAGE_RECEIVED, _notificationHandler); - } - - _notificationHandler = (message) => { - if (app.globalData.messageManager && app.globalData.messageManager.notificationMuted) return; - if (!shouldShowNotification(message)) return; - const pages = getCurrentPages(); - if (pages.length) { - const curPage = pages[pages.length - 1]; - const route = curPage.route || ''; - if (route === 'pages/chat/chat' || route === 'pages/group-chat/group-chat') return; - } - const senderName = message.senderData?.name || (message.senderId || '用户'); - let content = ''; - if (message.type === 'text') content = message.payload?.text || '[文本]'; - else if (message.type === 'image') content = '[图片]'; - else if (message.type === 'audio') content = '[语音]'; - else content = '[新消息]'; - const avatar = resolveAvatarUrl(message.senderData?.avatar, app); - - if (app.globalData.globalNotification && app.globalData.globalNotification.show) { - app.globalData.globalNotification.show({ - senderName, - content, - avatar, - message, - notificationType: message.groupId ? 'group' : 'private', - groupId: message.groupId, - orderId: message.orderId, - }); - } else { - wx.showToast({ title: `${senderName}: ${content}`, icon: 'none', duration: 2000 }); - } - }; - wx.goEasy.im.on(wx.GoEasy.IM_EVENT.PRIVATE_MESSAGE_RECEIVED, _notificationHandler); - wx.goEasy.im.on(wx.GoEasy.IM_EVENT.GROUP_MESSAGE_RECEIVED, _notificationHandler); -} - -function detachGlobalListeners() { - if (!wx.goEasy || !wx.goEasy.im) return; - if (_convListener) { - wx.goEasy.im.off(wx.GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, _convListener); - _convListener = null; - } - if (_privateListener) { - wx.goEasy.im.off(wx.GoEasy.IM_EVENT.PRIVATE_MESSAGE_RECEIVED, _privateListener); - _privateListener = null; - } - if (_groupListener) { - wx.goEasy.im.off(wx.GoEasy.IM_EVENT.GROUP_MESSAGE_RECEIVED, _groupListener); - _groupListener = null; - } - if (_notificationHandler) { - wx.goEasy.im.off(wx.GoEasy.IM_EVENT.PRIVATE_MESSAGE_RECEIVED, _notificationHandler); - wx.goEasy.im.off(wx.GoEasy.IM_EVENT.GROUP_MESSAGE_RECEIVED, _notificationHandler); - _notificationHandler = null; - } - _listenersBoundForUser = null; -} - -function disconnectGoEasy(app) { - stopHeartbeat(app); - detachGlobalListeners(); - if (wx.goEasy && wx.goEasy.disconnect) { - wx.goEasy.disconnect({ onSuccess: () => {}, onFailed: () => {} }); - } - wx.removeStorageSync('savedGoEasyConnection'); -} - -async function switchRoleAndReconnect(app, newRole) { - disconnectGoEasy(app); - app.globalData.currentRole = newRole; - wx.setStorageSync('currentRole', newRole); - if (app.globalData.primaryRole !== newRole && ['normal', 'dashou', 'shangjia'].includes(newRole)) { - app.globalData.primaryRole = newRole; - wx.setStorageSync('primaryRole', newRole); - } - startHeartbeat(app); - await ensureConnection(app); - return true; -} - -function startHeartbeat(app) { - stopHeartbeat(app); - _heartbeatTimer = setInterval(() => { - if (!wx.getStorageSync('uid')) return; - if (isConnected()) { - loadConversations(app); - } else { - ensureConnection(app); - } - }, 45000); -} - -function stopHeartbeat(app) { - if (_heartbeatTimer) { - clearInterval(_heartbeatTimer); - _heartbeatTimer = null; - } -} - -function initAppStateListener(app) { - wx.onAppShow(() => { - setTimeout(() => { - if (!wx.getStorageSync('uid')) return; - if (!isConnected()) { - ensureConnection(app); - } else { - loadConversations(app); - setTimeout(() => { - const unread = (app.globalData.messageManager && app.globalData.messageManager.unreadTotal) || 0; - updateTabBarBadge(app, unread); - }, 300); - } - }, 200); - }); -} - -function initNetworkListener(app) { - wx.onNetworkStatusChange((res) => { - if (res.isConnected) { - setTimeout(() => ensureConnection(app), 1000); - } - }); -} - -module.exports = { initGlobalMessageSystem }; +// utils/chat-core.js — 全局 IM 连接、监听、重连、角标 +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'; + +let _globalPrivateHandler = null; +let _globalGroupHandler = null; +let _globalConvHandler = null; +let _imLockDepth = 0; +let _loadConvTimer = null; +let _loadConvInFlight = false; + +async function withImLock(fn) { + if (_imLockDepth > 0) { + return await fn(); + } + _imLockDepth += 1; + try { + return await fn(); + } finally { + _imLockDepth -= 1; + } +} + +function initGlobalMessageSystem(app) { + app.updateUnreadCount = (count) => updateUnreadCount(app, count); + app.disconnectGoEasy = () => disconnectGoEasy(app); + app.ensureConnection = () => ensureConnection(app); + app.connectWithIdentity = (identityType, userId, isAutoRestore) => + connectWithIdentity(app, identityType, userId, isAutoRestore); + app.connectForCurrentRole = () => connectForCurrentRole(app); + app.switchRoleAndReconnect = (newRole) => switchRoleAndReconnect(app, newRole); + app.toggleDoNotDisturb = (enabled) => toggleDoNotDisturb(app, enabled); + app.toggleNotificationMute = (enabled) => toggleNotificationMute(app, enabled); + app.closeNotification = () => closeNotification(app); + app.loadConversations = () => loadConversations(app); + app.updateCurrentPageState = () => updateCurrentPageState(app); + app.getSavedConnection = () => getSavedConnection(app); + app.clearSavedConnection = () => clearSavedConnection(app); + app.saveConnectionState = () => saveConnectionState(app); + app.handleNewMessage = (message) => handleNewMessage(app, message); + app.ensureImForRole = (role) => ensureImForRole(app, role); + app.syncConnectionStatus = () => syncConnectionStatus(app); + + loadUserSettings(app); + initNetworkListener(app); + initAppStateListener(app); + bindGoEasyLifecycle(app); + setupEventSystem(app); + checkAndRestoreConnection(app); +} + +function bindGoEasyLifecycle(app) { + if (!wx.goEasy || app._goEasyLifecycleBound) return; + app._goEasyLifecycleBound = true; + try { + wx.goEasy.on('connected', () => { + app.globalData.goEasyConnection.status = 'connected'; + app.globalData.goEasyConnection.autoReconnect = true; + setupMessageListeners(app); + loadConversations(app); + startHeartbeat(app); + syncConnectionStatus(app); + }); + wx.goEasy.on('disconnected', () => { + app.globalData.goEasyConnection.status = 'disconnected'; + if (!app._imSuppressDisconnectEvent) { + app.emitEvent('connectionChanged', { status: 'disconnected' }); + } + if (app.globalData.goEasyConnection.autoReconnect) { + setTimeout(() => ensureConnection(app), app.globalData.goEasyConnection.config.reconnectDelay); + } + }); + } catch (e) { + console.warn('绑定 GoEasy 生命周期失败:', e); + } +} + +function getCurrentGoEasyUserId() { + return (wx.goEasy && wx.goEasy.im && wx.goEasy.im.userId) || null; +} + +function getExpectedImUserId(app) { + const uid = wx.getStorageSync('uid'); + if (!uid) return ''; + const role = app.globalData.currentRole || 'normal'; + return getImUserIdForRole(role, uid); +} + +function loadUserSettings(app) { + try { + const messageSettings = wx.getStorageSync(app.globalData.messageManager.cacheKeys.messageSettings); + if (messageSettings) { + app.globalData.messageManager = { ...app.globalData.messageManager, ...messageSettings }; + } + const notificationMuted = wx.getStorageSync(app.globalData.messageManager.cacheKeys.notificationMuted); + if (notificationMuted !== '') { + app.globalData.messageManager.notificationMuted = notificationMuted; + } + const savedUnread = wx.getStorageSync(app.globalData.messageManager.cacheKeys.unreadTotal); + if (savedUnread !== '' && savedUnread !== undefined) { + app.globalData.messageManager.unreadTotal = savedUnread; + updateTabBarBadge(app, savedUnread); + } + const savedMessages = wx.getStorageSync(app.globalData.messageManager.cacheKeys.latestMessages); + if (savedMessages) { + app.globalData.messageManager.latestMessages = savedMessages; + } + } catch (error) { + console.warn('加载用户设置失败:', error); + } +} + +function initNetworkListener(app) { + wx.onNetworkStatusChange((res) => { + if (res.isConnected) { + app.globalData.goEasyConnection.autoReconnect = true; + app.globalData.goEasyConnection.reconnectAttempts = 0; + app.emitEvent('connectionChanged', { status: 'reconnecting', reason: 'network' }); + ensureConnection(app); + } else { + app.globalData.goEasyConnection.status = 'disconnected'; + app.emitEvent('connectionChanged', { status: 'disconnected', reason: 'network' }); + } + }); +} + +function initAppStateListener(app) { + wx.onAppShow(() => { + updateCurrentPageState(app); + app.globalData.goEasyConnection.autoReconnect = true; + ensureConnection(app); + setTimeout(() => loadConversations(app), 400); + }); + wx.onAppHide(() => { + pauseHeartbeat(app); + saveConnectionState(app); + }); +} + +function checkAndRestoreConnection(app) { + try { + const savedConnection = wx.getStorageSync(app.globalData.goEasyConnection.cacheKeys.savedConnection); + if (savedConnection && savedConnection.userId && savedConnection.identityType) { + const now = Date.now(); + const lastTime = savedConnection.lastConnectTime || 0; + const hoursDiff = (now - lastTime) / (1000 * 60 * 60); + const validityHours = app.globalData.goEasyConnection.config.cacheValidityHours || 12; + if (hoursDiff > validityHours) { + clearSavedConnection(app); + } + } + ensureConnection(app); + } catch (error) { + console.warn('检查保存的连接信息失败:', error); + } +} + +function setupEventSystem(app) { + if (!app.globalData.eventListeners) { + app.globalData.eventListeners = {}; + } +} + +function isSdkConnected() { + if (!wx.goEasy?.getConnectionStatus) return false; + const s = wx.goEasy.getConnectionStatus(); + return s === 'connected' || s === 'reconnected'; +} + +function isImConnected() { + return isSdkConnected(); +} + +/** 已连接且身份正确则直接恢复监听,避免重复 connect 触发 408 */ +function adoptExistingConnection(app, identityType, userId) { + app.globalData.goEasyConnection.status = 'connected'; + app.globalData.goEasyConnection.userId = userId; + app.globalData.goEasyConnection.identityType = identityType; + app.globalData.goEasyConnection.autoReconnect = true; + app.globalData.goEasyConnection.reconnectAttempts = 0; + saveConnectionState(app); + startHeartbeat(app); + setupMessageListeners(app); + loadConversations(app); + syncConnectionStatus(app); +} + +/** 根据 SDK 真实状态同步 UI(避免已连接仍显示未连接) */ +function syncConnectionStatus(app) { + if (app.globalData.chatEnabled === false) { + app.emitEvent('connectionChanged', { status: 'disabled', reason: 'chat_disabled' }); + return; + } + const expectedId = getExpectedImUserId(app); + if (!expectedId) { + app.emitEvent('connectionChanged', { status: 'disconnected', reason: 'no_uid' }); + return; + } + const cur = getCurrentGoEasyUserId(); + if (isSdkConnected() && (!cur || cur === expectedId)) { + app.globalData.goEasyConnection.status = 'connected'; + app.globalData.goEasyConnection.userId = cur || expectedId; + app.globalData.goEasyConnection.autoReconnect = true; + app.emitEvent('connectionChanged', { status: 'connected' }); + return; + } + const st = app.globalData.goEasyConnection.status; + if (st === 'connecting' || st === 'reconnecting') { + app.emitEvent('connectionChanged', { status: st }); + } else { + app.emitEvent('connectionChanged', { status: 'disconnected' }); + } +} + +function ensureConnection(app) { + if (!wx.goEasy?.connect) return; + if (app.globalData.chatEnabled === false) { + syncConnectionStatus(app); + return; + } + + const expectedId = getExpectedImUserId(app); + if (!expectedId) return; + + const role = app.globalData.currentRole || 'normal'; + + if (isImConnected()) { + const cur = getCurrentGoEasyUserId(); + if (cur === expectedId) { + setupMessageListeners(app); + startHeartbeat(app); + loadConversations(app); + syncConnectionStatus(app); + return; + } + connectForCurrentRole(app); + return; + } + + app.globalData.goEasyConnection.autoReconnect = true; + app.emitEvent('connectionChanged', { status: 'connecting' }); + + const saved = getSavedConnection(app); + if (saved && saved.userId === expectedId && saved.identityType === role) { + connectWithIdentity(app, saved.identityType, saved.userId, true); + return; + } + + connectForCurrentRole(app); +} + +async function ensureImForRole(app, role) { + const uid = wx.getStorageSync('uid'); + if (!uid) throw new Error('未登录'); + const targetUserId = getImUserIdForRole(role, uid); + if (!targetUserId) throw new Error('无效身份'); + + if (isImConnected() && getCurrentGoEasyUserId() === targetUserId) { + setupMessageListeners(app); + loadConversations(app); + syncConnectionStatus(app); + return; + } + + if (app.globalData.currentRole !== role) { + app.globalData.currentRole = role; + wx.setStorageSync('currentRole', role); + } + + await connectWithIdentity(app, role, targetUserId, true); +} + +function getSavedConnection(app) { + try { + const saved = wx.getStorageSync(app.globalData.goEasyConnection.cacheKeys.savedConnection); + if (!saved) return null; + if (typeof saved === 'string') return JSON.parse(saved); + return saved; + } catch (error) { + console.error('获取保存的连接信息失败:', error); + return null; + } +} + +function saveConnectionState(app) { + try { + const { goEasyConnection } = app.globalData; + const connectionState = { + userId: goEasyConnection.userId, + identityType: goEasyConnection.identityType, + lastConnectTime: Date.now(), + autoReconnect: goEasyConnection.autoReconnect, + status: goEasyConnection.status, + }; + wx.setStorageSync(goEasyConnection.cacheKeys.savedConnection, JSON.stringify(connectionState)); + } catch (error) { + console.warn('保存连接状态失败:', error); + } +} + +function clearSavedConnection(app) { + const { cacheKeys } = app.globalData.goEasyConnection; + wx.removeStorageSync(cacheKeys.savedConnection); + wx.removeStorageSync(cacheKeys.userId); + wx.removeStorageSync(cacheKeys.identityType); + app.globalData.goEasyConnection.userId = ''; + app.globalData.goEasyConnection.identityType = ''; + app.globalData.goEasyConnection.lastConnectTime = 0; +} + +function disconnectGoEasy(app, options = {}) { + const silent = options.silent === true; + const keepAutoReconnect = options.keepAutoReconnect === true; + + return withImLock(() => new Promise((resolve) => { + if (silent) app._imSuppressDisconnectEvent = true; + app.globalData.goEasyConnection.status = 'disconnected'; + if (!keepAutoReconnect) { + app.globalData.goEasyConnection.autoReconnect = false; + } + stopHeartbeat(app); + removeGlobalMessageListeners(); + + const finish = () => { + clearSavedConnection(app); + if (!keepAutoReconnect) { + app.globalData.messageManager.unreadTotal = 0; + updateTabBarBadge(app, 0); + } + if (!silent) { + app.emitEvent('connectionChanged', { status: 'disconnected', manual: true }); + } + app._imSuppressDisconnectEvent = false; + resolve(); + }; + + if (wx.goEasy && wx.goEasy.disconnect) { + wx.goEasy.disconnect({ + onSuccess: finish, + onFailed: finish, + }); + } else { + finish(); + } + })); +} + +async function connectWithIdentity(app, identityType, userId, isAutoRestore = false) { + if (!wx.goEasy?.connect) { + return Promise.reject(new Error('聊天服务未就绪')); + } + + return withImLock(async () => { + if (!isAutoRestore) { + const quanxian = await jianquanxian(app); + if (!quanxian.allowed) { + throw new Error(quanxian.reason || '无聊天权限'); + } + } else { + const uid = wx.getStorageSync('uid'); + if (!uid) throw new Error('未登录'); + } + + const currentId = getCurrentGoEasyUserId(); + if (isSdkConnected()) { + if (!currentId || currentId === userId) { + adoptExistingConnection(app, identityType, userId); + return; + } + await disconnectGoEasy(app, { silent: true, keepAutoReconnect: true }); + app.globalData.goEasyConnection.autoReconnect = true; + await new Promise((r) => setTimeout(r, 400)); + } + + if (currentId && currentId !== userId && !isSdkConnected()) { + await disconnectGoEasy(app, { silent: true, keepAutoReconnect: true }); + app.globalData.goEasyConnection.autoReconnect = true; + await new Promise((r) => setTimeout(r, 400)); + } + + app.globalData.goEasyConnection.autoReconnect = true; + app.globalData.goEasyConnection.status = 'connecting'; + app.globalData.goEasyConnection.userId = userId; + app.globalData.goEasyConnection.identityType = identityType; + if (!isAutoRestore) app.globalData.goEasyConnection.reconnectAttempts = 0; + saveConnectionState(app); + app.emitEvent('connectionChanged', { status: 'connecting', identityType, userId }); + + const uid = wx.getStorageSync('uid'); + const role = identityType || app.globalData.currentRole || 'normal'; + const freshUser = getFreshImUser(app, role, uid); + const currentUser = app.globalData.currentUser || { + id: uid, + name: freshUser.name, + avatar: freshUser.avatar, + }; + + return new Promise((resolve, reject) => { + wx.goEasy.connect({ + id: userId, + 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(); + app.globalData.goEasyConnection.reconnectAttempts = 0; + saveConnectionState(app); + startHeartbeat(app); + setupMessageListeners(app); + loadConversations(app); + app.emitEvent('connectionChanged', { status: 'connected', identityType, userId }); + resolve(); + }, + onFailed: (error) => { + console.error('GoEasy连接失败:', error); + // 408 = 已连接,勿再 connect,直接复用当前连接 + if (error.code === 408) { + adoptExistingConnection(app, identityType, userId); + resolve(); + return; + } + if (error.code === 900) { + app.globalData.goEasyConnection.autoReconnect = false; + app.globalData.goEasyConnection.status = 'disconnected'; + clearSavedConnection(app); + stopHeartbeat(app); + wx.showModal({ + title: '消息功能暂不可用', + content: '当前使用人数爆满,消息功能暂时无法使用。请联系管理员升级套餐后重试。', + showCancel: false, + confirmText: '我知道了', + }); + app.emitEvent('connectionChanged', { status: 'disconnected', error, reason: 'overcapacity' }); + reject(error); + return; + } + app.globalData.goEasyConnection.status = 'reconnecting'; + app.emitEvent('connectionChanged', { status: 'reconnecting', error, isAutoRestore }); + const { autoReconnect, reconnectAttempts, maxReconnectAttempts } = app.globalData.goEasyConnection; + if (autoReconnect && reconnectAttempts < maxReconnectAttempts && !isSdkConnected()) { + app.globalData.goEasyConnection.reconnectAttempts++; + const delay = app.globalData.goEasyConnection.config.reconnectDelay * reconnectAttempts; + setTimeout(() => { + if (app.globalData.goEasyConnection.autoReconnect) { + connectWithIdentity(app, identityType, userId, true).then(resolve).catch(reject); + } + }, delay); + } else { + reject(error); + } + }, + }); + }); + }); +} + +async function connectForCurrentRole(app) { + const uid = wx.getStorageSync('uid'); + if (!uid) return; + const role = app.globalData.currentRole || 'normal'; + const targetUserId = getImUserIdForRole(role, uid); + + const currentUserId = getCurrentGoEasyUserId(); + if (currentUserId === targetUserId && isImConnected()) { + setupMessageListeners(app); + startHeartbeat(app); + loadConversations(app); + syncConnectionStatus(app); + return; + } + if (currentUserId && currentUserId !== targetUserId) { + await disconnectGoEasy(app, { silent: true, keepAutoReconnect: true }); + app.globalData.goEasyConnection.autoReconnect = true; + } + await connectWithIdentity(app, role, targetUserId, true); +} + +async function switchRoleAndReconnect(app, newRole) { + try { + await withImLock(async () => { + await disconnectGoEasy(app, { silent: true, keepAutoReconnect: true }); + app.globalData.currentRole = newRole; + wx.setStorageSync('currentRole', newRole); + app.globalData.goEasyConnection.autoReconnect = true; + app.globalData.goEasyConnection.reconnectAttempts = 0; + const uid = wx.getStorageSync('uid'); + const targetUserId = getImUserIdForRole(newRole, uid); + // 切换身份用轻量连接,避免鉴权接口失败把 IM 永久断开 + await connectWithIdentity(app, newRole, targetUserId, true); + }); + return true; + } catch (error) { + console.warn('切换身份 IM 重连失败', error); + syncConnectionStatus(app); + throw error; + } +} + +function startHeartbeat(app) { + const { goEasyConnection } = app.globalData; + stopHeartbeat(app); + let convTick = 0; + goEasyConnection.heartbeatInterval = setInterval(() => { + if (goEasyConnection.status === 'connected' && wx.goEasy?.im) { + wx.goEasy.im.ping({ + onSuccess: () => { + convTick += 1; + if (convTick % 3 === 0) { + loadConversations(app); + } + }, + onFailed: (error) => { + console.error('心跳失败:', error); + if (isSdkConnected()) return; + goEasyConnection.status = 'reconnecting'; + app.emitEvent('connectionChanged', { status: 'reconnecting' }); + attemptReconnect(app); + }, + }); + } else if (goEasyConnection.autoReconnect && !isSdkConnected()) { + ensureConnection(app); + } + }, goEasyConnection.config.heartbeatInterval); +} + +function stopHeartbeat(app) { + if (app.globalData.goEasyConnection.heartbeatInterval) { + clearInterval(app.globalData.goEasyConnection.heartbeatInterval); + app.globalData.goEasyConnection.heartbeatInterval = null; + } +} + +function pauseHeartbeat(app) { stopHeartbeat(app); } + +function attemptReconnect(app) { + const { goEasyConnection } = app.globalData; + if (!goEasyConnection.autoReconnect || isSdkConnected()) return; + if (goEasyConnection.reconnectAttempts >= goEasyConnection.maxReconnectAttempts) { + goEasyConnection.reconnectAttempts = 0; + } + if (goEasyConnection.userId && goEasyConnection.identityType) { + connectWithIdentity(app, goEasyConnection.identityType, goEasyConnection.userId, true); + } else { + connectForCurrentRole(app); + } +} + +function removeGlobalMessageListeners() { + if (!wx.goEasy?.im) return; + if (_globalPrivateHandler) { + wx.goEasy.im.off(wx.GoEasy.IM_EVENT.PRIVATE_MESSAGE_RECEIVED, _globalPrivateHandler); + _globalPrivateHandler = null; + } + if (_globalGroupHandler) { + wx.goEasy.im.off(wx.GoEasy.IM_EVENT.GROUP_MESSAGE_RECEIVED, _globalGroupHandler); + _globalGroupHandler = null; + } + if (_globalConvHandler) { + wx.goEasy.im.off(wx.GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, _globalConvHandler); + _globalConvHandler = null; + } +} + +function setupMessageListeners(app) { + if (!wx.goEasy?.im || typeof wx.goEasy.im.on !== 'function') { + return; + } + removeGlobalMessageListeners(); + + _globalPrivateHandler = (message) => { + app.handleNewMessage(message); + }; + _globalGroupHandler = (message) => { + app.handleNewMessage(message); + }; + _globalConvHandler = (conversations) => { + const normalized = normalizeConversationPayload(conversations); + const unreadTotal = normalized?.unreadTotal ?? conversations?.unreadTotal ?? 0; + applyUnreadTotal(app, unreadTotal); + if (normalized && typeof app.emitEvent === 'function') { + app.emitEvent('conversationsUpdated', normalized); + } + }; + + wx.goEasy.im.on(wx.GoEasy.IM_EVENT.PRIVATE_MESSAGE_RECEIVED, _globalPrivateHandler); + wx.goEasy.im.on(wx.GoEasy.IM_EVENT.GROUP_MESSAGE_RECEIVED, _globalGroupHandler); + wx.goEasy.im.on(wx.GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, _globalConvHandler); +} + +function handleNewMessage(app, message) { + const messageId = message.messageId || message.id; + const fingerprint = `${message.senderId}_${message.timestamp}_${message.type}`; + const msgKey = messageId || fingerprint; + + if (!app._processedMessages) app._processedMessages = new Set(); + if (app._processedMessages.has(msgKey)) return; + app._processedMessages.add(msgKey); + if (app._processedMessages.size > 300) { + app._processedMessages.delete(app._processedMessages.values().next().value); + } + + cacheMessage(app, message); + + if (shouldShowNotification(app, message)) { + let notificationType = 'private'; + let extraData = {}; + if (message.groupId) { + notificationType = 'group'; + extraData = { + groupId: message.groupId, + orderId: message.orderId || '', + groupName: '', + groupAvatar: '', + isCross: message.isCross || 0, + }; + const groupInfo = app.globalData.groupInfoMap?.[message.groupId]; + if (groupInfo) { + extraData.groupName = groupInfo.name || ''; + extraData.groupAvatar = groupInfo.avatar || ''; + } + } else if (message.teamId) { + notificationType = 'cs'; + extraData = { teamId: message.teamId }; + } + showNotificationDirect(app, { + senderId: message.senderId, + senderName: message.senderData?.name || '用户', + avatar: message.senderData?.avatar || (app.globalData.ossImageUrl + app.globalData.morentouxiang), + content: formatMessageForNotification(message), + message, + notificationType, + ...extraData, + }); + } +} + +function shouldShowNotification(app, message) { + if (app.globalData.messageManager.notificationMuted) return false; + if (isDoNotDisturbTime(app)) return false; + if (isMessageFromCurrentChat(app, message)) return false; + return true; +} + +function isDoNotDisturbTime(app) { + const { doNotDisturb, doNotDisturbStart, doNotDisturbEnd } = app.globalData.messageManager; + if (!doNotDisturb) return false; + const now = new Date(); + const currentTime = now.getHours() * 60 + now.getMinutes(); + const [sH, sM] = doNotDisturbStart.split(':').map(Number); + const [eH, eM] = doNotDisturbEnd.split(':').map(Number); + const startTime = sH * 60 + sM; + const endTime = eH * 60 + eM; + if (startTime < endTime) { + return currentTime >= startTime && currentTime < endTime; + } + return currentTime >= startTime || currentTime < endTime; +} + +function isMessageFromCurrentChat(app, message) { + const pageState = app.globalData.pageState || {}; + if (!pageState.isInChatPage || !pageState.currentChatId) return false; + if (message.groupId) { + return message.groupId === pageState.currentChatId; + } + const myId = app.globalData.goEasyConnection?.userId || getCurrentGoEasyUserId(); + if (!myId) return false; + const peerId = message.senderId === myId ? message.receiverId : message.senderId; + return peerId === pageState.currentChatId || message.senderId === pageState.currentChatId; +} + +function showNotificationDirect(app, data) { + if (app.globalData.globalNotification?.show) { + try { + app.globalData.globalNotification.show(data); + return; + } catch (error) { + console.warn('全局通知显示失败:', error); + } + } + app.emitEvent('showNotification', data); +} + +function applyUnreadTotal(app, unreadTotal) { + const prev = app.globalData.messageManager.unreadTotal; + app.globalData.messageManager.unreadTotal = unreadTotal; + updateTabBarBadge(app, unreadTotal); + wx.setStorageSync(app.globalData.messageManager.cacheKeys.unreadTotal, unreadTotal); + if (prev !== unreadTotal) { + app.emitEvent('unreadCountChanged', { unreadTotal }); + } +} + +async function updateUnreadCount(app, customCount) { + let unreadTotal = customCount; + if (unreadTotal === undefined) { + try { + const result = await getUnreadCount(app); + unreadTotal = result.unreadTotal || 0; + } catch (error) { + console.error('获取未读数失败:', error); + return; + } + } + applyUnreadTotal(app, unreadTotal); +} + +function getUnreadCount(app) { + return new Promise((resolve, reject) => { + if (!getCurrentGoEasyUserId()) { + resolve({ unreadTotal: 0 }); + return; + } + wx.goEasy.im.latestConversations({ + onSuccess: (result) => resolve({ + unreadTotal: result.unreadTotal || 0, + conversations: result.content?.conversations || [], + }), + onFailed: reject, + }); + }); +} + +function formatMessageForNotification(message) { + switch (message.type) { + case 'text': return message.payload.text || '[文本]'; + case 'image': return '[图片]'; + case 'audio': return '[语音]'; + case 'video': return '[视频]'; + case 'file': return '[文件]'; + case 'order': return '[订单]'; + default: return '[新消息]'; + } +} + +function cacheMessage(app, message) { + if (message.groupId && message.type === 'text' && message.payload?.text) { + const card = parseOrderCardText(message.payload.text); + if (card && card.zhuangtai != null) { + if (!app.globalData.groupInfoMap) app.globalData.groupInfoMap = {}; + const meta = { + ...(app.globalData.groupInfoMap[message.groupId] || {}), + orderId: card.orderId, + orderZhuangtai: card.zhuangtai, + orderDesc: card.jieshao, + }; + app.globalData.groupInfoMap[message.groupId] = meta; + persistGroupMeta(app, message.groupId, meta); + } + } + const { latestMessages } = app.globalData.messageManager; + latestMessages.unshift({ + id: message.messageId, + type: message.type, + senderId: message.senderId, + senderName: message.senderData?.name, + content: formatMessageForNotification(message), + timestamp: message.timestamp || Date.now(), + conversationId: message.conversationId, + }); + if (latestMessages.length > 50) latestMessages.length = 50; + wx.setStorageSync(app.globalData.messageManager.cacheKeys.latestMessages, latestMessages); +} + +function normalizeConversationPayload(result) { + if (!result) return null; + const content = result.content || result; + const list = content.conversations || result.conversations; + if (!list || !Array.isArray(list)) return null; + return { + conversations: list, + unreadTotal: content.unreadTotal ?? result.unreadTotal ?? 0, + }; +} + +function loadConversations(app) { + if (_loadConvTimer) clearTimeout(_loadConvTimer); + _loadConvTimer = setTimeout(() => { + _loadConvTimer = null; + loadConversationsNow(app); + }, 280); +} + +function loadConversationsNow(app) { + if (_loadConvInFlight) return; + if (!isSdkConnected() || !wx.goEasy?.im?.latestConversations) return; + _loadConvInFlight = true; + wx.goEasy.im.latestConversations({ + onSuccess: (result) => { + _loadConvInFlight = false; + const normalized = normalizeConversationPayload(result); + if (result.unreadTotal !== undefined) { + applyUnreadTotal(app, result.unreadTotal); + } else if (normalized) { + applyUnreadTotal(app, normalized.unreadTotal); + } + if (normalized && typeof app.emitEvent === 'function') { + app.emitEvent('conversationsUpdated', normalized); + } + const conversations = normalized?.conversations || []; + if (!app.globalData.groupInfoMap) app.globalData.groupInfoMap = {}; + conversations.forEach((c) => { + if (c.type === 'group' && c.groupId) { + app.globalData.groupInfoMap[c.groupId] = c.data || {}; + } + }); + _ensureGroupSubscriptions(app, conversations); + }, + onFailed: (error) => { + _loadConvInFlight = false; + console.error('加载会话列表失败:', error); + if (!isSdkConnected() && app.globalData.goEasyConnection.autoReconnect) { + attemptReconnect(app); + } + }, + }); +} + +function _ensureGroupSubscriptions(app, conversations) { + const groupIds = conversations + .filter((c) => c.type === 'group' && c.groupId) + .map((c) => c.groupId); + + if (groupIds.length === 0) return; + if (!wx.goEasy?.im || typeof wx.goEasy.im.subscribeGroup !== 'function') return; + + wx.goEasy.im.subscribeGroup({ + groupIds, + onSuccess: () => {}, + onFailed: (error) => console.error('全局订阅群组失败:', error), + }); +} + +function updateCurrentPageState(app) { + try { + 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 = chatPages.indexOf(currentPage.route) >= 0; + app.globalData.pageState.lastPageUpdate = Date.now(); + } + } catch (error) { + console.warn('更新页面状态失败:', error); + } +} + +function saveUserSettings(app) { + try { + wx.setStorageSync(app.globalData.messageManager.cacheKeys.messageSettings, { + soundEnabled: app.globalData.messageManager.soundEnabled, + vibrationEnabled: app.globalData.messageManager.vibrationEnabled, + doNotDisturb: app.globalData.messageManager.doNotDisturb, + doNotDisturbStart: app.globalData.messageManager.doNotDisturbStart, + doNotDisturbEnd: app.globalData.messageManager.doNotDisturbEnd, + notificationMuted: app.globalData.messageManager.notificationMuted, + notificationStyle: app.globalData.messageManager.notificationStyle, + }); + } catch (error) { + console.warn('保存用户设置失败:', error); + } +} + +function toggleDoNotDisturb(app, enabled) { + app.globalData.messageManager.doNotDisturb = enabled; + saveUserSettings(app); + wx.showToast({ title: `免打扰${enabled ? '开启' : '关闭'}`, icon: 'success', duration: 2000 }); +} + +function toggleNotificationMute(app, enabled) { + app.globalData.messageManager.notificationMuted = enabled; + wx.setStorageSync(app.globalData.messageManager.cacheKeys.notificationMuted, enabled); + saveUserSettings(app); + wx.showToast({ title: `通知${enabled ? '静音' : '开启'}`, icon: 'success', duration: 2000 }); +} + +function closeNotification(app) { + app.emitEvent('hideNotification'); +} + +function updateTabBarBadge(app, unreadCount) { + const { messageManager } = app.globalData; + if (!messageManager.showTabBarBadge) return; + let badgeText = ''; + if (unreadCount > 0) { + badgeText = unreadCount > 99 ? '99+' : unreadCount.toString(); + } + messageManager.tabBarBadgeText = badgeText; + app.emitEvent('tabBarBadgeChanged', { + index: messageManager.tabBarIndex, + badgeText, + showRedDot: unreadCount > 0, + forceUpdate: true, + }); + updateTabBarDirectly(app, badgeText); + saveTabBarBadgeToStorage(app, badgeText); +} + +function updateTabBarDirectly(app, badgeText) { + try { + const pages = getCurrentPages(); + for (let i = pages.length - 1; i >= 0; i--) { + const tabBar = pages[i].selectComponent('#custom-tab-bar'); + if (tabBar && tabBar.setData) { + tabBar.setData({ badgeText }); + break; + } + } + } catch (error) { console.warn('直接更新TabBar失败:', error); } +} + +function saveTabBarBadgeToStorage(app, badgeText) { + try { + wx.setStorageSync('tabBarMessageBadge', badgeText); + } catch (error) { console.warn('保存TabBar徽章失败:', error); } +} + +module.exports = { initGlobalMessageSystem }; diff --git a/utils/grab-order-gate.js b/utils/grab-order-gate.js new file mode 100644 index 0000000..5fc5799 --- /dev/null +++ b/utils/grab-order-gate.js @@ -0,0 +1,25 @@ +/** + * 抢单前检查(仅点击抢单时调用) + */ +import request from './request.js'; + +/** 检查是否有待处理罚款(待缴纳 / 申诉中) */ +export async function checkPenaltyForGrab() { + try { + const res = await request({ url: '/yonghu/cffktjhq', method: 'POST' }); + if (res?.data?.code === 0) { + const fk = res.data.data?.fakuan || {}; + const pending = Number(fk.daijiaona || 0) + Number(fk.shensuzhong || 0); + if (pending > 0) { + return { + blocked: true, + msg: '您有未缴纳或申诉中的罚单,请先处理后再抢单', + url: '/pages/penalty/penalty/penalty', + }; + } + } + } catch (e) { + console.error('checkPenaltyForGrab failed', e); + } + return { blocked: false }; +} diff --git a/utils/group-chat.js b/utils/group-chat.js new file mode 100644 index 0000000..f259133 --- /dev/null +++ b/utils/group-chat.js @@ -0,0 +1,241 @@ +/** + * 打手-商家/老板配对群:group_Ds{打手}_Sj{商家} + */ +export const ORDER_CARD_PREFIX = '[ORDER_CARD]'; + +const ZHUANGTAI_MAP = { + 1: '已下单', 2: '进行中', 3: '已完成', 4: '退款中', + 5: '已退款', 6: '退款失败', 7: '指定中', 8: '结算中', +}; + +export function getZhuangtaiText(zhuangtai) { + if (zhuangtai == null || zhuangtai === '') return ''; + const n = Number(zhuangtai); + return ZHUANGTAI_MAP[n] || ZHUANGTAI_MAP[zhuangtai] || ''; +} + +export function buildDashouShangjiaGroupId(dashouUid, shangjiaUid) { + return `group_Ds${dashouUid}_Sj${shangjiaUid}`; +} + +export function buildDashouBossGroupId(dashouUid, bossUid) { + return `group_Ds${dashouUid}_Boss${bossUid}`; +} + +export function resolveLocalGroupId(identityType, myUid, partnerUid, fadanPingtai, orderId, isCross) { + const cross = isCross === 1 || isCross === true || String(isCross) === '1'; + if (cross && orderId) { + return `group_${orderId}`; + } + if (!myUid) return orderId ? `group_${orderId}` : null; + if (!partnerUid) return orderId ? `group_${orderId}` : null; + if (identityType === 'shangjia') { + return buildDashouShangjiaGroupId(partnerUid, myUid); + } + if (identityType === 'dashou') { + if (fadanPingtai === 2 || String(fadanPingtai) === '2') { + return buildDashouShangjiaGroupId(myUid, partnerUid); + } + return buildDashouBossGroupId(myUid, partnerUid); + } + return orderId ? `group_${orderId}` : null; +} + +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; +} + +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 { + 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; + } +} + +import { getChatImageUrl } from '../static/lib/utils.js'; + +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; + } + } + if (msg.type === 'custom' && msg.payload) { + const inner = msg.payload.payload || msg.payload; + if (inner && (inner.orderId || inner.dingdan_id)) { + msg.type = 'order'; + msg.payload = { + orderId: inner.orderId || inner.dingdan_id, + jieshao: inner.jieshao || '', + jine: inner.jine || '', + beizhu: inner.beizhu || '', + zhuangtai: inner.zhuangtai, + nicheng: inner.nicheng || '', + create_time: inner.create_time || '', + }; + } + } + if (msg.type === 'image') { + const url = getChatImageUrl(msg); + msg.payload = { ...(msg.payload || {}), url }; + msg.imageUrl = url; + } + 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 = {}; + + 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/xiaoxilj.js b/utils/xiaoxilj.js index 8f330cc..e4eda3f 100644 --- a/utils/xiaoxilj.js +++ b/utils/xiaoxilj.js @@ -1,218 +1,269 @@ -/** - * 连接管理模块 - xiaoxilj.js (根治发送失败:跳转前确保群组已订阅) - * 从详情页跳转订单群聊时,提前订阅群组,避免发送消息时订阅未完成而失败 - */ -const app = getApp(); - -function waitForConnection(expectedUserId, timeout = 10000) { - return new Promise((resolve, reject) => { - const timer = setTimeout(() => { - app.off('connectionChanged', handler); - reject(new Error('连接超时')); - }, timeout); - - const handler = (event) => { - if (event.status === 'connected') { - if (event.userId === 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(); - clearTimeout(timer); - app.off('connectionChanged', handler); - reject(new Error('连接失败')); - } - }; - app.on('connectionChanged', handler); - }); -} - -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}`); - } - - wx.removeStorageSync('savedGoEasyConnection'); - wx.removeStorageSync('goEasyUserId'); - wx.removeStorageSync('currentGoEasyIdentity'); - 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; - - 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/chat/chat?data=' + encodeURIComponent(JSON.stringify(param)); - wx.navigateTo({ url: path }); - } catch (err) { - console.error('连接失败:', err); - wx.showToast({ title: err.message || '连接失败', icon: 'none' }); - throw err; - } - } - - async connectToGroupChat(params) { - const { identityType, userId, orderId, groupName, groupAvatar, isCross } = params; - if (!identityType || !userId || !orderId) { - 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}`); - } - - - // 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. 从会话列表查找群组;找不到则用订单号作为 groupId(首聊场景) - const orderIdStr = String(orderId); - let realGroupId = orderIdStr; - try { - const result = await new Promise((resolve, reject) => { - wx.goEasy.im.latestConversations({ - onSuccess: (res) => { - const conversations = res.content?.conversations || []; - const found = conversations.find((c) => { - if (c.type !== 'group') return false; - if (c.groupId === orderIdStr) return true; - const data = c.data || {}; - return String(data.orderId || '') === orderIdStr; - }); - resolve(found ? found.groupId : orderIdStr); - }, - onFailed: reject, - }); - }); - realGroupId = result || orderIdStr; - } catch (err) { - console.warn('获取会话列表失败,使用订单号作为群ID', err); - realGroupId = orderIdStr; - } - - // 4. 🔥 关键:主动订阅群组并等待成功,确保发送消息时订阅已完成 - try { - await new Promise((resolve, reject) => { - wx.goEasy.im.subscribeGroup({ - groupIds: [realGroupId], - onSuccess: () => { - resolve(); - }, - onFailed: (error) => { - console.error('订阅群组失败:', error); - reject(error); - } - }); - }); - } catch (err) { - wx.showToast({ title: '订阅群组失败', icon: 'none' }); - throw err; - } - - // 5. 跳转群聊页(参数与消息列表完全一致) - const param = { - groupId: realGroupId, - orderId: orderIdStr, - groupName: groupName || '订单群聊', - groupAvatar: groupAvatar || '', - isCross: isCross || 0, - currentUserId: userId, - currentUserName: app.globalData.currentUser?.name || `用户${uid}`, - currentUserAvatar: avatar, - }; - const path = '/pages/group-chat/group-chat?data=' + encodeURIComponent(JSON.stringify(param)); - wx.navigateTo({ url: path }); - } -} - -export default new ConnectionManager(); \ No newline at end of file +/** + * 订单群聊跳转:先确定群 ID,IM 连接失败也尽量能进聊天页 + */ +import request from './request'; +import { persistGroupMeta } from './im-user.js'; +import { resolveLocalGroupId } from './group-chat.js'; + +const app = getApp(); + +function formatError(err) { + if (!err) return '进入聊天失败'; + if (typeof err === 'string') return err; + if (err.message) return err.message; + if (err.content) return String(err.content); + if (err.msg) return String(err.msg); + if (err.code) return `连接失败(${err.code})`; + try { + return JSON.stringify(err); + } catch (e) { + return '进入聊天失败'; + } +} + +function withTimeout(promise, ms, errMsg) { + return new Promise((resolve, reject) => { + const timer = setTimeout(() => reject(new Error(errMsg || '操作超时')), ms); + promise + .then((v) => { clearTimeout(timer); resolve(v); }) + .catch((e) => { clearTimeout(timer); reject(e); }); + }); +} + +function getConnectedUserId() { + const fromState = app.globalData?.goEasyConnection?.userId; + const fromIm = wx.goEasy?.im?.userId; + return fromState || fromIm || ''; +} + +function isImConnected() { + const status = wx.goEasy?.getConnectionStatus?.() || 'disconnected'; + return status === 'connected' || status === 'reconnected'; +} + +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, + }; + + if (!app.globalData.chatEnabled || !wx.goEasy?.im) { + throw new Error('聊天服务未就绪,请稍后重试'); + } + + if (isImConnected() && getConnectedUserId() === userId) { + return; + } + + const connectedId = getConnectedUserId(); + if (connectedId && connectedId !== userId && app.disconnectGoEasy) { + await app.disconnectGoEasy(); + } + + if (!app.connectWithIdentity) { + throw new Error('聊天功能未初始化'); + } + + await withTimeout( + app.connectWithIdentity(identityType, userId, true), + 12000, + 'IM连接超时' + ); + + if (!isImConnected()) { + throw new Error('IM连接失败,请检查网络后重试'); + } +} + +async function prepareGroupChat(identityType, userId, orderId, partnerUid, fadanPingtai, groupName, isCross) { + let chatData = null; + + try { + const res = await withTimeout( + request({ + url: '/dingdan/ltdhzb', + method: 'POST', + data: { + dingdan_id: orderId, + identityType, + push_order_card: false, + }, + }), + 12000, + '准备群聊超时' + ); + const body = res?.data || {}; + if (body.code === 0 && body.data?.groupId) { + chatData = body.data; + } else if (body.msg) { + console.warn('ltdhzb:', body.msg); + } + } catch (e) { + console.warn('ltdhzb 请求失败,尝试本地 groupId', e); + } + + if (!chatData?.groupId) { + const myUid = userId.replace(/^(Ds|Sj|Boss)/, ''); + let localGroupId = resolveLocalGroupId( + identityType, myUid, partnerUid, fadanPingtai, orderId, isCross + ); + if (!localGroupId && orderId) { + localGroupId = `group_${orderId}`; + } + if (!localGroupId) { + throw new Error('无法确定群聊,请确认订单已接单且对方信息完整'); + } + chatData = { + groupId: localGroupId, + orderId, + groupName: groupName || '订单群聊', + isCross: isCross || fadanPingtai || 0, + }; + try { + await request({ + url: '/dingdan/ltdhzb', + method: 'POST', + data: { dingdan_id: orderId, identityType, push_order_card: false }, + }); + } catch (e) { /* ignore */ } + } + + return chatData; +} + +class ConnectionManager { + async connectAndChat(params) { + const { identityType, userId, targetUser } = params; + if (!identityType || !userId || !targetUser || !targetUser.id) { + throw new Error('参数不完整'); + } + + await ensureIdentityConnection(identityType, userId); + + 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 }; + wx.navigateTo({ url: '/pages/chat/chat?data=' + encodeURIComponent(JSON.stringify(param)) }); + } + + async connectToGroupChat(params) { + const { + identityType, userId, orderId, groupName, groupAvatar, isCross, + partnerUid, fadanPingtai, + } = params; + + if (!identityType || !userId || !orderId) { + throw new Error('参数不完整'); + } + + app.globalData.currentRole = identityType; + wx.setStorageSync('currentRole', identityType); + + wx.showLoading({ title: '建立联系中...', mask: true }); + + try { + const chatData = await prepareGroupChat( + identityType, userId, orderId, partnerUid, fadanPingtai || isCross, groupName, isCross + ); + + 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 = {}; + 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); + + let imReady = false; + try { + await ensureIdentityConnection(identityType, userId); + imReady = true; + try { + await withTimeout( + new Promise((resolve, reject) => { + wx.goEasy.im.subscribeGroup({ + groupIds: [realGroupId], + onSuccess: () => resolve(), + onFailed: (error) => reject(error), + }); + }), + 8000, + '订阅群聊超时' + ); + } catch (subErr) { + console.warn('订阅群聊失败,仍尝试进入页面', subErr); + } + } catch (imErr) { + console.warn('IM连接失败,仍进入聊天页由页面重连', imErr); + } + + const uid = userId.replace(/^(Ds|Sj|Boss)/, ''); + const param = { + groupId: realGroupId, + orderId: chatData.orderId || orderId, + groupName: chatData.counterpartName || chatData.groupName || groupName || '订单群聊', + groupAvatar: avatar, + isCross: chatData.isCross != null ? chatData.isCross : (isCross || 0), + currentUserId: userId, + currentUserName: app.globalData.currentUser?.name || `用户${uid}`, + currentUserAvatar: app.globalData.currentUser?.avatar || + (wx.getStorageSync('touxiang') || (app.globalData.ossImageUrl + app.globalData.morentouxiang)), + orderZhuangtai: chatData.orderZhuangtai, + orderJine: chatData.orderJine, + orderDesc: chatData.orderDesc || '', + imReady, + }; + + wx.hideLoading(); + wx.navigateTo({ + url: '/pages/group-chat/group-chat?data=' + encodeURIComponent(JSON.stringify(param)), + fail: (navErr) => { + wx.showToast({ title: formatError(navErr), icon: 'none', duration: 2500 }); + }, + }); + } catch (err) { + wx.hideLoading(); + console.error('跳转群聊失败:', err); + wx.showToast({ title: formatError(err), icon: 'none', duration: 2500 }); + throw err; + } + } +} + +export default new ConnectionManager();