diff --git a/client/web/src/components/Panel/group/MembersPanel.tsx b/client/web/src/components/Panel/group/MembersPanel.tsx index 24e8faa0..c1b4e08c 100644 --- a/client/web/src/components/Panel/group/MembersPanel.tsx +++ b/client/web/src/components/Panel/group/MembersPanel.tsx @@ -28,7 +28,6 @@ export const MembersPanel: React.FC = React.memo((props) => { const groupId = props.groupId; const groupInfo = useGroupInfo(groupId); const members = groupInfo?.members ?? []; - const userInfoList = useUserInfoList(members.map((m) => m.userId)); const membersOnlineStatus = useCachedOnlineStatus( members.map((m) => m.userId) ); @@ -38,21 +37,20 @@ export const MembersPanel: React.FC = React.memo((props) => { const { hideGroupMemberDiscriminator } = getGroupConfigWithInfo(groupInfo); const { + userInfos, searchText, setSearchText, isSearching, searchResult: filteredGroupMembers, getMemberHasMute, - handleMuteMember, - handleUnmuteMember, - handleRemoveGroupMember, + generateActionMenu, } = useGroupMemberAction(groupId); const groupedMembers = useMemo(() => { const online: UserBaseInfo[] = []; const offline: UserBaseInfo[] = []; - userInfoList.forEach((m, i) => { + userInfos.forEach((m, i) => { if (membersOnlineStatus[i] === true) { online.push(m); } else { @@ -64,86 +62,19 @@ export const MembersPanel: React.FC = React.memo((props) => { online, offline, }; - }, [userInfoList, membersOnlineStatus]); + }, [userInfos, membersOnlineStatus]); if (!groupInfo) { return ; } - if (userInfoList.length === 0) { + if (userInfos.length === 0) { return ; } const renderUser = (member: UserBaseInfo) => { - const hasMute = getMemberHasMute(member._id); - if (allowManageUser) { - const muteItems: MenuProps['items'] = hasMute - ? [ - { - key: 'unmute', - label: t('解除禁言'), - onClick: () => handleUnmuteMember(member._id), - }, - ] - : [ - { - key: 'mute', - label: t('禁言'), - children: [ - { - key: '1m', - label: t('1分钟'), - onClick: () => handleMuteMember(member._id, 1 * 60 * 1000), - }, - { - key: '5m', - label: t('5分钟'), - onClick: () => handleMuteMember(member._id, 5 * 60 * 1000), - }, - { - key: '10m', - label: t('10分钟'), - onClick: () => handleMuteMember(member._id, 10 * 60 * 1000), - }, - { - key: '30m', - label: t('30分钟'), - onClick: () => handleMuteMember(member._id, 30 * 60 * 1000), - }, - { - key: '1d', - label: t('1天'), - onClick: () => - handleMuteMember(member._id, 1 * 24 * 60 * 60 * 1000), - }, - { - key: '7d', - label: t('7天'), - onClick: () => - handleMuteMember(member._id, 7 * 24 * 60 * 60 * 1000), - }, - { - key: '30d', - label: t('30天'), - onClick: () => - handleMuteMember(member._id, 30 * 24 * 60 * 60 * 1000), - }, - ], - }, - ]; - - const menu: MenuProps = { - items: _compact([ - ...muteItems, - { - key: 'delete', - label: t('移出群组'), - danger: true, - onClick: () => handleRemoveGroupMember(member._id), - }, - ] as MenuProps['items']), - }; + const menu: MenuProps = generateActionMenu(member); return ( diff --git a/client/web/src/components/modals/GroupDetail/Member.tsx b/client/web/src/components/modals/GroupDetail/Member.tsx new file mode 100644 index 00000000..9df26ad1 --- /dev/null +++ b/client/web/src/components/modals/GroupDetail/Member.tsx @@ -0,0 +1,68 @@ +import { FullModalCommonTitle } from '@/components/FullModal/CommonTitle'; +import { IconBtn } from '@/components/IconBtn'; +import { UserListItem } from '@/components/UserListItem'; +import { useGroupMemberAction } from '@/hooks/useGroupMemberAction'; +import { Dropdown, Input, MenuProps } from 'antd'; +import React from 'react'; +import { Icon } from 'tailchat-design'; +import { t, UserBaseInfo } from 'tailchat-shared'; + +/** + * 群组成员管理 + */ +export const GroupMember: React.FC<{ groupId: string }> = React.memo( + (props) => { + const groupId = props.groupId; + const { + userInfos, + searchText, + setSearchText, + isSearching, + searchResult: filteredGroupMembers, + generateActionMenu, + } = useGroupMemberAction(groupId); + + const renderUser = (member: UserBaseInfo) => { + const menu: MenuProps = generateActionMenu(member); + + return ( + + +
+ +
+
+ , + ]} + /> + ); + }; + + return ( +
+ {t('成员管理')} + +
+ } + value={searchText} + onChange={(e) => setSearchText(e.target.value)} + /> +
+ +
+ {isSearching + ? filteredGroupMembers.map(renderUser) + : userInfos.map(renderUser)} +
+
+ ); + } +); +GroupMember.displayName = 'GroupMember'; diff --git a/client/web/src/hooks/useGroupMemberAction.ts b/client/web/src/hooks/useGroupMemberAction.ts index 9a1c2da3..27e99fc3 100644 --- a/client/web/src/hooks/useGroupMemberAction.ts +++ b/client/web/src/hooks/useGroupMemberAction.ts @@ -1,4 +1,5 @@ import { openReconfirmModalP } from '@/components/Modal'; +import type { MenuProps } from 'antd'; import { useCallback } from 'react'; import { formatFullTime, @@ -10,8 +11,10 @@ import { useGroupInfo, useGroupMemberInfos, useMemoizedFn, + UserBaseInfo, useSearch, } from 'tailchat-shared'; +import _compact from 'lodash/compact'; /** * 群组成员管理相关操作 @@ -62,7 +65,84 @@ export function useGroupMemberAction(groupId: string) { [members] ); + const generateActionMenu = useMemoizedFn( + (member: UserBaseInfo): MenuProps => { + const hasMute = getMemberHasMute(member._id); + + const muteItems: MenuProps['items'] = hasMute + ? [ + { + key: 'unmute', + label: t('解除禁言'), + onClick: () => handleUnmuteMember(member._id), + }, + ] + : [ + { + key: 'mute', + label: t('禁言'), + children: [ + { + key: '1m', + label: t('1分钟'), + onClick: () => handleMuteMember(member._id, 1 * 60 * 1000), + }, + { + key: '5m', + label: t('5分钟'), + onClick: () => handleMuteMember(member._id, 5 * 60 * 1000), + }, + { + key: '10m', + label: t('10分钟'), + onClick: () => handleMuteMember(member._id, 10 * 60 * 1000), + }, + { + key: '30m', + label: t('30分钟'), + onClick: () => handleMuteMember(member._id, 30 * 60 * 1000), + }, + { + key: '1d', + label: t('1天'), + onClick: () => + handleMuteMember(member._id, 1 * 24 * 60 * 60 * 1000), + }, + { + key: '7d', + label: t('7天'), + onClick: () => + handleMuteMember(member._id, 7 * 24 * 60 * 60 * 1000), + }, + { + key: '30d', + label: t('30天'), + onClick: () => + handleMuteMember(member._id, 30 * 24 * 60 * 60 * 1000), + }, + ], + }, + ]; + + const menu: MenuProps = { + items: _compact([ + ...muteItems, + { + key: 'delete', + label: t('移出群组'), + danger: true, + onClick: () => handleRemoveGroupMember(member._id), + }, + ] as MenuProps['items']), + }; + + return menu; + } + ); + return { + userInfos, + // 搜索相关 searchText, setSearchText, @@ -75,6 +155,8 @@ export function useGroupMemberAction(groupId: string) { handleMuteMember, handleUnmuteMember, handleRemoveGroupMember, + + generateActionMenu, }; } diff --git a/client/web/tsconfig.json b/client/web/tsconfig.json index b32637ef..e4d53743 100644 --- a/client/web/tsconfig.json +++ b/client/web/tsconfig.json @@ -10,5 +10,5 @@ }, "typeRoots": ["./node_modules/@types", "./types"] }, - "exclude": ["e2e", "plugins"] + "exclude": ["e2e", "plugins", "tailchat.d.ts"] }