feat: 角色组成员管理

pull/49/head
moonrailgun 3 years ago
parent 31fbd35cdc
commit 1556eed93f

@ -86,6 +86,7 @@ export {
showToasts, showToasts,
setToasts, setToasts,
showErrorToasts, showErrorToasts,
showSuccessToasts,
showAlert, showAlert,
setAlert, setAlert,
showGlobalLoading, showGlobalLoading,

@ -1,3 +1,4 @@
import { t } from '../i18n';
import { buildRegFn } from './buildRegFn'; import { buildRegFn } from './buildRegFn';
/** /**
@ -23,6 +24,13 @@ export function showErrorToasts(error: unknown) {
showToasts(msg, 'error'); showToasts(msg, 'error');
} }
/**
*
*/
export function showSuccessToasts(msg = t('操作成功')) {
showToasts(msg, 'success');
}
interface AlertOptions { interface AlertOptions {
message: React.ReactNode; message: React.ReactNode;
onConfirm?: () => void | Promise<void>; onConfirm?: () => void | Promise<void>;

@ -7,7 +7,7 @@ export enum GroupPanelType {
} }
export interface GroupMember { export interface GroupMember {
role: string; // 角色 roles: string[]; // 角色
userId: string; userId: string;
/** /**
* xxx * xxx
@ -139,6 +139,42 @@ export async function quitGroup(groupId: string) {
}); });
} }
/**
*
* @param groupId ID
* @param memberIds
* @param roles
*/
export async function appendGroupMemberRoles(
groupId: string,
memberIds: string[],
roles: string[]
) {
await request.post('/api/group/appendGroupMemberRoles', {
groupId,
memberIds,
roles,
});
}
/**
*
* @param groupId ID
* @param memberIds
* @param roles
*/
export async function removeGroupMemberRoles(
groupId: string,
memberIds: string[],
roles: string[]
) {
await request.post('/api/group/removeGroupMemberRoles', {
groupId,
memberIds,
roles,
});
}
/** /**
* *
* 7 * 7

@ -1,4 +1,4 @@
import { Checkbox, Input } from 'antd'; import { Checkbox, Empty, Input } from 'antd';
import React, { useCallback, useState } from 'react'; import React, { useCallback, useState } from 'react';
import { t, useUserInfoList } from 'tailchat-shared'; import { t, useUserInfoList } from 'tailchat-shared';
import _take from 'lodash/take'; import _take from 'lodash/take';
@ -47,6 +47,11 @@ export const UserPicker: React.FC<UserPickerProps> = React.memo((props) => {
[selectedIds, onChange] [selectedIds, onChange]
); );
const matchedList = _take(
userInfoList.filter((info) => info.nickname.includes(searchValue)),
10
);
return ( return (
<div> <div>
{withSearch && ( {withSearch && (
@ -64,28 +69,29 @@ export const UserPicker: React.FC<UserPickerProps> = React.memo((props) => {
})} })}
</div> </div>
{_take( {matchedList.length > 0 ? (
userInfoList.filter((info) => info.nickname.includes(searchValue)), matchedList.map((info) => {
10 return (
).map((info) => { <div key={info._id} className="my-1">
return ( <Checkbox
<div key={info._id} className="my-1"> className="mr-2 items-center"
<Checkbox checked={selectedIds.includes(info._id)}
className="mr-2 items-center" onChange={(e) => handleSelectUser(info._id, e.target.checked)}
checked={selectedIds.includes(info._id)} >
onChange={(e) => handleSelectUser(info._id, e.target.checked)} <div className="flex items-center">
> <Avatar size="small" name={info.nickname} src={info.avatar} />
<div className="flex items-center">
<Avatar size="small" name={info.nickname} src={info.avatar} />
<div className="ml-1 text-typography-light dark:text-typography-dark"> <div className="ml-1 text-typography-light dark:text-typography-dark">
{info.nickname} {info.nickname}
</div>
</div> </div>
</div> </Checkbox>
</Checkbox> </div>
</div> );
); })
})} ) : (
<Empty />
)}
</div> </div>
); );
}); });

@ -4,17 +4,29 @@ import {
} from '@/components/FullModal/Field'; } from '@/components/FullModal/Field';
import { IconBtn } from '@/components/IconBtn'; import { IconBtn } from '@/components/IconBtn';
import { Loading } from '@/components/Loading'; import { Loading } from '@/components/Loading';
import { closeModal, openModal } from '@/components/Modal';
import { PillTabPane, PillTabs } from '@/components/PillTabs'; import { PillTabPane, PillTabs } from '@/components/PillTabs';
import { UserListItem } from '@/components/UserListItem'; import { UserListItem } from '@/components/UserListItem';
import { AllPermission, permissionList } from '@/utils/role-helper'; import { AllPermission, permissionList } from '@/utils/role-helper';
import { Button, Input } from 'antd'; import { Button, Input } from 'antd';
import React, { useCallback, useMemo, useState } from 'react'; import React, { useCallback, useMemo, useState } from 'react';
import { Icon } from 'tailchat-design'; import { Icon } from 'tailchat-design';
import { t, useGroupInfo, useSearch, useUserInfoList } from 'tailchat-shared'; import {
model,
showErrorToasts,
showSuccessToasts,
t,
useGroupInfo,
useSearch,
useUserId,
useUserInfoList,
} from 'tailchat-shared';
import { SelectGroupMember } from '../../SelectGroupMember';
import { PermissionItem } from './PermissionItem'; import { PermissionItem } from './PermissionItem';
import { RoleItem } from './RoleItem'; import { RoleItem } from './RoleItem';
import { useModifyPermission } from './useModifyPermission'; import { useModifyPermission } from './useModifyPermission';
import { useRoleActions } from './useRoleActions'; import { useRoleActions } from './useRoleActions';
import _compact from 'lodash/compact';
interface GroupPermissionProps { interface GroupPermissionProps {
groupId: string; groupId: string;
@ -26,8 +38,12 @@ export const GroupRole: React.FC<GroupPermissionProps> = React.memo((props) => {
); );
const groupInfo = useGroupInfo(groupId); const groupInfo = useGroupInfo(groupId);
const roles = groupInfo?.roles ?? []; const roles = groupInfo?.roles ?? [];
const members = (groupInfo?.members ?? []).filter((m) => m.role === roleId); const members =
const userInfoList = useUserInfoList(members.map((m) => m.userId)); roleId === AllPermission
? []
: (groupInfo?.members ?? []).filter((m) => m.roles.includes(roleId));
const memberIds = members.map((m) => m.userId);
const userInfoList = useUserInfoList(memberIds);
const { const {
searchText, searchText,
setSearchText, setSearchText,
@ -37,6 +53,7 @@ export const GroupRole: React.FC<GroupPermissionProps> = React.memo((props) => {
dataSource: userInfoList, dataSource: userInfoList,
filterFn: (item, searchText) => item.nickname.includes(searchText), filterFn: (item, searchText) => item.nickname.includes(searchText),
}); });
const userId = useUserId();
const currentRoleInfo = useMemo( const currentRoleInfo = useMemo(
() => roles.find((r) => r._id === roleId), () => roles.find((r) => r._id === roleId),
[roles, roleId] [roles, roleId]
@ -63,7 +80,42 @@ export const GroupRole: React.FC<GroupPermissionProps> = React.memo((props) => {
handleSwitchPermission, handleSwitchPermission,
} = useModifyPermission(currentRolePermissions); } = useModifyPermission(currentRolePermissions);
const handleAddMember = useCallback(() => {}, []); const handleAddMember = useCallback(() => {
const key = openModal(
<SelectGroupMember
groupId={groupId}
withoutMemberIds={_compact([...memberIds, userId])}
onConfirm={async (selectedIds) => {
if (!currentRoleInfo?._id) {
showErrorToasts(t('当前没有选择任何角色组'));
return;
}
await model.group.appendGroupMemberRoles(groupId, selectedIds, [
currentRoleInfo._id,
]);
showSuccessToasts();
closeModal(key);
}}
/>
);
}, [groupId, memberIds, currentRoleInfo?._id]);
const handleRemoveMember = useCallback(
async (memberId: string) => {
if (!currentRoleInfo?._id) {
showErrorToasts(t('当前没有选择任何角色组'));
return;
}
await model.group.removeGroupMemberRoles(
groupId,
[memberId],
[currentRoleInfo._id]
);
showSuccessToasts();
},
[groupId, currentRoleInfo?._id]
);
const handleResetPermission = useCallback(() => { const handleResetPermission = useCallback(() => {
setEditingPermission( setEditingPermission(
@ -169,7 +221,13 @@ export const GroupRole: React.FC<GroupPermissionProps> = React.memo((props) => {
<UserListItem <UserListItem
key={m._id} key={m._id}
userId={m._id} userId={m._id}
actions={[<IconBtn key="remove" icon="mdi:close" />]} actions={[
<IconBtn
key="remove"
icon="mdi:close"
onClick={() => handleRemoveMember(m._id)}
/>,
]}
/> />
))} ))}
</PillTabPane> </PillTabPane>

@ -0,0 +1,59 @@
import { Button } from 'antd';
import React, { useMemo, useState } from 'react';
import { t, useAsyncFn, useGroupMemberIds } from 'tailchat-shared';
import { ModalWrapper } from '../Modal';
import { UserPicker } from '../UserPicker/UserPicker';
import _without from 'lodash/without';
interface SelectGroupMemberProps {
/**
* id
*/
groupId: string;
/**
* id
*
*/
withoutMemberIds?: string[];
/**
*
*/
onConfirm: (selectedUserIds: string[]) => void | Promise<void>;
}
/**
*
*/
export const SelectGroupMember: React.FC<SelectGroupMemberProps> = React.memo(
(props) => {
const { groupId, withoutMemberIds = [], onConfirm } = props;
const [selectedUserIds, setSelectedUserIds] = useState<string[]>([]);
const groupMemberIds = useGroupMemberIds(groupId);
const filterMemberIds = useMemo(
() => _without(groupMemberIds, ...withoutMemberIds),
[groupMemberIds, withoutMemberIds]
);
const [{ loading }, handleConfirm] = useAsyncFn(async () => {
await onConfirm(selectedUserIds);
}, [onConfirm, selectedUserIds]);
return (
<ModalWrapper title={t('选择群组成员')}>
<UserPicker
allUserIds={filterMemberIds}
selectedIds={selectedUserIds}
onChange={setSelectedUserIds}
/>
<div className="text-right">
<Button type="primary" loading={loading} onClick={handleConfirm}>
{t('确认')}
</Button>
</div>
</ModalWrapper>
);
}
);
SelectGroupMember.displayName = 'SelectGroupMember';
Loading…
Cancel
Save