refactor: 拆分身份组管理逻辑

pull/49/head
moonrailgun 3 years ago
parent a4f0c9389e
commit 12431a2c97

@ -51,8 +51,15 @@ export interface GroupInfo {
members: GroupMember[]; members: GroupMember[];
panels: GroupPanel[]; panels: GroupPanel[];
roles: GroupRole[]; roles: GroupRole[];
/**
*
*
*/
fallbackPermissions: string[]; fallbackPermissions: string[];
pinnedPanelId?: string; // 被钉选的面板Id /**
* Id
*/
pinnedPanelId?: string;
} }
/** /**

@ -21,10 +21,22 @@
@apply rounded bg-opacity-20; @apply rounded bg-opacity-20;
.ant-tabs-tab-btn { .ant-tabs-tab-btn {
@apply text-black dark:text-white ; @apply text-black dark:text-white;
} }
} }
} }
&.ant-tabs-tab-disabled {
@apply text-opacity-50;
&.ant-tabs-tab-active {
@apply text-red-500 rounded bg-opacity-20 bg-red-500;
}
}
.ant-tabs-tab-btn {
color: inherit;
}
} }
} }

@ -56,7 +56,7 @@ export const UserPicker: React.FC<UserPickerProps> = React.memo((props) => {
<div> <div>
{withSearch && ( {withSearch && (
<Input <Input
placeholder={t('搜索好友')} placeholder={t('搜索用户')}
className="mb-2" className="mb-2"
value={searchValue} value={searchValue}
onChange={(e) => setSearchValue(e.target.value)} onChange={(e) => setSearchValue(e.target.value)}
@ -78,7 +78,7 @@ export const UserPicker: React.FC<UserPickerProps> = React.memo((props) => {
checked={selectedIds.includes(info._id)} checked={selectedIds.includes(info._id)}
onChange={(e) => handleSelectUser(info._id, e.target.checked)} onChange={(e) => handleSelectUser(info._id, e.target.checked)}
> >
<div className="flex items-center"> <div className="flex items-center w-full">
<Avatar size="small" name={info.nickname} src={info.avatar} /> <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">

@ -1,32 +1,13 @@
import {
DefaultFullModalInputEditorRender,
FullModalField,
} from '@/components/FullModal/Field';
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 { AllPermission } from '@/utils/role-helper';
import { AllPermission, permissionList } from '@/utils/role-helper'; import React, { useMemo, useState } from 'react';
import { Button, Input } from 'antd'; import { t, useGroupInfo } from 'tailchat-shared';
import React, { useCallback, useMemo, useState } from 'react';
import { Icon } from 'tailchat-design';
import {
model,
showErrorToasts,
showSuccessToasts,
t,
useGroupInfo,
useSearch,
useUserId,
useUserInfoList,
} from 'tailchat-shared';
import { SelectGroupMember } from '../../SelectGroupMember';
import { PermissionItem } from './PermissionItem';
import { RoleItem } from './RoleItem'; import { RoleItem } from './RoleItem';
import { useModifyPermission } from './useModifyPermission';
import { useRoleActions } from './useRoleActions'; import { useRoleActions } from './useRoleActions';
import _compact from 'lodash/compact'; import { RoleSummary } from './tabs/summary';
import { RolePermission } from './tabs/permission';
import { RoleMember } from './tabs/member';
interface GroupPermissionProps { interface GroupPermissionProps {
groupId: string; groupId: string;
@ -38,33 +19,11 @@ 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 =
roleId === AllPermission
? []
: (groupInfo?.members ?? []).filter((m) => m.roles.includes(roleId));
const memberIds = members.map((m) => m.userId);
const userInfoList = useUserInfoList(memberIds);
const {
searchText,
setSearchText,
isSearching,
searchResult: filterMembers,
} = useSearch({
dataSource: userInfoList,
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]
); );
const currentRolePermissions: string[] = useMemo(() => {
if (roleId === AllPermission) {
return groupInfo?.fallbackPermissions ?? [];
}
return currentRoleInfo?.permissions ?? [];
}, [roleId, currentRoleInfo, groupInfo]);
const { const {
loading, loading,
@ -73,56 +32,6 @@ export const GroupRole: React.FC<GroupPermissionProps> = React.memo((props) => {
handleChangeRoleName, handleChangeRoleName,
} = useRoleActions(groupId, roleId); } = useRoleActions(groupId, roleId);
const {
isEditing,
editingPermission,
setEditingPermission,
handleSwitchPermission,
} = useModifyPermission(currentRolePermissions);
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(() => {
setEditingPermission(
permissionList.filter((p) => p.default === true).map((p) => p.key)
);
}, []);
return ( return (
<Loading spinning={loading} className="h-full"> <Loading spinning={loading} className="h-full">
<div className="flex h-full"> <div className="flex h-full">
@ -157,79 +66,32 @@ export const GroupRole: React.FC<GroupPermissionProps> = React.memo((props) => {
tab={t('概述')} tab={t('概述')}
disabled={roleId === AllPermission} disabled={roleId === AllPermission}
> >
{/* 权限概述 */}
{currentRoleInfo && ( {currentRoleInfo && (
<div className="px-2"> <RoleSummary
<FullModalField currentRoleInfo={currentRoleInfo}
title={t('角色名称')} onChangeRoleName={handleChangeRoleName}
value={currentRoleInfo.name} />
editable={true}
renderEditor={DefaultFullModalInputEditorRender}
onSave={handleChangeRoleName}
/>
</div>
)} )}
</PillTabPane> </PillTabPane>
<PillTabPane key="permission" tab={t('权限')}> <PillTabPane key="permission" tab={t('权限')}>
<div className="mb-2 space-x-2 text-right"> <RolePermission
<Button onClick={handleResetPermission}> roleId={roleId}
{t('重置为默认值')} fallbackPermissions={groupInfo?.fallbackPermissions ?? []}
</Button> currentRoleInfo={currentRoleInfo}
<Button onSavePermission={handleSavePermission}
type="primary" />
disabled={!isEditing}
onClick={() => handleSavePermission(editingPermission)}
>
{t('保存')}
</Button>
</div>
{/* 权限详情 */}
{permissionList.map((p) => (
<PermissionItem
key={p.key}
title={p.title}
desc={p.desc}
checked={editingPermission.includes(p.key)}
onChange={(checked) => handleSwitchPermission(p.key, checked)}
/>
))}
</PillTabPane> </PillTabPane>
<PillTabPane <PillTabPane
key="member" key="member"
tab={t('管理成员')} tab={t('管理成员')}
disabled={roleId === AllPermission} disabled={roleId === AllPermission}
> >
{/* 管理成员 */} {currentRoleInfo && (
<div className="text-right mb-2 flex space-x-1"> <RoleMember
<Input groupId={groupId}
placeholder={t('搜索成员')} currentRoleInfo={currentRoleInfo}
size="middle"
suffix={
<Icon fontSize={20} color="grey" icon="mdi:magnify" />
}
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
/>
<Button type="primary" onClick={handleAddMember}>
{t('添加成员')}
</Button>
</div>
{(isSearching ? filterMembers : userInfoList).map((m) => (
<UserListItem
key={m._id}
userId={m._id}
actions={[
<IconBtn
key="remove"
icon="mdi:close"
onClick={() => handleRemoveMember(m._id)}
/>,
]}
/> />
))} )}
</PillTabPane> </PillTabPane>
</PillTabs> </PillTabs>
</div> </div>

@ -0,0 +1,116 @@
import { IconBtn } from '@/components/IconBtn';
import { closeModal, openModal } from '@/components/Modal';
import { SelectGroupMember } from '@/components/modals/SelectGroupMember';
import { UserListItem } from '@/components/UserListItem';
import { Button, Input } from 'antd';
import React, { useCallback } from 'react';
import { Icon } from 'tailchat-design';
import {
model,
showErrorToasts,
showSuccessToasts,
t,
useAsyncRequest,
useGroupInfo,
useSearch,
useUserId,
useUserInfoList,
} from 'tailchat-shared';
import _compact from 'lodash/compact';
interface RoleMemberProps {
groupId: string;
currentRoleInfo: model.group.GroupRole;
}
export const RoleMember: React.FC<RoleMemberProps> = React.memo((props) => {
const roleId = props.currentRoleInfo._id;
const userId = useUserId();
const groupInfo = useGroupInfo(props.groupId);
const members = (groupInfo?.members ?? []).filter((m) =>
m.roles.includes(roleId)
);
const memberIds = members.map((m) => m.userId);
const userInfoList = useUserInfoList(memberIds);
const {
searchText,
setSearchText,
isSearching,
searchResult: filterMembers,
} = useSearch({
dataSource: userInfoList,
filterFn: (item, searchText) => item.nickname.includes(searchText),
});
const handleAddMember = useCallback(() => {
const key = openModal(
<SelectGroupMember
groupId={props.groupId}
withoutMemberIds={_compact([...memberIds, userId])}
onConfirm={async (selectedIds) => {
try {
await model.group.appendGroupMemberRoles(
props.groupId,
selectedIds,
[props.currentRoleInfo._id]
);
showSuccessToasts();
closeModal(key);
} catch (err) {
showErrorToasts(err);
}
}}
/>
);
}, [userId, props.groupId, memberIds, props.currentRoleInfo._id]);
const [, handleRemoveMember] = useAsyncRequest(
async (memberId: string) => {
if (!props.currentRoleInfo?._id) {
showErrorToasts(t('当前没有选择任何角色组'));
return;
}
await model.group.removeGroupMemberRoles(
props.groupId,
[memberId],
[props.currentRoleInfo._id]
);
showSuccessToasts();
},
[props.groupId, props.currentRoleInfo?._id]
);
return (
<div>
{/* 管理成员 */}
<div className="text-right mb-2 flex space-x-1">
<Input
placeholder={t('搜索成员')}
size="middle"
suffix={<Icon fontSize={20} color="grey" icon="mdi:magnify" />}
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
/>
<Button type="primary" onClick={handleAddMember}>
{t('添加成员')}
</Button>
</div>
{(isSearching ? filterMembers : userInfoList).map((m) => (
<UserListItem
key={m._id}
userId={m._id}
actions={[
<IconBtn
key="remove"
icon="mdi:close"
onClick={() => handleRemoveMember(m._id)}
/>,
]}
/>
))}
</div>
);
});
RoleMember.displayName = 'RoleMember';

@ -0,0 +1,65 @@
import { AllPermission, permissionList } from '@/utils/role-helper';
import { Button } from 'antd';
import React, { useCallback, useMemo } from 'react';
import { model, t } from 'tailchat-shared';
import { PermissionItem } from '../PermissionItem';
import { useModifyPermission } from '../useModifyPermission';
interface RolePermissionProps {
roleId: typeof AllPermission | string;
currentRoleInfo?: model.group.GroupRole;
fallbackPermissions: string[];
onSavePermission: (permissions: string[]) => Promise<void>;
}
export const RolePermission: React.FC<RolePermissionProps> = React.memo(
(props) => {
const currentRolePermissions: string[] = useMemo(() => {
if (props.roleId === AllPermission) {
return props.fallbackPermissions;
}
return props.currentRoleInfo?.permissions ?? [];
}, [props.roleId, props.fallbackPermissions, props.currentRoleInfo]);
const {
isEditing,
editingPermission,
setEditingPermission,
handleSwitchPermission,
} = useModifyPermission(currentRolePermissions);
const handleResetPermission = useCallback(() => {
setEditingPermission(
permissionList.filter((p) => p.default === true).map((p) => p.key)
);
}, []);
// 权限概述
return (
<div>
<div className="mb-2 space-x-2 text-right">
<Button onClick={handleResetPermission}>{t('重置为默认值')}</Button>
<Button
type="primary"
disabled={!isEditing}
onClick={() => props.onSavePermission(editingPermission)}
>
{t('保存')}
</Button>
</div>
{/* 权限详情 */}
{permissionList.map((p) => (
<PermissionItem
key={p.key}
title={p.title}
desc={p.desc}
checked={editingPermission.includes(p.key)}
onChange={(checked) => handleSwitchPermission(p.key, checked)}
/>
))}
</div>
);
}
);
RolePermission.displayName = 'RolePermission';

@ -0,0 +1,26 @@
import {
DefaultFullModalInputEditorRender,
FullModalField,
} from '@/components/FullModal/Field';
import React from 'react';
import { model, t } from 'tailchat-shared';
interface RoleSummaryProps {
currentRoleInfo: model.group.GroupRole;
onChangeRoleName: (roleName: string) => void;
}
export const RoleSummary: React.FC<RoleSummaryProps> = React.memo((props) => {
// 权限概述
return (
<div className="px-2">
<FullModalField
title={t('身份组名称')}
value={props.currentRoleInfo.name}
editable={true}
renderEditor={DefaultFullModalInputEditorRender}
onSave={props.onChangeRoleName}
/>
</div>
);
});
RoleSummary.displayName = 'RoleSummary';
Loading…
Cancel
Save