refactor: 拆分身份组管理逻辑

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

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

@ -21,10 +21,22 @@
@apply rounded bg-opacity-20;
.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>
{withSearch && (
<Input
placeholder={t('搜索好友')}
placeholder={t('搜索用户')}
className="mb-2"
value={searchValue}
onChange={(e) => setSearchValue(e.target.value)}
@ -78,7 +78,7 @@ export const UserPicker: React.FC<UserPickerProps> = React.memo((props) => {
checked={selectedIds.includes(info._id)}
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} />
<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 { closeModal, openModal } from '@/components/Modal';
import { PillTabPane, PillTabs } from '@/components/PillTabs';
import { UserListItem } from '@/components/UserListItem';
import { AllPermission, permissionList } from '@/utils/role-helper';
import { Button, Input } from 'antd';
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 { AllPermission } from '@/utils/role-helper';
import React, { useMemo, useState } from 'react';
import { t, useGroupInfo } from 'tailchat-shared';
import { RoleItem } from './RoleItem';
import { useModifyPermission } from './useModifyPermission';
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 {
groupId: string;
@ -38,33 +19,11 @@ export const GroupRole: React.FC<GroupPermissionProps> = React.memo((props) => {
);
const groupInfo = useGroupInfo(groupId);
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(
() => roles.find((r) => r._id === roleId),
[roles, roleId]
);
const currentRolePermissions: string[] = useMemo(() => {
if (roleId === AllPermission) {
return groupInfo?.fallbackPermissions ?? [];
}
return currentRoleInfo?.permissions ?? [];
}, [roleId, currentRoleInfo, groupInfo]);
const {
loading,
@ -73,56 +32,6 @@ export const GroupRole: React.FC<GroupPermissionProps> = React.memo((props) => {
handleChangeRoleName,
} = 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 (
<Loading spinning={loading} className="h-full">
<div className="flex h-full">
@ -157,79 +66,32 @@ export const GroupRole: React.FC<GroupPermissionProps> = React.memo((props) => {
tab={t('概述')}
disabled={roleId === AllPermission}
>
{/* 权限概述 */}
{currentRoleInfo && (
<div className="px-2">
<FullModalField
title={t('角色名称')}
value={currentRoleInfo.name}
editable={true}
renderEditor={DefaultFullModalInputEditorRender}
onSave={handleChangeRoleName}
/>
</div>
<RoleSummary
currentRoleInfo={currentRoleInfo}
onChangeRoleName={handleChangeRoleName}
/>
)}
</PillTabPane>
<PillTabPane key="permission" tab={t('权限')}>
<div className="mb-2 space-x-2 text-right">
<Button onClick={handleResetPermission}>
{t('重置为默认值')}
</Button>
<Button
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)}
/>
))}
<RolePermission
roleId={roleId}
fallbackPermissions={groupInfo?.fallbackPermissions ?? []}
currentRoleInfo={currentRoleInfo}
onSavePermission={handleSavePermission}
/>
</PillTabPane>
<PillTabPane
key="member"
tab={t('管理成员')}
disabled={roleId === AllPermission}
>
{/* 管理成员 */}
<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)}
/>,
]}
{currentRoleInfo && (
<RoleMember
groupId={groupId}
currentRoleInfo={currentRoleInfo}
/>
))}
)}
</PillTabPane>
</PillTabs>
</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