优化聊天:配对群组、稳定连接、消息列表与订单详情跳转
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
@@ -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) });
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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
112
utils/group-chat.js
Normal 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 || '');
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user