Files
xingque/utils/chat-history.js
XingQue f4565b0ebd feat: 客服自动回复、历史消息修复、扫码兼容页与商家头像
- 新增 scriptService 对接话术接口,cs-chat 支持关键词自动回复
- chat-history 增加 waitChatImReady,修复私聊/群聊/客服历史加载
- 恢复 dashouduan/guanshiduan 兼容页跳转 fighter 保留旧二维码
- 商家我的页头像与标签挤压样式优化

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-01 11:59:21 +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),
};
}