From 3b82908e8af7ebedfba71f25a2a5aa8d3a6d175b 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: Thu, 23 Oct 2025 20:42:29 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84SidebarMenu=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=EF=BC=8C=E7=A7=BB=E9=99=A4=E4=B8=8D=E5=BF=85=E8=A6=81=E7=9A=84?= =?UTF-8?q?useEffect=EF=BC=8C=E4=BC=98=E5=8C=96=E6=B6=88=E6=81=AF=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E5=92=8C=E8=81=94=E7=B3=BB=E4=BA=BA=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E7=9A=84=E5=8A=A0=E8=BD=BD=E9=80=BB=E8=BE=91=EF=BC=8C=E5=90=88?= =?UTF-8?q?=E5=B9=B6=E6=A0=B7=E5=BC=8F=E6=96=87=E4=BB=B6=E4=BB=A5=E6=8F=90?= =?UTF-8?q?=E5=8D=87=E4=BB=A3=E7=A0=81=E5=8F=AF=E8=AF=BB=E6=80=A7=E5=92=8C?= =?UTF-8?q?=E7=BB=B4=E6=8A=A4=E6=80=A7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...essageList.module.scss => com.module.scss} | 24 ++ .../SidebarMenu/MessageList/index.tsx | 272 ++++++++++-------- ...hatFriends.module.scss => com.module.scss} | 0 .../SidebarMenu/WechatFriends/index.tsx | 166 +++++------ .../weChat/components/SidebarMenu/index.tsx | 8 +- .../src/store/module/weChat/message.data.ts | 6 + Touchkebao/src/store/module/weChat/message.ts | 22 ++ Touchkebao/src/utils/filter.ts | 1 + 8 files changed, 296 insertions(+), 203 deletions(-) rename Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/{MessageList.module.scss => com.module.scss} (90%) rename Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/WechatFriends/{WechatFriends.module.scss => com.module.scss} (100%) create mode 100644 Touchkebao/src/utils/filter.ts diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/MessageList.module.scss b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/com.module.scss similarity index 90% rename from Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/MessageList.module.scss rename to Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/com.module.scss index 4e93b88c8..77c9db244 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/MessageList.module.scss +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/com.module.scss @@ -187,3 +187,27 @@ font-size: 12px; text-align: center; } + +// 骨架屏样式 +.skeletonContainer { + padding: 10px; +} + +.skeletonItem { + display: flex; + align-items: flex-start; + padding: 12px 16px; + gap: 12px; + border-bottom: 1px solid #f0f0f0; +} + +.skeletonContent { + flex: 1; +} + +.skeletonHeader { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; +} 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 73b083dc4..039b7154d 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 @@ -1,5 +1,5 @@ import React, { useEffect, useState, useRef } from "react"; -import { List, Avatar, Badge, Modal, Input, message, Spin } from "antd"; +import { List, Avatar, Badge, Modal, Input, message, Skeleton } from "antd"; import { UserOutlined, TeamOutlined, @@ -7,21 +7,19 @@ import { DeleteOutlined, EditOutlined, } from "@ant-design/icons"; +import styles from "./com.module.scss"; import { useWeChatStore } from "@weChatStore/weChat"; import { useMessageStore } from "@weChatStore/message"; import { useCustomerStore } from "@weChatStore/customer"; import { useContactStore } from "@weChatStore/contacts"; import { useWebSocketStore } from "@/store/module/websocket/websocket"; - import { updateConfig } from "@/pages/pc/ckbox/api"; -import { getMessageList } from "./api"; -import { dataProcessing } from "./api"; -import styles from "./MessageList.module.scss"; +import { getMessageList, dataProcessing } from "./api"; import { formatWechatTime } from "@/utils/common"; import { MessageManager } from "@/utils/dbAction/message"; import { ChatSession } from "@/utils/db"; - import { useUserStore } from "@/store/module/user"; + interface MessageListProps {} const MessageList: React.FC = () => { @@ -33,8 +31,13 @@ const MessageList: React.FC = () => { const currentUserId = user?.id || 0; // Store状态 - const { loading, refreshing, refreshTrigger, setLoading, setRefreshing } = - useMessageStore(); + const { + loading, + refreshTrigger, + hasLoadedOnce, + setLoading, + setHasLoadedOnce, + } = useMessageStore(); // 组件内部状态:会话列表数据 const [sessions, setSessions] = useState([]); @@ -346,6 +349,18 @@ const MessageList: React.FC = () => { const initializeSessions = async () => { if (!currentUserId) return; + // 如果已经加载过一次,只从本地数据库读取,不请求接口 + if (hasLoadedOnce) { + try { + const cachedSessions = + await MessageManager.getUserSessions(currentUserId); + setSessions(cachedSessions); + } catch (error) { + console.error("从本地加载会话列表失败:", error); + } + return; + } + setLoading(true); try { @@ -358,9 +373,9 @@ const MessageList: React.FC = () => { setSessions(cachedSessions); setLoading(false); - // 2. 后台静默同步 - setRefreshing(true); + // 2. 后台静默同步(不显示同步提示) await syncWithServer(); + setHasLoadedOnce(true); // 标记已加载过 } else { // 无缓存,直接API加载 await syncWithServer(); @@ -368,12 +383,11 @@ const MessageList: React.FC = () => { await MessageManager.getUserSessions(currentUserId); setSessions(newSessions); setLoading(false); + setHasLoadedOnce(true); // 标记已加载过 } } catch (error) { console.error("初始化会话列表失败:", error); setLoading(false); - } finally { - setRefreshing(false); } }; @@ -522,117 +536,147 @@ const MessageList: React.FC = () => { } }; + // 渲染骨架屏 + const renderSkeleton = () => ( +
+ {Array(8) + .fill(null) + .map((_, index) => ( +
+ +
+
+ + +
+ +
+
+ ))} +
+ ); + return (
- {loading &&
加载中...
} - {refreshing && !loading && ( -
正在同步消息...
- )} - - ( - onContactClick(session)} - onContextMenu={e => handleContextMenu(e, session)} - > -
- - - ) : ( - - ) - } - /> - -
-
-
- {session.conRemark || session.nickname || session.wechatId} -
-
- {formatWechatTime(session?.lastUpdateTime)} + {loading ? ( + // 加载状态:显示骨架屏 + renderSkeleton() + ) : ( + <> + ( + onContactClick(session)} + onContextMenu={e => handleContextMenu(e, session)} + > +
+ + + ) : ( + + ) + } + /> + +
+
+
+ {session.conRemark || + session.nickname || + session.wechatId} +
+
+ {formatWechatTime(session?.lastUpdateTime)} +
+
+
+ {session.content} +
-
{session.content}
+
+ )} + /> + + {/* 右键菜单 */} + {contextMenu.visible && contextMenu.session && ( +
+
handleTogglePin(contextMenu.session!)} + > + + {(contextMenu.session.config as any)?.top ? "取消置顶" : "置顶"} +
+
handleEditRemark(contextMenu.session!)} + > + + 修改备注 +
+
handleDelete(contextMenu.session!)} + > + + 删除
- - )} - /> + )} - {/* 右键菜单 */} - {contextMenu.visible && contextMenu.session && ( -
-
handleTogglePin(contextMenu.session!)} + {/* 修改备注Modal */} + + setEditRemarkModal({ + visible: false, + session: null, + remark: "", + }) + } + okText="保存" + cancelText="取消" > - - {(contextMenu.session.config as any)?.top ? "取消置顶" : "置顶"} -
-
handleEditRemark(contextMenu.session!)} - > - - 修改备注 -
-
handleDelete(contextMenu.session!)} - > - - 删除 -
-
+ + setEditRemarkModal(prev => ({ + ...prev, + remark: e.target.value, + })) + } + placeholder="请输入备注" + maxLength={20} + /> + + )} - - {/* 修改备注Modal */} - - setEditRemarkModal({ - visible: false, - session: null, - remark: "", - }) - } - okText="保存" - cancelText="取消" - > - - setEditRemarkModal(prev => ({ - ...prev, - remark: e.target.value, - })) - } - placeholder="请输入备注" - maxLength={20} - /> -
); }; diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/WechatFriends/WechatFriends.module.scss b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/WechatFriends/com.module.scss similarity index 100% rename from Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/WechatFriends/WechatFriends.module.scss rename to Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/WechatFriends/com.module.scss diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/WechatFriends/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/WechatFriends/index.tsx index 3c0b6b361..961f5e80e 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/WechatFriends/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/WechatFriends/index.tsx @@ -1,7 +1,7 @@ import React, { useState, useCallback, useEffect } from "react"; import { List, Avatar, Skeleton, Collapse } from "antd"; import type { CollapseProps } from "antd"; -import styles from "./WechatFriends.module.scss"; +import styles from "./com.module.scss"; import { Contact } from "@/utils/db"; import { ContactManager } from "@/utils/dbAction"; import { ContactGroupByLabel } from "@/pages/pc/ckbox/data"; @@ -22,28 +22,24 @@ interface WechatFriendsProps { const ContactListSimple: React.FC = ({ selectedContactId, }) => { - // 本地状态(用于数据同步,不直接用于显示) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [contacts, setContacts] = useState([]); + // 基础状态 const [contactGroups, setContactGroups] = useState([]); const [labels, setLabels] = useState([]); const [loading, setLoading] = useState(false); - const [refreshing, setRefreshing] = useState(false); const [activeKey, setActiveKey] = useState([]); - // 分页相关状态 - const [groupContacts, setGroupContacts] = useState<{ - [groupKey: string]: Contact[]; - }>({}); - const [groupPages, setGroupPages] = useState<{ [groupKey: string]: number }>( - {}, - ); - const [groupLoading, setGroupLoading] = useState<{ - [groupKey: string]: boolean; - }>({}); - const [groupHasMore, setGroupHasMore] = useState<{ - [groupKey: string]: boolean; - }>({}); + // 分页相关状态(合并为对象,减少状态数量) + const [groupData, setGroupData] = useState<{ + contacts: { [groupKey: string]: Contact[] }; + pages: { [groupKey: string]: number }; + loading: { [groupKey: string]: boolean }; + hasMore: { [groupKey: string]: boolean }; + }>({ + contacts: {}, + pages: {}, + loading: {}, + hasMore: {}, + }); // 使用新的 contacts store const { searchResults, isSearchMode, setCurrentContact } = useContactStore(); @@ -53,19 +49,26 @@ const ContactListSimple: React.FC = ({ const currentCustomer = useCustomerStore(state => state.currentCustomer); const { setCurrentContact: setWeChatCurrentContact } = useWeChatStore(); - // 从服务器同步数据 - const syncWithServer = useCallback(async (userId: number) => { - setRefreshing(true); - - try { - const updatedContacts = await syncContactsFromServer(userId); - setContacts(updatedContacts); - } catch (error) { - console.error("同步联系人失败:", error); - } finally { - setRefreshing(false); - } - }, []); + // 从服务器同步数据(静默同步,不显示提示) + const syncWithServer = useCallback( + async (userId: number) => { + try { + await syncContactsFromServer(userId); + // 同步完成后,重新加载分组统计 + if (labels.length > 0) { + const groupStats = await getGroupStatistics( + userId, + labels, + currentCustomer?.id, + ); + setContactGroups(groupStats); + } + } catch (error) { + console.error("同步联系人失败:", error); + } + }, + [labels, currentCustomer?.id], + ); // 获取标签列表 useEffect(() => { @@ -86,23 +89,19 @@ const ContactListSimple: React.FC = ({ const loadData = async () => { if (!currentUser?.id) return; - setLoading(true); - try { - // 1. 先从本地数据库加载 + // 检查本地数据库是否有数据 const localContacts = await ContactManager.getUserContacts( currentUser.id, ); if (localContacts && localContacts.length > 0) { - // 有本地数据,立即显示 - setContacts(localContacts); - setLoading(false); - + // 有本地数据,直接显示(不显示 loading) // 后台静默同步 syncWithServer(currentUser.id); } else { - // 没有本地数据,从服务器获取 + // 没有本地数据,显示骨架屏并从服务器获取 + setLoading(true); await syncWithServer(currentUser.id); setLoading(false); } @@ -144,11 +143,13 @@ const ContactListSimple: React.FC = ({ if (!label || !currentUser?.id) return; // 如果已经加载过数据,不重复加载 - if (groupContacts[groupKey] && groupContacts[groupKey].length > 0) { - return; - } + if (groupData.contacts[groupKey]?.length > 0) return; - setGroupLoading(prev => ({ ...prev, [groupKey]: true })); + // 设置加载状态 + setGroupData(prev => ({ + ...prev, + loading: { ...prev.loading, [groupKey]: true }, + })); try { const realGroupIds = labels @@ -164,16 +165,19 @@ const ContactListSimple: React.FC = ({ 20, ); - setGroupContacts(prev => ({ ...prev, [groupKey]: contacts })); - setGroupPages(prev => ({ ...prev, [groupKey]: 1 })); - setGroupHasMore(prev => ({ - ...prev, - [groupKey]: contacts.length === 20, + // 更新分组数据 + setGroupData(prev => ({ + contacts: { ...prev.contacts, [groupKey]: contacts }, + pages: { ...prev.pages, [groupKey]: 1 }, + loading: { ...prev.loading, [groupKey]: false }, + hasMore: { ...prev.hasMore, [groupKey]: contacts.length === 20 }, })); } catch (error) { console.error("加载分组数据失败:", error); - } finally { - setGroupLoading(prev => ({ ...prev, [groupKey]: false })); + setGroupData(prev => ({ + ...prev, + loading: { ...prev.loading, [groupKey]: false }, + })); } }, [ @@ -181,24 +185,28 @@ const ContactListSimple: React.FC = ({ labels, currentUser?.id, currentCustomer?.id, - groupContacts, + groupData.contacts, ], ); // 加载更多联系人 const handleLoadMore = useCallback( async (groupKey: string) => { - if (groupLoading[groupKey] || !groupHasMore[groupKey]) return; + if (groupData.loading[groupKey] || !groupData.hasMore[groupKey]) return; const groupIndex = parseInt(groupKey); const label = contactGroups[groupIndex]; if (!label || !currentUser?.id) return; - setGroupLoading(prev => ({ ...prev, [groupKey]: true })); + // 设置加载状态 + setGroupData(prev => ({ + ...prev, + loading: { ...prev.loading, [groupKey]: true }, + })); try { - const currentPage = groupPages[groupKey] || 1; + const currentPage = groupData.pages[groupKey] || 1; const nextPage = currentPage + 1; const realGroupIds = labels @@ -214,30 +222,25 @@ const ContactListSimple: React.FC = ({ 20, ); - setGroupContacts(prev => ({ - ...prev, - [groupKey]: [...(prev[groupKey] || []), ...newContacts], - })); - setGroupPages(prev => ({ ...prev, [groupKey]: nextPage })); - setGroupHasMore(prev => ({ - ...prev, - [groupKey]: newContacts.length === 20, + // 更新分组数据 + setGroupData(prev => ({ + contacts: { + ...prev.contacts, + [groupKey]: [...(prev.contacts[groupKey] || []), ...newContacts], + }, + pages: { ...prev.pages, [groupKey]: nextPage }, + loading: { ...prev.loading, [groupKey]: false }, + hasMore: { ...prev.hasMore, [groupKey]: newContacts.length === 20 }, })); } catch (error) { console.error("加载更多联系人失败:", error); - } finally { - setGroupLoading(prev => ({ ...prev, [groupKey]: false })); + setGroupData(prev => ({ + ...prev, + loading: { ...prev.loading, [groupKey]: false }, + })); } }, - [ - contactGroups, - labels, - currentUser?.id, - currentCustomer?.id, - groupLoading, - groupHasMore, - groupPages, - ], + [contactGroups, labels, currentUser?.id, currentCustomer?.id, groupData], ); // 联系人点击处理 @@ -307,7 +310,7 @@ const ContactListSimple: React.FC = ({ // 渲染加载更多按钮 const renderLoadMoreButton = (groupKey: string) => { - if (!groupHasMore[groupKey]) { + if (!groupData.hasMore[groupKey]) { return
没有更多了
; } @@ -316,9 +319,9 @@ const ContactListSimple: React.FC = ({
); @@ -343,7 +346,7 @@ const ContactListSimple: React.FC = ({ className: styles.groupPanel, children: isActive ? ( <> - {groupLoading[groupKey] && !groupContacts[groupKey] ? ( + {groupData.loading[groupKey] && !groupData.contacts[groupKey] ? ( // 首次加载显示骨架屏
{Array(3) @@ -365,10 +368,10 @@ const ContactListSimple: React.FC = ({ <> - {(groupContacts[groupKey]?.length || 0) > 0 && + {(groupData.contacts[groupKey]?.length || 0) > 0 && renderLoadMoreButton(groupKey)} )} @@ -381,7 +384,7 @@ const ContactListSimple: React.FC = ({ return (
{loading ? ( - // 加载状态:显示骨架屏 + // 加载状态:显示骨架屏(只在首次无本地数据时显示) renderSkeleton() ) : isSearchMode ? ( // 搜索模式:直接显示搜索结果列表 @@ -399,9 +402,6 @@ const ContactListSimple: React.FC = ({ ) : ( // 正常模式:显示分组列表 <> - {refreshing && ( -
正在同步联系人...
- )} = ({ loading = false }) => { clearSearchKeyword(); }; - useEffect(() => { - setActiveTab("contracts"); - }, [currentCustomer]); - // 渲染骨架屏 const renderSkeleton = () => (
diff --git a/Touchkebao/src/store/module/weChat/message.data.ts b/Touchkebao/src/store/module/weChat/message.data.ts index 37dc1b6d2..a333e9e89 100644 --- a/Touchkebao/src/store/module/weChat/message.data.ts +++ b/Touchkebao/src/store/module/weChat/message.data.ts @@ -34,6 +34,8 @@ export interface MessageState { refreshTrigger: number; //最后刷新时间 lastRefreshTime: string | null; + //是否已经加载过一次(避免重复请求) + hasLoadedOnce: boolean; //设置加载状态 setLoading: (loading: boolean) => void; @@ -41,6 +43,10 @@ export interface MessageState { setRefreshing: (refreshing: boolean) => void; //触发刷新(通知组件重新查询) triggerRefresh: () => void; + //设置已加载标识 + setHasLoadedOnce: (loaded: boolean) => void; + //重置加载状态(用于登出或切换用户) + resetLoadState: () => void; // ==================== 保留原有接口(向后兼容) ==================== //消息列表(废弃,保留兼容) diff --git a/Touchkebao/src/store/module/weChat/message.ts b/Touchkebao/src/store/module/weChat/message.ts index 52533cee6..51503f193 100644 --- a/Touchkebao/src/store/module/weChat/message.ts +++ b/Touchkebao/src/store/module/weChat/message.ts @@ -15,6 +15,7 @@ export const useMessageStore = create()( refreshing: false, refreshTrigger: 0, lastRefreshTime: null, + hasLoadedOnce: false, setLoading: (loading: boolean) => set({ loading }), setRefreshing: (refreshing: boolean) => set({ refreshing }), @@ -23,6 +24,14 @@ export const useMessageStore = create()( refreshTrigger: get().refreshTrigger + 1, lastRefreshTime: new Date().toISOString(), }), + setHasLoadedOnce: (loaded: boolean) => set({ hasLoadedOnce: loaded }), + resetLoadState: () => + set({ + hasLoadedOnce: false, + loading: false, + refreshing: false, + refreshTrigger: 0, + }), // ==================== 保留原有接口(向后兼容) ==================== messageList: [], @@ -42,6 +51,7 @@ export const useMessageStore = create()( partialize: state => ({ // 只持久化必要的状态,不持久化数据 lastRefreshTime: state.lastRefreshTime, + hasLoadedOnce: state.hasLoadedOnce, // 保留原有持久化字段(向后兼容) messageList: [], currentMessage: null, @@ -99,3 +109,15 @@ export const setRefreshing = (refreshing: boolean) => * 触发刷新(通知组件重新查询数据库) */ export const triggerRefresh = () => useMessageStore.getState().triggerRefresh(); + +/** + * 设置已加载标识 + * @param loaded 是否已加载 + */ +export const setHasLoadedOnce = (loaded: boolean) => + useMessageStore.getState().setHasLoadedOnce(loaded); + +/** + * 重置加载状态(用于登出或切换用户) + */ +export const resetLoadState = () => useMessageStore.getState().resetLoadState(); diff --git a/Touchkebao/src/utils/filter.ts b/Touchkebao/src/utils/filter.ts new file mode 100644 index 000000000..eb8128623 --- /dev/null +++ b/Touchkebao/src/utils/filter.ts @@ -0,0 +1 @@ +//消息过滤器