import React, { useState, useCallback, useEffect, useMemo, memo } from "react"; import { Modal, Input, Avatar, Button, Checkbox, message } from "antd"; import { SearchOutlined } from "@ant-design/icons"; import { getFriendList } from "../FriendSelection/api"; import type { FriendSelectionItem } from "../FriendSelection/data"; import styles from "./TwoColumnSelection.module.scss"; // 使用 React.memo 优化好友列表项组件 const FriendListItem = memo<{ friend: FriendSelectionItem; isSelected: boolean; onSelect: (friend: FriendSelectionItem) => void; }>(({ friend, isSelected, onSelect }) => { return (
onSelect(friend)} > {friend.nickname?.charAt(0)}
{friend.nickname}
{friend.wechatId}
); }); FriendListItem.displayName = "FriendListItem"; interface TwoColumnSelectionProps { visible: boolean; onCancel: () => void; onConfirm: ( selectedIds: string[], selectedItems: FriendSelectionItem[], ) => void; title?: string; deviceIds?: number[]; enableDeviceFilter?: boolean; dataSource?: FriendSelectionItem[]; } const TwoColumnSelection: React.FC = ({ visible, onCancel, onConfirm, title = "选择好友", deviceIds = [], enableDeviceFilter = true, dataSource, }) => { const [rawFriends, setRawFriends] = useState([]); const [selectedFriends, setSelectedFriends] = useState( [], ); const [searchQuery, setSearchQuery] = useState(""); const [loading, setLoading] = useState(false); const [currentPage, setCurrentPage] = useState(1); const [totalPages, setTotalPages] = useState(1); // 使用 useMemo 缓存过滤结果,避免每次渲染都重新计算 const filteredFriends = useMemo(() => { const sourceData = dataSource || rawFriends; if (!searchQuery.trim()) { return sourceData; } const query = searchQuery.toLowerCase(); return sourceData.filter( item => item.name?.toLowerCase().includes(query) || item.nickname?.toLowerCase().includes(query), ); }, [dataSource, rawFriends, searchQuery]); // 分页显示好友列表,避免一次性渲染太多项目 const ITEMS_PER_PAGE = 50; const [displayPage, setDisplayPage] = useState(1); const friends = useMemo(() => { const startIndex = 0; const endIndex = displayPage * ITEMS_PER_PAGE; return filteredFriends.slice(startIndex, endIndex); }, [filteredFriends, displayPage]); const hasMoreFriends = filteredFriends.length > friends.length; // 使用 useMemo 缓存选中状态映射,避免每次渲染都重新计算 const selectedFriendsMap = useMemo(() => { const map = new Map(); selectedFriends.forEach(friend => { map.set(friend.id, true); }); return map; }, [selectedFriends]); // 获取好友列表 const fetchFriends = useCallback( async (page: number, keyword: string = "") => { setLoading(true); try { const params: any = { page, pageSize: 20, }; if (keyword) { params.keyword = keyword; } if (enableDeviceFilter && deviceIds.length > 0) { params.deviceIds = deviceIds; } const response = await getFriendList(params); if (response.success) { setRawFriends(response.data.list || []); setTotalPages(Math.ceil((response.data.total || 0) / 20)); } else { setRawFriends([]); message.error(response.message || "获取好友列表失败"); } } catch (error) { console.error("获取好友列表失败:", error); message.error("获取好友列表失败"); } finally { setLoading(false); } }, [deviceIds, enableDeviceFilter], ); // 初始化数据加载 useEffect(() => { if (visible && !dataSource) { // 只有在没有外部数据源时才调用 API fetchFriends(1); setCurrentPage(1); } }, [visible, dataSource, fetchFriends]); // 重置搜索状态 useEffect(() => { if (visible) { setSearchQuery(""); setSelectedFriends([]); setLoading(false); } }, [visible]); // 防抖搜索处理 const handleSearch = useCallback(() => { let timeoutId: NodeJS.Timeout; return (value: string) => { clearTimeout(timeoutId); timeoutId = setTimeout(() => { setDisplayPage(1); // 重置分页 if (!dataSource) { fetchFriends(1, value); } }, 300); }; }, [dataSource, fetchFriends])(); // API搜索处理(当没有外部数据源时) const handleApiSearch = useCallback( async (keyword: string) => { if (!dataSource) { await fetchFriends(1, keyword); } }, [dataSource, fetchFriends], ); // 加载更多好友 const handleLoadMore = useCallback(() => { setDisplayPage(prev => prev + 1); }, []); // 防抖搜索 useEffect(() => { if (!dataSource && searchQuery.trim()) { const timer = setTimeout(() => { handleApiSearch(searchQuery); }, 300); return () => clearTimeout(timer); } }, [searchQuery, dataSource, handleApiSearch]); // 选择好友 - 使用 useCallback 优化性能 const handleSelectFriend = useCallback((friend: FriendSelectionItem) => { setSelectedFriends(prev => { const isSelected = prev.some(f => f.id === friend.id); if (isSelected) { return prev.filter(f => f.id !== friend.id); } else { return [...prev, friend]; } }); }, []); // 移除已选好友 - 使用 useCallback 优化性能 const handleRemoveFriend = useCallback((friendId: number) => { setSelectedFriends(prev => prev.filter(f => f.id !== friendId)); }, []); // 确认选择 - 使用 useCallback 优化性能 const handleConfirmSelection = useCallback(() => { const selectedIds = selectedFriends.map(f => f.id.toString()); onConfirm(selectedIds, selectedFriends); setSelectedFriends([]); setSearchQuery(""); }, [selectedFriends, onConfirm]); // 取消选择 - 使用 useCallback 优化性能 const handleCancel = useCallback(() => { setSelectedFriends([]); setSearchQuery(""); onCancel(); }, [onCancel]); return ( 取消 , , ]} className={styles.twoColumnModal} >
{/* 左侧:好友列表 */}
{ const value = e.target.value; setSearchQuery(value); // 立即更新显示 handleSearch(value); // 防抖处理搜索 }} prefix={} allowClear />
{loading ? (
加载中...
) : friends.length > 0 ? ( // 使用 React.memo 优化列表项渲染 friends.map(friend => { const isSelected = selectedFriendsMap.has(friend.id); return ( ); }) ) : (
{searchQuery ? `没有找到包含"${searchQuery}"的好友` : "暂无好友"}
)} {hasMoreFriends && (
)}
{/* 右侧:已选好友 */}
已选联系人 ({selectedFriends.length})
{selectedFriends.length > 0 ? ( selectedFriends.map(friend => (
{friend.nickname?.charAt(0)}
{friend.nickname}
)) ) : (
暂无选择
)}
); }; export default TwoColumnSelection;