chore: 抢单端UI回退前备份当前完整工作区
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
6e2f7bc39f
commit
e8fb32c1fe
53 files changed
+3410
-1065
No files matched your search
+66
-7
@@ -1,4 +1,4 @@
|
||||
/** 统一头像 URL:无有效头像时始终返回 ossImageUrl + morentouxiang */
|
||||
/** 统一头像 URL:无有效头像时始终返回 ossImageUrl + morentouxiang(后端配置) */
|
||||
|
||||
function isBlankPath(p) {
|
||||
if (p == null) return true;
|
||||
@@ -7,28 +7,71 @@ function isBlankPath(p) {
|
||||
return !s || s === 'null' || s === 'undefined' || s === 'none';
|
||||
}
|
||||
|
||||
function readConfigFallback(app) {
|
||||
if (!app || typeof app.readConfigFromStorage !== 'function') return null;
|
||||
try {
|
||||
return app.readConfigFromStorage();
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getOssBase(app) {
|
||||
let oss = app.globalData?.ossImageUrl || '';
|
||||
if (!oss) {
|
||||
const cfg = readConfigFallback(app);
|
||||
oss = cfg?.cos?.ossImageUrl || '';
|
||||
}
|
||||
return oss;
|
||||
}
|
||||
|
||||
function getDefaultRelative(app) {
|
||||
let def = app.globalData?.morentouxiang || '';
|
||||
if (!def) {
|
||||
const cfg = readConfigFallback(app);
|
||||
def = cfg?.otherConfig?.morentouxiang || '';
|
||||
}
|
||||
return def || 'avatar/default.jpg';
|
||||
}
|
||||
|
||||
export function getDefaultAvatarUrl(app) {
|
||||
return resolveAvatarUrl('', app);
|
||||
}
|
||||
|
||||
export function resolveAvatarUrl(rawPath, app) {
|
||||
app = app || getApp();
|
||||
const oss = app.globalData.ossImageUrl || '';
|
||||
const def = app.globalData.morentouxiang || 'avatar/default.jpg';
|
||||
const oss = getOssBase(app);
|
||||
const def = getDefaultRelative(app);
|
||||
|
||||
if (!isBlankPath(rawPath)) {
|
||||
if (rawPath.startsWith('http://') || rawPath.startsWith('https://')) {
|
||||
return rawPath;
|
||||
}
|
||||
const path = rawPath.startsWith('/') ? rawPath : '/' + rawPath;
|
||||
return oss + path;
|
||||
const path = rawPath.startsWith('/') ? rawPath : `/${rawPath}`;
|
||||
if (oss) return oss + path;
|
||||
return path;
|
||||
}
|
||||
|
||||
if (def.startsWith('http://') || def.startsWith('https://')) {
|
||||
return def;
|
||||
}
|
||||
const defPath = def.startsWith('/') ? def : '/' + def;
|
||||
return oss + defPath;
|
||||
const defPath = def.startsWith('/') ? def : `/${def}`;
|
||||
if (oss) return oss + defPath;
|
||||
// 配置未就绪时仍返回相对路径,由 binderror 回退;有 oss 时必须拼完整 URL
|
||||
return defPath;
|
||||
}
|
||||
|
||||
/** 是否已是可加载的完整头像 URL */
|
||||
export function isUsableAvatarUrl(url) {
|
||||
return url && (url.startsWith('http://') || url.startsWith('https://'));
|
||||
}
|
||||
|
||||
export function ensureAvatarUrl(rawPath, app) {
|
||||
app = app || getApp();
|
||||
const resolved = resolveAvatarUrl(rawPath, app);
|
||||
if (isUsableAvatarUrl(resolved)) return resolved;
|
||||
const def = getDefaultAvatarUrl(app);
|
||||
return isUsableAvatarUrl(def) ? def : resolved;
|
||||
}
|
||||
|
||||
export function resolveAvatarFromStorage(app) {
|
||||
@@ -36,3 +79,19 @@ export function resolveAvatarFromStorage(app) {
|
||||
const tx = wx.getStorageSync('touxiang') || '';
|
||||
return resolveAvatarUrl(tx, app);
|
||||
}
|
||||
|
||||
/** 配置加载后刷新页面头像(聊天页等订阅 configApplied) */
|
||||
export function subscribeConfigAvatarRefresh(page, refreshFn) {
|
||||
const app = getApp();
|
||||
if (!app || typeof refreshFn !== 'function') return null;
|
||||
const handler = () => refreshFn();
|
||||
if (app.on) app.on('configApplied', handler);
|
||||
return () => { if (app.off) app.off('configApplied', handler); };
|
||||
}
|
||||
|
||||
/** 消息列表/聊天页 image 加载失败时回退默认头像 */
|
||||
export function onAvatarImageError(page, dataKey, app) {
|
||||
if (!page || !dataKey) return;
|
||||
const def = getDefaultAvatarUrl(app || getApp());
|
||||
page.setData({ [dataKey]: def });
|
||||
}
|
||||
@@ -272,6 +272,10 @@ const BASE_METHODS = {
|
||||
const result = {};
|
||||
Object.keys(urlMap).forEach((key) => {
|
||||
const path = urlMap[key];
|
||||
if (!path || typeof path !== 'string') {
|
||||
result[key] = '';
|
||||
return;
|
||||
}
|
||||
result[key] = path.startsWith('http') || path.startsWith('/') ? path : ossImageUrl + path;
|
||||
});
|
||||
return result;
|
||||
|
||||
+74
-3
@@ -42,6 +42,8 @@ function initGlobalMessageSystem(app) {
|
||||
app.handleNewMessage = (message) => handleNewMessage(app, message);
|
||||
app.ensureImForRole = (role) => ensureImForRole(app, role);
|
||||
app.syncConnectionStatus = () => syncConnectionStatus(app);
|
||||
app.restoreTabBarBadge = () => restoreCachedBadge(app);
|
||||
app.startImWhenReady = () => startImWhenReady(app);
|
||||
|
||||
loadUserSettings(app);
|
||||
initNetworkListener(app);
|
||||
@@ -132,6 +134,7 @@ function initAppStateListener(app) {
|
||||
wx.onAppShow(() => {
|
||||
updateCurrentPageState(app);
|
||||
app.globalData.goEasyConnection.autoReconnect = true;
|
||||
restoreCachedBadge(app);
|
||||
ensureConnection(app);
|
||||
setTimeout(() => loadConversations(app), 400);
|
||||
});
|
||||
@@ -205,6 +208,9 @@ function syncConnectionStatus(app) {
|
||||
app.globalData.goEasyConnection.status = 'connected';
|
||||
app.globalData.goEasyConnection.userId = cur || expectedId;
|
||||
app.globalData.goEasyConnection.autoReconnect = true;
|
||||
setupMessageListeners(app);
|
||||
loadConversations(app);
|
||||
restoreCachedBadge(app);
|
||||
app.emitEvent('connectionChanged', { status: 'connected' });
|
||||
return;
|
||||
}
|
||||
@@ -607,6 +613,7 @@ function handleNewMessage(app, message) {
|
||||
}
|
||||
|
||||
cacheMessage(app, message);
|
||||
loadConversations(app);
|
||||
|
||||
if (shouldShowNotification(app, message)) {
|
||||
let notificationType = 'private';
|
||||
@@ -687,8 +694,57 @@ function showNotificationDirect(app, data) {
|
||||
app.emitEvent('showNotification', data);
|
||||
}
|
||||
|
||||
function startImWhenReady(app) {
|
||||
if (!wx.getStorageSync('uid')) return;
|
||||
app.globalData.goEasyConnection.autoReconnect = true;
|
||||
restoreCachedBadge(app);
|
||||
ensureConnection(app);
|
||||
setTimeout(() => {
|
||||
if (isSdkConnected()) {
|
||||
setupMessageListeners(app);
|
||||
loadConversations(app);
|
||||
}
|
||||
restoreCachedBadge(app);
|
||||
}, 450);
|
||||
}
|
||||
|
||||
function restoreCachedBadge(app) {
|
||||
const { messageManager } = app.globalData;
|
||||
let unread = messageManager.unreadTotal;
|
||||
if (unread == null || unread === '') {
|
||||
try {
|
||||
const saved = wx.getStorageSync(messageManager.cacheKeys.unreadTotal);
|
||||
if (saved !== '' && saved != null) unread = Number(saved) || 0;
|
||||
} catch (e) {}
|
||||
}
|
||||
if (!unread) {
|
||||
try {
|
||||
const badgeStr = wx.getStorageSync('tabBarMessageBadge');
|
||||
if (badgeStr && badgeStr !== '0') {
|
||||
unread = badgeStr === '99+' ? 100 : (parseInt(badgeStr, 10) || 0);
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
if (unread > 0) {
|
||||
updateTabBarBadge(app, unread);
|
||||
} else if (messageManager.tabBarBadgeText) {
|
||||
app.emitEvent('tabBarBadgeChanged', {
|
||||
index: messageManager.tabBarIndex,
|
||||
badgeText: messageManager.tabBarBadgeText,
|
||||
showRedDot: true,
|
||||
forceUpdate: true,
|
||||
});
|
||||
updateTabBarDirectly(app, messageManager.tabBarBadgeText);
|
||||
}
|
||||
}
|
||||
|
||||
function applyUnreadTotal(app, unreadTotal) {
|
||||
const prev = app.globalData.messageManager.unreadTotal;
|
||||
const connected = isSdkConnected();
|
||||
if (unreadTotal === 0 && !connected && prev > 0) {
|
||||
restoreCachedBadge(app);
|
||||
return;
|
||||
}
|
||||
app.globalData.messageManager.unreadTotal = unreadTotal;
|
||||
updateTabBarBadge(app, unreadTotal);
|
||||
wx.setStorageSync(app.globalData.messageManager.cacheKeys.unreadTotal, unreadTotal);
|
||||
@@ -789,7 +845,13 @@ function loadConversations(app) {
|
||||
|
||||
function loadConversationsNow(app) {
|
||||
if (_loadConvInFlight) return;
|
||||
if (!isSdkConnected() || !wx.goEasy?.im?.latestConversations) return;
|
||||
if (!isSdkConnected() || !wx.goEasy?.im?.latestConversations) {
|
||||
restoreCachedBadge(app);
|
||||
if (app.globalData.goEasyConnection.autoReconnect) {
|
||||
ensureConnection(app);
|
||||
}
|
||||
return;
|
||||
}
|
||||
_loadConvInFlight = true;
|
||||
wx.goEasy.im.latestConversations({
|
||||
onSuccess: (result) => {
|
||||
@@ -815,6 +877,7 @@ function loadConversationsNow(app) {
|
||||
onFailed: (error) => {
|
||||
_loadConvInFlight = false;
|
||||
console.error('加载会话列表失败:', error);
|
||||
restoreCachedBadge(app);
|
||||
if (!isSdkConnected() && app.globalData.goEasyConnection.autoReconnect) {
|
||||
attemptReconnect(app);
|
||||
}
|
||||
@@ -842,7 +905,14 @@ 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'];
|
||||
const chatPages = [
|
||||
'pages/chat/chat',
|
||||
'pages/group-chat/group-chat',
|
||||
'pages/cs-chat/cs-chat',
|
||||
'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();
|
||||
@@ -907,7 +977,8 @@ 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');
|
||||
const tabBar = pages[i].selectComponent('#custom-tab-bar')
|
||||
|| pages[i].selectComponent('tab-bar');
|
||||
if (tabBar && tabBar.setData) {
|
||||
tabBar.setData({ badgeText });
|
||||
break;
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* 聊天历史消息加载(私聊 / 群聊 / 客服共用)
|
||||
* - 排除本地/欢迎语等合成消息的 timestamp,避免下拉加载错位
|
||||
* - 统一 IM 连接等待与 history API 封装
|
||||
*/
|
||||
|
||||
const SYNTHETIC_MSG_ID = /^(local-|img-|order-|welcome-)/;
|
||||
|
||||
export function isHistorySyntheticMessage(msg) {
|
||||
return !!(msg && msg.messageId && SYNTHETIC_MSG_ID.test(String(msg.messageId)));
|
||||
}
|
||||
|
||||
/** 取当前列表里最早一条「真实」服务端消息的时间戳,供 GoEasy history 分页 */
|
||||
export function getOldestHistoryTimestamp(messages, fallback = null) {
|
||||
if (!Array.isArray(messages)) return fallback;
|
||||
let oldest = null;
|
||||
for (const m of messages) {
|
||||
if (!m || m.timestamp == null) continue;
|
||||
if (isHistorySyntheticMessage(m)) continue;
|
||||
if (oldest == null || m.timestamp < oldest) oldest = m.timestamp;
|
||||
}
|
||||
return oldest != null ? oldest : fallback;
|
||||
}
|
||||
|
||||
export function isImConnected() {
|
||||
const status = wx.goEasy?.getConnectionStatus?.() || 'disconnected';
|
||||
return (status === 'connected' || status === 'reconnected') && !!wx.goEasy?.im;
|
||||
}
|
||||
|
||||
/** 等待 IM 连接就绪(下拉加载历史前必须先连上) */
|
||||
export function waitImConnected(app, timeoutMs = 8000) {
|
||||
return new Promise((resolve) => {
|
||||
if (isImConnected()) {
|
||||
resolve(true);
|
||||
return;
|
||||
}
|
||||
if (app?.ensureConnection) app.ensureConnection();
|
||||
const start = Date.now();
|
||||
const timer = setInterval(() => {
|
||||
if (isImConnected()) {
|
||||
clearInterval(timer);
|
||||
resolve(true);
|
||||
} else if (Date.now() - start >= timeoutMs) {
|
||||
clearInterval(timer);
|
||||
resolve(false);
|
||||
}
|
||||
}, 200);
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchHistoryMessages({ scene, id, lastTimestamp, limit = 20 }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!wx.goEasy?.im?.history) {
|
||||
reject(new Error('IM未初始化'));
|
||||
return;
|
||||
}
|
||||
if (!id) {
|
||||
reject(new Error('会话ID为空'));
|
||||
return;
|
||||
}
|
||||
wx.goEasy.im.history({
|
||||
type: scene,
|
||||
id: String(id),
|
||||
lastTimestamp: lastTimestamp ?? null,
|
||||
limit,
|
||||
onSuccess: (res) => resolve(res?.content || []),
|
||||
onFailed: (err) => reject(err || new Error('加载历史消息失败')),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并一页历史消息
|
||||
* @returns {{ messages, hasMore, lastTimestamp }}
|
||||
*/
|
||||
export function mergeHistoryMessages({ incoming, existing, refresh, limit = 20, mapMessage }) {
|
||||
let list = Array.isArray(incoming) ? [...incoming] : [];
|
||||
list.sort((a, b) => a.timestamp - b.timestamp);
|
||||
if (typeof mapMessage === 'function') {
|
||||
list = list.map((m, idx) => mapMessage(m, idx, list));
|
||||
}
|
||||
|
||||
const prev = Array.isArray(existing) ? existing : [];
|
||||
let final = refresh ? list : [...list, ...prev];
|
||||
|
||||
if (!refresh) {
|
||||
for (let i = 0; i < final.length; i += 1) {
|
||||
if (i === 0) final[i].showTime = true;
|
||||
else final[i].showTime = (final[i].timestamp - final[i - 1].timestamp) / 60000 > 5;
|
||||
}
|
||||
}
|
||||
|
||||
const ids = new Set();
|
||||
const unique = [];
|
||||
for (const m of final) {
|
||||
if (!m?.messageId || ids.has(m.messageId)) continue;
|
||||
ids.add(m.messageId);
|
||||
unique.push(m);
|
||||
}
|
||||
|
||||
return {
|
||||
messages: unique,
|
||||
hasMore: list.length >= limit,
|
||||
lastTimestamp: getOldestHistoryTimestamp(unique, null),
|
||||
};
|
||||
}
|
||||
+35
-3
@@ -4,8 +4,9 @@
|
||||
export const ORDER_CARD_PREFIX = '[ORDER_CARD]';
|
||||
|
||||
const ZHUANGTAI_MAP = {
|
||||
1: '已下单', 2: '进行中', 3: '已完成', 4: '退款中',
|
||||
1: '待抢单', 2: '进行中', 3: '已完成', 4: '退款中',
|
||||
5: '已退款', 6: '退款失败', 7: '指定中', 8: '结算中',
|
||||
9: '未支付', 14: '支付失败', 25: '已废弃',
|
||||
};
|
||||
|
||||
export function getZhuangtaiText(zhuangtai) {
|
||||
@@ -38,6 +39,12 @@ export function resolveLocalGroupId(identityType, myUid, partnerUid, fadanPingta
|
||||
}
|
||||
return buildDashouBossGroupId(myUid, partnerUid);
|
||||
}
|
||||
if (identityType === 'boss' || identityType === 'normal') {
|
||||
if (partnerUid && myUid) {
|
||||
return buildDashouBossGroupId(partnerUid, myUid);
|
||||
}
|
||||
return orderId ? `group_${orderId}` : null;
|
||||
}
|
||||
return orderId ? `group_${orderId}` : null;
|
||||
}
|
||||
|
||||
@@ -105,6 +112,32 @@ export function applyPairMetaToConversation(item, myGoEasyId, defaultAvatar, oss
|
||||
return;
|
||||
}
|
||||
|
||||
const parsed = parsePairGroupId(item.groupId);
|
||||
if (parsed && myGoEasyId) {
|
||||
const isDashou = myGoEasyId === parsed.dashouId;
|
||||
const isPartner = myGoEasyId === parsed.partnerId;
|
||||
if (isDashou) {
|
||||
d.counterpartId = parsed.partnerId;
|
||||
d.counterpartYonghuid = parsed.partnerId.replace(/^(Sj|Boss)/, '');
|
||||
d.partnerRoleLabel = parsed.partnerRole === 'boss' ? '老板' : '商家';
|
||||
if (!d.name || d.name === item.groupId) {
|
||||
d.name = d.partnerNicheng || `${d.partnerRoleLabel}${d.counterpartYonghuid.slice(-6)}`;
|
||||
}
|
||||
if (!d.avatar || d.avatar === defaultAvatar) {
|
||||
d.avatar = d.partnerAvatar || defaultAvatar;
|
||||
}
|
||||
} else if (isPartner) {
|
||||
d.counterpartId = parsed.dashouId;
|
||||
d.counterpartYonghuid = parsed.dashouId.replace(/^Ds/, '');
|
||||
if (!d.name || d.name === item.groupId) {
|
||||
d.name = d.dashouNicheng || `打手${d.counterpartYonghuid.slice(-6)}`;
|
||||
}
|
||||
if (!d.avatar || d.avatar === defaultAvatar) {
|
||||
d.avatar = d.dashouAvatar || defaultAvatar;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const counterpartId = getCounterpartGoEasyId(item.groupId, myGoEasyId);
|
||||
if (counterpartId) {
|
||||
d.counterpartId = counterpartId;
|
||||
@@ -215,6 +248,7 @@ 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;
|
||||
// QQ/微信:取「最后消息时间」与「最近进入会话时间」较大值;新消息到达会更新 msgTs 并置顶
|
||||
return Math.max(msgTs, openTs);
|
||||
}
|
||||
|
||||
@@ -222,8 +256,6 @@ 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;
|
||||
|
||||
+3
-2
@@ -24,9 +24,10 @@ export const IM_ROLE_PREFIX = {
|
||||
kaoheguan: 'Ds',
|
||||
};
|
||||
|
||||
import { getDefaultAvatarUrl } from './avatar.js';
|
||||
|
||||
export function getDefaultAvatar(app) {
|
||||
const oss = app.globalData.ossImageUrl || '';
|
||||
return oss + (app.globalData.morentouxiang || '');
|
||||
return getDefaultAvatarUrl(app || getApp());
|
||||
}
|
||||
|
||||
export function getImPrefixForRole(role) {
|
||||
|
||||
@@ -4,9 +4,9 @@ const imRequest = require('./imRequest.js');
|
||||
/**
|
||||
* 打手 IM 权限检测
|
||||
* POST /yonghu/dshqltqx
|
||||
* 后端返回 { code: 0, allow: 1 } 或 { code: 0, allow: 0 }
|
||||
*/
|
||||
async function dashoujianquan(app) {
|
||||
async function dashoujianquan(app, options = {}) {
|
||||
const silent = options && options.silent === true;
|
||||
const uid = wx.getStorageSync('uid');
|
||||
if (!uid) return { allowed: false, reason: '未登录' };
|
||||
|
||||
@@ -21,7 +21,8 @@ async function dashoujianquan(app) {
|
||||
const allow = res.data.allow;
|
||||
if (allow === 1) {
|
||||
return { allowed: true };
|
||||
} else {
|
||||
}
|
||||
if (!silent) {
|
||||
await app.disconnectGoEasy();
|
||||
wx.showToast({
|
||||
title: '当前身份已不支持消息聊天功能,请充值会员或联系管理员后尝试',
|
||||
@@ -30,22 +31,24 @@ async function dashoujianquan(app) {
|
||||
mask: true
|
||||
});
|
||||
await new Promise(r => setTimeout(r, 2100));
|
||||
return { allowed: false, reason: '接单员消息权限被限制' };
|
||||
}
|
||||
return { allowed: false, reason: '接单员消息权限被限制' };
|
||||
}
|
||||
throw new Error(res.data?.msg || '鉴权接口异常');
|
||||
} catch (err) {
|
||||
console.error('打手鉴权失败:', err);
|
||||
await app.disconnectGoEasy();
|
||||
wx.showToast({
|
||||
title: '消息服务暂不可用,请稍后重试',
|
||||
icon: 'none',
|
||||
duration: 2000,
|
||||
mask: true
|
||||
});
|
||||
await new Promise(r => setTimeout(r, 2100));
|
||||
if (!silent) {
|
||||
await app.disconnectGoEasy();
|
||||
wx.showToast({
|
||||
title: '消息服务暂不可用,请稍后重试',
|
||||
icon: 'none',
|
||||
duration: 2000,
|
||||
mask: true
|
||||
});
|
||||
await new Promise(r => setTimeout(r, 2100));
|
||||
}
|
||||
return { allowed: false, reason: '鉴权请求失败' };
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { dashoujianquan };
|
||||
module.exports = { dashoujianquan };
|
||||
@@ -14,21 +14,21 @@ const { dashoujianquan } = require('./dashoujianquan.js');
|
||||
* @param {Object} app 全局 App 实例
|
||||
* @returns {Promise<{ allowed: boolean, reason?: string }>}
|
||||
*/
|
||||
async function jianquanxian(app) {
|
||||
async function jianquanxian(app, options = {}) {
|
||||
const silent = options && options.silent === true;
|
||||
// 1. 检测登录态(用 uid 或 token)
|
||||
const token = wx.getStorageSync('token') || wx.getStorageSync('uid');
|
||||
if (!token) {
|
||||
// 没登录:先强制断开(如果已连接)
|
||||
await app.disconnectGoEasy();
|
||||
// 弹窗提示,强制阅读 2 秒
|
||||
wx.showToast({
|
||||
title: '您当前处于未登录状态,消息功能暂不可用',
|
||||
icon: 'none',
|
||||
duration: 2000,
|
||||
mask: true
|
||||
});
|
||||
// 等待提示结束,确保用户看到
|
||||
await new Promise(resolve => setTimeout(resolve, 2100));
|
||||
if (!silent) {
|
||||
await app.disconnectGoEasy();
|
||||
wx.showToast({
|
||||
title: '您当前处于未登录状态,消息功能暂不可用',
|
||||
icon: 'none',
|
||||
duration: 2000,
|
||||
mask: true
|
||||
});
|
||||
await new Promise(resolve => setTimeout(resolve, 2100));
|
||||
}
|
||||
return { allowed: false, reason: '未登录' };
|
||||
}
|
||||
|
||||
@@ -37,17 +37,16 @@ async function jianquanxian(app) {
|
||||
|
||||
// 3. 根据角色分发到具体鉴权模块
|
||||
switch (role) {
|
||||
case 'normal': // 点单老板
|
||||
return laobanjianquan(app);
|
||||
case 'normal':
|
||||
return laobanjianquan(app, { silent });
|
||||
|
||||
case 'dashou': // 打手
|
||||
return dashoujianquan(app);
|
||||
case 'dashou':
|
||||
return dashoujianquan(app, { silent });
|
||||
|
||||
// 其他角色暂未细分,统一放行(后续可新增对应文件)
|
||||
case 'shangjia':
|
||||
case 'guanshi':
|
||||
case 'zuzhang':
|
||||
case 'kaoheguan':
|
||||
case 'kaoheguan':
|
||||
return { allowed: true };
|
||||
|
||||
default:
|
||||
|
||||
@@ -4,9 +4,9 @@ const imRequest = require('./imRequest.js');
|
||||
/**
|
||||
* 老板(点单用户)IM 权限检测
|
||||
* POST /yonghu/lbhqltqx
|
||||
* 后端返回 { code: 0, allow: 1 } 或 { code: 0, allow: 0 }
|
||||
*/
|
||||
async function laobanjianquan(app) {
|
||||
async function laobanjianquan(app, options = {}) {
|
||||
const silent = options && options.silent === true;
|
||||
const uid = wx.getStorageSync('uid');
|
||||
if (!uid) return { allowed: false, reason: '未登录' };
|
||||
|
||||
@@ -21,7 +21,8 @@ async function laobanjianquan(app) {
|
||||
const allow = res.data.allow;
|
||||
if (allow === 1) {
|
||||
return { allowed: true };
|
||||
} else {
|
||||
}
|
||||
if (!silent) {
|
||||
await app.disconnectGoEasy();
|
||||
wx.showToast({
|
||||
title: '当前身份已不支持使用消息功能',
|
||||
@@ -30,22 +31,24 @@ async function laobanjianquan(app) {
|
||||
mask: true
|
||||
});
|
||||
await new Promise(r => setTimeout(r, 1100));
|
||||
return { allowed: false, reason: '当前身份无消息权限' };
|
||||
}
|
||||
return { allowed: false, reason: '当前身份无消息权限' };
|
||||
}
|
||||
throw new Error(res.data?.msg || '鉴权接口异常');
|
||||
} catch (err) {
|
||||
console.error('老板鉴权失败:', err);
|
||||
await app.disconnectGoEasy();
|
||||
wx.showToast({
|
||||
title: '消息服务暂不可用,请稍后重试',
|
||||
icon: 'none',
|
||||
duration: 2000,
|
||||
mask: true
|
||||
});
|
||||
await new Promise(r => setTimeout(r, 2100));
|
||||
if (!silent) {
|
||||
await app.disconnectGoEasy();
|
||||
wx.showToast({
|
||||
title: '消息服务暂不可用,请稍后重试',
|
||||
icon: 'none',
|
||||
duration: 2000,
|
||||
mask: true
|
||||
});
|
||||
await new Promise(r => setTimeout(r, 2100));
|
||||
}
|
||||
return { allowed: false, reason: '鉴权请求失败' };
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { laobanjianquan };
|
||||
module.exports = { laobanjianquan };
|
||||
@@ -0,0 +1,12 @@
|
||||
/** 与消息页「联系客服」一致:跳转客服会话 */
|
||||
export function openCustomerServiceChat() {
|
||||
const app = getApp();
|
||||
if (app.globalData.chatEnabled && app.ensureConnection) {
|
||||
app.globalData.goEasyConnection.autoReconnect = true;
|
||||
app.ensureConnection();
|
||||
}
|
||||
const param = { teamId: 'support_team' };
|
||||
wx.navigateTo({
|
||||
url: '/pages/cs-chat/cs-chat?data=' + encodeURIComponent(JSON.stringify(param)),
|
||||
});
|
||||
}
|
||||
@@ -249,8 +249,9 @@ export function reconnectForRole(role) {
|
||||
const connected = status === 'connected' || status === 'reconnected';
|
||||
|
||||
if (connected && currentUserId === targetUserId) {
|
||||
if (app.loadConversations) app.loadConversations();
|
||||
if (app.restoreTabBarBadge) app.restoreTabBarBadge();
|
||||
if (app.ensureConnection) app.ensureConnection();
|
||||
if (app.loadConversations) app.loadConversations();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -224,8 +224,9 @@ export function syncStaffUi(page) {
|
||||
const ctx = staff ? (getStaffContext() || {}) : null;
|
||||
const wallet = (ctx && ctx.wallet) || {};
|
||||
const portal = isMerchantPortalUser();
|
||||
const staffFlag = Number(wx.getStorageSync('staffstatus')) === 1;
|
||||
page.setData({
|
||||
isShangjia: portal,
|
||||
isShangjia: portal || staffFlag || !!(page.data && page.data.isShangjia),
|
||||
isStaffMode: staff,
|
||||
isMerchantOwner: !staff && Number(wx.getStorageSync('shangjiastatus')) === 1,
|
||||
staffRoleName: staff ? (ctx.role_name || '客服') : '',
|
||||
|
||||
+296
-269
@@ -1,269 +1,296 @@
|
||||
/**
|
||||
* 订单群聊跳转:先确定群 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();
|
||||
/**
|
||||
* 订单群聊跳转:与文赫一致,新群用打手+商家/老板配对 groupId;旧 group_{订单号} 会话保留
|
||||
*/
|
||||
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.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 '进入聊天失败';
|
||||
}
|
||||
}
|
||||
|
||||
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('参数不完整');
|
||||
}
|
||||
|
||||
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),
|
||||
};
|
||||
|
||||
wx.navigateTo({
|
||||
url: '/pages/chat/chat?data=' + encodeURIComponent(JSON.stringify({ to, currentUser })),
|
||||
});
|
||||
}
|
||||
|
||||
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 || 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 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 || 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,
|
||||
};
|
||||
|
||||
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();
|
||||
@@ -0,0 +1,53 @@
|
||||
import request from './request.js';
|
||||
import { getDefaultAvatarUrl } from './avatar.js';
|
||||
|
||||
export const ZHIDING_HF_MAP = { 1: '想接', 2: '已婉拒', 3: '等会接' };
|
||||
|
||||
export async function fetchMyZhidingOrders() {
|
||||
try {
|
||||
const res = await request({ url: '/dingdan/zdcx', method: 'POST', data: {} });
|
||||
const body = res?.data;
|
||||
if (body && (body.code === 200 || body.code === 0) && body.data) {
|
||||
return body.data.list || [];
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('fetchMyZhidingOrders failed', e);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export async function submitZhidingResponse(dingdanId, huifu, dengHuiNote = '') {
|
||||
const res = await request({
|
||||
url: '/dingdan/zdhf',
|
||||
method: 'POST',
|
||||
data: {
|
||||
dingdan_id: dingdanId,
|
||||
huifu,
|
||||
deng_hui_note: dengHuiNote,
|
||||
},
|
||||
});
|
||||
return res?.data;
|
||||
}
|
||||
|
||||
export function processZhidingList(list, app, uid, highlightOrderId = '') {
|
||||
const defAvatar = getDefaultAvatarUrl(app);
|
||||
const oss = app.globalData?.ossImageUrl || '';
|
||||
return (list || []).map((item) => {
|
||||
let avatar = defAvatar;
|
||||
if (item.zhiding_avatar) {
|
||||
avatar = item.zhiding_avatar.startsWith('http') ? item.zhiding_avatar : oss + item.zhiding_avatar;
|
||||
}
|
||||
return {
|
||||
...item,
|
||||
isMyZhiding: true,
|
||||
zhiding_avatar_full: avatar,
|
||||
zhiding_hf_text: item.zhiding_hf_text || ZHIDING_HF_MAP[item.zhiding_hf] || '',
|
||||
isHighlighted: highlightOrderId && item.dingdan_id === highlightOrderId,
|
||||
};
|
||||
}).filter((item) => String(item.zhiding_uid) === String(uid));
|
||||
}
|
||||
|
||||
/** 横幅只展示尚未回复的指定单(已等会接/不想接的不占横幅) */
|
||||
export function filterZhidingBannerList(list) {
|
||||
return (list || []).filter((item) => !item.zhiding_hf);
|
||||
}
|
||||
Reference in new issue
Block a user