diff --git a/Cunkebao/src/components/FriendSelection/TwoColumnSelection.tsx b/Cunkebao/src/components/FriendSelection/TwoColumnSelection.tsx index 58e99c1a..78858b89 100644 --- a/Cunkebao/src/components/FriendSelection/TwoColumnSelection.tsx +++ b/Cunkebao/src/components/FriendSelection/TwoColumnSelection.tsx @@ -1,10 +1,35 @@ -import React, { useState, useCallback, useEffect } from 'react'; +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 './api'; import type { FriendSelectionItem } from './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; @@ -12,6 +37,7 @@ interface TwoColumnSelectionProps { title?: string; deviceIds?: number[]; enableDeviceFilter?: boolean; + dataSource?: FriendSelectionItem[]; } const TwoColumnSelection: React.FC = ({ @@ -21,14 +47,50 @@ const TwoColumnSelection: React.FC = ({ title = '选择好友', deviceIds = [], enableDeviceFilter = true, + dataSource, }) => { - const [friends, setFriends] = useState([]); + 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); @@ -37,21 +99,22 @@ const TwoColumnSelection: React.FC = ({ page, pageSize: 20, }; - + if (keyword) { params.keyword = keyword; } - + if (enableDeviceFilter && deviceIds.length > 0) { params.deviceIds = deviceIds; } - + const response = await getFriendList(params); - + if (response.success) { - setFriends(response.data.list || []); + setRawFriends(response.data.list || []); setTotalPages(Math.ceil((response.data.total || 0) / 20)); } else { + setRawFriends([]); message.error(response.message || '获取好友列表失败'); } } catch (error) { @@ -62,57 +125,100 @@ const TwoColumnSelection: React.FC = ({ } }, [deviceIds, enableDeviceFilter]); - // 初始化和搜索 + // 初始化数据加载 useEffect(() => { - if (visible) { - fetchFriends(1, searchQuery); + if (visible && !dataSource) { + // 只有在没有外部数据源时才调用 API + fetchFriends(1); setCurrentPage(1); } - }, [visible, searchQuery, fetchFriends]); + }, [visible, dataSource, fetchFriends]); - // 处理搜索 - const handleSearch = (value: string) => { - setSearchQuery(value); - }; - - // 选择好友 - const handleSelectFriend = (friend: FriendSelectionItem) => { - const isSelected = selectedFriends.some(f => f.id === friend.id); - if (isSelected) { - setSelectedFriends(selectedFriends.filter(f => f.id !== friend.id)); - } else { - setSelectedFriends([...selectedFriends, friend]); + // 重置搜索状态 + useEffect(() => { + if (visible) { + setSearchQuery(''); + setSelectedFriends([]); + setLoading(false); } - }; + }, [visible]); - // 移除已选好友 - const handleRemoveFriend = (friendId: number) => { - setSelectedFriends(selectedFriends.filter(f => f.id !== friendId)); - }; + // 防抖搜索处理 + const handleSearch = useCallback(() => { + let timeoutId: NodeJS.Timeout; + return (value: string) => { + clearTimeout(timeoutId); + timeoutId = setTimeout(() => { + setDisplayPage(1); // 重置分页 + if (!dataSource) { + fetchFriends(1, value); + } + }, 300); + }; + }, [dataSource, fetchFriends])(); - // 确认选择 - const handleConfirmSelection = () => { + // 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]); - // 取消选择 - const handleCancelSelection = () => { + // 取消选择 - 使用 useCallback 优化性能 + const handleCancel = useCallback(() => { setSelectedFriends([]); setSearchQuery(''); onCancel(); - }; + }, [onCancel]); return ( + , + + )} @@ -170,7 +285,7 @@ const TwoColumnSelection: React.FC = ({
已选联系人 ({selectedFriends.length})
- +
{selectedFriends.length > 0 ? ( selectedFriends.map(friend => ( @@ -203,4 +318,4 @@ const TwoColumnSelection: React.FC = ({ ); }; -export default TwoColumnSelection; \ No newline at end of file +export default TwoColumnSelection; diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/ProfileCard/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/ProfileCard/index.tsx index df6032de..055376e6 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/ProfileCard/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/ProfileCard/index.tsx @@ -82,7 +82,6 @@ const Person: React.FC = ({ ); - const [hoveredMember, setHoveredMember] = useState(null); const [isAddFriendModalVisible, setIsAddFriendModalVisible] = useState(false); const [selectedMember, setSelectedMember] = useState(null); @@ -95,9 +94,9 @@ const Person: React.FC = ({ const [isRemoveAdminSelectionVisible, setIsRemoveAdminSelectionVisible] = useState(false); const [isTransferOwnerSelectionVisible, setIsTransferOwnerSelectionVisible] = useState(false); const [selectedFriends, setSelectedFriends] = useState([]); + const [contractList, setContractList] = useState([]); const handleAddFriend = (member) => { - console.log(selectedFriends); setSelectedMember(member); setGreeting(`你好, 我来自群聊${contractInfo.name}`); setIsAddFriendModalVisible(true); @@ -106,17 +105,15 @@ const Person: React.FC = ({ // 群管理操作处理函数 const handleAddMember = (selectedIds: number[], selectedItems: FriendSelectionItem[]) => { console.log('添加成员:', selectedIds, selectedItems); - sendCommand("CmdchatroomInvite", { + sendCommand("CmdChatroomInvite", { wechatChatroomId: contract.id, - extra: JSON.stringify({ - selectedIds - }) + wechatFriendIds: selectedIds }); messageApi.success(`已添加 ${selectedItems.length} 个成员`); setIsFriendSelectionVisible(false); }; - //删除群成员 √ + //删除群成员 const handleRemoveMember = (selectedIds: string[]) => { console.log('删除成员:', selectedIds); sendCommand("CmdChatroomOperate", { @@ -203,9 +200,14 @@ const Person: React.FC = ({ // 构建联系人或群聊详细信息 - const kfSelectedUser = useCkChatStore(state => - state.getKfUserInfo(contract.wechatAccountId || 0), + const kfSelectedUser = useCkChatStore( + state => state.getKfUserInfo(contract.wechatAccountId || 0), ); + +const getSomeContractList = useCkChatStore( + state => state.getSomeContractList, + ); + const { sendCommand } = useWebSocketStore(); // 权限控制:检查当前客服是否有群管理权限 @@ -875,7 +877,25 @@ const Person: React.FC = ({ }}>
+ ); + } + + // 默认为文本消息 + return
{content}
; + }; + + // 用于分组消息并添加时间戳的辅助函数 + const groupMessagesByTime = (messages: ChatRecord[]) => { + return messages + .filter(msg => msg !== null && msg !== undefined) // 过滤掉null和undefined的消息 + .map(msg => ({ + time: formatWechatTime(msg?.wechatTime), + messages: [msg], + })); + }; + const groupMemberAvatar = (msg: ChatRecord) => { + if (!msg?.sender) { + return undefined; + } + const groupMember = currentGroupMembers.find( + (v) => v?.wechatId === msg.sender.wechatId + ); + return groupMember?.avatar; + }; + const clearWechatidInContent = (sender, content: string) => { + if (!sender || !sender.wechatId || !content) return content; + return content.replace(new RegExp(`${sender.wechatId}:\n`, "g"), ""); + }; + const renderMessage = (msg: ChatRecord) => { + console.log(msg); + // 添加null检查,防止访问null对象的属性 + if (!msg) return null; + + const isOwn = msg?.isSend; + const isGroup = !!contract.chatroomId; + return ( +
+
+ {/* 如果不是群聊 */} + {!isGroup && !isOwn && ( + <> + } + className={styles.messageAvatar} + /> + +
+ {!isOwn && ( +
+ {contract.nickname} +
+ )} + {parseMessageContent(msg?.content, msg)} +
+ + )} + {/* 如果是群聊 */} + {isGroup && !isOwn && ( + <> + } + className={styles.messageAvatar} + /> + +
+ {!isOwn && ( +
+ {msg?.sender?.nickname} +
+ )} + {parseMessageContent( + clearWechatidInContent(msg?.sender, msg?.content), + msg, + )} +
+ + )} + + {isOwn && ( +
+ {parseMessageContent(msg?.content, msg)} +
+ )} +
+
+ ); + }; + const chatMenu = ( }> @@ -68,11 +667,17 @@ const ChatWindow: React.FC = ({ contract }) => { - +