优化聊天:配对群组、稳定连接、消息列表与订单详情跳转

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-06-23 22:48:42 +08:00
parent 5e90f7c87c
commit 1ea106f101
8 changed files with 365 additions and 365 deletions

View File

@@ -1,6 +1,8 @@
const app = getApp();
import { formatDate } from '../../static/lib/utils';
import { sendGroupMessage } from '../../utils/message-sender';
import { showConfirmByScene } from '../../utils/scriptService.js';
import { normalizeGroupMessage } from '../../utils/group-chat.js';
Page({
data: {
@@ -89,33 +91,45 @@ Page({
},*/
onShow() {
this.autoConnect();
this.ensureChatConnection();
},
onHide() {
this.clearAllListeners();
this.clearPageListeners();
},
autoConnect() {
ensureChatConnection() {
const role = app.globalData.currentRole || 'normal';
const uid = wx.getStorageSync('uid');
const prefixMap = { normal:'Boss', dashou:'Ds', shangjia:'Sj', guanshi:'Gs', zuzhang:'Zz' };
const prefix = prefixMap[role] || 'Boss';
const targetUserId = prefix + uid;
const targetUserId = (prefixMap[role] || 'Boss') + uid;
const status = wx.goEasy.getConnectionStatus ? wx.goEasy.getConnectionStatus() : 'disconnected';
if (status === 'connected' || status === 'reconnected') {
const currentUserId = wx.goEasy.im ? wx.goEasy.im.userId : null;
if (currentUserId === targetUserId) {
this.subscribeGroupIfNeeded();
this.setupAllListeners();
this.loadHistory(true);
return;
} else {
wx.goEasy.disconnect();
}
const onReady = () => {
this.subscribeGroupIfNeeded();
this.setupAllListeners();
this.loadHistory(true).then(() => this.markGroupMessageAsRead());
};
const status = wx.goEasy?.getConnectionStatus?.() || 'disconnected';
const currentUserId = wx.goEasy?.im?.userId;
if ((status === 'connected' || status === 'reconnected') && currentUserId === targetUserId) {
onReady();
return;
}
this.connectGoEasy(targetUserId);
if (app.connectWithIdentity) {
app.connectWithIdentity(role, targetUserId, true).then(onReady).catch(() => {
const s = wx.goEasy?.getConnectionStatus?.();
if (s === 'connected' || s === 'reconnected') onReady();
});
} else {
this.connectGoEasy(targetUserId);
}
},
clearPageListeners() {
this.clearAllListeners();
},
connectGoEasy(userId) {
@@ -130,10 +144,7 @@ Page({
onSuccess: () => {
this.subscribeGroupIfNeeded();
this.setupAllListeners();
this.loadHistory(true).then(()=>{
this.markGroupMessageAsRead
});
this.loadHistory(true).then(() => this.markGroupMessageAsRead());
},
onFailed: (error) => {
if (error.code === 408) {
@@ -213,6 +224,7 @@ Page({
let list = res.content || [];
list.sort((a, b) => a.timestamp - b.timestamp);
list.forEach((m, idx) => {
normalizeGroupMessage(m);
m.formattedTime = formatDate(m.timestamp);
if (idx === 0) m.showTime = true;
else m.showTime = (m.timestamp - list[idx-1].timestamp) / 60000 > 5;
@@ -248,6 +260,7 @@ Page({
this._msgHandler = (msg) => {
if (msg.groupId !== this.data.groupId) return;
if (msg.senderId === this.data.currentUser.id) return;
normalizeGroupMessage(msg);
msg.formattedTime = formatDate(msg.timestamp);
const msgs = this.data.messages;
const last = msgs.length ? msgs[msgs.length-1] : null;
@@ -301,8 +314,11 @@ Page({
sendMessage() {
if (this.data.pendingImage) {
this.sendImageMsg(this.data.pendingImage);
this.setData({ pendingImage: '' });
const file = this.data.pendingImage;
showConfirmByScene('chat_image_confirm', () => {
this.sendImageMsg(file);
this.setData({ pendingImage: '' });
});
return;
}
const text = this.data.inputText.trim();

View File

@@ -1,6 +1,7 @@
// pages/sjddxq/sjddxq.js - 商家订单详情(完整版,已添加罚款/退款操作及教程按钮)
const app = getApp()
import request from '../../utils/request.js'
import connectionManager from '../../utils/xiaoxilj.js'
import PopupService from '../../services/popupService.js' // 新增:导入弹窗服务
Page({
@@ -94,55 +95,17 @@ Page({
ensureIMConnection() {
const uid = wx.getStorageSync('uid')
if (!uid) return
app.globalData.currentRole = 'shangjia'
wx.setStorageSync('currentRole', 'shangjia')
const targetUserId = 'Sj' + uid
if (wx.goEasy && wx.goEasy.getConnectionStatus) {
const status = wx.goEasy.getConnectionStatus()
if (status === 'connected' || status === 'reconnected') {
const currentUserId = wx.goEasy.im ? wx.goEasy.im.userId : null
if (currentUserId === targetUserId) {
this.setData({ currentIMUserId: targetUserId })
return
} else {
wx.goEasy.disconnect()
}
}
const status = wx.goEasy?.getConnectionStatus?.() || 'disconnected'
const currentUserId = wx.goEasy?.im?.userId
if ((status === 'connected' || status === 'reconnected') && currentUserId === targetUserId) {
this.setData({ currentIMUserId: targetUserId })
return
}
this.connectGoEasy(targetUserId)
},
connectGoEasy(userId) {
let currentUser = app.globalData.currentUser
if (!currentUser) {
const uid = wx.getStorageSync('uid')
currentUser = {
id: uid,
name: '用户' + (uid ? uid.substring(0, 6) : ''),
avatar: app.globalData.ossImageUrl + app.globalData.morentouxiang
}
}
wx.goEasy.connect({
id: userId,
data: {
name: currentUser.name,
avatar: currentUser.avatar || (app.globalData.ossImageUrl + app.globalData.morentouxiang)
},
onSuccess: () => {
this.setData({ currentIMUserId: userId })
try {
wx.setStorageSync('savedGoEasyConnection', JSON.stringify({
userId, identityType: 'shangjia', lastConnectTime: Date.now()
}))
wx.setStorageSync('goEasyUserId', userId)
} catch (e) {}
},
onFailed: (error) => {
if (error.code === 408) {
this.setData({ currentIMUserId: userId })
} else {
console.error('IM连接失败', error)
}
}
})
if (app.ensureConnection) app.ensureConnection()
this.setData({ currentIMUserId: targetUserId })
},
jiexiTiaozhuanCanshu(options) {
@@ -265,7 +228,7 @@ Page({
})
},
// ===== 联系打手(替打手订阅 + 发机器人消息 + 跳转) =====
// ===== 联系打手(后端准备配对群 + 稳定跳转) =====
goToChatWithDashou() {
const uid = wx.getStorageSync('uid')
if (!uid) return
@@ -276,110 +239,14 @@ Page({
return
}
const targetUserId = 'Sj' + uid
if (this.data.currentIMUserId !== targetUserId) {
this.ensureIMConnection()
wx.showToast({ title: '连接中,请稍后再试', icon: 'none' })
return
}
const that = this
const groupName = this.data.jibenShuju.nicheng + '的订单群'
const isCross = this.data.jibenShuju.fadanpingtai || 0
const dashouUid = this.data.dashouInfo.yonghuid
// 第一步:商家自己订阅群组
wx.goEasy.im.subscribeGroup({
groupIds: [orderId],
onSuccess: () => {
// 如果有打手,则需要帮打手订阅并发送消息
if (dashouUid) {
that.bangDashouDingyue(orderId, dashouUid)
.then(() => {
that.tiaozhuanQunLiao(orderId, groupName, isCross)
})
.catch((err) => {
console.error('替打手订阅失败,仍可跳转群聊', err)
wx.showToast({ title: '通知打手可能失败', icon: 'none' })
that.tiaozhuanQunLiao(orderId, groupName, isCross)
})
} else {
// 无打手,直接跳转
that.tiaozhuanQunLiao(orderId, groupName, isCross)
}
},
onFailed: (error) => {
console.error('订阅群组失败', error)
wx.showToast({ title: '连接群聊失败', icon: 'none' })
}
})
},
// 帮打手订阅群组 + 发送机器人消息
bangDashouDingyue(orderId, dashouUid) {
return new Promise((resolve, reject) => {
const dashouImId = 'Ds' + dashouUid
const appkey = 'BC-32e2991c69bc47e1b538e73aaed37fc1' // 你的GoEasy AppKey
const restHost = 'https://rest-hz.goeasy.io'
// 1. 通过 REST API 为打手订阅群组
wx.request({
url: restHost + '/v2/im/subscribe-groups',
method: 'POST',
header: { 'content-type': 'application/json' },
data: {
appkey: appkey,
userIds: [dashouImId],
groupIds: [orderId]
},
success: (res) => {
if (res.data.code === 200) {
// 2. 订阅成功后,以打手身份发送一条系统消息
wx.request({
url: restHost + '/v2/im/message',
method: 'POST',
header: { 'content-type': 'application/json' },
data: {
appkey: appkey,
senderId: dashouImId,
senderData: { name: '系统通知', avatar: '' },
to: {
type: 'group',
id: orderId,
data: { name: '订单群', avatar: '' }
},
type: 'text',
payload: '此消息为系统自动发送,打手看到后会尽快回复您。'
},
success: (msgRes) => {
if (msgRes.data.code === 200) {
resolve()
} else {
reject(new Error('发送消息失败'))
}
},
fail: reject
})
} else {
reject(new Error('订阅打手失败'))
}
},
fail: reject
})
})
},
// 跳转群聊页面
tiaozhuanQunLiao(orderId, groupName, isCross) {
const param = {
groupId: orderId,
orderId: orderId,
groupName: groupName,
groupAvatar: '',
isCross: isCross
}
wx.navigateTo({
url: '/pages/qunliaotian/qunliaotian?data=' + encodeURIComponent(JSON.stringify(param))
connectionManager.connectToGroupChat({
identityType: 'shangjia',
userId: 'Sj' + uid,
orderId,
groupName: (this.data.dashouInfo.nicheng || this.data.jibenShuju.nicheng) + '的订单群',
isCross: this.data.jibenShuju.fadanpingtai || 0
}).catch(err => {
console.error('跳转群聊失败', err)
})
},

View File

@@ -2,6 +2,7 @@
const app = getApp();
import { formatDate } from '../../static/lib/utils';
const { jianquanxian } = require('../../utils/imAuth/jianquanxian');
import { enrichGroupConversation, isOrderGroupConversation } from '../../utils/group-chat.js';
Page({
data: {
@@ -9,7 +10,7 @@ Page({
filteredConversations: [],
activeTab: 0,
tabUnread: [0, 0, 0],
displayCount: 10,
displayCount: 4,
searchKeyword: '',
actionPopup: { visible: false, conversation: null },
currentUser: null,
@@ -81,26 +82,42 @@ Page({
return true;
},
// ========== 自动连接 ==========
// ========== 自动连接(复用全局连接,避免反复 disconnect ==========
autoConnect() {
const role = app.globalData.currentRole || 'normal';
const uid = wx.getStorageSync('uid');
const prefixMap = { normal: 'Boss', dashou: 'Ds', shangjia: 'Sj', guanshi: 'Gs', zuzhang: 'Zz' };
const prefix = prefixMap[role] || 'Boss';
const targetUserId = prefix + uid;
const targetUserId = (prefixMap[role] || 'Boss') + uid;
const status = wx.goEasy.getConnectionStatus ? wx.goEasy.getConnectionStatus() : 'disconnected';
if (status === 'connected' || status === 'reconnected') {
const currentUserId = wx.goEasy.im ? wx.goEasy.im.userId : null;
if (currentUserId === targetUserId) {
this.setupConversationListener();
this.loadConversations();
return;
} else {
wx.goEasy.disconnect();
}
const onReady = () => {
this.setupConversationListener();
this.loadConversations();
};
const status = wx.goEasy?.getConnectionStatus?.() || 'disconnected';
const currentUserId = wx.goEasy?.im?.userId;
if ((status === 'connected' || status === 'reconnected') && currentUserId === targetUserId) {
onReady();
return;
}
this.connectGoEasy(targetUserId);
if (currentUserId && currentUserId !== targetUserId && app.disconnectGoEasy) {
app.disconnectGoEasy().then(() => {
app.connectWithIdentity(role, targetUserId, true).then(onReady).catch(() => {});
});
return;
}
if (!app.connectWithIdentity) {
this.connectGoEasy(targetUserId);
return;
}
app.connectWithIdentity(role, targetUserId, true).then(onReady).catch(() => {
const s = wx.goEasy?.getConnectionStatus?.();
if (s === 'connected' || s === 'reconnected') onReady();
});
},
connectGoEasy(userId) {
@@ -168,12 +185,27 @@ Page({
}
let list = content.conversations;
const defaultAvatar = app.globalData.ossImageUrl + app.globalData.morentouxiang;
const myGoEasyId = wx.goEasy?.im?.userId || '';
const ossBase = app.globalData.ossImageUrl || '';
const groupMeta = app.globalData.groupInfoMap || {};
list.forEach(item => {
if (item.data && item.data.avatar) {
if (!item.data.avatar.startsWith('http')) {
item.data.avatar = app.globalData.ossImageUrl + item.data.avatar;
if (item.type === 'group') {
const meta = groupMeta[item.groupId];
if (meta) {
if (!item.data) item.data = {};
if (meta.name) item.data.name = meta.name;
if (meta.avatar) item.data.avatar = meta.avatar;
if (meta.orderDesc) item.data.orderDesc = meta.orderDesc;
if (meta.orderId) item.data.orderId = meta.orderId;
if (meta.orderZhuangtai != null) item.data.orderZhuangtai = meta.orderZhuangtai;
}
} else {
enrichGroupConversation(item, myGoEasyId, defaultAvatar, ossBase);
} else if (item.data && item.data.avatar) {
if (!item.data.avatar.startsWith('http')) {
item.data.avatar = ossBase + item.data.avatar;
}
} else if (item.data) {
item.data.avatar = defaultAvatar;
}
if (item.lastMessage && item.lastMessage.timestamp) {
@@ -189,7 +221,7 @@ Page({
return (b.lastMessage.timestamp || 0) - (a.lastMessage.timestamp || 0);
});
this.setData({ allConversations: list, displayCount: 10 }, () => {
this.setData({ allConversations: list, displayCount: 4 }, () => {
this.applyFilter();
});
@@ -234,7 +266,7 @@ Page({
const { allConversations, activeTab, searchKeyword } = this.data;
let filtered = [];
if (activeTab === 0) {
filtered = allConversations.filter(c => c.type === 'group' && c.data && c.data.orderId);
filtered = allConversations.filter(c => isOrderGroupConversation(c));
} else if (activeTab === 1) {
filtered = allConversations.filter(c => c.type === 'private');
} else if (activeTab === 2) {
@@ -253,7 +285,7 @@ Page({
const tabUnread = [0, 0, 0];
allConversations.forEach(c => {
if (c.type === 'group' && c.data && c.data.orderId) tabUnread[0] += c.unread || 0;
if (isOrderGroupConversation(c)) tabUnread[0] += c.unread || 0;
else if (c.type === 'private') tabUnread[1] += c.unread || 0;
else if (c.type === 'cs') tabUnread[2] += c.unread || 0;
});
@@ -263,17 +295,17 @@ Page({
switchTab(e) {
const index = parseInt(e.currentTarget.dataset.index);
this.setData({ activeTab: index, displayCount: 10, searchKeyword: '' }, () => this.applyFilter());
this.setData({ activeTab: index, displayCount: 4, searchKeyword: '' }, () => this.applyFilter());
},
onSearchInput(e) {
this.setData({ searchKeyword: e.detail.value, displayCount: 10 }, () => this.applyFilter());
this.setData({ searchKeyword: e.detail.value, displayCount: 4 }, () => this.applyFilter());
},
onScrollToLower() {
const { displayCount, filteredConversations } = this.data;
if (displayCount < filteredConversations.length) {
this.setData({ displayCount: Math.min(displayCount + 10, filteredConversations.length) });
this.setData({ displayCount: Math.min(displayCount + 4, filteredConversations.length) });
}
},

View File

@@ -29,14 +29,25 @@
<!-- 会话列表 (上拉加载更多) -->
<scroll-view class="conversation-list" scroll-y="true" bindscrolltolower="onScrollToLower" style="height: {{scrollHeight}}px">
<block wx:for="{{filteredConversations}}" wx:key="key" wx:for-index="i" wx:if="{{i < displayCount}}">
<view class="conversation-item {{item.unread > 0 ? 'has-unread' : ''}}" data-conversation="{{item}}" bindtap="chat" bindlongpress="showAction">
<image class="avatar" src="{{item.data.avatar}}" mode="aspectFill" />
<view class="conversation-item {{item.unread > 0 ? 'has-unread' : ''}} {{item.type === 'group' ? 'order-group-item' : ''}}" data-conversation="{{item}}" bindtap="chat" bindlongpress="showAction">
<image class="avatar {{item.type === 'group' ? 'avatar-round' : ''}}" src="{{item.data.avatar}}" mode="aspectFill" />
<view class="info">
<view class="top-line">
<text class="name">{{item.data.name}}</text>
<text class="time">{{item.lastMessage.date}}</text>
</view>
<view class="bottom-line">
<block wx:if="{{item.type === 'group'}}">
<view class="order-block" wx:if="{{item.data.orderId || item.data.orderDesc}}">
<text class="order-id" wx:if="{{item.data.orderId}}">单号 {{item.data.orderId}}</text>
<text class="order-desc" wx:if="{{item.data.orderDesc}}">{{item.data.orderDesc}}</text>
<text class="order-status" wx:if="{{item.data.orderZhuangtaiText}}">{{item.data.orderZhuangtaiText}}</text>
</view>
<view class="bottom-line">
<text class="last-msg">{{item.displayLastMsg || item.lastMessage.payload.text}}</text>
<view class="unread-dot" wx:if="{{item.unread > 0}}">{{item.unread > 99 ? '99+' : item.unread}}</view>
</view>
</block>
<view class="bottom-line" wx:else>
<text class="last-msg">{{item.lastMessage.type === 'text' ? item.lastMessage.payload.text : '[其他消息]'}}</text>
<view class="unread-dot" wx:if="{{item.unread > 0}}">{{item.unread > 99 ? '99+' : item.unread}}</view>
</view>

View File

@@ -14,13 +14,19 @@
.tab-badge { background: #fa5151; color: #fff; font-size: 20rpx; border-radius: 20rpx; padding: 2rpx 12rpx; margin-left: 8rpx; min-width: 32rpx; text-align: center; }
.conversation-list { background: #fff; }
.conversation-item { display: flex; align-items: center; padding: 20rpx 24rpx; border-bottom: 1rpx solid #f0f0f0; }
.conversation-item { display: flex; align-items: flex-start; padding: 24rpx 24rpx; border-bottom: 1rpx solid #f0f0f0; }
.order-group-item { min-height: 160rpx; padding: 28rpx 24rpx; }
.has-unread { background: #fafafa; }
.avatar { width: 96rpx; height: 96rpx; border-radius: 12rpx; margin-right: 20rpx; background: #eee; }
.info { flex: 1; }
.avatar { width: 96rpx; height: 96rpx; border-radius: 12rpx; margin-right: 20rpx; background: #eee; flex-shrink: 0; }
.avatar-round { border-radius: 50%; }
.info { flex: 1; min-width: 0; }
.top-line { display: flex; justify-content: space-between; margin-bottom: 8rpx; }
.name { font-size: 30rpx; color: #1a1a1a; font-weight: 500; }
.time { font-size: 22rpx; color: #999; }
.time { font-size: 22rpx; color: #999; flex-shrink: 0; margin-left: 12rpx; }
.order-block { background: #f7f8fa; border-radius: 12rpx; padding: 16rpx 18rpx; margin-bottom: 10rpx; }
.order-id { display: block; font-size: 24rpx; color: #576b95; margin-bottom: 6rpx; }
.order-desc { display: block; font-size: 26rpx; color: #333; line-height: 1.5; word-break: break-all; }
.order-status { display: inline-block; font-size: 22rpx; color: #07c160; margin-top: 8rpx; padding: 2rpx 12rpx; background: #e8f8ef; border-radius: 8rpx; }
.bottom-line { display: flex; justify-content: space-between; align-items: center; }
.last-msg { font-size: 26rpx; color: #888; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.unread-dot { background: #fa5151; color: #fff; font-size: 20rpx; border-radius: 50%; min-width: 36rpx; height: 36rpx; line-height: 36rpx; text-align: center; margin-left: 12rpx; }

View File

@@ -131,7 +131,9 @@ function ensureConnection(app) {
function getSavedConnection(app) {
try {
const saved = wx.getStorageSync(app.globalData.goEasyConnection.cacheKeys.savedConnection);
return saved ? JSON.parse(saved) : null;
if (!saved) return null;
if (typeof saved === 'string') return JSON.parse(saved);
return saved;
} catch (error) {
console.error('获取保存的连接信息失败:', error);
return null;
@@ -583,8 +585,9 @@ function updateCurrentPageState(app) {
const pages = getCurrentPages();
if (pages.length > 0) {
const currentPage = pages[pages.length - 1];
const chatPages = ['pages/liaotian/liaotian', 'pages/qunliaotian/qunliaotian', 'pages/kefuliaotian/kefuliaotian'];
app.globalData.pageState.currentPage = currentPage.route;
app.globalData.pageState.isInChatPage = currentPage.route === 'pages/liaotian/liaotian';
app.globalData.pageState.isInChatPage = chatPages.indexOf(currentPage.route) >= 0;
app.globalData.pageState.lastPageUpdate = Date.now();
}
} catch (error) {

112
utils/group-chat.js Normal file
View File

@@ -0,0 +1,112 @@
/**
* 打手-商家/老板配对群group_Ds{打手}_Sj{商家} 或 group_Ds{打手}_Boss{老板}
*/
export const ORDER_CARD_PREFIX = '[ORDER_CARD]';
const ZHUANGTAI_MAP = {
1: '已下单', 2: '进行中', 3: '已完成', 4: '退款中',
5: '已退款', 6: '退款失败', 7: '指定中', 8: '结算中',
};
export function isPairGroupId(groupId) {
return /^group_Ds\w+_(Sj|Boss)\w+$/.test(groupId || '');
}
export function parsePairGroupId(groupId) {
if (!groupId) return null;
let m = /^group_Ds(\w+)_Sj(\w+)$/.exec(groupId);
if (m) return { dashouId: `Ds${m[1]}`, partnerId: `Sj${m[2]}`, partnerRole: 'shangjia' };
m = /^group_Ds(\w+)_Boss(\w+)$/.exec(groupId);
if (m) return { dashouId: `Ds${m[1]}`, partnerId: `Boss${m[2]}`, partnerRole: 'boss' };
return null;
}
export function getCounterpartGoEasyId(groupId, myGoEasyId) {
const parsed = parsePairGroupId(groupId);
if (!parsed || !myGoEasyId) return null;
if (myGoEasyId === parsed.dashouId) return parsed.partnerId;
if (myGoEasyId === parsed.partnerId) return parsed.dashouId;
return null;
}
export function parseOrderCardText(text) {
if (!text || !text.startsWith(ORDER_CARD_PREFIX)) return null;
try {
const data = JSON.parse(text.slice(ORDER_CARD_PREFIX.length));
return {
orderId: data.dingdan_id,
jieshao: data.jieshao || '',
jine: data.jine || '',
beizhu: data.beizhu || '',
zhuangtai: data.zhuangtai,
nicheng: data.nicheng || '',
create_time: data.create_time || '',
};
} catch (e) {
return null;
}
}
export function normalizeGroupMessage(msg) {
if (!msg) return msg;
if (msg.type === 'text' && msg.payload && msg.payload.text) {
const card = parseOrderCardText(msg.payload.text);
if (card) {
msg.type = 'order';
msg.payload = card;
}
}
return msg;
}
export function formatLastMessagePreview(msg) {
if (!msg) return '';
if (msg.type === 'text' && msg.payload && msg.payload.text) {
const card = parseOrderCardText(msg.payload.text);
if (card) return `[订单] ${card.jieshao || card.orderId}`;
return msg.payload.text;
}
if (msg.type === 'order' && msg.payload) {
return `[订单] ${msg.payload.jieshao || msg.payload.orderId || ''}`;
}
if (msg.type === 'image') return '[图片]';
return '[消息]';
}
export function enrichGroupConversation(item, myGoEasyId, defaultAvatar, ossBase) {
if (!item || item.type !== 'group') return item;
if (!item.data) item.data = {};
const counterpartId = getCounterpartGoEasyId(item.groupId, myGoEasyId);
if (counterpartId) {
item.data.counterpartId = counterpartId;
if (!item.data.name || item.data.name === item.groupId) {
item.data.name = `用户${counterpartId.replace(/^(Ds|Sj|Boss)/, '').slice(-6)}`;
}
}
if (item.lastMessage && item.lastMessage.senderId && item.lastMessage.senderId !== myGoEasyId) {
const sd = item.lastMessage.senderData || {};
if (sd.avatar) item.data.avatar = sd.avatar;
if (sd.name) item.data.name = sd.name;
}
let avatar = item.data.avatar || '';
if (avatar && !avatar.startsWith('http')) {
avatar = (ossBase || '') + avatar.replace(/^\//, '');
}
item.data.avatar = avatar || defaultAvatar;
if (item.data.orderZhuangtai != null) {
item.data.orderZhuangtaiText = ZHUANGTAI_MAP[item.data.orderZhuangtai] || '';
}
item.displayLastMsg = formatLastMessagePreview(item.lastMessage);
return item;
}
export function isOrderGroupConversation(c) {
if (!c || c.type !== 'group') return false;
if (c.data && c.data.orderId) return true;
return isPairGroupId(c.groupId) || /^group_\w+$/.test(c.groupId || '');
}

View File

@@ -1,11 +1,18 @@
/**
* 连接管理模块 - xiaoxilj.js (根治发送失败:跳转前确保群组已订阅)
* 从详情页跳转订单群聊时,提前订阅群组,避免发送消息时订阅未完成而失败
* 连接管理模块 - 订单群聊跳转(后端准备群 + 稳定连接)
*/
import request from './request';
const app = getApp();
function waitForConnection(expectedUserId, timeout = 10000) {
function waitForConnection(expectedUserId, timeout = 15000) {
return new Promise((resolve, reject) => {
const status = wx.goEasy?.getConnectionStatus?.();
const currentUserId = wx.goEasy?.im?.userId;
if ((status === 'connected' || status === 'reconnected') && currentUserId === expectedUserId) {
resolve();
return;
}
const timer = setTimeout(() => {
app.off('connectionChanged', handler);
reject(new Error('连接超时'));
@@ -13,87 +20,76 @@ function waitForConnection(expectedUserId, timeout = 10000) {
const handler = (event) => {
if (event.status === 'connected') {
if (event.userId === expectedUserId) {
const uid = event.userId || wx.goEasy?.im?.userId;
if (uid === expectedUserId) {
clearTimeout(timer);
app.off('connectionChanged', handler);
resolve();
} else {
console.error(`连接身份不匹配: 期望 ${expectedUserId}, 实际 ${event.userId}`);
wx.removeStorageSync('savedGoEasyConnection');
wx.removeStorageSync('goEasyUserId');
wx.removeStorageSync('currentGoEasyIdentity');
if (app.clearSavedConnection) app.clearSavedConnection();
clearTimeout(timer);
app.off('connectionChanged', handler);
reject(new Error(`连接身份不匹配: 期望 ${expectedUserId}, 实际 ${event.userId}`));
}
} else if (event.status === 'disconnected' && !event.manual) {
wx.removeStorageSync('savedGoEasyConnection');
wx.removeStorageSync('goEasyUserId');
wx.removeStorageSync('currentGoEasyIdentity');
if (app.clearSavedConnection) app.clearSavedConnection();
} else if (event.status === 'disconnected' && event.manual) {
clearTimeout(timer);
app.off('connectionChanged', handler);
reject(new Error('连接失败'));
reject(new Error('连接已断开'));
}
};
app.on('connectionChanged', handler);
});
}
async function ensureIdentityConnection(identityType, userId) {
app.globalData.currentRole = identityType;
wx.setStorageSync('currentRole', identityType);
const uid = userId.replace(/^(Ds|Sj|Boss|Gs|Zz|Kh)/, '');
const avatar = wx.getStorageSync('touxiang') ||
(app.globalData.ossImageUrl + app.globalData.morentouxiang);
app.globalData.currentUser = {
id: userId,
name: app.globalData.currentUser?.name || `用户${uid}`,
avatar: avatar,
};
const status = wx.goEasy?.getConnectionStatus?.() || 'disconnected';
const currentUserId = wx.goEasy?.im?.userId;
if ((status === 'connected' || status === 'reconnected') && currentUserId === userId) {
return;
}
if (currentUserId && currentUserId !== userId && app.disconnectGoEasy) {
await app.disconnectGoEasy();
}
const waitPromise = waitForConnection(userId);
const connectPromise = app.connectWithIdentity(identityType, userId, true);
await waitPromise;
await connectPromise;
}
class ConnectionManager {
// 原有的私聊方法保持不变
async connectAndChat(params) {
const { identityType, userId, targetUser } = params;
if (!identityType || !userId || !targetUser || !targetUser.id) {
throw new Error('参数不完整');
}
if (identityType === 'dashou' && !userId.startsWith('Ds')) {
throw new Error(`打手身份的用户ID必须以Ds开头实际为 ${userId}`);
}
if (identityType === 'shangjia' && !userId.startsWith('Sj')) {
throw new Error(`商家身份的用户ID必须以Sj开头实际为 ${userId}`);
}
if (identityType === 'boss' && !userId.startsWith('Boss')) {
throw new Error(`老板身份的用户ID必须以Boss开头实际为 ${userId}`);
}
await ensureIdentityConnection(identityType, userId);
wx.removeStorageSync('savedGoEasyConnection');
wx.removeStorageSync('goEasyUserId');
wx.removeStorageSync('currentGoEasyIdentity');
if (app.clearSavedConnection) app.clearSavedConnection();
const currentUser = {
id: userId,
name: app.globalData.currentUser?.name || '用户',
avatar: app.globalData.currentUser?.avatar || (app.globalData.ossImageUrl + app.globalData.morentouxiang),
};
if (wx.goEasy && wx.goEasy.getConnectionStatus() === 'connected') {
wx.goEasy.disconnect();
}
const to = {
id: targetUser.id,
name: targetUser.name || '用户',
avatar: targetUser.avatar || (app.globalData.ossImageUrl + app.globalData.morentouxiang),
};
const waitPromise = waitForConnection(userId);
app.connectWithIdentity(identityType, userId, false);
try {
await waitPromise;
const currentUser = {
id: userId,
name: app.globalData.currentUser?.name || '用户',
avatar: app.globalData.currentUser?.avatar || (app.globalData.ossImageUrl + app.globalData.morentouxiang)
};
const to = {
id: targetUser.id,
name: targetUser.name || '用户',
avatar: targetUser.avatar || (app.globalData.ossImageUrl + app.globalData.morentouxiang)
};
const param = { to, currentUser };
const path = '/pages/liaotian/liaotian?data=' + encodeURIComponent(JSON.stringify(param));
wx.navigateTo({ url: path });
} catch (err) {
console.error('连接失败:', err);
wx.showToast({ title: err.message || '连接失败', icon: 'none' });
throw err;
}
const param = { to, currentUser };
const path = '/pages/liaotian/liaotian?data=' + encodeURIComponent(JSON.stringify(param));
wx.navigateTo({ url: path });
}
async connectToGroupChat(params) {
@@ -102,113 +98,70 @@ class ConnectionManager {
throw new Error('参数不完整identityType, userId, orderId 必填');
}
// 校验 userId 前缀
if (identityType === 'dashou' && !userId.startsWith('Ds')) {
throw new Error(`打手身份的用户ID必须以Ds开头实际为 ${userId}`);
}
if (identityType === 'shangjia' && !userId.startsWith('Sj')) {
throw new Error(`商家身份的用户ID必须以Sj开头实际为 ${userId}`);
}
if (identityType === 'boss' && !userId.startsWith('Boss')) {
throw new Error(`老板身份的用户ID必须以Boss开头实际为 ${userId}`);
}
wx.showLoading({ title: '建立联系中...', mask: true });
// 1. 同步全局角色,确保群聊页初始化正确
app.globalData.currentRole = identityType;
wx.setStorageSync('currentRole', identityType);
const uid = userId.replace(/^(Ds|Sj|Boss|Gs|Zz)/, '');
const avatar = wx.getStorageSync('touxiang') ||
(app.globalData.ossImageUrl + app.globalData.morentouxiang);
app.globalData.currentUser = {
id: userId,
name: app.globalData.currentUser?.name || `用户${uid}`,
avatar: avatar
};
// 2. 确保 GoEasy 已使用目标身份连接
let needConnect = false;
if (!wx.goEasy || !wx.goEasy.getConnectionStatus || wx.goEasy.getConnectionStatus() !== 'connected') {
needConnect = true;
} else {
const currentUserId = wx.goEasy.im?.userId;
if (currentUserId !== userId) {
needConnect = true;
}
}
if (needConnect) {
wx.removeStorageSync('savedGoEasyConnection');
wx.removeStorageSync('goEasyUserId');
if (app.clearSavedConnection) app.clearSavedConnection();
if (wx.goEasy && wx.goEasy.getConnectionStatus() === 'connected') {
wx.goEasy.disconnect();
}
const waitPromise = waitForConnection(userId);
app.connectWithIdentity(identityType, userId, false);
try {
await waitPromise;
} catch (err) {
wx.showToast({ title: '连接失败', icon: 'none' });
throw err;
}
}
// 3. 从会话列表查找真实群组ID
let realGroupId = null;
try {
const result = await new Promise((resolve, reject) => {
wx.goEasy.im.latestConversations({
onSuccess: (res) => {
const conversations = res.content?.conversations || [];
const found = conversations.find(c => c.type === 'group' && c.data && c.data.orderId === orderId);
resolve(found ? found.groupId : null);
},
onFailed: reject
});
await ensureIdentityConnection(identityType, userId);
const res = await request({
url: '/dingdan/ltdhzb',
method: 'POST',
data: {
dingdan_id: orderId,
identityType,
push_order_card: true,
},
});
realGroupId = result;
} catch (err) {
console.error('获取会话列表失败:', err);
wx.showToast({ title: '获取群聊信息失败', icon: 'none' });
throw err;
}
if (!realGroupId) {
wx.showToast({ title: '暂未创建群聊', icon: 'none' });
return;
}
const body = res?.data || {};
if (body.code !== 0 || !body.data || !body.data.groupId) {
throw new Error(body.msg || '准备群聊失败');
}
const chatData = body.data;
const realGroupId = chatData.groupId;
let avatar = chatData.counterpartAvatar || chatData.groupAvatar || groupAvatar || '';
if (avatar && !avatar.startsWith('http')) {
avatar = app.globalData.ossImageUrl + avatar.replace(/^\//, '');
}
if (!app.globalData.groupInfoMap) app.globalData.groupInfoMap = {};
app.globalData.groupInfoMap[realGroupId] = {
name: chatData.counterpartName || chatData.groupName || groupName,
avatar,
orderId: chatData.orderId || orderId,
orderDesc: chatData.orderDesc || '',
orderZhuangtai: chatData.orderZhuangtai,
};
// 4. 🔥 关键:主动订阅群组并等待成功,确保发送消息时订阅已完成
try {
await new Promise((resolve, reject) => {
wx.goEasy.im.subscribeGroup({
groupIds: [realGroupId],
onSuccess: () => {
resolve();
},
onFailed: (error) => {
console.error('订阅群组失败:', error);
reject(error);
}
onSuccess: () => resolve(),
onFailed: (error) => reject(error),
});
});
const param = {
groupId: realGroupId,
orderId: chatData.orderId || orderId,
groupName: chatData.counterpartName || chatData.groupName || groupName || '订单群聊',
groupAvatar: avatar,
isCross: chatData.isCross != null ? chatData.isCross : (isCross || 0),
};
wx.hideLoading();
wx.navigateTo({
url: '/pages/qunliaotian/qunliaotian?data=' + encodeURIComponent(JSON.stringify(param)),
});
} catch (err) {
wx.showToast({ title: '订阅群组失败', icon: 'none' });
wx.hideLoading();
console.error('跳转群聊失败:', err);
wx.showToast({ title: err.message || '进入聊天失败', icon: 'none' });
throw err;
}
// 5. 跳转群聊页(参数与消息列表完全一致)
const param = {
groupId: realGroupId,
orderId: orderId,
groupName: groupName || '订单群聊',
groupAvatar: groupAvatar || '',
isCross: isCross || 0
};
const path = '/pages/qunliaotian/qunliaotian?data=' + encodeURIComponent(JSON.stringify(param));
wx.navigateTo({ url: path });
}
}
export default new ConnectionManager();
export default new ConnectionManager();