/** * 聊天历史消息加载(私聊 / 群聊 / 客服共用) * - 排除本地/欢迎语等合成消息的 timestamp,避免下拉加载错位 * - 统一 IM 连接等待与 history API 封装 */ const SYNTHETIC_MSG_ID = /^(local-|img-|order-|welcome-|auto-reply-)/; 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; } /** 等待 IM 连接就绪(优先 ensureImForRole,避免未连上就拉历史) */ export async function waitChatImReady(app, timeoutMs = 12000) { const role = app?.globalData?.currentRole || 'normal'; if (app?.ensureImForRole) { try { await app.ensureImForRole(role); if (isImConnected()) return true; } catch (e) { console.warn('[IM] ensureImForRole failed', e); } } if (app?.ensureConnection) app.ensureConnection(); return waitImConnected(app, timeoutMs); } 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), }; }