重构SidebarMenu组件,移除不必要的useEffect,优化消息列表和联系人组件的加载逻辑,合并样式文件以提升代码可读性和维护性。
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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<MessageListProps> = () => {
|
||||
@@ -33,8 +31,13 @@ const MessageList: React.FC<MessageListProps> = () => {
|
||||
const currentUserId = user?.id || 0;
|
||||
|
||||
// Store状态
|
||||
const { loading, refreshing, refreshTrigger, setLoading, setRefreshing } =
|
||||
useMessageStore();
|
||||
const {
|
||||
loading,
|
||||
refreshTrigger,
|
||||
hasLoadedOnce,
|
||||
setLoading,
|
||||
setHasLoadedOnce,
|
||||
} = useMessageStore();
|
||||
|
||||
// 组件内部状态:会话列表数据
|
||||
const [sessions, setSessions] = useState<ChatSession[]>([]);
|
||||
@@ -346,6 +349,18 @@ const MessageList: React.FC<MessageListProps> = () => {
|
||||
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<MessageListProps> = () => {
|
||||
setSessions(cachedSessions);
|
||||
setLoading(false);
|
||||
|
||||
// 2. 后台静默同步
|
||||
setRefreshing(true);
|
||||
// 2. 后台静默同步(不显示同步提示)
|
||||
await syncWithServer();
|
||||
setHasLoadedOnce(true); // 标记已加载过
|
||||
} else {
|
||||
// 无缓存,直接API加载
|
||||
await syncWithServer();
|
||||
@@ -368,12 +383,11 @@ const MessageList: React.FC<MessageListProps> = () => {
|
||||
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<MessageListProps> = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 渲染骨架屏
|
||||
const renderSkeleton = () => (
|
||||
<div className={styles.skeletonContainer}>
|
||||
{Array(8)
|
||||
.fill(null)
|
||||
.map((_, index) => (
|
||||
<div key={`skeleton-${index}`} className={styles.skeletonItem}>
|
||||
<Skeleton.Avatar active size={48} shape="circle" />
|
||||
<div className={styles.skeletonContent}>
|
||||
<div className={styles.skeletonHeader}>
|
||||
<Skeleton.Input active size="small" style={{ width: "40%" }} />
|
||||
<Skeleton.Input active size="small" style={{ width: "20%" }} />
|
||||
</div>
|
||||
<Skeleton.Input
|
||||
active
|
||||
size="small"
|
||||
style={{ width: "70%", marginTop: "8px" }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.messageList}>
|
||||
{loading && <div className={styles.loading}>加载中...</div>}
|
||||
{refreshing && !loading && (
|
||||
<div className={styles.refreshingTip}>正在同步消息...</div>
|
||||
)}
|
||||
|
||||
<List
|
||||
dataSource={filteredSessions as any[]}
|
||||
renderItem={session => (
|
||||
<List.Item
|
||||
key={session.id}
|
||||
className={`${styles.messageItem} ${
|
||||
currentContract?.id === session.id ? styles.active : ""
|
||||
} ${(session.config as any)?.top ? styles.pinned : ""}`}
|
||||
onClick={() => onContactClick(session)}
|
||||
onContextMenu={e => handleContextMenu(e, session)}
|
||||
>
|
||||
<div className={styles.messageInfo}>
|
||||
<Badge count={session.config.unreadCount || 0} size="small">
|
||||
<Avatar
|
||||
size={48}
|
||||
src={session.avatar || session.chatroomAvatar}
|
||||
icon={
|
||||
session?.type === "group" ? (
|
||||
<TeamOutlined />
|
||||
) : (
|
||||
<UserOutlined />
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Badge>
|
||||
<div className={styles.messageDetails}>
|
||||
<div className={styles.messageHeader}>
|
||||
<div className={styles.messageName}>
|
||||
{session.conRemark || session.nickname || session.wechatId}
|
||||
</div>
|
||||
<div className={styles.messageTime}>
|
||||
{formatWechatTime(session?.lastUpdateTime)}
|
||||
{loading ? (
|
||||
// 加载状态:显示骨架屏
|
||||
renderSkeleton()
|
||||
) : (
|
||||
<>
|
||||
<List
|
||||
dataSource={filteredSessions as any[]}
|
||||
renderItem={session => (
|
||||
<List.Item
|
||||
key={session.id}
|
||||
className={`${styles.messageItem} ${
|
||||
currentContract?.id === session.id ? styles.active : ""
|
||||
} ${(session.config as any)?.top ? styles.pinned : ""}`}
|
||||
onClick={() => onContactClick(session)}
|
||||
onContextMenu={e => handleContextMenu(e, session)}
|
||||
>
|
||||
<div className={styles.messageInfo}>
|
||||
<Badge count={session.config.unreadCount || 0} size="small">
|
||||
<Avatar
|
||||
size={48}
|
||||
src={session.avatar || session.chatroomAvatar}
|
||||
icon={
|
||||
session?.type === "group" ? (
|
||||
<TeamOutlined />
|
||||
) : (
|
||||
<UserOutlined />
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Badge>
|
||||
<div className={styles.messageDetails}>
|
||||
<div className={styles.messageHeader}>
|
||||
<div className={styles.messageName}>
|
||||
{session.conRemark ||
|
||||
session.nickname ||
|
||||
session.wechatId}
|
||||
</div>
|
||||
<div className={styles.messageTime}>
|
||||
{formatWechatTime(session?.lastUpdateTime)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.messageContent}>
|
||||
{session.content}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.messageContent}>{session.content}</div>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* 右键菜单 */}
|
||||
{contextMenu.visible && contextMenu.session && (
|
||||
<div
|
||||
ref={contextMenuRef}
|
||||
className={styles.contextMenu}
|
||||
style={{
|
||||
position: "fixed",
|
||||
left: contextMenu.x,
|
||||
top: contextMenu.y,
|
||||
zIndex: 1000,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={styles.menuItem}
|
||||
onClick={() => handleTogglePin(contextMenu.session!)}
|
||||
>
|
||||
<PushpinOutlined />
|
||||
{(contextMenu.session.config as any)?.top ? "取消置顶" : "置顶"}
|
||||
</div>
|
||||
<div
|
||||
className={styles.menuItem}
|
||||
onClick={() => handleEditRemark(contextMenu.session!)}
|
||||
>
|
||||
<EditOutlined />
|
||||
修改备注
|
||||
</div>
|
||||
<div
|
||||
className={styles.menuItem}
|
||||
onClick={() => handleDelete(contextMenu.session!)}
|
||||
>
|
||||
<DeleteOutlined />
|
||||
删除
|
||||
</div>
|
||||
</div>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 右键菜单 */}
|
||||
{contextMenu.visible && contextMenu.session && (
|
||||
<div
|
||||
ref={contextMenuRef}
|
||||
className={styles.contextMenu}
|
||||
style={{
|
||||
position: "fixed",
|
||||
left: contextMenu.x,
|
||||
top: contextMenu.y,
|
||||
zIndex: 1000,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={styles.menuItem}
|
||||
onClick={() => handleTogglePin(contextMenu.session!)}
|
||||
{/* 修改备注Modal */}
|
||||
<Modal
|
||||
title="修改备注"
|
||||
open={editRemarkModal.visible}
|
||||
onOk={handleSaveRemark}
|
||||
onCancel={() =>
|
||||
setEditRemarkModal({
|
||||
visible: false,
|
||||
session: null,
|
||||
remark: "",
|
||||
})
|
||||
}
|
||||
okText="保存"
|
||||
cancelText="取消"
|
||||
>
|
||||
<PushpinOutlined />
|
||||
{(contextMenu.session.config as any)?.top ? "取消置顶" : "置顶"}
|
||||
</div>
|
||||
<div
|
||||
className={styles.menuItem}
|
||||
onClick={() => handleEditRemark(contextMenu.session!)}
|
||||
>
|
||||
<EditOutlined />
|
||||
修改备注
|
||||
</div>
|
||||
<div
|
||||
className={styles.menuItem}
|
||||
onClick={() => handleDelete(contextMenu.session!)}
|
||||
>
|
||||
<DeleteOutlined />
|
||||
删除
|
||||
</div>
|
||||
</div>
|
||||
<Input
|
||||
value={editRemarkModal.remark}
|
||||
onChange={e =>
|
||||
setEditRemarkModal(prev => ({
|
||||
...prev,
|
||||
remark: e.target.value,
|
||||
}))
|
||||
}
|
||||
placeholder="请输入备注"
|
||||
maxLength={20}
|
||||
/>
|
||||
</Modal>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 修改备注Modal */}
|
||||
<Modal
|
||||
title="修改备注"
|
||||
open={editRemarkModal.visible}
|
||||
onOk={handleSaveRemark}
|
||||
onCancel={() =>
|
||||
setEditRemarkModal({
|
||||
visible: false,
|
||||
session: null,
|
||||
remark: "",
|
||||
})
|
||||
}
|
||||
okText="保存"
|
||||
cancelText="取消"
|
||||
>
|
||||
<Input
|
||||
value={editRemarkModal.remark}
|
||||
onChange={e =>
|
||||
setEditRemarkModal(prev => ({
|
||||
...prev,
|
||||
remark: e.target.value,
|
||||
}))
|
||||
}
|
||||
placeholder="请输入备注"
|
||||
maxLength={20}
|
||||
/>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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<WechatFriendsProps> = ({
|
||||
selectedContactId,
|
||||
}) => {
|
||||
// 本地状态(用于数据同步,不直接用于显示)
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [contacts, setContacts] = useState<Contact[]>([]);
|
||||
// 基础状态
|
||||
const [contactGroups, setContactGroups] = useState<ContactGroupByLabel[]>([]);
|
||||
const [labels, setLabels] = useState<ContactGroupByLabel[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [activeKey, setActiveKey] = useState<string[]>([]);
|
||||
|
||||
// 分页相关状态
|
||||
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<WechatFriendsProps> = ({
|
||||
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<WechatFriendsProps> = ({
|
||||
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<WechatFriendsProps> = ({
|
||||
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<WechatFriendsProps> = ({
|
||||
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<WechatFriendsProps> = ({
|
||||
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<WechatFriendsProps> = ({
|
||||
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<WechatFriendsProps> = ({
|
||||
|
||||
// 渲染加载更多按钮
|
||||
const renderLoadMoreButton = (groupKey: string) => {
|
||||
if (!groupHasMore[groupKey]) {
|
||||
if (!groupData.hasMore[groupKey]) {
|
||||
return <div className={styles.noMoreText}>没有更多了</div>;
|
||||
}
|
||||
|
||||
@@ -316,9 +319,9 @@ const ContactListSimple: React.FC<WechatFriendsProps> = ({
|
||||
<button
|
||||
className={styles.loadMoreBtn}
|
||||
onClick={() => handleLoadMore(groupKey)}
|
||||
disabled={groupLoading[groupKey]}
|
||||
disabled={groupData.loading[groupKey]}
|
||||
>
|
||||
{groupLoading[groupKey] ? "加载中..." : "加载更多"}
|
||||
{groupData.loading[groupKey] ? "加载中..." : "加载更多"}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
@@ -343,7 +346,7 @@ const ContactListSimple: React.FC<WechatFriendsProps> = ({
|
||||
className: styles.groupPanel,
|
||||
children: isActive ? (
|
||||
<>
|
||||
{groupLoading[groupKey] && !groupContacts[groupKey] ? (
|
||||
{groupData.loading[groupKey] && !groupData.contacts[groupKey] ? (
|
||||
// 首次加载显示骨架屏
|
||||
<div className={styles.groupSkeleton}>
|
||||
{Array(3)
|
||||
@@ -365,10 +368,10 @@ const ContactListSimple: React.FC<WechatFriendsProps> = ({
|
||||
<>
|
||||
<List
|
||||
className={styles.list}
|
||||
dataSource={groupContacts[groupKey] || []}
|
||||
dataSource={groupData.contacts[groupKey] || []}
|
||||
renderItem={renderContactItem}
|
||||
/>
|
||||
{(groupContacts[groupKey]?.length || 0) > 0 &&
|
||||
{(groupData.contacts[groupKey]?.length || 0) > 0 &&
|
||||
renderLoadMoreButton(groupKey)}
|
||||
</>
|
||||
)}
|
||||
@@ -381,7 +384,7 @@ const ContactListSimple: React.FC<WechatFriendsProps> = ({
|
||||
return (
|
||||
<div className={styles.contractListSimple}>
|
||||
{loading ? (
|
||||
// 加载状态:显示骨架屏
|
||||
// 加载状态:显示骨架屏(只在首次无本地数据时显示)
|
||||
renderSkeleton()
|
||||
) : isSearchMode ? (
|
||||
// 搜索模式:直接显示搜索结果列表
|
||||
@@ -399,9 +402,6 @@ const ContactListSimple: React.FC<WechatFriendsProps> = ({
|
||||
) : (
|
||||
// 正常模式:显示分组列表
|
||||
<>
|
||||
{refreshing && (
|
||||
<div className={styles.refreshingTip}>正在同步联系人...</div>
|
||||
)}
|
||||
<Collapse
|
||||
className={styles.groupCollapse}
|
||||
activeKey={activeKey}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useState } from "react";
|
||||
import { Input, Skeleton } from "antd";
|
||||
import { SearchOutlined, ChromeOutlined } from "@ant-design/icons";
|
||||
import { SearchOutlined } from "@ant-design/icons";
|
||||
import WechatFriends from "./WechatFriends";
|
||||
import MessageList from "./MessageList/index";
|
||||
import FriendsCircle from "./FriendsCicle";
|
||||
@@ -26,10 +26,6 @@ const SidebarMenu: React.FC<SidebarMenuProps> = ({ loading = false }) => {
|
||||
clearSearchKeyword();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setActiveTab("contracts");
|
||||
}, [currentCustomer]);
|
||||
|
||||
// 渲染骨架屏
|
||||
const renderSkeleton = () => (
|
||||
<div className={styles.skeletonContainer}>
|
||||
|
||||
@@ -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;
|
||||
|
||||
// ==================== 保留原有接口(向后兼容) ====================
|
||||
//消息列表(废弃,保留兼容)
|
||||
|
||||
@@ -15,6 +15,7 @@ export const useMessageStore = create<MessageState>()(
|
||||
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<MessageState>()(
|
||||
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<MessageState>()(
|
||||
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();
|
||||
|
||||
1
Touchkebao/src/utils/filter.ts
Normal file
1
Touchkebao/src/utils/filter.ts
Normal file
@@ -0,0 +1 @@
|
||||
//消息过滤器
|
||||
Reference in New Issue
Block a user