- 新增 scriptService 对接话术接口,cs-chat 支持关键词自动回复 - chat-history 增加 waitChatImReady,修复私聊/群聊/客服历史加载 - 恢复 dashouduan/guanshiduan 兼容页跳转 fighter 保留旧二维码 - 商家我的页头像与标签挤压样式优化 Co-authored-by: Cursor <cursoragent@cursor.com>
122 lines
3.7 KiB
JavaScript
122 lines
3.7 KiB
JavaScript
/**
|
||
* 聊天历史消息加载(私聊 / 群聊 / 客服共用)
|
||
* - 排除本地/欢迎语等合成消息的 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),
|
||
};
|
||
}
|