重构聊天消息处理和分页逻辑。为消息记录参数引入一个新的接口,以增强类型安全性。更新获取聊天消息和聊天室消息的API调用,以支持分页。通过删除不必要的刷新触发器和优化会话状态管理来优化MessageList组件。改进加载状态处理,并确保组件之间消息加载行为的一致性。
This commit is contained in:
@@ -35,26 +35,38 @@ export function updateConfig(params) {
|
||||
return request2("/api/WechatFriend/updateConfig", params, "PUT");
|
||||
}
|
||||
//获取聊天记录-2 获取列表
|
||||
export function getChatMessages(params: {
|
||||
wechatAccountId: number;
|
||||
wechatFriendId?: number;
|
||||
wechatChatroomId?: number;
|
||||
From: number;
|
||||
To: number;
|
||||
Count: number;
|
||||
olderData: boolean;
|
||||
}) {
|
||||
export interface messreocrParams {
|
||||
From?: number | string;
|
||||
To?: number | string;
|
||||
/**
|
||||
* 当前页码,从 1 开始
|
||||
*/
|
||||
page?: number;
|
||||
/**
|
||||
* 每页条数
|
||||
*/
|
||||
limit?: number;
|
||||
/**
|
||||
* 群id
|
||||
*/
|
||||
wechatChatroomId?: number | string;
|
||||
/**
|
||||
* 好友id
|
||||
*/
|
||||
wechatFriendId?: number | string;
|
||||
/**
|
||||
* 微信账号ID
|
||||
*/
|
||||
wechatAccountId?: number | string;
|
||||
/**
|
||||
* 关键词、类型等扩展参数
|
||||
*/
|
||||
[property: string]: any;
|
||||
}
|
||||
export function getChatMessages(params: messreocrParams) {
|
||||
return request("/v1/kefu/message/details", params, "GET");
|
||||
}
|
||||
export function getChatroomMessages(params: {
|
||||
wechatAccountId: number;
|
||||
wechatFriendId?: number;
|
||||
wechatChatroomId?: number;
|
||||
From: number;
|
||||
To: number;
|
||||
Count: number;
|
||||
olderData: boolean;
|
||||
}) {
|
||||
export function getChatroomMessages(params: messreocrParams) {
|
||||
return request("/v1/kefu/message/details", params, "GET");
|
||||
}
|
||||
//=====================旧==============================
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
import { useCurrentContact } from "@/store/module/weChat/weChat";
|
||||
import { ContactManager } from "@/utils/dbAction/contact";
|
||||
import { MessageManager } from "@/utils/dbAction/message";
|
||||
import { triggerRefresh } from "@/store/module/weChat/message";
|
||||
import { useUserStore } from "@/store/module/user";
|
||||
import { useWeChatStore } from "@/store/module/weChat/weChat";
|
||||
const { TextArea } = Input;
|
||||
@@ -110,10 +109,7 @@ const ToContract: React.FC<ToContractProps> = ({
|
||||
await ContactManager.deleteContact(currentContact.id);
|
||||
console.log("✅ 已从联系人数据库删除");
|
||||
|
||||
// 3. 触发会话列表刷新
|
||||
triggerRefresh();
|
||||
|
||||
// 4. 清空当前选中的联系人(关闭聊天窗口)
|
||||
// 3. 清空当前选中的联系人(关闭聊天窗口)
|
||||
clearCurrentContact();
|
||||
|
||||
message.success("转接成功,已清理本地数据");
|
||||
@@ -167,10 +163,7 @@ const ToContract: React.FC<ToContractProps> = ({
|
||||
await ContactManager.deleteContact(currentContact.id);
|
||||
console.log("✅ 已从联系人数据库删除");
|
||||
|
||||
// 3. 触发会话列表刷新
|
||||
triggerRefresh();
|
||||
|
||||
// 4. 清空当前选中的联系人(关闭聊天窗口)
|
||||
// 3. 清空当前选中的联系人(关闭聊天窗口)
|
||||
clearCurrentContact();
|
||||
|
||||
message.success("转回成功,已清理本地数据");
|
||||
|
||||
@@ -145,6 +145,9 @@ const MessageRecord: React.FC<MessageRecordProps> = ({ contract }) => {
|
||||
const [selectedRecords, setSelectedRecords] = useState<ChatRecord[]>([]);
|
||||
|
||||
const currentMessages = useWeChatStore(state => state.currentMessages);
|
||||
const currentMessagesHasMore = useWeChatStore(
|
||||
state => state.currentMessagesHasMore,
|
||||
);
|
||||
|
||||
const loadChatMessages = useWeChatStore(state => state.loadChatMessages);
|
||||
const messagesLoading = useWeChatStore(state => state.messagesLoading);
|
||||
@@ -552,8 +555,14 @@ const MessageRecord: React.FC<MessageRecordProps> = ({ contract }) => {
|
||||
};
|
||||
|
||||
// 用于分组消息并添加时间戳的辅助函数
|
||||
const groupMessagesByTime = (messages: ChatRecord[]) => {
|
||||
return messages
|
||||
const groupMessagesByTime = (messages: ChatRecord[] | null | undefined) => {
|
||||
const safeMessages = Array.isArray(messages)
|
||||
? messages
|
||||
: Array.isArray((messages as any)?.list)
|
||||
? ((messages as any).list as ChatRecord[])
|
||||
: [];
|
||||
|
||||
return safeMessages
|
||||
.filter(msg => msg !== null && msg !== undefined) // 过滤掉null和undefined的消息
|
||||
.map(msg => ({
|
||||
time: formatWechatTime(String(msg?.wechatTime)),
|
||||
@@ -680,33 +689,10 @@ const MessageRecord: React.FC<MessageRecordProps> = ({ contract }) => {
|
||||
);
|
||||
};
|
||||
const loadMoreMessages = () => {
|
||||
// 兼容性处理:检查消息数组和时间戳
|
||||
if (!currentMessages || currentMessages.length === 0) {
|
||||
console.warn("No messages available for loading more");
|
||||
if (messagesLoading || !currentMessagesHasMore) {
|
||||
return;
|
||||
}
|
||||
|
||||
const firstMessage = currentMessages[0];
|
||||
if (!firstMessage || !firstMessage.createTime) {
|
||||
console.warn("Invalid message or createTime");
|
||||
return;
|
||||
}
|
||||
|
||||
// 兼容性处理:确保时间戳格式正确
|
||||
let timestamp;
|
||||
try {
|
||||
const date = new Date(firstMessage.createTime);
|
||||
if (isNaN(date.getTime())) {
|
||||
console.warn("Invalid createTime format:", firstMessage.createTime);
|
||||
return;
|
||||
}
|
||||
timestamp = date.getTime() - 24 * 36000 * 1000;
|
||||
} catch (error) {
|
||||
console.error("Error parsing createTime:", error);
|
||||
return;
|
||||
}
|
||||
|
||||
loadChatMessages(false, timestamp);
|
||||
loadChatMessages(false);
|
||||
};
|
||||
|
||||
const handleForwardMessage = (messageData: ChatRecord) => {
|
||||
@@ -785,8 +771,17 @@ const MessageRecord: React.FC<MessageRecordProps> = ({ contract }) => {
|
||||
|
||||
return (
|
||||
<div className={styles.messagesContainer}>
|
||||
<div className={styles.loadMore} onClick={() => loadMoreMessages()}>
|
||||
点击加载更早的信息 {messagesLoading ? <LoadingOutlined /> : ""}
|
||||
<div
|
||||
className={styles.loadMore}
|
||||
onClick={() => loadMoreMessages()}
|
||||
style={{
|
||||
cursor:
|
||||
currentMessagesHasMore && !messagesLoading ? "pointer" : "default",
|
||||
opacity: currentMessagesHasMore ? 1 : 0.6,
|
||||
}}
|
||||
>
|
||||
{currentMessagesHasMore ? "点击加载更早的信息" : "已经没有更早的消息了"}
|
||||
{messagesLoading ? <LoadingOutlined /> : ""}
|
||||
</div>
|
||||
{groupMessagesByTime(currentMessages).map((group, groupIndex) => (
|
||||
<React.Fragment key={`group-${groupIndex}`}>
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
getWechatFriendDetail,
|
||||
getWechatChatroomDetail,
|
||||
} from "./api";
|
||||
import { useMessageStore, triggerRefresh } from "@weChatStore/message";
|
||||
import { useMessageStore } from "@weChatStore/message";
|
||||
import { useWebSocketStore } from "@storeModule/websocket/websocket";
|
||||
import { useCustomerStore } from "@weChatStore/customer";
|
||||
import { useContactStore } from "@weChatStore/contacts";
|
||||
@@ -39,14 +39,12 @@ const MessageList: React.FC<MessageListProps> = () => {
|
||||
// Store状态
|
||||
const {
|
||||
loading,
|
||||
refreshTrigger,
|
||||
hasLoadedOnce,
|
||||
setLoading,
|
||||
setHasLoadedOnce,
|
||||
sessions,
|
||||
setSessions: setSessionState,
|
||||
} = useMessageStore();
|
||||
|
||||
// 组件内部状态:会话列表数据
|
||||
const [sessions, setSessions] = useState<ChatSession[]>([]);
|
||||
const [filteredSessions, setFilteredSessions] = useState<ChatSession[]>([]);
|
||||
|
||||
// 右键菜单相关状态
|
||||
@@ -74,6 +72,8 @@ const MessageList: React.FC<MessageListProps> = () => {
|
||||
});
|
||||
|
||||
const contextMenuRef = useRef<HTMLDivElement>(null);
|
||||
const previousUserIdRef = useRef<number | null>(null);
|
||||
const loadRequestRef = useRef(0);
|
||||
|
||||
// 右键菜单事件处理
|
||||
const handleContextMenu = (e: React.MouseEvent, session: ChatSession) => {
|
||||
@@ -105,7 +105,7 @@ const MessageList: React.FC<MessageListProps> = () => {
|
||||
|
||||
try {
|
||||
// 1. 立即更新UI并重新排序(乐观更新)
|
||||
setSessions(prev => {
|
||||
setSessionState(prev => {
|
||||
const updatedSessions = prev.map(s =>
|
||||
s.id === session.id
|
||||
? {
|
||||
@@ -141,7 +141,7 @@ const MessageList: React.FC<MessageListProps> = () => {
|
||||
message.success(`${newPinned === 1 ? "置顶" : "取消置顶"}成功`);
|
||||
} catch (error) {
|
||||
// 4. 失败时回滚UI
|
||||
setSessions(prev =>
|
||||
setSessionState(prev =>
|
||||
prev.map(s =>
|
||||
s.id === session.id
|
||||
? { ...s, config: { ...s.config, top: currentPinned } }
|
||||
@@ -162,7 +162,7 @@ const MessageList: React.FC<MessageListProps> = () => {
|
||||
onOk: async () => {
|
||||
try {
|
||||
// 1. 立即从UI移除
|
||||
setSessions(prev => prev.filter(s => s.id !== session.id));
|
||||
setSessionState(prev => prev.filter(s => s.id !== session.id));
|
||||
|
||||
// 2. 后台调用API
|
||||
await updateConfig({
|
||||
@@ -180,7 +180,7 @@ const MessageList: React.FC<MessageListProps> = () => {
|
||||
message.success("删除成功");
|
||||
} catch (error) {
|
||||
// 4. 失败时恢复UI
|
||||
setSessions(prev => [...prev, session]);
|
||||
setSessionState(prev => [...prev, session]);
|
||||
message.error("删除失败");
|
||||
}
|
||||
|
||||
@@ -212,7 +212,7 @@ const MessageList: React.FC<MessageListProps> = () => {
|
||||
|
||||
try {
|
||||
// 1. 立即更新UI
|
||||
setSessions(prev =>
|
||||
setSessionState(prev =>
|
||||
prev.map(s =>
|
||||
s.id === session.id ? { ...s, conRemark: editRemarkModal.remark } : s,
|
||||
),
|
||||
@@ -258,7 +258,7 @@ const MessageList: React.FC<MessageListProps> = () => {
|
||||
message.success("备注更新成功");
|
||||
} catch (error) {
|
||||
// 4. 失败时回滚UI
|
||||
setSessions(prev =>
|
||||
setSessionState(prev =>
|
||||
prev.map(s =>
|
||||
s.id === session.id ? { ...s, conRemark: oldRemark } : s,
|
||||
),
|
||||
@@ -357,112 +357,93 @@ const MessageList: React.FC<MessageListProps> = () => {
|
||||
`会话同步完成: 新增${syncResult.added}, 更新${syncResult.updated}, 删除${syncResult.deleted}`,
|
||||
);
|
||||
|
||||
// 如果有数据变更,触发UI刷新
|
||||
if (
|
||||
syncResult.added > 0 ||
|
||||
syncResult.updated > 0 ||
|
||||
syncResult.deleted > 0
|
||||
) {
|
||||
triggerRefresh();
|
||||
}
|
||||
// 会话管理器会在有变更时触发订阅回调
|
||||
} catch (error) {
|
||||
console.error("同步服务器数据失败:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// 切换账号时重置加载状态
|
||||
useEffect(() => {
|
||||
if (!currentUserId) return;
|
||||
if (previousUserIdRef.current === currentUserId) return;
|
||||
previousUserIdRef.current = currentUserId;
|
||||
setHasLoadedOnce(false);
|
||||
setSessionState([]);
|
||||
}, [currentUserId, setHasLoadedOnce, setSessionState]);
|
||||
|
||||
// 初始化加载会话列表
|
||||
useEffect(() => {
|
||||
if (!currentUserId || currentUserId === 0) {
|
||||
console.warn("currentUserId 无效,跳过加载:", currentUserId);
|
||||
return;
|
||||
}
|
||||
|
||||
let isCancelled = false;
|
||||
const requestId = ++loadRequestRef.current;
|
||||
|
||||
const initializeSessions = async () => {
|
||||
if (!currentUserId || currentUserId === 0) {
|
||||
console.warn("currentUserId 无效,跳过加载:", currentUserId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果已经加载过一次,只从本地数据库读取,不请求接口
|
||||
if (hasLoadedOnce) {
|
||||
console.log("已加载过,只从本地数据库读取");
|
||||
setLoading(true); // 显示骨架屏
|
||||
|
||||
try {
|
||||
const cachedSessions =
|
||||
await MessageManager.getUserSessions(currentUserId);
|
||||
console.log("从本地加载会话数:", cachedSessions.length);
|
||||
|
||||
// 如果本地数据为空,重置 hasLoadedOnce 并重新加载
|
||||
if (cachedSessions.length === 0) {
|
||||
console.warn("本地数据为空,重置加载状态并重新加载");
|
||||
setHasLoadedOnce(false);
|
||||
// 不 return,继续执行下面的首次加载逻辑
|
||||
} else {
|
||||
setSessions(cachedSessions);
|
||||
setLoading(false); // 数据加载完成,关闭骨架屏
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("从本地加载会话列表失败:", error);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.log("首次加载,开始初始化...");
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
// 1. 优先从本地数据库加载
|
||||
const cachedSessions =
|
||||
await MessageManager.getUserSessions(currentUserId);
|
||||
|
||||
console.log("本地缓存会话数:", cachedSessions.length);
|
||||
if (isCancelled || loadRequestRef.current !== requestId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cachedSessions.length > 0) {
|
||||
// 有缓存数据,立即显示
|
||||
console.log("有缓存数据,立即显示");
|
||||
setSessions(cachedSessions);
|
||||
setLoading(false);
|
||||
setSessionState(cachedSessions);
|
||||
}
|
||||
|
||||
// 2. 后台静默同步(不显示同步提示)
|
||||
console.log("后台静默同步中...");
|
||||
const needsFullSync = cachedSessions.length === 0 || !hasLoadedOnce;
|
||||
|
||||
if (needsFullSync) {
|
||||
await syncWithServer();
|
||||
setHasLoadedOnce(true); // 标记已加载过
|
||||
console.log("同步完成");
|
||||
if (isCancelled || loadRequestRef.current !== requestId) {
|
||||
return;
|
||||
}
|
||||
setHasLoadedOnce(true);
|
||||
} else {
|
||||
// 无缓存,直接API加载
|
||||
console.log("无缓存,从服务器加载...");
|
||||
await syncWithServer();
|
||||
const newSessions =
|
||||
await MessageManager.getUserSessions(currentUserId);
|
||||
console.log("从服务器加载会话数:", newSessions.length);
|
||||
setSessions(newSessions);
|
||||
setLoading(false);
|
||||
setHasLoadedOnce(true); // 标记已加载过
|
||||
syncWithServer().catch(error => {
|
||||
console.error("后台同步失败:", error);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("初始化会话列表失败:", error);
|
||||
setLoading(false);
|
||||
if (!isCancelled) {
|
||||
console.error("初始化会话列表失败:", error);
|
||||
}
|
||||
} finally {
|
||||
if (!isCancelled && loadRequestRef.current === requestId) {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
initializeSessions();
|
||||
|
||||
return () => {
|
||||
isCancelled = true;
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentUserId]);
|
||||
|
||||
// 监听refreshTrigger,重新查询数据库
|
||||
// 订阅数据库变更,自动更新Store
|
||||
useEffect(() => {
|
||||
const refreshSessions = async () => {
|
||||
if (!currentUserId || refreshTrigger === 0) return;
|
||||
if (!currentUserId) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const updatedSessions =
|
||||
await MessageManager.getUserSessions(currentUserId);
|
||||
setSessions(updatedSessions);
|
||||
} catch (error) {
|
||||
console.error("刷新会话列表失败:", error);
|
||||
}
|
||||
};
|
||||
const unsubscribe = MessageManager.onSessionsUpdate(
|
||||
({ userId: ownerId, sessions: updatedSessions }) => {
|
||||
if (ownerId !== currentUserId) return;
|
||||
setSessionState(updatedSessions);
|
||||
},
|
||||
);
|
||||
|
||||
refreshSessions();
|
||||
}, [refreshTrigger, currentUserId]);
|
||||
return unsubscribe;
|
||||
}, [currentUserId, setSessionState]);
|
||||
|
||||
// 根据客服和搜索关键词筛选会话
|
||||
useEffect(() => {
|
||||
@@ -689,8 +670,7 @@ const MessageList: React.FC<MessageListProps> = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 触发静默刷新:通知组件从数据库重新查询
|
||||
triggerRefresh();
|
||||
// MessageManager 的回调会自动把最新数据发给 Store
|
||||
};
|
||||
|
||||
window.addEventListener(
|
||||
@@ -718,7 +698,7 @@ const MessageList: React.FC<MessageListProps> = () => {
|
||||
// 标记为已读(不更新时间和排序)
|
||||
if (session.config.unreadCount > 0) {
|
||||
// 立即更新UI(只更新未读数量)
|
||||
setSessions(prev =>
|
||||
setSessionState(prev =>
|
||||
prev.map(s =>
|
||||
s.id === session.id
|
||||
? { ...s, config: { ...s.config, unreadCount: 0 } }
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { ChatSession } from "@/utils/db";
|
||||
|
||||
export interface Message {
|
||||
id: number;
|
||||
wechatId: string;
|
||||
@@ -26,13 +28,15 @@ export interface Message {
|
||||
}
|
||||
|
||||
//Store State - 会话列表状态管理(不存储数据,只管理状态)
|
||||
export type SessionsUpdater =
|
||||
| ChatSession[]
|
||||
| ((previous: ChatSession[]) => ChatSession[]);
|
||||
|
||||
export interface MessageState {
|
||||
//加载状态
|
||||
loading: boolean;
|
||||
//后台同步状态
|
||||
refreshing: boolean;
|
||||
//刷新触发器(用于通知组件重新查询数据库)
|
||||
refreshTrigger: number;
|
||||
//最后刷新时间
|
||||
lastRefreshTime: string | null;
|
||||
//是否已经加载过一次(避免重复请求)
|
||||
@@ -42,8 +46,6 @@ export interface MessageState {
|
||||
setLoading: (loading: boolean) => void;
|
||||
//设置同步状态
|
||||
setRefreshing: (refreshing: boolean) => void;
|
||||
//触发刷新(通知组件重新查询)
|
||||
triggerRefresh: () => void;
|
||||
//设置已加载标识
|
||||
setHasLoadedOnce: (loaded: boolean) => void;
|
||||
//重置加载状态(用于登出或切换用户)
|
||||
@@ -60,4 +62,16 @@ export interface MessageState {
|
||||
updateMessageStatus: (messageId: number, status: string) => void;
|
||||
//更新当前选中的消息(废弃,保留兼容)
|
||||
updateCurrentMessage: (message: Message) => void;
|
||||
|
||||
// ==================== 新的会话数据接口 ====================
|
||||
// 当前会话列表
|
||||
sessions: ChatSession[];
|
||||
// 设置或更新会话列表(支持回调写法)
|
||||
setSessions: (updater: SessionsUpdater) => void;
|
||||
// 新增或替换某个会话
|
||||
upsertSession: (session: ChatSession) => void;
|
||||
// 按 ID 和类型移除会话
|
||||
removeSessionById: (sessionId: number, type: ChatSession["type"]) => void;
|
||||
// 清空所有会话(登出/切账号使用)
|
||||
clearSessions: () => void;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,43 @@
|
||||
import { create } from "zustand";
|
||||
import { persist } from "zustand/middleware";
|
||||
import { Message, MessageState } from "./message.data";
|
||||
import { ChatSession } from "@/utils/db";
|
||||
import { Message, MessageState, SessionsUpdater } from "./message.data";
|
||||
|
||||
const computeSortKey = (session: ChatSession) => {
|
||||
const isTop = session.config?.top ? 1 : 0;
|
||||
const timestamp = new Date(session.lastUpdateTime || new Date()).getTime();
|
||||
const displayName = (
|
||||
session.conRemark ||
|
||||
session.nickname ||
|
||||
(session as any).wechatId ||
|
||||
""
|
||||
).toLowerCase();
|
||||
|
||||
return `${isTop}|${timestamp}|${displayName}`;
|
||||
};
|
||||
|
||||
const normalizeSessions = (sessions: ChatSession[]) => {
|
||||
if (!Array.isArray(sessions)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [...sessions]
|
||||
.map(session => ({
|
||||
...session,
|
||||
sortKey: computeSortKey(session),
|
||||
}))
|
||||
.sort((a, b) => b.sortKey.localeCompare(a.sortKey));
|
||||
};
|
||||
|
||||
const resolveUpdater = (
|
||||
updater: SessionsUpdater,
|
||||
previous: ChatSession[],
|
||||
): ChatSession[] => {
|
||||
if (typeof updater === "function") {
|
||||
return updater(previous);
|
||||
}
|
||||
return updater;
|
||||
};
|
||||
|
||||
/**
|
||||
* 会话列表状态管理Store
|
||||
@@ -13,24 +50,18 @@ export const useMessageStore = create<MessageState>()(
|
||||
// ==================== 新增状态管理 ====================
|
||||
loading: false,
|
||||
refreshing: false,
|
||||
refreshTrigger: 0,
|
||||
lastRefreshTime: null,
|
||||
hasLoadedOnce: false,
|
||||
|
||||
setLoading: (loading: boolean) => set({ loading }),
|
||||
setRefreshing: (refreshing: boolean) => set({ refreshing }),
|
||||
triggerRefresh: () =>
|
||||
set({
|
||||
refreshTrigger: get().refreshTrigger + 1,
|
||||
lastRefreshTime: new Date().toISOString(),
|
||||
}),
|
||||
setHasLoadedOnce: (loaded: boolean) => set({ hasLoadedOnce: loaded }),
|
||||
resetLoadState: () =>
|
||||
set({
|
||||
hasLoadedOnce: false,
|
||||
loading: false,
|
||||
refreshing: false,
|
||||
refreshTrigger: 0,
|
||||
sessions: [],
|
||||
}),
|
||||
|
||||
// ==================== 保留原有接口(向后兼容) ====================
|
||||
@@ -45,6 +76,45 @@ export const useMessageStore = create<MessageState>()(
|
||||
message.id === messageId ? { ...message, status } : message,
|
||||
),
|
||||
}),
|
||||
|
||||
// ==================== 会话数据接口 ====================
|
||||
sessions: [],
|
||||
setSessions: (updater: SessionsUpdater) =>
|
||||
set(state => ({
|
||||
sessions: normalizeSessions(resolveUpdater(updater, state.sessions)),
|
||||
lastRefreshTime: new Date().toISOString(),
|
||||
})),
|
||||
upsertSession: (session: ChatSession) =>
|
||||
set(state => {
|
||||
const next = [...state.sessions];
|
||||
const index = next.findIndex(
|
||||
s => s.id === session.id && s.type === session.type,
|
||||
);
|
||||
|
||||
if (index > -1) {
|
||||
next[index] = session;
|
||||
} else {
|
||||
next.push(session);
|
||||
}
|
||||
return {
|
||||
sessions: normalizeSessions(next),
|
||||
lastRefreshTime: new Date().toISOString(),
|
||||
};
|
||||
}),
|
||||
removeSessionById: (sessionId: number, type: ChatSession["type"]) =>
|
||||
set(state => ({
|
||||
sessions: normalizeSessions(
|
||||
state.sessions.filter(
|
||||
s => !(s.id === sessionId && s.type === type),
|
||||
),
|
||||
),
|
||||
lastRefreshTime: new Date().toISOString(),
|
||||
})),
|
||||
clearSessions: () =>
|
||||
set({
|
||||
sessions: [],
|
||||
lastRefreshTime: new Date().toISOString(),
|
||||
}),
|
||||
}),
|
||||
{
|
||||
name: "message-storage",
|
||||
@@ -105,11 +175,6 @@ export const setLoading = (loading: boolean) =>
|
||||
export const setRefreshing = (refreshing: boolean) =>
|
||||
useMessageStore.getState().setRefreshing(refreshing);
|
||||
|
||||
/**
|
||||
* 触发刷新(通知组件重新查询数据库)
|
||||
*/
|
||||
export const triggerRefresh = () => useMessageStore.getState().triggerRefresh();
|
||||
|
||||
/**
|
||||
* 设置已加载标识
|
||||
* @param loaded 是否已加载
|
||||
|
||||
@@ -40,6 +40,12 @@ export interface WeChatState {
|
||||
// ==================== 聊天消息管理 ====================
|
||||
/** 当前聊天的消息列表 */
|
||||
currentMessages: ChatRecord[];
|
||||
/** 当前聊天记录分页页码 */
|
||||
currentMessagesPage: number;
|
||||
/** 单页消息条数 */
|
||||
currentMessagesPageSize: number;
|
||||
/** 是否还有更多历史消息 */
|
||||
currentMessagesHasMore: boolean;
|
||||
/** 添加新消息 */
|
||||
addMessage: (message: ChatRecord) => void;
|
||||
/** 更新指定消息 */
|
||||
@@ -83,7 +89,7 @@ export interface WeChatState {
|
||||
|
||||
// ==================== 消息加载方法 ====================
|
||||
/** 加载聊天消息 */
|
||||
loadChatMessages: (Init: boolean, To?: number) => Promise<void>;
|
||||
loadChatMessages: (Init: boolean, pageOverride?: number) => Promise<void>;
|
||||
/** 搜索消息 */
|
||||
SearchMessage: (params: {
|
||||
From: number;
|
||||
|
||||
@@ -28,6 +28,7 @@ let pendingMessages: ChatRecord[] = []; // 待处理的消息队列
|
||||
let currentAiGenerationId: string | null = null; // 当前AI生成的唯一ID
|
||||
const AI_REQUEST_DELAY = 3000; // 3秒延迟
|
||||
const FILE_MESSAGE_TYPE = "file";
|
||||
const DEFAULT_MESSAGE_PAGE_SIZE = 20;
|
||||
|
||||
type FileMessagePayload = {
|
||||
type?: string;
|
||||
@@ -120,6 +121,108 @@ const isFileLikeMessage = (msg: ChatRecord): boolean => {
|
||||
return false;
|
||||
};
|
||||
|
||||
const normalizeMessages = (source: any): ChatRecord[] => {
|
||||
if (Array.isArray(source)) {
|
||||
return source;
|
||||
}
|
||||
if (Array.isArray(source?.list)) {
|
||||
return source.list;
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
const parseTimeValue = (value: unknown): number => {
|
||||
if (value === null || value === undefined) {
|
||||
return 0;
|
||||
}
|
||||
if (typeof value === "number") {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
const numeric = Number(value);
|
||||
if (!Number.isNaN(numeric)) {
|
||||
return numeric;
|
||||
}
|
||||
const parsed = Date.parse(value);
|
||||
if (!Number.isNaN(parsed)) {
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
if (value instanceof Date) {
|
||||
return value.getTime();
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
const getMessageTimestamp = (msg: ChatRecord): number => {
|
||||
const candidates = [
|
||||
(msg as any)?.wechatTime,
|
||||
(msg as any)?.createTime,
|
||||
(msg as any)?.msgTime,
|
||||
(msg as any)?.timestamp,
|
||||
(msg as any)?.time,
|
||||
];
|
||||
|
||||
for (const candidate of candidates) {
|
||||
const parsed = parseTimeValue(candidate);
|
||||
if (parsed) {
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
|
||||
return typeof msg.id === "number" ? msg.id : 0;
|
||||
};
|
||||
|
||||
const sortMessagesByTime = (messages: ChatRecord[]): ChatRecord[] => {
|
||||
return [...messages].sort(
|
||||
(a, b) => getMessageTimestamp(a) - getMessageTimestamp(b),
|
||||
);
|
||||
};
|
||||
|
||||
const resolvePaginationState = (
|
||||
source: any,
|
||||
requestedPage: number,
|
||||
requestedLimit: number,
|
||||
listLength: number,
|
||||
) => {
|
||||
const page =
|
||||
typeof source?.page === "number"
|
||||
? source.page
|
||||
: typeof source?.current === "number"
|
||||
? source.current
|
||||
: requestedPage;
|
||||
|
||||
const limit =
|
||||
typeof source?.limit === "number"
|
||||
? source.limit
|
||||
: typeof source?.pageSize === "number"
|
||||
? source.pageSize
|
||||
: requestedLimit;
|
||||
|
||||
let hasMore: boolean;
|
||||
if (typeof source?.hasNext === "boolean") {
|
||||
hasMore = source.hasNext;
|
||||
} else if (typeof source?.hasNextPage === "boolean") {
|
||||
hasMore = source.hasNextPage;
|
||||
} else if (typeof source?.pages === "number") {
|
||||
hasMore = page < source.pages;
|
||||
} else if (typeof source?.total === "number" && limit > 0) {
|
||||
hasMore = page * limit < source.total;
|
||||
} else {
|
||||
hasMore = listLength >= limit && listLength > 0;
|
||||
}
|
||||
|
||||
if (listLength === 0) {
|
||||
hasMore = false;
|
||||
}
|
||||
|
||||
return {
|
||||
page,
|
||||
limit: limit || requestedLimit || DEFAULT_MESSAGE_PAGE_SIZE,
|
||||
hasMore,
|
||||
};
|
||||
};
|
||||
|
||||
const normalizeFilePayload = (
|
||||
payload: FileMessagePayload | null | undefined,
|
||||
msg: ChatRecord,
|
||||
@@ -348,6 +451,10 @@ export const useWeChatStore = create<WeChatState>()(
|
||||
currentContract: null,
|
||||
/** 当前聊天的消息列表 */
|
||||
currentMessages: [],
|
||||
/** 当前消息分页信息 */
|
||||
currentMessagesPage: 1,
|
||||
currentMessagesPageSize: DEFAULT_MESSAGE_PAGE_SIZE,
|
||||
currentMessagesHasMore: true,
|
||||
|
||||
// ==================== 聊天消息管理方法 ====================
|
||||
/** 添加新消息到当前聊天 */
|
||||
@@ -429,7 +536,13 @@ export const useWeChatStore = create<WeChatState>()(
|
||||
aiRequestTimer = null;
|
||||
}
|
||||
pendingMessages = [];
|
||||
set({ currentContract: null, currentMessages: [] });
|
||||
set({
|
||||
currentContract: null,
|
||||
currentMessages: [],
|
||||
currentMessagesPage: 1,
|
||||
currentMessagesHasMore: true,
|
||||
currentMessagesPageSize: DEFAULT_MESSAGE_PAGE_SIZE,
|
||||
});
|
||||
},
|
||||
/** 设置当前联系人并加载相关数据 */
|
||||
setCurrentContact: (contract: ContractData | weChatGroup) => {
|
||||
@@ -443,7 +556,13 @@ export const useWeChatStore = create<WeChatState>()(
|
||||
|
||||
const state = useWeChatStore.getState();
|
||||
// 切换联系人时清空当前消息,等待重新加载
|
||||
set({ currentMessages: [], isLoadingAiChat: false });
|
||||
set({
|
||||
currentMessages: [],
|
||||
currentMessagesPage: 1,
|
||||
currentMessagesHasMore: true,
|
||||
currentMessagesPageSize: DEFAULT_MESSAGE_PAGE_SIZE,
|
||||
isLoadingAiChat: false,
|
||||
});
|
||||
|
||||
const params: any = {};
|
||||
|
||||
@@ -468,62 +587,91 @@ export const useWeChatStore = create<WeChatState>()(
|
||||
id: contract.id,
|
||||
config: { chat: true },
|
||||
});
|
||||
state.loadChatMessages(true, 4704624000000);
|
||||
state.loadChatMessages(true);
|
||||
},
|
||||
|
||||
// ==================== 消息加载方法 ====================
|
||||
/** 加载聊天消息 */
|
||||
loadChatMessages: async (Init: boolean, To?: number) => {
|
||||
loadChatMessages: async (Init: boolean, pageOverride?: number) => {
|
||||
const state = useWeChatStore.getState();
|
||||
const contact = state.currentContract;
|
||||
set({ messagesLoading: true });
|
||||
set({ isLoadingData: Init });
|
||||
|
||||
if (!contact) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Init && !state.currentMessagesHasMore) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextPage = Init
|
||||
? 1
|
||||
: (pageOverride ?? state.currentMessagesPage + 1);
|
||||
const limit =
|
||||
state.currentMessagesPageSize || DEFAULT_MESSAGE_PAGE_SIZE;
|
||||
|
||||
if (state.messagesLoading && !Init) {
|
||||
return;
|
||||
}
|
||||
|
||||
set({
|
||||
messagesLoading: true,
|
||||
isLoadingData: Init,
|
||||
});
|
||||
|
||||
try {
|
||||
const params: any = {
|
||||
wechatAccountId: contact.wechatAccountId,
|
||||
From: 1,
|
||||
To: To || +new Date(),
|
||||
Count: 20,
|
||||
olderData: true,
|
||||
page: nextPage,
|
||||
limit,
|
||||
};
|
||||
|
||||
if ("chatroomId" in contact && contact.chatroomId) {
|
||||
// 群聊消息加载
|
||||
const isGroup =
|
||||
"chatroomId" in contact && Boolean(contact.chatroomId);
|
||||
|
||||
if (isGroup) {
|
||||
params.wechatChatroomId = contact.id;
|
||||
const messages = await getChatroomMessages(params);
|
||||
const currentGroupMembers = await getGroupMembers({
|
||||
} else {
|
||||
params.wechatFriendId = contact.id;
|
||||
}
|
||||
|
||||
const response = isGroup
|
||||
? await getChatroomMessages(params)
|
||||
: await getChatMessages(params);
|
||||
|
||||
const normalizedMessages = normalizeMessages(response);
|
||||
const sortedMessages = sortMessagesByTime(normalizedMessages);
|
||||
const paginationMeta = resolvePaginationState(
|
||||
response,
|
||||
nextPage,
|
||||
limit,
|
||||
sortedMessages.length,
|
||||
);
|
||||
|
||||
let nextGroupMembers = state.currentGroupMembers;
|
||||
if (Init && isGroup) {
|
||||
nextGroupMembers = await getGroupMembers({
|
||||
id: contact.id,
|
||||
});
|
||||
if (Init) {
|
||||
set({ currentMessages: messages || [], currentGroupMembers });
|
||||
} else {
|
||||
set({
|
||||
currentMessages: [
|
||||
...(messages.list || []),
|
||||
...state.currentMessages,
|
||||
],
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// 私聊消息加载
|
||||
params.wechatFriendId = contact.id;
|
||||
const messages = await getChatMessages(params);
|
||||
if (Init) {
|
||||
set({ currentMessages: messages || [] });
|
||||
} else {
|
||||
set({
|
||||
currentMessages: [
|
||||
...(messages.list || []),
|
||||
...state.currentMessages,
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
set({ messagesLoading: false });
|
||||
|
||||
set(current => ({
|
||||
currentMessages: Init
|
||||
? sortedMessages
|
||||
: [...sortedMessages, ...current.currentMessages],
|
||||
currentGroupMembers:
|
||||
Init && isGroup ? nextGroupMembers : current.currentGroupMembers,
|
||||
currentMessagesPage: paginationMeta.page,
|
||||
currentMessagesPageSize: paginationMeta.limit,
|
||||
currentMessagesHasMore: paginationMeta.hasMore,
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error("获取聊天消息失败:", error);
|
||||
} finally {
|
||||
set({ messagesLoading: false });
|
||||
set({
|
||||
messagesLoading: false,
|
||||
isLoadingData: false,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -546,11 +694,11 @@ export const useWeChatStore = create<WeChatState>()(
|
||||
try {
|
||||
const params: any = {
|
||||
wechatAccountId: contact.wechatAccountId,
|
||||
keyword,
|
||||
From,
|
||||
To,
|
||||
keyword,
|
||||
Count,
|
||||
olderData: true,
|
||||
page: 1,
|
||||
limit: Count,
|
||||
};
|
||||
|
||||
if ("chatroomId" in contact && contact.chatroomId) {
|
||||
@@ -560,12 +708,23 @@ export const useWeChatStore = create<WeChatState>()(
|
||||
const currentGroupMembers = await getGroupMembers({
|
||||
id: contact.id,
|
||||
});
|
||||
set({ currentMessages: messages || [], currentGroupMembers });
|
||||
set({
|
||||
currentMessages: sortMessagesByTime(normalizeMessages(messages)),
|
||||
currentGroupMembers,
|
||||
currentMessagesPage: 1,
|
||||
currentMessagesHasMore: false,
|
||||
currentMessagesPageSize: Count || state.currentMessagesPageSize,
|
||||
});
|
||||
} else {
|
||||
// 私聊消息搜索
|
||||
params.wechatFriendId = contact.id;
|
||||
const messages = await getChatMessages(params);
|
||||
set({ currentMessages: messages || [] });
|
||||
set({
|
||||
currentMessages: sortMessagesByTime(normalizeMessages(messages)),
|
||||
currentMessagesPage: 1,
|
||||
currentMessagesHasMore: false,
|
||||
currentMessagesPageSize: Count || state.currentMessagesPageSize,
|
||||
});
|
||||
}
|
||||
set({ messagesLoading: false });
|
||||
} catch (error) {
|
||||
@@ -831,6 +990,9 @@ export const useWeChatStore = create<WeChatState>()(
|
||||
set({
|
||||
currentContract: null,
|
||||
currentMessages: [],
|
||||
currentMessagesPage: 1,
|
||||
currentMessagesHasMore: true,
|
||||
currentMessagesPageSize: DEFAULT_MESSAGE_PAGE_SIZE,
|
||||
messagesLoading: false,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -25,8 +25,15 @@ const serializeExtendFields = (value: any) => {
|
||||
return "{}";
|
||||
};
|
||||
|
||||
interface SessionUpdatePayload {
|
||||
userId: number;
|
||||
sessions: ChatSession[];
|
||||
}
|
||||
|
||||
export class MessageManager {
|
||||
private static updateCallbacks = new Set<(sessions: ChatSession[]) => void>();
|
||||
private static updateCallbacks = new Set<
|
||||
(payload: SessionUpdatePayload) => void
|
||||
>();
|
||||
|
||||
// ==================== 回调管理 ====================
|
||||
|
||||
@@ -35,9 +42,11 @@ export class MessageManager {
|
||||
* @param callback 回调函数
|
||||
* @returns 取消注册的函数
|
||||
*/
|
||||
static onSessionsUpdate(callback: (sessions: ChatSession[]) => void) {
|
||||
static onSessionsUpdate(callback: (payload: SessionUpdatePayload) => void) {
|
||||
this.updateCallbacks.add(callback);
|
||||
return () => this.updateCallbacks.delete(callback);
|
||||
return () => {
|
||||
this.updateCallbacks.delete(callback);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,7 +58,7 @@ export class MessageManager {
|
||||
const sessions = await this.getUserSessions(userId);
|
||||
this.updateCallbacks.forEach(callback => {
|
||||
try {
|
||||
callback(sessions);
|
||||
callback({ userId, sessions });
|
||||
} catch (error) {
|
||||
console.error("会话更新回调执行失败:", error);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user