feat: 增加成员管理面板

并抽象用户菜单生成函数
pull/70/head
moonrailgun 2 years ago
parent 7645300bda
commit 31c62b21a4

@ -28,7 +28,6 @@ export const MembersPanel: React.FC<MembersPanelProps> = React.memo((props) => {
const groupId = props.groupId; const groupId = props.groupId;
const groupInfo = useGroupInfo(groupId); const groupInfo = useGroupInfo(groupId);
const members = groupInfo?.members ?? []; const members = groupInfo?.members ?? [];
const userInfoList = useUserInfoList(members.map((m) => m.userId));
const membersOnlineStatus = useCachedOnlineStatus( const membersOnlineStatus = useCachedOnlineStatus(
members.map((m) => m.userId) members.map((m) => m.userId)
); );
@ -38,21 +37,20 @@ export const MembersPanel: React.FC<MembersPanelProps> = React.memo((props) => {
const { hideGroupMemberDiscriminator } = getGroupConfigWithInfo(groupInfo); const { hideGroupMemberDiscriminator } = getGroupConfigWithInfo(groupInfo);
const { const {
userInfos,
searchText, searchText,
setSearchText, setSearchText,
isSearching, isSearching,
searchResult: filteredGroupMembers, searchResult: filteredGroupMembers,
getMemberHasMute, getMemberHasMute,
handleMuteMember, generateActionMenu,
handleUnmuteMember,
handleRemoveGroupMember,
} = useGroupMemberAction(groupId); } = useGroupMemberAction(groupId);
const groupedMembers = useMemo(() => { const groupedMembers = useMemo(() => {
const online: UserBaseInfo[] = []; const online: UserBaseInfo[] = [];
const offline: UserBaseInfo[] = []; const offline: UserBaseInfo[] = [];
userInfoList.forEach((m, i) => { userInfos.forEach((m, i) => {
if (membersOnlineStatus[i] === true) { if (membersOnlineStatus[i] === true) {
online.push(m); online.push(m);
} else { } else {
@ -64,86 +62,19 @@ export const MembersPanel: React.FC<MembersPanelProps> = React.memo((props) => {
online, online,
offline, offline,
}; };
}, [userInfoList, membersOnlineStatus]); }, [userInfos, membersOnlineStatus]);
if (!groupInfo) { if (!groupInfo) {
return <Problem />; return <Problem />;
} }
if (userInfoList.length === 0) { if (userInfos.length === 0) {
return <Skeleton />; return <Skeleton />;
} }
const renderUser = (member: UserBaseInfo) => { const renderUser = (member: UserBaseInfo) => {
const hasMute = getMemberHasMute(member._id);
if (allowManageUser) { if (allowManageUser) {
const muteItems: MenuProps['items'] = hasMute const menu: MenuProps = generateActionMenu(member);
? [
{
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 ( return (
<Dropdown key={member._id} trigger={['contextMenu']} menu={menu}> <Dropdown key={member._id} trigger={['contextMenu']} menu={menu}>

@ -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 (
<UserListItem
key={member._id}
userId={member._id}
actions={[
<div key="more">
<Dropdown menu={menu} trigger={['click']} placement="bottomRight">
<div>
<IconBtn icon="mdi:dots-vertical" />
</div>
</Dropdown>
</div>,
]}
/>
);
};
return (
<div className="flex flex-col h-full">
<FullModalCommonTitle>{t('成员管理')}</FullModalCommonTitle>
<div className="py-2">
<Input
placeholder={t('搜索成员')}
size="large"
suffix={<Icon fontSize={20} color="grey" icon="mdi:magnify" />}
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
/>
</div>
<div className="flex-1 overflow-auto">
{isSearching
? filteredGroupMembers.map(renderUser)
: userInfos.map(renderUser)}
</div>
</div>
);
}
);
GroupMember.displayName = 'GroupMember';

@ -1,4 +1,5 @@
import { openReconfirmModalP } from '@/components/Modal'; import { openReconfirmModalP } from '@/components/Modal';
import type { MenuProps } from 'antd';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { import {
formatFullTime, formatFullTime,
@ -10,8 +11,10 @@ import {
useGroupInfo, useGroupInfo,
useGroupMemberInfos, useGroupMemberInfos,
useMemoizedFn, useMemoizedFn,
UserBaseInfo,
useSearch, useSearch,
} from 'tailchat-shared'; } from 'tailchat-shared';
import _compact from 'lodash/compact';
/** /**
* *
@ -62,7 +65,84 @@ export function useGroupMemberAction(groupId: string) {
[members] [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 { return {
userInfos,
// 搜索相关 // 搜索相关
searchText, searchText,
setSearchText, setSearchText,
@ -75,6 +155,8 @@ export function useGroupMemberAction(groupId: string) {
handleMuteMember, handleMuteMember,
handleUnmuteMember, handleUnmuteMember,
handleRemoveGroupMember, handleRemoveGroupMember,
generateActionMenu,
}; };
} }

@ -10,5 +10,5 @@
}, },
"typeRoots": ["./node_modules/@types", "./types"] "typeRoots": ["./node_modules/@types", "./types"]
}, },
"exclude": ["e2e", "plugins"] "exclude": ["e2e", "plugins", "tailchat.d.ts"]
} }

Loading…
Cancel
Save