perf: scroll page code
This commit is contained in:
parent
ec0cef09a2
commit
62bcff2ff0
@ -20,6 +20,10 @@ const TeamMemberSchema = new Schema({
|
|||||||
ref: userCollectionName,
|
ref: userCollectionName,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
avatar: {
|
||||||
|
type: String,
|
||||||
|
default: () => getRandomUserAvatar()
|
||||||
|
},
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'Member'
|
default: 'Member'
|
||||||
@ -36,10 +40,6 @@ const TeamMemberSchema = new Schema({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
avatar: {
|
|
||||||
type: String,
|
|
||||||
default: getRandomUserAvatar()
|
|
||||||
},
|
|
||||||
|
|
||||||
// Abandoned
|
// Abandoned
|
||||||
role: {
|
role: {
|
||||||
|
|||||||
@ -97,6 +97,46 @@ const MySelect = <T = any,>(
|
|||||||
|
|
||||||
const isSelecting = loading || isLoading;
|
const isSelecting = loading || isLoading;
|
||||||
|
|
||||||
|
const ListRender = useMemo(() => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{list.map((item, i) => (
|
||||||
|
<Box key={i}>
|
||||||
|
<MenuItem
|
||||||
|
{...menuItemStyles}
|
||||||
|
{...(value === item.value
|
||||||
|
? {
|
||||||
|
ref: SelectedItemRef,
|
||||||
|
color: 'primary.700',
|
||||||
|
bg: 'myGray.100',
|
||||||
|
fontWeight: '600'
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
color: 'myGray.900'
|
||||||
|
})}
|
||||||
|
onClick={() => {
|
||||||
|
if (onChange && value !== item.value) {
|
||||||
|
onChange(item.value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
whiteSpace={'pre-wrap'}
|
||||||
|
fontSize={'sm'}
|
||||||
|
display={'block'}
|
||||||
|
>
|
||||||
|
<Box>{item.label}</Box>
|
||||||
|
{item.description && (
|
||||||
|
<Box color={'myGray.500'} fontSize={'xs'}>
|
||||||
|
{item.description}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</MenuItem>
|
||||||
|
{item.showBorder && <MyDivider my={2} />}
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
css={css({
|
css={css({
|
||||||
@ -164,45 +204,7 @@ const MySelect = <T = any,>(
|
|||||||
maxH={'40vh'}
|
maxH={'40vh'}
|
||||||
overflowY={'auto'}
|
overflowY={'auto'}
|
||||||
>
|
>
|
||||||
{(() => {
|
{ScrollData ? <ScrollData>{ListRender}</ScrollData> : ListRender}
|
||||||
const component = list.map((item, i) => (
|
|
||||||
<Box key={i}>
|
|
||||||
<MenuItem
|
|
||||||
{...menuItemStyles}
|
|
||||||
{...(value === item.value
|
|
||||||
? {
|
|
||||||
ref: SelectedItemRef,
|
|
||||||
color: 'primary.700',
|
|
||||||
bg: 'myGray.100',
|
|
||||||
fontWeight: '600'
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
color: 'myGray.900'
|
|
||||||
})}
|
|
||||||
onClick={() => {
|
|
||||||
if (onChange && value !== item.value) {
|
|
||||||
onChange(item.value);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
whiteSpace={'pre-wrap'}
|
|
||||||
fontSize={'sm'}
|
|
||||||
display={'block'}
|
|
||||||
>
|
|
||||||
<Box>{item.label}</Box>
|
|
||||||
{item.description && (
|
|
||||||
<Box color={'myGray.500'} fontSize={'xs'}>
|
|
||||||
{item.description}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</MenuItem>
|
|
||||||
{item.showBorder && <MyDivider my={2} />}
|
|
||||||
</Box>
|
|
||||||
));
|
|
||||||
if (ScrollData) {
|
|
||||||
return <ScrollData>{component}</ScrollData>;
|
|
||||||
}
|
|
||||||
return component;
|
|
||||||
})()}
|
|
||||||
</MenuList>
|
</MenuList>
|
||||||
</Menu>
|
</Menu>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@ -15,7 +15,7 @@ function UserBox({ sourceMember, avatarSize = '1.25rem', ...props }: UserBoxProp
|
|||||||
<HStack space="1" {...props}>
|
<HStack space="1" {...props}>
|
||||||
<Avatar src={sourceMember.avatar} w={avatarSize} />
|
<Avatar src={sourceMember.avatar} w={avatarSize} />
|
||||||
<Box>{sourceMember.name}</Box>
|
<Box>{sourceMember.name}</Box>
|
||||||
{sourceMember.status === 'leave' && <Tag color="gray">{t('account_team:leaved')}</Tag>}
|
{sourceMember.status === 'leave' && <Tag color="gray">{t('common:user_leaved')}</Tag>}
|
||||||
</HStack>
|
</HStack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -269,8 +269,10 @@ export function useScrollPagination<
|
|||||||
({
|
({
|
||||||
children,
|
children,
|
||||||
ScrollContainerRef,
|
ScrollContainerRef,
|
||||||
|
isLoading,
|
||||||
...props
|
...props
|
||||||
}: {
|
}: {
|
||||||
|
isLoading?: boolean;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
ScrollContainerRef?: RefObject<HTMLDivElement>;
|
ScrollContainerRef?: RefObject<HTMLDivElement>;
|
||||||
} & BoxProps) => {
|
} & BoxProps) => {
|
||||||
@ -302,7 +304,7 @@ export function useScrollPagination<
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box {...props} ref={ref} overflow={'overlay'}>
|
<MyBox {...props} ref={ref} overflow={'overlay'} isLoading={isLoading}>
|
||||||
{scrollLoadType === 'top' && total > 0 && isLoading && (
|
{scrollLoadType === 'top' && total > 0 && isLoading && (
|
||||||
<Box mt={2} fontSize={'xs'} color={'blackAlpha.500'} textAlign={'center'}>
|
<Box mt={2} fontSize={'xs'} color={'blackAlpha.500'} textAlign={'center'}>
|
||||||
{t('common:common.is_requesting')}
|
{t('common:common.is_requesting')}
|
||||||
@ -325,7 +327,7 @@ export function useScrollPagination<
|
|||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{isEmpty && EmptyTip}
|
{isEmpty && EmptyTip}
|
||||||
</Box>
|
</MyBox>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@ -39,6 +39,7 @@
|
|||||||
"classification": "Classification",
|
"classification": "Classification",
|
||||||
"click_to_resume": "Click to Resume",
|
"click_to_resume": "Click to Resume",
|
||||||
"code_editor": "Code Editor",
|
"code_editor": "Code Editor",
|
||||||
|
"code_error.account_error": "Incorrect account name or password",
|
||||||
"code_error.app_error.invalid_app_type": "Invalid Application Type",
|
"code_error.app_error.invalid_app_type": "Invalid Application Type",
|
||||||
"code_error.app_error.invalid_owner": "Unauthorized Application Owner",
|
"code_error.app_error.invalid_owner": "Unauthorized Application Owner",
|
||||||
"code_error.app_error.not_exist": "Application Does Not Exist",
|
"code_error.app_error.not_exist": "Application Does Not Exist",
|
||||||
@ -95,7 +96,6 @@
|
|||||||
"code_error.team_error.website_sync_not_enough": "Unauthorized to Use Website Sync",
|
"code_error.team_error.website_sync_not_enough": "Unauthorized to Use Website Sync",
|
||||||
"code_error.token_error_code.403": "Invalid Login Status, Please Re-login",
|
"code_error.token_error_code.403": "Invalid Login Status, Please Re-login",
|
||||||
"code_error.user_error.balance_not_enough": "Insufficient Account Balance",
|
"code_error.user_error.balance_not_enough": "Insufficient Account Balance",
|
||||||
"code_error.account_error": "Incorrect account name or password",
|
|
||||||
"code_error.user_error.bin_visitor_guest": "You Are Currently a Guest, Unauthorized to Operate",
|
"code_error.user_error.bin_visitor_guest": "You Are Currently a Guest, Unauthorized to Operate",
|
||||||
"code_error.user_error.un_auth_user": "User Not Found",
|
"code_error.user_error.un_auth_user": "User Not Found",
|
||||||
"common.Action": "Action",
|
"common.Action": "Action",
|
||||||
@ -1273,6 +1273,7 @@
|
|||||||
"user.team.role.Visitor": "visitor",
|
"user.team.role.Visitor": "visitor",
|
||||||
"user.team.role.writer": "writable member",
|
"user.team.role.writer": "writable member",
|
||||||
"user.type": "Type",
|
"user.type": "Type",
|
||||||
|
"user_leaved": "Leaved",
|
||||||
"verification": "Verification",
|
"verification": "Verification",
|
||||||
"workflow.template.communication": "Communication",
|
"workflow.template.communication": "Communication",
|
||||||
"xx_search_result": "{{key}} Search Results",
|
"xx_search_result": "{{key}} Search Results",
|
||||||
|
|||||||
@ -35,7 +35,6 @@
|
|||||||
"user_team_invite_member": "邀请成员",
|
"user_team_invite_member": "邀请成员",
|
||||||
"user_team_leave_team": "离开团队",
|
"user_team_leave_team": "离开团队",
|
||||||
"user_team_leave_team_failed": "离开团队失败",
|
"user_team_leave_team_failed": "离开团队失败",
|
||||||
"leaved": "已离职",
|
|
||||||
"waiting": "待接受",
|
"waiting": "待接受",
|
||||||
"sync_immediately": "立即同步",
|
"sync_immediately": "立即同步",
|
||||||
"sync_member_failed": "同步成员失败",
|
"sync_member_failed": "同步成员失败",
|
||||||
|
|||||||
@ -43,6 +43,7 @@
|
|||||||
"classification": "分类",
|
"classification": "分类",
|
||||||
"click_to_resume": "点击恢复",
|
"click_to_resume": "点击恢复",
|
||||||
"code_editor": "代码编辑",
|
"code_editor": "代码编辑",
|
||||||
|
"code_error.account_error": "账号名或密码错误",
|
||||||
"code_error.app_error.invalid_app_type": "错误的应用类型",
|
"code_error.app_error.invalid_app_type": "错误的应用类型",
|
||||||
"code_error.app_error.invalid_owner": "非法的应用所有者",
|
"code_error.app_error.invalid_owner": "非法的应用所有者",
|
||||||
"code_error.app_error.not_exist": "应用不存在",
|
"code_error.app_error.not_exist": "应用不存在",
|
||||||
@ -99,7 +100,6 @@
|
|||||||
"code_error.team_error.website_sync_not_enough": "无权使用Web站点同步~",
|
"code_error.team_error.website_sync_not_enough": "无权使用Web站点同步~",
|
||||||
"code_error.token_error_code.403": "登录状态无效,请重新登录",
|
"code_error.token_error_code.403": "登录状态无效,请重新登录",
|
||||||
"code_error.user_error.balance_not_enough": "账号余额不足~",
|
"code_error.user_error.balance_not_enough": "账号余额不足~",
|
||||||
"code_error.account_error": "账号名或密码错误",
|
|
||||||
"code_error.user_error.bin_visitor_guest": "您当前身份为游客,无权操作",
|
"code_error.user_error.bin_visitor_guest": "您当前身份为游客,无权操作",
|
||||||
"code_error.user_error.un_auth_user": "找不到该用户",
|
"code_error.user_error.un_auth_user": "找不到该用户",
|
||||||
"common.Action": "操作",
|
"common.Action": "操作",
|
||||||
@ -1268,6 +1268,7 @@
|
|||||||
"user.team.role.Visitor": "访客",
|
"user.team.role.Visitor": "访客",
|
||||||
"user.team.role.writer": "可写成员",
|
"user.team.role.writer": "可写成员",
|
||||||
"user.type": "类型",
|
"user.type": "类型",
|
||||||
|
"user_leaved": "已离开",
|
||||||
"verification": "验证",
|
"verification": "验证",
|
||||||
"workflow.template.communication": "通信",
|
"workflow.template.communication": "通信",
|
||||||
"xx_search_result": "{{key}} 的搜索结果",
|
"xx_search_result": "{{key}} 的搜索结果",
|
||||||
|
|||||||
@ -39,6 +39,7 @@
|
|||||||
"classification": "分類",
|
"classification": "分類",
|
||||||
"click_to_resume": "點選繼續",
|
"click_to_resume": "點選繼續",
|
||||||
"code_editor": "程式碼編輯器",
|
"code_editor": "程式碼編輯器",
|
||||||
|
"code_error.account_error": "帳號名稱或密碼錯誤",
|
||||||
"code_error.app_error.invalid_app_type": "無效的應用程式類型",
|
"code_error.app_error.invalid_app_type": "無效的應用程式類型",
|
||||||
"code_error.app_error.invalid_owner": "非法的應用程式擁有者",
|
"code_error.app_error.invalid_owner": "非法的應用程式擁有者",
|
||||||
"code_error.app_error.not_exist": "應用程式不存在",
|
"code_error.app_error.not_exist": "應用程式不存在",
|
||||||
@ -95,7 +96,6 @@
|
|||||||
"code_error.team_error.website_sync_not_enough": "無權使用網站同步",
|
"code_error.team_error.website_sync_not_enough": "無權使用網站同步",
|
||||||
"code_error.token_error_code.403": "登入狀態無效,請重新登入",
|
"code_error.token_error_code.403": "登入狀態無效,請重新登入",
|
||||||
"code_error.user_error.balance_not_enough": "帳戶餘額不足",
|
"code_error.user_error.balance_not_enough": "帳戶餘額不足",
|
||||||
"code_error.account_error": "帳號名稱或密碼錯誤",
|
|
||||||
"code_error.user_error.bin_visitor_guest": "您目前身份為訪客,無權操作",
|
"code_error.user_error.bin_visitor_guest": "您目前身份為訪客,無權操作",
|
||||||
"code_error.user_error.un_auth_user": "找不到此使用者",
|
"code_error.user_error.un_auth_user": "找不到此使用者",
|
||||||
"common.Action": "操作",
|
"common.Action": "操作",
|
||||||
@ -1273,6 +1273,7 @@
|
|||||||
"user.team.role.Visitor": "訪客",
|
"user.team.role.Visitor": "訪客",
|
||||||
"user.team.role.writer": "可寫入成員",
|
"user.team.role.writer": "可寫入成員",
|
||||||
"user.type": "類型",
|
"user.type": "類型",
|
||||||
|
"user_leaved": "已離開",
|
||||||
"verification": "驗證",
|
"verification": "驗證",
|
||||||
"workflow.template.communication": "通訊",
|
"workflow.template.communication": "通訊",
|
||||||
"xx_search_result": "{{key}} 的搜尋結果",
|
"xx_search_result": "{{key}} 的搜尋結果",
|
||||||
|
|||||||
@ -31,6 +31,7 @@ import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
|||||||
import { delLeaveTeam } from '@/web/support/user/team/api';
|
import { delLeaveTeam } from '@/web/support/user/team/api';
|
||||||
import { postSyncMembers } from '@/web/support/user/api';
|
import { postSyncMembers } from '@/web/support/user/api';
|
||||||
import MyLoading from '@fastgpt/web/components/common/MyLoading';
|
import MyLoading from '@fastgpt/web/components/common/MyLoading';
|
||||||
|
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
|
||||||
|
|
||||||
const InviteModal = dynamic(() => import('./InviteModal'));
|
const InviteModal = dynamic(() => import('./InviteModal'));
|
||||||
const TeamTagModal = dynamic(() => import('@/components/support/user/team/TeamTagModal'));
|
const TeamTagModal = dynamic(() => import('@/components/support/user/team/TeamTagModal'));
|
||||||
@ -169,84 +170,83 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
|
|
||||||
<Box flex={'1 0 0'} overflow={'auto'}>
|
<Box flex={'1 0 0'} overflow={'auto'}>
|
||||||
<TableContainer overflow={'unset'} fontSize={'sm'}>
|
<TableContainer overflow={'unset'} fontSize={'sm'}>
|
||||||
{MemberScrollData && (
|
<MemberScrollData>
|
||||||
<MemberScrollData>
|
<Table overflow={'unset'}>
|
||||||
<Table overflow={'unset'}>
|
<Thead>
|
||||||
<Thead>
|
<Tr bgColor={'white !important'}>
|
||||||
<Tr bgColor={'white !important'}>
|
<Th borderLeftRadius="6px" bgColor="myGray.100">
|
||||||
<Th borderLeftRadius="6px" bgColor="myGray.100">
|
{t('account_team:user_name')}
|
||||||
{t('account_team:user_name')}
|
</Th>
|
||||||
|
<Th bgColor="myGray.100">{t('account_team:member_group')}</Th>
|
||||||
|
{!isSyncMember && (
|
||||||
|
<Th borderRightRadius="6px" bgColor="myGray.100">
|
||||||
|
{t('common:common.Action')}
|
||||||
</Th>
|
</Th>
|
||||||
<Th bgColor="myGray.100">{t('account_team:member_group')}</Th>
|
)}
|
||||||
|
</Tr>
|
||||||
|
</Thead>
|
||||||
|
<Tbody>
|
||||||
|
{members?.map((item) => (
|
||||||
|
<Tr key={item.userId} overflow={'unset'}>
|
||||||
|
<Td>
|
||||||
|
<HStack>
|
||||||
|
<Avatar src={item.avatar} w={['18px', '22px']} borderRadius={'50%'} />
|
||||||
|
<Box className={'textEllipsis'}>
|
||||||
|
{item.memberName}
|
||||||
|
{item.status === 'waiting' && (
|
||||||
|
<Tag ml="2" colorSchema="yellow">
|
||||||
|
{t('account_team:waiting')}
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</HStack>
|
||||||
|
</Td>
|
||||||
|
<Td maxW={'300px'}>
|
||||||
|
<GroupTags
|
||||||
|
names={groups
|
||||||
|
?.filter((group) =>
|
||||||
|
group.members.map((m) => m.tmbId).includes(item.tmbId)
|
||||||
|
)
|
||||||
|
.map((g) => g.name)}
|
||||||
|
max={3}
|
||||||
|
/>
|
||||||
|
</Td>
|
||||||
{!isSyncMember && (
|
{!isSyncMember && (
|
||||||
<Th borderRightRadius="6px" bgColor="myGray.100">
|
<Td>
|
||||||
{t('common:common.Action')}
|
{userInfo?.team.permission.hasManagePer &&
|
||||||
</Th>
|
item.role !== TeamMemberRoleEnum.owner &&
|
||||||
|
item.tmbId !== userInfo?.team.tmbId && (
|
||||||
|
<Icon
|
||||||
|
name={'common/trash'}
|
||||||
|
cursor={'pointer'}
|
||||||
|
w="1rem"
|
||||||
|
p="1"
|
||||||
|
borderRadius="sm"
|
||||||
|
_hover={{
|
||||||
|
color: 'red.600',
|
||||||
|
bgColor: 'myGray.100'
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
openRemoveMember(
|
||||||
|
() =>
|
||||||
|
delRemoveMember(item.tmbId).then(() =>
|
||||||
|
Promise.all([refetchGroups(), refetchMembers()])
|
||||||
|
),
|
||||||
|
undefined,
|
||||||
|
t('account_team:remove_tip', {
|
||||||
|
username: item.memberName
|
||||||
|
})
|
||||||
|
)();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Td>
|
||||||
)}
|
)}
|
||||||
</Tr>
|
</Tr>
|
||||||
</Thead>
|
))}
|
||||||
<Tbody>
|
</Tbody>
|
||||||
{members?.map((item) => (
|
</Table>
|
||||||
<Tr key={item.userId} overflow={'unset'}>
|
</MemberScrollData>
|
||||||
<Td>
|
|
||||||
<HStack>
|
|
||||||
<Avatar src={item.avatar} w={['18px', '22px']} borderRadius={'50%'} />
|
|
||||||
<Box className={'textEllipsis'}>
|
|
||||||
{item.memberName}
|
|
||||||
{item.status === 'waiting' && (
|
|
||||||
<Tag ml="2" colorSchema="yellow">
|
|
||||||
{t('account_team:waiting')}
|
|
||||||
</Tag>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</HStack>
|
|
||||||
</Td>
|
|
||||||
<Td maxW={'300px'}>
|
|
||||||
<GroupTags
|
|
||||||
names={groups
|
|
||||||
?.filter((group) =>
|
|
||||||
group.members.map((m) => m.tmbId).includes(item.tmbId)
|
|
||||||
)
|
|
||||||
.map((g) => g.name)}
|
|
||||||
max={3}
|
|
||||||
/>
|
|
||||||
</Td>
|
|
||||||
{!isSyncMember && (
|
|
||||||
<Td>
|
|
||||||
userInfo?.team.permission.hasManagePer && item.role !==
|
|
||||||
TeamMemberRoleEnum.owner && item.tmbId !== userInfo?.team.tmbId && (
|
|
||||||
<Icon
|
|
||||||
name={'common/trash'}
|
|
||||||
cursor={'pointer'}
|
|
||||||
w="1rem"
|
|
||||||
p="1"
|
|
||||||
borderRadius="sm"
|
|
||||||
_hover={{
|
|
||||||
color: 'red.600',
|
|
||||||
bgColor: 'myGray.100'
|
|
||||||
}}
|
|
||||||
onClick={() => {
|
|
||||||
openRemoveMember(
|
|
||||||
() =>
|
|
||||||
delRemoveMember(item.tmbId).then(() =>
|
|
||||||
Promise.all([refetchGroups(), refetchMembers()])
|
|
||||||
),
|
|
||||||
undefined,
|
|
||||||
t('account_team:remove_tip', {
|
|
||||||
username: item.memberName
|
|
||||||
})
|
|
||||||
)();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
</Td>
|
|
||||||
)}
|
|
||||||
</Tr>
|
|
||||||
))}
|
|
||||||
</Tbody>
|
|
||||||
</Table>
|
|
||||||
</MemberScrollData>
|
|
||||||
)}
|
|
||||||
<ConfirmRemoveMemberModal />
|
<ConfirmRemoveMemberModal />
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@ -163,14 +163,20 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
<Flex justify={'space-between'} align={'center'} pb={'1rem'}>
|
<Flex justify={'space-between'} align={'center'} pb={'1rem'}>
|
||||||
{Tabs}
|
{Tabs}
|
||||||
</Flex>
|
</Flex>
|
||||||
<MyBox flex={'1 0 0'} overflow={'auto'} isLoading={isLoadingOrgs}>
|
<MyBox
|
||||||
|
flex={'1 0 0'}
|
||||||
|
h={0}
|
||||||
|
display={'flex'}
|
||||||
|
flexDirection={'column'}
|
||||||
|
isLoading={isLoadingOrgs}
|
||||||
|
>
|
||||||
<Box mb={3}>
|
<Box mb={3}>
|
||||||
<Path paths={paths} rootName={userInfo?.team?.teamName} onClick={setParentPath} />
|
<Path paths={paths} rootName={userInfo?.team?.teamName} onClick={setParentPath} />
|
||||||
</Box>
|
</Box>
|
||||||
<Flex w={'100%'} gap={'4'}>
|
<Flex flex={'1 0 0'} h={0} w={'100%'} gap={'4'}>
|
||||||
{/* Table */}
|
{/* Table */}
|
||||||
<TableContainer overflow={'unset'} fontSize={'sm'} flexGrow={1}>
|
<TableContainer h={'100%'} overflowY={'auto'} fontSize={'sm'} flexGrow={1}>
|
||||||
<Table overflow={'unset'}>
|
<Table>
|
||||||
<Thead>
|
<Thead>
|
||||||
<Tr bg={'white !important'}>
|
<Tr bg={'white !important'}>
|
||||||
<Th bg="myGray.100" borderLeftRadius="6px">
|
<Th bg="myGray.100" borderLeftRadius="6px">
|
||||||
@ -326,33 +332,33 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
</VStack>
|
</VStack>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
{!!editOrg && (
|
|
||||||
<OrgInfoModal
|
|
||||||
editOrg={editOrg}
|
|
||||||
onClose={() => setEditOrg(undefined)}
|
|
||||||
onSuccess={refetchOrgs}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{!!movingOrg && (
|
|
||||||
<OrgMoveModal
|
|
||||||
orgs={orgs}
|
|
||||||
movingOrg={movingOrg}
|
|
||||||
onClose={() => setMovingOrg(undefined)}
|
|
||||||
onSuccess={refetchOrgs}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{!!manageMemberOrg && (
|
|
||||||
<OrgMemberManageModal
|
|
||||||
currentOrg={manageMemberOrg}
|
|
||||||
refetchOrgs={refetchOrgs}
|
|
||||||
onClose={() => setManageMemberOrg(undefined)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<ConfirmDeleteOrgModal />
|
|
||||||
<ConfirmDeleteMember />
|
|
||||||
</MyBox>
|
</MyBox>
|
||||||
|
|
||||||
|
{!!editOrg && (
|
||||||
|
<OrgInfoModal
|
||||||
|
editOrg={editOrg}
|
||||||
|
onClose={() => setEditOrg(undefined)}
|
||||||
|
onSuccess={refetchOrgs}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!!movingOrg && (
|
||||||
|
<OrgMoveModal
|
||||||
|
orgs={orgs}
|
||||||
|
movingOrg={movingOrg}
|
||||||
|
onClose={() => setMovingOrg(undefined)}
|
||||||
|
onSuccess={refetchOrgs}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!!manageMemberOrg && (
|
||||||
|
<OrgMemberManageModal
|
||||||
|
currentOrg={manageMemberOrg}
|
||||||
|
refetchOrgs={refetchOrgs}
|
||||||
|
onClose={() => setManageMemberOrg(undefined)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<ConfirmDeleteOrgModal />
|
||||||
|
<ConfirmDeleteMember />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,7 +26,7 @@ type TeamModalContextType = {
|
|||||||
refetchTeams: () => void;
|
refetchTeams: () => void;
|
||||||
refetchGroups: () => void;
|
refetchGroups: () => void;
|
||||||
teamSize: number;
|
teamSize: number;
|
||||||
MemberScrollData?: ReturnType<typeof useScrollPagination>['ScrollData'];
|
MemberScrollData: ReturnType<typeof useScrollPagination>['ScrollData'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TeamContext = createContext<TeamModalContextType>({
|
export const TeamContext = createContext<TeamModalContextType>({
|
||||||
@ -51,7 +51,7 @@ export const TeamContext = createContext<TeamModalContextType>({
|
|||||||
},
|
},
|
||||||
|
|
||||||
teamSize: 0,
|
teamSize: 0,
|
||||||
MemberScrollData: undefined
|
MemberScrollData: () => <></>
|
||||||
});
|
});
|
||||||
|
|
||||||
export const TeamModalContextProvider = ({ children }: { children: ReactNode }) => {
|
export const TeamModalContextProvider = ({ children }: { children: ReactNode }) => {
|
||||||
|
|||||||
@ -61,72 +61,81 @@ const Team = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<AccountContainer isLoading={isLoading}>
|
<AccountContainer isLoading={isLoading}>
|
||||||
{/* header */}
|
<Flex h={'100%'} flexDirection={'column'}>
|
||||||
<Flex
|
{/* header */}
|
||||||
w={'100%'}
|
<Flex
|
||||||
h={'3.5rem'}
|
w={'100%'}
|
||||||
px={'1.56rem'}
|
h={'3.5rem'}
|
||||||
py={'0.56rem'}
|
px={'1.56rem'}
|
||||||
borderBottom={'1px solid'}
|
py={'0.56rem'}
|
||||||
borderColor={'myGray.200'}
|
borderBottom={'1px solid'}
|
||||||
bg={'myGray.25'}
|
borderColor={'myGray.200'}
|
||||||
align={'center'}
|
bg={'myGray.25'}
|
||||||
gap={6}
|
align={'center'}
|
||||||
justify={'space-between'}
|
gap={6}
|
||||||
>
|
justify={'space-between'}
|
||||||
<Flex align={'center'}>
|
>
|
||||||
<Flex gap={2} color={'myGray.900'}>
|
<Flex align={'center'}>
|
||||||
<Icon name="support/user/usersLight" w={'1.25rem'} h={'1.25rem'} />
|
<Flex gap={2} color={'myGray.900'}>
|
||||||
<Box fontWeight={'500'} fontSize={'1rem'}>
|
<Icon name="support/user/usersLight" w={'1.25rem'} h={'1.25rem'} />
|
||||||
{t('account:team')}
|
<Box fontWeight={'500'} fontSize={'1rem'}>
|
||||||
</Box>
|
{t('account:team')}
|
||||||
</Flex>
|
</Box>
|
||||||
<Flex align={'center'} ml={6}>
|
|
||||||
<TeamSelector height={'28px'} />
|
|
||||||
</Flex>
|
|
||||||
{userInfo?.team?.role === TeamMemberRoleEnum.owner && (
|
|
||||||
<Flex align={'center'} justify={'center'} ml={2} p={'0.44rem'}>
|
|
||||||
<MyIcon
|
|
||||||
name="edit"
|
|
||||||
w="18px"
|
|
||||||
cursor="pointer"
|
|
||||||
_hover={{
|
|
||||||
color: 'primary.500'
|
|
||||||
}}
|
|
||||||
onClick={() => {
|
|
||||||
if (!userInfo?.team) return;
|
|
||||||
setEditTeamData({
|
|
||||||
id: userInfo.team.teamId,
|
|
||||||
name: userInfo.team.teamName,
|
|
||||||
avatar: userInfo.team.avatar
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
<Flex align={'center'} ml={6}>
|
||||||
|
<TeamSelector height={'28px'} />
|
||||||
|
</Flex>
|
||||||
|
{userInfo?.team?.role === TeamMemberRoleEnum.owner && (
|
||||||
|
<Flex align={'center'} justify={'center'} ml={2} p={'0.44rem'}>
|
||||||
|
<MyIcon
|
||||||
|
name="edit"
|
||||||
|
w="18px"
|
||||||
|
cursor="pointer"
|
||||||
|
_hover={{
|
||||||
|
color: 'primary.500'
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
if (!userInfo?.team) return;
|
||||||
|
setEditTeamData({
|
||||||
|
id: userInfo.team.teamId,
|
||||||
|
name: userInfo.team.teamName,
|
||||||
|
avatar: userInfo.team.avatar
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
float={'right'}
|
||||||
|
color={'myGray.900'}
|
||||||
|
h={'1.25rem'}
|
||||||
|
px={'0.5rem'}
|
||||||
|
py={'0.125rem'}
|
||||||
|
fontSize={'0.75rem'}
|
||||||
|
borderRadius={'1.25rem'}
|
||||||
|
bg={'myGray.150'}
|
||||||
|
>
|
||||||
|
{t('account_team:total_team_members', { amount: teamSize })}
|
||||||
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
|
{/* table */}
|
||||||
<Box
|
<Box
|
||||||
float={'right'}
|
py={'1.5rem'}
|
||||||
color={'myGray.900'}
|
px={'2rem'}
|
||||||
h={'1.25rem'}
|
flex={'1 0 0'}
|
||||||
px={'0.5rem'}
|
display={'flex'}
|
||||||
py={'0.125rem'}
|
flexDirection={'column'}
|
||||||
fontSize={'0.75rem'}
|
overflow={'auto'}
|
||||||
borderRadius={'1.25rem'}
|
|
||||||
bg={'myGray.150'}
|
|
||||||
>
|
>
|
||||||
{t('account_team:total_team_members', { amount: teamSize })}
|
{teamTab === TeamTabEnum.member && <MemberTable Tabs={Tabs} />}
|
||||||
|
{teamTab === TeamTabEnum.org && <OrgManage Tabs={Tabs} />}
|
||||||
|
{teamTab === TeamTabEnum.group && <GroupManage Tabs={Tabs} />}
|
||||||
|
{teamTab === TeamTabEnum.permission && <PermissionManage Tabs={Tabs} />}
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
{/* table */}
|
|
||||||
<Box py={'1.5rem'} px={'2rem'}>
|
|
||||||
{teamTab === TeamTabEnum.member && <MemberTable Tabs={Tabs} />}
|
|
||||||
{teamTab === TeamTabEnum.org && <OrgManage Tabs={Tabs} />}
|
|
||||||
{teamTab === TeamTabEnum.group && <GroupManage Tabs={Tabs} />}
|
|
||||||
{teamTab === TeamTabEnum.permission && <PermissionManage Tabs={Tabs} />}
|
|
||||||
</Box>
|
|
||||||
</AccountContainer>
|
</AccountContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { NextAPI } from '@/service/middleware/entry';
|
import { NextAPI } from '@/service/middleware/entry';
|
||||||
|
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
||||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||||
import { MongoUser } from '@fastgpt/service/support/user/schema';
|
import { MongoUser } from '@fastgpt/service/support/user/schema';
|
||||||
import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema';
|
import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema';
|
||||||
@ -19,18 +20,35 @@ export default NextAPI(handler);
|
|||||||
|
|
||||||
const moveUserAvatar = async () => {
|
const moveUserAvatar = async () => {
|
||||||
try {
|
try {
|
||||||
const users = await MongoUser.find({});
|
const users = await MongoUser.find({}, '_id avatar');
|
||||||
|
console.log('Total users:', users.length);
|
||||||
|
let success = 0;
|
||||||
for await (const user of users) {
|
for await (const user of users) {
|
||||||
await MongoTeamMember.updateOne(
|
// @ts-ignore
|
||||||
{
|
if (!user.avatar) continue;
|
||||||
_id: user._id
|
try {
|
||||||
},
|
await mongoSessionRun(async (session) => {
|
||||||
{
|
await MongoTeamMember.updateOne(
|
||||||
avatar: (user as any).avatar // 删除 avatar 字段, 因为 Type 改了,所以这里不能直接写 user.avatar
|
{
|
||||||
}
|
userId: user._id
|
||||||
);
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
avatar: (user as any).avatar // 删除 avatar 字段, 因为 Type 改了,所以这里不能直接写 user.avatar
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ session }
|
||||||
|
);
|
||||||
|
// @ts-ignore
|
||||||
|
user.avatar = undefined;
|
||||||
|
await user.save({ session });
|
||||||
|
});
|
||||||
|
success++;
|
||||||
|
console.log('Move avatar success:', success);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
console.log('Move avatar success:', users.length);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import {
|
|||||||
getWorkflowVersionList,
|
getWorkflowVersionList,
|
||||||
updateAppVersion
|
updateAppVersion
|
||||||
} from '@/web/core/app/api/version';
|
} from '@/web/core/app/api/version';
|
||||||
import { useVirtualScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
||||||
import CustomRightDrawer from '@fastgpt/web/components/common/MyDrawer/CustomRightDrawer';
|
import CustomRightDrawer from '@fastgpt/web/components/common/MyDrawer/CustomRightDrawer';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { Box, BoxProps, Button, Flex, Input } from '@chakra-ui/react';
|
import { Box, BoxProps, Button, Flex, Input } from '@chakra-ui/react';
|
||||||
@ -22,7 +22,6 @@ import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
|||||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||||
import type { AppVersionSchemaType, VersionListItemType } from '@fastgpt/global/core/app/version';
|
import type { AppVersionSchemaType, VersionListItemType } from '@fastgpt/global/core/app/version';
|
||||||
import type { SimpleAppSnapshotType } from './SimpleApp/useSnapshots';
|
import type { SimpleAppSnapshotType } from './SimpleApp/useSnapshots';
|
||||||
import UserBox from '@fastgpt/web/components/common/UserBox';
|
|
||||||
|
|
||||||
const PublishHistoriesSlider = <T extends SimpleAppSnapshotType | WorkflowSnapshotsType>({
|
const PublishHistoriesSlider = <T extends SimpleAppSnapshotType | WorkflowSnapshotsType>({
|
||||||
onClose,
|
onClose,
|
||||||
@ -183,18 +182,18 @@ const TeamCloud = ({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||||
|
|
||||||
const { scrollDataList, ScrollList, isLoading, setData } = useVirtualScrollPagination(
|
const {
|
||||||
getWorkflowVersionList,
|
ScrollData,
|
||||||
{
|
data: scrollDataList,
|
||||||
itemHeight: 40,
|
setData,
|
||||||
overscan: 20,
|
isLoading
|
||||||
|
} = useScrollPagination(getWorkflowVersionList, {
|
||||||
pageSize: 30,
|
pageSize: 30,
|
||||||
defaultParams: {
|
params: {
|
||||||
appId: appDetail._id
|
appId: appDetail._id
|
||||||
}
|
},
|
||||||
}
|
refreshDeps: [appDetail._id]
|
||||||
);
|
});
|
||||||
const [editIndex, setEditIndex] = useState<number | undefined>(undefined);
|
const [editIndex, setEditIndex] = useState<number | undefined>(undefined);
|
||||||
const [hoveredIndex, setHoveredIndex] = useState<number | undefined>(undefined);
|
const [hoveredIndex, setHoveredIndex] = useState<number | undefined>(undefined);
|
||||||
|
|
||||||
@ -231,14 +230,13 @@ const TeamCloud = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollList isLoading={isLoading || isLoadingVersion} flex={'1 0 0'} px={5}>
|
<ScrollData isLoading={isLoading || isLoadingVersion} flex={'1 0 0'} px={5}>
|
||||||
{scrollDataList.map((data, index) => {
|
{scrollDataList.map((item, index) => {
|
||||||
const item = data.data;
|
const firstPublishedIndex = scrollDataList.findIndex((data) => data.isPublish);
|
||||||
const firstPublishedIndex = scrollDataList.findIndex((data) => data.data.isPublish);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
key={data.index}
|
key={item._id}
|
||||||
alignItems={'center'}
|
alignItems={'center'}
|
||||||
py={editIndex !== index ? 2 : 1}
|
py={editIndex !== index ? 2 : 1}
|
||||||
px={3}
|
px={3}
|
||||||
@ -260,7 +258,7 @@ const TeamCloud = ({
|
|||||||
Trigger={
|
Trigger={
|
||||||
<Box>
|
<Box>
|
||||||
<Avatar
|
<Avatar
|
||||||
src={data.data.sourceMember.avatar}
|
src={item.sourceMember.avatar}
|
||||||
borderRadius={'50%'}
|
borderRadius={'50%'}
|
||||||
w={'24px'}
|
w={'24px'}
|
||||||
h={'24px'}
|
h={'24px'}
|
||||||
@ -269,10 +267,25 @@ const TeamCloud = ({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{() => (
|
{() => (
|
||||||
<Flex alignItems={'center'} h={'full'} pl={5} gap={3}>
|
<Flex alignItems={'center'} h={'full'} pl={5} gap={2}>
|
||||||
<UserBox sourceMember={data.data.sourceMember} avatarSize="36px" fontSize="sm" />
|
<Box>
|
||||||
<Box fontSize={'12px'} color={'myGray.500'}>
|
<Avatar
|
||||||
{formatTime2YMDHMS(item.time)}
|
src={item.sourceMember.avatar}
|
||||||
|
borderRadius={'50%'}
|
||||||
|
w={'36px'}
|
||||||
|
h={'36px'}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Flex gap={1} fontSize={'sm'} color={'myGray.900'}>
|
||||||
|
<Box>{item.sourceMember.name}</Box>
|
||||||
|
{item.sourceMember.status === 'leave' && (
|
||||||
|
<Tag color="gray">{t('common:user_leaved')}</Tag>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
<Box fontSize={'xs'} mt={2} color={'myGray.500'}>
|
||||||
|
{formatTime2YMDHMS(item.time)}
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
@ -340,6 +353,6 @@ const TeamCloud = ({
|
|||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ScrollList>
|
</ScrollData>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user