restore: 恢复橙色逍遥梦UI版本(2ea2860)到主分支

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-07-09 00:17:03 +08:00
parent 267de7c780
commit 566aeaa3b7
228 changed files with 22076 additions and 11864 deletions

View File

@@ -1,215 +1,296 @@
/**
* 连接管理模块 - xiaoxilj.js (根治发送失败:跳转前确保群组已订阅)
* 从详情页跳转订单群聊时,提前订阅群组,避免发送消息时订阅未完成而失败
* 订单群聊跳转:与文赫一致,新群用打手+商家/老板配对 groupId旧 group_{订单号} 会话保留
*/
import request from './request';
import { persistGroupMeta } from './im-user.js';
import { resolveLocalGroupId } from './group-chat.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);
function formatError(err) {
if (!err) return '进入聊天失败';
if (typeof err === 'string') return err;
if (err.message) return err.message;
if (err.msg) return String(err.msg);
if (err.content) return String(err.content);
if (err.code) return `连接失败(${err.code})`;
try {
return JSON.stringify(err);
} catch (e) {
return '进入聊天失败';
}
}
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);
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 mapIdentityForApi(identityType) {
if (identityType === 'dashou') return 'dashou';
if (identityType === 'shangjia') return 'shangjia';
return 'normal';
}
function validateIdentity(identityType, userId) {
if (identityType === 'dashou' && !userId.startsWith('Ds')) {
throw new Error('接单员 ID 必须以 Ds 开头');
}
if (identityType === 'shangjia' && !userId.startsWith('Sj')) {
throw new Error('商家 ID 必须以 Sj 开头');
}
if ((identityType === 'boss' || identityType === 'normal') && !userId.startsWith('Boss')) {
throw new Error('老板 ID 必须以 Boss 开头');
}
}
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) {
validateIdentity(identityType, userId);
app.globalData.currentRole = identityType;
wx.setStorageSync('currentRole', identityType);
const uid = userId.replace(/^(Ds|Sj|Boss)/, '');
const avatar = wx.getStorageSync('touxiang') ||
(app.globalData.ossImageUrl + app.globalData.morentouxiang);
app.globalData.currentUser = {
id: userId,
name: app.globalData.currentUser?.name || `用户${uid}`,
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) {
const orderIdStr = String(orderId || '').replace(/^group_/, '');
let chatData = null;
try {
const res = await withTimeout(
request({
url: '/dingdan/ltdhzb',
method: 'POST',
data: {
dingdan_id: orderIdStr,
identityType: mapIdentityForApi(identityType),
push_order_card: false,
},
}),
12000,
'准备群聊超时'
);
const body = res?.data || {};
if ((body.code === 0 || body.code === 200) && 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, orderIdStr, isCross
);
if (!localGroupId && orderIdStr) {
localGroupId = `group_${orderIdStr}`;
}
if (!localGroupId) {
throw new Error('无法确定群聊,请确认订单已接单且对方信息完整');
}
chatData = {
groupId: localGroupId,
orderId: orderIdStr,
groupName: groupName || '订单群聊',
isCross: isCross || fadanPingtai || 0,
};
try {
await request({
url: '/dingdan/ltdhzb',
method: 'POST',
data: {
dingdan_id: orderIdStr,
identityType: mapIdentityForApi(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('参数不完整');
}
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/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;
}
wx.navigateTo({
url: '/pages/chat/chat?data=' + encodeURIComponent(JSON.stringify({ to, currentUser })),
});
}
async connectToGroupChat(params) {
const { identityType, userId, orderId, groupName, groupAvatar, isCross } = params;
const {
identityType, userId, orderId, groupName, groupAvatar, isCross,
partnerUid, fadanPingtai,
} = params;
if (!identityType || !userId || !orderId) {
throw new Error('参数不完整identityType, userId, orderId 必填');
throw new Error('参数不完整');
}
// 校验 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
};
wx.showLoading({ title: '建立联系中...', mask: true });
// 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;
}
}
try {
const chatData = await prepareGroupChat(
identityType, userId, orderId, partnerUid, fadanPingtai || isCross, groupName, isCross
);
if (needConnect) {
wx.removeStorageSync('savedGoEasyConnection');
wx.removeStorageSync('goEasyUserId');
if (app.clearSavedConnection) app.clearSavedConnection();
if (wx.goEasy && wx.goEasy.getConnectionStatus() === 'connected') {
wx.goEasy.disconnect();
const realGroupId = chatData.groupId;
let avatar = chatData.counterpartAvatar || chatData.groupAvatar || groupAvatar || '';
if (avatar && !avatar.startsWith('http')) {
avatar = app.globalData.ossImageUrl + avatar.replace(/^\//, '');
}
const waitPromise = waitForConnection(userId);
app.connectWithIdentity(identityType, userId, false);
if (!app.globalData.groupInfoMap) app.globalData.groupInfoMap = {};
const meta = {
name: chatData.counterpartName || chatData.groupName || groupName,
avatar,
orderId: chatData.orderId || String(orderId).replace(/^group_/, ''),
orderDesc: chatData.orderDesc || '',
orderZhuangtai: chatData.orderZhuangtai,
orderJine: chatData.orderJine,
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 waitPromise;
} catch (err) {
wx.showToast({ title: '连接失败', icon: 'none' });
throw err;
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);
}
}
// 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
});
});
realGroupId = result;
} catch (err) {
console.error('获取会话列表失败:', err);
wx.showToast({ title: '获取群聊信息失败', icon: 'none' });
throw err;
}
const uid = userId.replace(/^(Ds|Sj|Boss)/, '');
const param = {
groupId: realGroupId,
orderId: chatData.orderId || String(orderId).replace(/^group_/, ''),
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,
};
if (!realGroupId) {
wx.showToast({ title: '暂未创建群聊', icon: 'none' });
return;
}
// 4. 🔥 关键:主动订阅群组并等待成功,确保发送消息时订阅已完成
try {
await new Promise((resolve, reject) => {
wx.goEasy.im.subscribeGroup({
groupIds: [realGroupId],
onSuccess: () => {
resolve();
},
onFailed: (error) => {
console.error('订阅群组失败:', error);
reject(error);
}
});
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.showToast({ title: '订阅群组失败', icon: 'none' });
wx.hideLoading();
console.error('跳转群聊失败:', err);
wx.showToast({ title: formatError(err), icon: 'none', duration: 2500 });
throw err;
}
// 5. 跳转群聊页(参数与消息列表完全一致)
const param = {
groupId: realGroupId,
orderId: orderId,
groupName: groupName || '订单群聊',
groupAvatar: groupAvatar || '',
isCross: isCross || 0
};
const path = '/pages/group-chat/group-chat?data=' + encodeURIComponent(JSON.stringify(param));
wx.navigateTo({ url: path });
}
}
export default new ConnectionManager();
export default new ConnectionManager();