From 4684e880b18fb10f9d1eed8428c14bd589289ec0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Fri, 14 Nov 2025 18:23:54 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=E8=81=8A=E5=A4=A9=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E5=A4=84=E7=90=86=E5=92=8C=E5=88=86=E9=A1=B5=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E3=80=82=E4=B8=BA=E6=B6=88=E6=81=AF=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E5=BC=95=E5=85=A5=E4=B8=80=E4=B8=AA=E6=96=B0?= =?UTF-8?q?=E7=9A=84=E6=8E=A5=E5=8F=A3=EF=BC=8C=E4=BB=A5=E5=A2=9E=E5=BC=BA?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E5=AE=89=E5=85=A8=E6=80=A7=E3=80=82=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E8=8E=B7=E5=8F=96=E8=81=8A=E5=A4=A9=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E5=92=8C=E8=81=8A=E5=A4=A9=E5=AE=A4=E6=B6=88=E6=81=AF=E7=9A=84?= =?UTF-8?q?API=E8=B0=83=E7=94=A8=EF=BC=8C=E4=BB=A5=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=88=86=E9=A1=B5=E3=80=82=E9=80=9A=E8=BF=87=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E4=B8=8D=E5=BF=85=E8=A6=81=E7=9A=84=E5=88=B7=E6=96=B0=E8=A7=A6?= =?UTF-8?q?=E5=8F=91=E5=99=A8=E5=92=8C=E4=BC=98=E5=8C=96=E4=BC=9A=E8=AF=9D?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E7=AE=A1=E7=90=86=E6=9D=A5=E4=BC=98=E5=8C=96?= =?UTF-8?q?MessageList=E7=BB=84=E4=BB=B6=E3=80=82=E6=94=B9=E8=BF=9B?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E7=8A=B6=E6=80=81=E5=A4=84=E7=90=86=EF=BC=8C?= =?UTF-8?q?=E5=B9=B6=E7=A1=AE=E4=BF=9D=E7=BB=84=E4=BB=B6=E4=B9=8B=E9=97=B4?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E5=8A=A0=E8=BD=BD=E8=A1=8C=E4=B8=BA=E7=9A=84?= =?UTF-8?q?=E4=B8=80=E8=87=B4=E6=80=A7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Touchkebao/src/pages/pc/ckbox/api.ts | 48 ++-- .../components/toContract/index.tsx | 11 +- .../components/MessageRecord/index.tsx | 53 ++-- .../SidebarMenu/MessageList/index.tsx | 158 +++++------ .../src/store/module/weChat/message.data.ts | 22 +- Touchkebao/src/store/module/weChat/message.ts | 91 ++++++- .../src/store/module/weChat/weChat.data.ts | 8 +- Touchkebao/src/store/module/weChat/weChat.ts | 252 ++++++++++++++---- Touchkebao/src/utils/dbAction/message.ts | 17 +- 9 files changed, 448 insertions(+), 212 deletions(-) diff --git a/Touchkebao/src/pages/pc/ckbox/api.ts b/Touchkebao/src/pages/pc/ckbox/api.ts index 3840caff..f5717fa7 100644 --- a/Touchkebao/src/pages/pc/ckbox/api.ts +++ b/Touchkebao/src/pages/pc/ckbox/api.ts @@ -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"); } //=====================旧============================== diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/toContract/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/toContract/index.tsx index 21dfa37a..1b4d86f7 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/toContract/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/toContract/index.tsx @@ -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 = ({ await ContactManager.deleteContact(currentContact.id); console.log("✅ 已从联系人数据库删除"); - // 3. 触发会话列表刷新 - triggerRefresh(); - - // 4. 清空当前选中的联系人(关闭聊天窗口) + // 3. 清空当前选中的联系人(关闭聊天窗口) clearCurrentContact(); message.success("转接成功,已清理本地数据"); @@ -167,10 +163,7 @@ const ToContract: React.FC = ({ await ContactManager.deleteContact(currentContact.id); console.log("✅ 已从联系人数据库删除"); - // 3. 触发会话列表刷新 - triggerRefresh(); - - // 4. 清空当前选中的联系人(关闭聊天窗口) + // 3. 清空当前选中的联系人(关闭聊天窗口) clearCurrentContact(); message.success("转回成功,已清理本地数据"); diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx index 1da7da52..29a01439 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx @@ -145,6 +145,9 @@ const MessageRecord: React.FC = ({ contract }) => { const [selectedRecords, setSelectedRecords] = useState([]); 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 = ({ 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 = ({ 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 = ({ contract }) => { return (
-
loadMoreMessages()}> - 点击加载更早的信息 {messagesLoading ? : ""} +
loadMoreMessages()} + style={{ + cursor: + currentMessagesHasMore && !messagesLoading ? "pointer" : "default", + opacity: currentMessagesHasMore ? 1 : 0.6, + }} + > + {currentMessagesHasMore ? "点击加载更早的信息" : "已经没有更早的消息了"} + {messagesLoading ? : ""}
{groupMessagesByTime(currentMessages).map((group, groupIndex) => ( diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/index.tsx index 21712a08..2625b581 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/index.tsx @@ -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 = () => { // Store状态 const { loading, - refreshTrigger, hasLoadedOnce, setLoading, setHasLoadedOnce, + sessions, + setSessions: setSessionState, } = useMessageStore(); - - // 组件内部状态:会话列表数据 - const [sessions, setSessions] = useState([]); const [filteredSessions, setFilteredSessions] = useState([]); // 右键菜单相关状态 @@ -74,6 +72,8 @@ const MessageList: React.FC = () => { }); const contextMenuRef = useRef(null); + const previousUserIdRef = useRef(null); + const loadRequestRef = useRef(0); // 右键菜单事件处理 const handleContextMenu = (e: React.MouseEvent, session: ChatSession) => { @@ -105,7 +105,7 @@ const MessageList: React.FC = () => { try { // 1. 立即更新UI并重新排序(乐观更新) - setSessions(prev => { + setSessionState(prev => { const updatedSessions = prev.map(s => s.id === session.id ? { @@ -141,7 +141,7 @@ const MessageList: React.FC = () => { 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 = () => { 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 = () => { message.success("删除成功"); } catch (error) { // 4. 失败时恢复UI - setSessions(prev => [...prev, session]); + setSessionState(prev => [...prev, session]); message.error("删除失败"); } @@ -212,7 +212,7 @@ const MessageList: React.FC = () => { 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 = () => { 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 = () => { `会话同步完成: 新增${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 = () => { } } - // 触发静默刷新:通知组件从数据库重新查询 - triggerRefresh(); + // MessageManager 的回调会自动把最新数据发给 Store }; window.addEventListener( @@ -718,7 +698,7 @@ const MessageList: React.FC = () => { // 标记为已读(不更新时间和排序) if (session.config.unreadCount > 0) { // 立即更新UI(只更新未读数量) - setSessions(prev => + setSessionState(prev => prev.map(s => s.id === session.id ? { ...s, config: { ...s.config, unreadCount: 0 } } diff --git a/Touchkebao/src/store/module/weChat/message.data.ts b/Touchkebao/src/store/module/weChat/message.data.ts index 0925bbfd..e59e7506 100644 --- a/Touchkebao/src/store/module/weChat/message.data.ts +++ b/Touchkebao/src/store/module/weChat/message.data.ts @@ -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; } diff --git a/Touchkebao/src/store/module/weChat/message.ts b/Touchkebao/src/store/module/weChat/message.ts index 51503f19..c1b2d12c 100644 --- a/Touchkebao/src/store/module/weChat/message.ts +++ b/Touchkebao/src/store/module/weChat/message.ts @@ -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()( // ==================== 新增状态管理 ==================== 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()( 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 是否已加载 diff --git a/Touchkebao/src/store/module/weChat/weChat.data.ts b/Touchkebao/src/store/module/weChat/weChat.data.ts index d23dfdf8..dc9aa163 100644 --- a/Touchkebao/src/store/module/weChat/weChat.data.ts +++ b/Touchkebao/src/store/module/weChat/weChat.data.ts @@ -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; + loadChatMessages: (Init: boolean, pageOverride?: number) => Promise; /** 搜索消息 */ SearchMessage: (params: { From: number; diff --git a/Touchkebao/src/store/module/weChat/weChat.ts b/Touchkebao/src/store/module/weChat/weChat.ts index 889d528b..89a001a9 100644 --- a/Touchkebao/src/store/module/weChat/weChat.ts +++ b/Touchkebao/src/store/module/weChat/weChat.ts @@ -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()( currentContract: null, /** 当前聊天的消息列表 */ currentMessages: [], + /** 当前消息分页信息 */ + currentMessagesPage: 1, + currentMessagesPageSize: DEFAULT_MESSAGE_PAGE_SIZE, + currentMessagesHasMore: true, // ==================== 聊天消息管理方法 ==================== /** 添加新消息到当前聊天 */ @@ -429,7 +536,13 @@ export const useWeChatStore = create()( 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()( 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()( 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()( 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()( 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()( set({ currentContract: null, currentMessages: [], + currentMessagesPage: 1, + currentMessagesHasMore: true, + currentMessagesPageSize: DEFAULT_MESSAGE_PAGE_SIZE, messagesLoading: false, }); }, diff --git a/Touchkebao/src/utils/dbAction/message.ts b/Touchkebao/src/utils/dbAction/message.ts index d42289bf..e34041b9 100644 --- a/Touchkebao/src/utils/dbAction/message.ts +++ b/Touchkebao/src/utils/dbAction/message.ts @@ -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); }