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 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<MembersPanelProps> = 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<MembersPanelProps> = React.memo((props) => {
online,
offline,
};
}, [userInfoList, membersOnlineStatus]);
}, [userInfos, membersOnlineStatus]);
if (!groupInfo) {
return <Problem />;
}
if (userInfoList.length === 0) {
if (userInfos.length === 0) {
return <Skeleton />;
}
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 (
<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 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,
};
}

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

Loading…
Cancel
Save