Files
xingque/utils/chat-history.js
2026-07-09 00:17:03 +08:00

122 lines
3.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 聊天历史消息加载(私聊 / 群聊 / 客服共用)
* - 排除本地/欢迎语等合成消息的 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),
};
}