Permission (#1687)
Co-authored-by: Archer <545436317@qq.com> Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
import { Box, BoxProps } from '@chakra-ui/react';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import React from 'react';
|
||||
import type { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
|
||||
export enum defaultPermissionEnum {
|
||||
private = 'private',
|
||||
read = 'read',
|
||||
edit = 'edit'
|
||||
}
|
||||
|
||||
type Props = Omit<BoxProps, 'onChange'> & {
|
||||
per: PermissionValueType;
|
||||
defaultPer: PermissionValueType;
|
||||
readPer: PermissionValueType;
|
||||
writePer: PermissionValueType;
|
||||
onChange: (v: PermissionValueType) => void;
|
||||
};
|
||||
|
||||
const DefaultPermissionList = ({
|
||||
per,
|
||||
defaultPer,
|
||||
readPer,
|
||||
writePer,
|
||||
onChange,
|
||||
...styles
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const defaultPermissionSelectList = [
|
||||
{ label: '仅协作者访问', value: defaultPer },
|
||||
{ label: '团队可访问', value: readPer },
|
||||
{ label: '团队可编辑', value: writePer }
|
||||
];
|
||||
|
||||
return (
|
||||
<Box {...styles}>
|
||||
<MySelect
|
||||
list={defaultPermissionSelectList}
|
||||
value={per}
|
||||
onchange={(v) => {
|
||||
onChange(v);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default DefaultPermissionList;
|
||||
@@ -1,19 +1,34 @@
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { PermissionTypeEnum, PermissionTypeMap } from '@fastgpt/global/support/permission/constant';
|
||||
import { Box, Flex, FlexProps } from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
import { Permission } from '@fastgpt/global/support/permission/controller';
|
||||
|
||||
const PermissionIconText = ({
|
||||
permission,
|
||||
defaultPermission,
|
||||
...props
|
||||
}: { permission: `${PermissionTypeEnum}` } & FlexProps) => {
|
||||
}: {
|
||||
permission?: `${PermissionTypeEnum}`;
|
||||
defaultPermission?: PermissionValueType;
|
||||
} & FlexProps) => {
|
||||
const { t } = useTranslation();
|
||||
return PermissionTypeMap[permission] ? (
|
||||
|
||||
const per = useMemo(() => {
|
||||
if (permission) return permission;
|
||||
if (defaultPermission) {
|
||||
return new Permission({ per: defaultPermission }).hasReadPer ? 'public' : 'private';
|
||||
}
|
||||
return 'private';
|
||||
}, [defaultPermission, permission]);
|
||||
|
||||
return PermissionTypeMap[per] ? (
|
||||
<Flex alignItems={'center'} {...props}>
|
||||
<MyIcon name={PermissionTypeMap[permission]?.iconLight as any} w={'14px'} />
|
||||
<MyIcon name={PermissionTypeMap[per]?.iconLight as any} w={'14px'} />
|
||||
<Box ml={'2px'} lineHeight={1}>
|
||||
{t(PermissionTypeMap[permission]?.label)}
|
||||
{t(PermissionTypeMap[per]?.label)}
|
||||
</Box>
|
||||
</Flex>
|
||||
) : null;
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
import {
|
||||
Flex,
|
||||
Box,
|
||||
Grid,
|
||||
ModalBody,
|
||||
InputGroup,
|
||||
InputLeftElement,
|
||||
Input,
|
||||
Checkbox,
|
||||
ModalFooter,
|
||||
Button,
|
||||
useToast
|
||||
} from '@chakra-ui/react';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import MyAvatar from '@/components/Avatar';
|
||||
import { useMemo, useState } from 'react';
|
||||
import PermissionSelect from './PermissionSelect';
|
||||
import PermissionTags from './PermissionTags';
|
||||
import { CollaboratorContext } from './context';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { getTeamMembers } from '@/web/support/user/team/api';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { Permission } from '@fastgpt/global/support/permission/controller';
|
||||
import { ChevronDownIcon } from '@chakra-ui/icons';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||
|
||||
export type AddModalPropsType = {
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export function AddMemberModal({ onClose }: AddModalPropsType) {
|
||||
const toast = useToast();
|
||||
const { userInfo } = useUserStore();
|
||||
|
||||
const { permissionList, collaboratorList, onUpdateCollaborators, getPreLabelList } =
|
||||
useContextSelector(CollaboratorContext, (v) => v);
|
||||
const [searchText, setSearchText] = useState<string>('');
|
||||
const {
|
||||
data: members = [],
|
||||
refetch: refetchMembers,
|
||||
isLoading: loadingMembers
|
||||
} = useQuery(['getMembers', userInfo?.team?.teamId], async () => {
|
||||
if (!userInfo?.team?.teamId) return [];
|
||||
const members = await getTeamMembers();
|
||||
return members;
|
||||
});
|
||||
const filterMembers = useMemo(() => {
|
||||
return members.filter((item) => {
|
||||
if (item.permission.isOwner) return false;
|
||||
if (item.tmbId === userInfo?.team?.tmbId) return false;
|
||||
if (!searchText) return true;
|
||||
return item.memberName.includes(searchText);
|
||||
});
|
||||
}, [members, searchText, userInfo?.team?.tmbId]);
|
||||
|
||||
const [selectedMemberIdList, setSelectedMembers] = useState<string[]>([]);
|
||||
const [selectedPermission, setSelectedPermission] = useState(permissionList['read'].value);
|
||||
const perLabel = useMemo(() => {
|
||||
return getPreLabelList(selectedPermission).join('、');
|
||||
}, [getPreLabelList, selectedPermission]);
|
||||
|
||||
const { mutate: onConfirm, isLoading: isUpdating } = useRequest({
|
||||
mutationFn: () => {
|
||||
return onUpdateCollaborators(selectedMemberIdList, selectedPermission);
|
||||
},
|
||||
successToast: '添加成功',
|
||||
errorToast: 'Error',
|
||||
onSuccess() {
|
||||
onClose();
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal isOpen onClose={onClose} iconSrc="modal/AddClb" title="添加协作者" minW="800px">
|
||||
<ModalBody>
|
||||
<MyBox
|
||||
isLoading={loadingMembers}
|
||||
display={'grid'}
|
||||
minH="400px"
|
||||
border="1px solid"
|
||||
borderColor="myGray.200"
|
||||
borderRadius="0.5rem"
|
||||
gridTemplateColumns="55% 45%"
|
||||
>
|
||||
<Flex
|
||||
flexDirection="column"
|
||||
borderRight="1px solid"
|
||||
borderColor="myGray.200"
|
||||
p="4"
|
||||
minH="200px"
|
||||
>
|
||||
<InputGroup alignItems="center" h="32px" my="2" py="1">
|
||||
<InputLeftElement>
|
||||
<MyIcon name="common/searchLight" w="16px" color={'myGray.500'} />
|
||||
</InputLeftElement>
|
||||
<Input
|
||||
placeholder="搜索用户名"
|
||||
fontSize="lg"
|
||||
bgColor="myGray.50"
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
/>
|
||||
</InputGroup>
|
||||
<Flex flexDirection="column" mt="2">
|
||||
{filterMembers.map((member) => {
|
||||
const onChange = () => {
|
||||
if (selectedMemberIdList.includes(member.tmbId)) {
|
||||
setSelectedMembers(selectedMemberIdList.filter((v) => v !== member.tmbId));
|
||||
} else {
|
||||
setSelectedMembers([...selectedMemberIdList, member.tmbId]);
|
||||
}
|
||||
};
|
||||
const collaborator = collaboratorList.find((v) => v.tmbId === member.tmbId);
|
||||
return (
|
||||
<Flex
|
||||
key={member.tmbId}
|
||||
mt="1"
|
||||
py="1"
|
||||
px="3"
|
||||
borderRadius="sm"
|
||||
alignItems="center"
|
||||
_hover={{
|
||||
bgColor: 'myGray.50',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
<Checkbox
|
||||
size="lg"
|
||||
mr="3"
|
||||
isChecked={selectedMemberIdList.includes(member.tmbId)}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<Flex
|
||||
flexDirection="row"
|
||||
onClick={onChange}
|
||||
w="full"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Flex flexDirection="row" alignItems="center">
|
||||
<MyAvatar src={member.avatar} w="32px" />
|
||||
<Box ml="2">{member.memberName}</Box>
|
||||
</Flex>
|
||||
{!!collaborator && <PermissionTags permission={collaborator.permission} />}
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex p="4" flexDirection="column">
|
||||
<Box>已选: {selectedMemberIdList.length}</Box>
|
||||
<Flex flexDirection="column" mt="2">
|
||||
{selectedMemberIdList.map((tmbId) => {
|
||||
const member = filterMembers.find((v) => v.tmbId === tmbId);
|
||||
return member ? (
|
||||
<Flex
|
||||
key={tmbId}
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
py="2"
|
||||
px={3}
|
||||
borderRadius={'md'}
|
||||
_hover={{ bg: 'myGray.50' }}
|
||||
_notLast={{ mb: 2 }}
|
||||
>
|
||||
<Avatar src={member.avatar} w="24px" />
|
||||
<Box w="full" fontSize="lg">
|
||||
{member.memberName}
|
||||
</Box>
|
||||
<MyIcon
|
||||
name="common/closeLight"
|
||||
w="16px"
|
||||
cursor={'pointer'}
|
||||
_hover={{
|
||||
color: 'red.600'
|
||||
}}
|
||||
onClick={() =>
|
||||
setSelectedMembers(selectedMemberIdList.filter((v) => v !== tmbId))
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
) : null;
|
||||
})}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</MyBox>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<PermissionSelect
|
||||
value={selectedPermission}
|
||||
Button={
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
bg={'myGray.50'}
|
||||
border="base"
|
||||
fontSize={'sm'}
|
||||
px={3}
|
||||
borderRadius={'md'}
|
||||
h={'32px'}
|
||||
>
|
||||
{perLabel}
|
||||
<ChevronDownIcon fontSize={'lg'} />
|
||||
</Flex>
|
||||
}
|
||||
onChange={(v) => setSelectedPermission(v)}
|
||||
/>
|
||||
<Button isLoading={isUpdating} ml="4" h={'32px'} onClick={onConfirm}>
|
||||
确认
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
import {
|
||||
ModalBody,
|
||||
Table,
|
||||
TableContainer,
|
||||
Tbody,
|
||||
Th,
|
||||
Thead,
|
||||
Tr,
|
||||
Td,
|
||||
Box,
|
||||
Flex
|
||||
} from '@chakra-ui/react';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import React from 'react';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import PermissionSelect from './PermissionSelect';
|
||||
import PermissionTags from './PermissionTags';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import { CollaboratorContext } from './context';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||
|
||||
export type ManageModalProps = {
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
function ManageModal({ onClose }: ManageModalProps) {
|
||||
const { userInfo } = useUserStore();
|
||||
const { collaboratorList, onUpdateCollaborators, onDelOneCollaborator } = useContextSelector(
|
||||
CollaboratorContext,
|
||||
(v) => v
|
||||
);
|
||||
|
||||
const { mutate: onDelete, isLoading: isDeleting } = useRequest({
|
||||
mutationFn: (tmbId: string) => onDelOneCollaborator(tmbId)
|
||||
});
|
||||
|
||||
const { mutate: onUpdate, isLoading: isUpdating } = useRequest({
|
||||
mutationFn: ({ tmbId, per }: { tmbId: string; per: PermissionValueType }) => {
|
||||
return onUpdateCollaborators([tmbId], per);
|
||||
},
|
||||
successToast: '更新成功',
|
||||
errorToast: 'Error'
|
||||
});
|
||||
|
||||
const loading = isDeleting || isUpdating;
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isLoading={loading}
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
minW="600px"
|
||||
title="管理协作者"
|
||||
iconSrc="common/settingLight"
|
||||
>
|
||||
<ModalBody>
|
||||
<TableContainer borderRadius="md" minH="400px">
|
||||
<Table>
|
||||
<Thead bg="myGray.100">
|
||||
<Tr>
|
||||
<Th border="none">名称</Th>
|
||||
<Th border="none">权限</Th>
|
||||
<Th border="none">操作</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{collaboratorList?.map((item) => {
|
||||
return (
|
||||
<Tr
|
||||
key={item.tmbId}
|
||||
_hover={{
|
||||
bg: 'myGray.50'
|
||||
}}
|
||||
>
|
||||
<Td border="none">
|
||||
<Flex alignItems="center">
|
||||
<Avatar src={item.avatar} w="24px" mr={2} />
|
||||
{item.name}
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td border="none">
|
||||
<PermissionTags permission={item.permission} />
|
||||
</Td>
|
||||
<Td border="none">
|
||||
{item.tmbId !== userInfo?.team?.tmbId && (
|
||||
<PermissionSelect
|
||||
Button={
|
||||
<MyIcon name={'edit'} w={'16px'} _hover={{ color: 'primary.600' }} />
|
||||
}
|
||||
value={item.permission}
|
||||
onChange={(per) => {
|
||||
onUpdate({
|
||||
tmbId: item.tmbId,
|
||||
per
|
||||
});
|
||||
}}
|
||||
onDelete={() => {
|
||||
onDelete(item.tmbId);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
</Tbody>
|
||||
</Table>
|
||||
{collaboratorList?.length === 0 && <EmptyTip text={'暂无协作者'} />}
|
||||
</TableContainer>
|
||||
</ModalBody>
|
||||
</MyModal>
|
||||
);
|
||||
}
|
||||
|
||||
export default ManageModal;
|
||||
@@ -0,0 +1,243 @@
|
||||
import {
|
||||
ButtonProps,
|
||||
Flex,
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuList,
|
||||
Box,
|
||||
Radio,
|
||||
useOutsideClick
|
||||
} from '@chakra-ui/react';
|
||||
import React, { useMemo, useRef, useState } from 'react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { Permission } from '@fastgpt/global/support/permission/controller';
|
||||
import { CollaboratorContext } from './context';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyDivider from '@fastgpt/web/components/common/MyDivider';
|
||||
|
||||
export type PermissionSelectProps = {
|
||||
value?: PermissionValueType;
|
||||
onChange: (value: PermissionValueType) => void;
|
||||
trigger?: 'hover' | 'click';
|
||||
offset?: [number, number];
|
||||
Button: React.ReactNode;
|
||||
|
||||
onDelete?: () => void;
|
||||
} & Omit<ButtonProps, 'onChange' | 'value'>;
|
||||
|
||||
const MenuStyle = {
|
||||
py: 2,
|
||||
px: 3,
|
||||
_hover: {
|
||||
bg: 'myGray.50'
|
||||
},
|
||||
borderRadius: 'md',
|
||||
cursor: 'pointer',
|
||||
fontSize: 'sm'
|
||||
};
|
||||
|
||||
function PermissionSelect({
|
||||
value,
|
||||
onChange,
|
||||
trigger = 'click',
|
||||
offset = [0, 5],
|
||||
Button,
|
||||
width = 'auto',
|
||||
onDelete,
|
||||
...props
|
||||
}: PermissionSelectProps) {
|
||||
const { t } = useTranslation();
|
||||
const { permissionList } = useContextSelector(CollaboratorContext, (v) => v);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const closeTimer = useRef<any>();
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const permissionSelectList = useMemo(() => {
|
||||
const list = Object.entries(permissionList).map(([key, value]) => {
|
||||
return {
|
||||
name: value.name,
|
||||
value: value.value,
|
||||
description: value.description,
|
||||
checkBoxType: value.checkBoxType
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
singleCheckBoxList: list.filter((item) => item.checkBoxType === 'single'),
|
||||
multipleCheckBoxList: list.filter((item) => item.checkBoxType === 'multiple')
|
||||
};
|
||||
}, [permissionList]);
|
||||
const selectedSingleValue = useMemo(() => {
|
||||
const per = new Permission({ per: value });
|
||||
|
||||
if (per.hasManagePer) return permissionList['manage'].value;
|
||||
if (per.hasWritePer) return permissionList['write'].value;
|
||||
|
||||
return permissionList['read'].value;
|
||||
}, [permissionList, value]);
|
||||
const selectedMultipleValues = useMemo(() => {
|
||||
const per = new Permission({ per: value });
|
||||
|
||||
return permissionSelectList.multipleCheckBoxList
|
||||
.filter((item) => {
|
||||
return per.checkPer(item.value);
|
||||
})
|
||||
.map((item) => item.value);
|
||||
}, [permissionSelectList.multipleCheckBoxList, value]);
|
||||
|
||||
useOutsideClick({
|
||||
ref: ref,
|
||||
handler: () => {
|
||||
setIsOpen(false);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Menu offset={offset} isOpen={isOpen} autoSelect={false} direction={'ltr'}>
|
||||
<Box
|
||||
ref={ref}
|
||||
onMouseEnter={() => {
|
||||
if (trigger === 'hover') {
|
||||
setIsOpen(true);
|
||||
}
|
||||
clearTimeout(closeTimer.current);
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
if (trigger === 'hover') {
|
||||
closeTimer.current = setTimeout(() => {
|
||||
setIsOpen(false);
|
||||
}, 100);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
position={'relative'}
|
||||
onClickCapture={() => {
|
||||
if (trigger === 'click') {
|
||||
setIsOpen(!isOpen);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<MenuButton
|
||||
w={'100%'}
|
||||
h={'100%'}
|
||||
position={'absolute'}
|
||||
top={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
left={0}
|
||||
/>
|
||||
<Box position={'relative'} cursor={'pointer'} userSelect={'none'}>
|
||||
{Button}
|
||||
</Box>
|
||||
</Box>
|
||||
<MenuList
|
||||
minW={isOpen ? `${width}px !important` : 0}
|
||||
p="3"
|
||||
border={'1px solid #fff'}
|
||||
boxShadow={
|
||||
'0px 2px 4px rgba(161, 167, 179, 0.25), 0px 0px 1px rgba(121, 141, 159, 0.25);'
|
||||
}
|
||||
zIndex={99}
|
||||
overflowY={'auto'}
|
||||
>
|
||||
{/* The list of single select permissions */}
|
||||
{permissionSelectList.singleCheckBoxList.map((item) => {
|
||||
const change = () => {
|
||||
const per = new Permission({ per: value });
|
||||
per.removePer(selectedSingleValue);
|
||||
per.addPer(item.value);
|
||||
onChange(per.value);
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex
|
||||
key={item.value}
|
||||
{...(selectedSingleValue === item.value
|
||||
? {
|
||||
color: 'primary.600'
|
||||
}
|
||||
: {})}
|
||||
{...MenuStyle}
|
||||
onClick={change}
|
||||
maxW={['70vw', '300px']}
|
||||
>
|
||||
<Radio size="lg" isChecked={selectedSingleValue === item.value} />
|
||||
<Box ml={4}>
|
||||
<Box>{item.name}</Box>
|
||||
<Box color={'myGray.500'}>{item.description}</Box>
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* <MyDivider my={3} />
|
||||
|
||||
{multipleValues.length > 0 && <Box m="4">其他权限(多选)</Box>} */}
|
||||
|
||||
{/* The list of multiple select permissions */}
|
||||
{/* {list
|
||||
.filter((item) => item.type === 'multiple')
|
||||
.map((item) => {
|
||||
const change = () => {
|
||||
if (checkPermission(valueState, item.value)) {
|
||||
setValueState(new Permission(valueState).remove(item.value).value);
|
||||
} else {
|
||||
setValueState(new Permission(valueState).add(item.value).value);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Flex
|
||||
key={item.value}
|
||||
{...(checkPermission(valueState, item.value)
|
||||
? {
|
||||
color: 'primary.500',
|
||||
bg: 'myWhite.300'
|
||||
}
|
||||
: {})}
|
||||
whiteSpace="pre-wrap"
|
||||
flexDirection="row"
|
||||
justifyContent="start"
|
||||
p="2"
|
||||
_hover={{
|
||||
bg: 'myGray.50'
|
||||
}}
|
||||
>
|
||||
<Checkbox
|
||||
size="lg"
|
||||
isChecked={checkPermission(valueState, item.value)}
|
||||
onChange={change}
|
||||
/>
|
||||
<Flex px="4" flexDirection="column" onClick={change}>
|
||||
<Box fontWeight="500">{item.name}</Box>
|
||||
<Box fontWeight="400">{item.description}</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
})}*/}
|
||||
{onDelete && (
|
||||
<>
|
||||
<MyDivider my={2} h={'2px'} borderColor={'myGray.200'} />
|
||||
<Flex
|
||||
{...MenuStyle}
|
||||
onClick={() => {
|
||||
onDelete();
|
||||
setIsOpen(false);
|
||||
}}
|
||||
>
|
||||
<MyIcon name="delete" w="20px" color="red.600" />
|
||||
<Box color="red.600">{t('common.Delete')}</Box>
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
</MenuList>
|
||||
</Box>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(PermissionSelect);
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
import Tag from '@fastgpt/web/components/common/Tag';
|
||||
import React from 'react';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { CollaboratorContext } from './context';
|
||||
|
||||
export type PermissionTagsProp = {
|
||||
permission: PermissionValueType;
|
||||
};
|
||||
|
||||
function PermissionTags({ permission }: PermissionTagsProp) {
|
||||
const { getPreLabelList } = useContextSelector(CollaboratorContext, (v) => v);
|
||||
|
||||
const perTagList = getPreLabelList(permission);
|
||||
|
||||
return (
|
||||
<Flex gap="2" alignItems="center">
|
||||
{perTagList.map((item) => (
|
||||
<Tag
|
||||
mixBlendMode={'multiply'}
|
||||
key={item}
|
||||
colorSchema="blue"
|
||||
border="none"
|
||||
py={2}
|
||||
px={3}
|
||||
fontSize={'xs'}
|
||||
>
|
||||
{item}
|
||||
</Tag>
|
||||
))}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
export default PermissionTags;
|
||||
@@ -0,0 +1,107 @@
|
||||
import { CollaboratorItemType } from '@fastgpt/global/support/permission/collaborator';
|
||||
import { PermissionList } from '@fastgpt/global/support/permission/constant';
|
||||
import { Permission } from '@fastgpt/global/support/permission/controller';
|
||||
import { PermissionListType, PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { ReactNode, useCallback } from 'react';
|
||||
import { createContext } from 'use-context-selector';
|
||||
|
||||
export type MemberManagerInputPropsType = {
|
||||
onGetCollaboratorList: () => Promise<CollaboratorItemType[]>;
|
||||
permissionList: PermissionListType;
|
||||
onUpdateCollaborators: (tmbIds: string[], permission: PermissionValueType) => any;
|
||||
onDelOneCollaborator: (tmbId: string) => any;
|
||||
};
|
||||
export type MemberManagerPropsType = MemberManagerInputPropsType & {
|
||||
collaboratorList: CollaboratorItemType[];
|
||||
refetchCollaboratorList: () => void;
|
||||
isFetchingCollaborator: boolean;
|
||||
getPreLabelList: (per: PermissionValueType) => string[];
|
||||
};
|
||||
|
||||
type CollaboratorContextType = MemberManagerPropsType & {};
|
||||
|
||||
export const CollaboratorContext = createContext<CollaboratorContextType>({
|
||||
collaboratorList: [],
|
||||
permissionList: PermissionList,
|
||||
onUpdateCollaborators: function () {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
onDelOneCollaborator: function () {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
getPreLabelList: function (): string[] {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
refetchCollaboratorList: function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
onGetCollaboratorList: function (): Promise<CollaboratorItemType[]> {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
isFetchingCollaborator: false
|
||||
});
|
||||
|
||||
export const CollaboratorContextProvider = ({
|
||||
onGetCollaboratorList,
|
||||
permissionList,
|
||||
onUpdateCollaborators,
|
||||
onDelOneCollaborator,
|
||||
children
|
||||
}: MemberManagerInputPropsType & {
|
||||
children: ReactNode;
|
||||
}) => {
|
||||
const {
|
||||
data: collaboratorList = [],
|
||||
refetch: refetchCollaboratorList,
|
||||
isLoading: isFetchingCollaborator
|
||||
} = useQuery(['collaboratorList'], onGetCollaboratorList);
|
||||
const onUpdateCollaboratorsThen = async (tmbIds: string[], permission: PermissionValueType) => {
|
||||
await onUpdateCollaborators(tmbIds, permission);
|
||||
refetchCollaboratorList();
|
||||
};
|
||||
const onDelOneCollaboratorThem = async (tmbId: string) => {
|
||||
await onDelOneCollaborator(tmbId);
|
||||
refetchCollaboratorList();
|
||||
};
|
||||
|
||||
const getPreLabelList = useCallback(
|
||||
(per: PermissionValueType) => {
|
||||
const Per = new Permission({ per });
|
||||
const labels: string[] = [];
|
||||
|
||||
if (Per.hasManagePer) {
|
||||
labels.push(permissionList['manage'].name);
|
||||
} else if (Per.hasWritePer) {
|
||||
labels.push(permissionList['write'].name);
|
||||
} else {
|
||||
labels.push(permissionList['read'].name);
|
||||
}
|
||||
|
||||
Object.values(permissionList).forEach((item) => {
|
||||
if (item.checkBoxType === 'multiple') {
|
||||
if (Per.checkPer(item.value)) {
|
||||
labels.push(item.name);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return labels;
|
||||
},
|
||||
[permissionList]
|
||||
);
|
||||
|
||||
const contextValue = {
|
||||
onGetCollaboratorList,
|
||||
collaboratorList,
|
||||
refetchCollaboratorList,
|
||||
isFetchingCollaborator,
|
||||
permissionList,
|
||||
onUpdateCollaborators: onUpdateCollaboratorsThen,
|
||||
onDelOneCollaborator: onDelOneCollaboratorThem,
|
||||
getPreLabelList
|
||||
};
|
||||
return (
|
||||
<CollaboratorContext.Provider value={contextValue}>{children}</CollaboratorContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,99 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Flex, Box, Button, Tag, TagLabel, useDisclosure } from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import { AddMemberModal } from './AddMemberModal';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import ManageModal from './ManageModal';
|
||||
import {
|
||||
CollaboratorContext,
|
||||
CollaboratorContextProvider,
|
||||
MemberManagerInputPropsType
|
||||
} from './context';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
|
||||
function MemberManger() {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
isOpen: isOpenAddMember,
|
||||
onOpen: onOpenAddMember,
|
||||
onClose: onCloseAddMember
|
||||
} = useDisclosure();
|
||||
const {
|
||||
isOpen: isOpenManageModal,
|
||||
onOpen: onOpenManageModal,
|
||||
onClose: onCloseManageModal
|
||||
} = useDisclosure();
|
||||
|
||||
const { collaboratorList, isFetchingCollaborator } = useContextSelector(
|
||||
CollaboratorContext,
|
||||
(v) => v
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex alignItems="center" flexDirection="row" justifyContent="space-between" w="full">
|
||||
<Box>协作者</Box>
|
||||
<Flex flexDirection="row" gap="2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="whitePrimary"
|
||||
leftIcon={<MyIcon w="4" name="common/settingLight" />}
|
||||
onClick={onOpenManageModal}
|
||||
>
|
||||
{t('permission.Manage')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="whitePrimary"
|
||||
leftIcon={<MyIcon w="4" name="support/permission/collaborator" />}
|
||||
onClick={onOpenAddMember}
|
||||
>
|
||||
{t('common.Add')}
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
{/* member list */}
|
||||
<MyBox
|
||||
isLoading={isFetchingCollaborator}
|
||||
mt={2}
|
||||
bg="myGray.100"
|
||||
borderRadius="md"
|
||||
size={'md'}
|
||||
>
|
||||
{collaboratorList?.length === 0 ? (
|
||||
<Box p={3} color="myGray.600" fontSize={'xs'} textAlign={'center'}>
|
||||
暂无协作者
|
||||
</Box>
|
||||
) : (
|
||||
<Flex gap="2" p={1.5}>
|
||||
{collaboratorList?.map((member) => {
|
||||
return (
|
||||
<Tag px="4" py="1.5" bgColor="white" key={member.tmbId} width="fit-content">
|
||||
<Flex alignItems="center">
|
||||
<Avatar src={member.avatar} w="24px" />
|
||||
<TagLabel mx="2">{member.name}</TagLabel>
|
||||
</Flex>
|
||||
</Tag>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
)}
|
||||
</MyBox>
|
||||
{isOpenAddMember && <AddMemberModal onClose={onCloseAddMember} />}
|
||||
{isOpenManageModal && <ManageModal onClose={onCloseManageModal} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function Render(props: MemberManagerInputPropsType) {
|
||||
return (
|
||||
<CollaboratorContextProvider {...props}>
|
||||
<MemberManger />
|
||||
</CollaboratorContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Render);
|
||||
Reference in New Issue
Block a user