perf: scroll page code
This commit is contained in:
@@ -31,6 +31,7 @@ import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { delLeaveTeam } from '@/web/support/user/team/api';
|
||||
import { postSyncMembers } from '@/web/support/user/api';
|
||||
import MyLoading from '@fastgpt/web/components/common/MyLoading';
|
||||
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
|
||||
|
||||
const InviteModal = dynamic(() => import('./InviteModal'));
|
||||
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'}>
|
||||
<TableContainer overflow={'unset'} fontSize={'sm'}>
|
||||
{MemberScrollData && (
|
||||
<MemberScrollData>
|
||||
<Table overflow={'unset'}>
|
||||
<Thead>
|
||||
<Tr bgColor={'white !important'}>
|
||||
<Th borderLeftRadius="6px" bgColor="myGray.100">
|
||||
{t('account_team:user_name')}
|
||||
<MemberScrollData>
|
||||
<Table overflow={'unset'}>
|
||||
<Thead>
|
||||
<Tr bgColor={'white !important'}>
|
||||
<Th borderLeftRadius="6px" bgColor="myGray.100">
|
||||
{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 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 && (
|
||||
<Th borderRightRadius="6px" bgColor="myGray.100">
|
||||
{t('common:common.Action')}
|
||||
</Th>
|
||||
<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>
|
||||
</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 && (
|
||||
<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>
|
||||
)}
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</MemberScrollData>
|
||||
<ConfirmRemoveMemberModal />
|
||||
</TableContainer>
|
||||
</Box>
|
||||
|
||||
@@ -163,14 +163,20 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
<Flex justify={'space-between'} align={'center'} pb={'1rem'}>
|
||||
{Tabs}
|
||||
</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}>
|
||||
<Path paths={paths} rootName={userInfo?.team?.teamName} onClick={setParentPath} />
|
||||
</Box>
|
||||
<Flex w={'100%'} gap={'4'}>
|
||||
<Flex flex={'1 0 0'} h={0} w={'100%'} gap={'4'}>
|
||||
{/* Table */}
|
||||
<TableContainer overflow={'unset'} fontSize={'sm'} flexGrow={1}>
|
||||
<Table overflow={'unset'}>
|
||||
<TableContainer h={'100%'} overflowY={'auto'} fontSize={'sm'} flexGrow={1}>
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr bg={'white !important'}>
|
||||
<Th bg="myGray.100" borderLeftRadius="6px">
|
||||
@@ -326,33 +332,33 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
</VStack>
|
||||
)}
|
||||
</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>
|
||||
|
||||
{!!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;
|
||||
refetchGroups: () => void;
|
||||
teamSize: number;
|
||||
MemberScrollData?: ReturnType<typeof useScrollPagination>['ScrollData'];
|
||||
MemberScrollData: ReturnType<typeof useScrollPagination>['ScrollData'];
|
||||
};
|
||||
|
||||
export const TeamContext = createContext<TeamModalContextType>({
|
||||
@@ -51,7 +51,7 @@ export const TeamContext = createContext<TeamModalContextType>({
|
||||
},
|
||||
|
||||
teamSize: 0,
|
||||
MemberScrollData: undefined
|
||||
MemberScrollData: () => <></>
|
||||
});
|
||||
|
||||
export const TeamModalContextProvider = ({ children }: { children: ReactNode }) => {
|
||||
|
||||
@@ -61,72 +61,81 @@ const Team = () => {
|
||||
|
||||
return (
|
||||
<AccountContainer isLoading={isLoading}>
|
||||
{/* header */}
|
||||
<Flex
|
||||
w={'100%'}
|
||||
h={'3.5rem'}
|
||||
px={'1.56rem'}
|
||||
py={'0.56rem'}
|
||||
borderBottom={'1px solid'}
|
||||
borderColor={'myGray.200'}
|
||||
bg={'myGray.25'}
|
||||
align={'center'}
|
||||
gap={6}
|
||||
justify={'space-between'}
|
||||
>
|
||||
<Flex align={'center'}>
|
||||
<Flex gap={2} color={'myGray.900'}>
|
||||
<Icon name="support/user/usersLight" w={'1.25rem'} h={'1.25rem'} />
|
||||
<Box fontWeight={'500'} fontSize={'1rem'}>
|
||||
{t('account:team')}
|
||||
</Box>
|
||||
</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 h={'100%'} flexDirection={'column'}>
|
||||
{/* header */}
|
||||
<Flex
|
||||
w={'100%'}
|
||||
h={'3.5rem'}
|
||||
px={'1.56rem'}
|
||||
py={'0.56rem'}
|
||||
borderBottom={'1px solid'}
|
||||
borderColor={'myGray.200'}
|
||||
bg={'myGray.25'}
|
||||
align={'center'}
|
||||
gap={6}
|
||||
justify={'space-between'}
|
||||
>
|
||||
<Flex align={'center'}>
|
||||
<Flex gap={2} color={'myGray.900'}>
|
||||
<Icon name="support/user/usersLight" w={'1.25rem'} h={'1.25rem'} />
|
||||
<Box fontWeight={'500'} fontSize={'1rem'}>
|
||||
{t('account:team')}
|
||||
</Box>
|
||||
</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>
|
||||
|
||||
{/* table */}
|
||||
<Box
|
||||
float={'right'}
|
||||
color={'myGray.900'}
|
||||
h={'1.25rem'}
|
||||
px={'0.5rem'}
|
||||
py={'0.125rem'}
|
||||
fontSize={'0.75rem'}
|
||||
borderRadius={'1.25rem'}
|
||||
bg={'myGray.150'}
|
||||
py={'1.5rem'}
|
||||
px={'2rem'}
|
||||
flex={'1 0 0'}
|
||||
display={'flex'}
|
||||
flexDirection={'column'}
|
||||
overflow={'auto'}
|
||||
>
|
||||
{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>
|
||||
</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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { MongoUser } from '@fastgpt/service/support/user/schema';
|
||||
import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema';
|
||||
@@ -19,18 +20,35 @@ export default NextAPI(handler);
|
||||
|
||||
const moveUserAvatar = async () => {
|
||||
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) {
|
||||
await MongoTeamMember.updateOne(
|
||||
{
|
||||
_id: user._id
|
||||
},
|
||||
{
|
||||
avatar: (user as any).avatar // 删除 avatar 字段, 因为 Type 改了,所以这里不能直接写 user.avatar
|
||||
}
|
||||
);
|
||||
// @ts-ignore
|
||||
if (!user.avatar) continue;
|
||||
try {
|
||||
await mongoSessionRun(async (session) => {
|
||||
await MongoTeamMember.updateOne(
|
||||
{
|
||||
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) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
getWorkflowVersionList,
|
||||
updateAppVersion
|
||||
} 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 { useTranslation } from 'next-i18next';
|
||||
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 type { AppVersionSchemaType, VersionListItemType } from '@fastgpt/global/core/app/version';
|
||||
import type { SimpleAppSnapshotType } from './SimpleApp/useSnapshots';
|
||||
import UserBox from '@fastgpt/web/components/common/UserBox';
|
||||
|
||||
const PublishHistoriesSlider = <T extends SimpleAppSnapshotType | WorkflowSnapshotsType>({
|
||||
onClose,
|
||||
@@ -183,18 +182,18 @@ const TeamCloud = ({
|
||||
const { t } = useTranslation();
|
||||
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||
|
||||
const { scrollDataList, ScrollList, isLoading, setData } = useVirtualScrollPagination(
|
||||
getWorkflowVersionList,
|
||||
{
|
||||
itemHeight: 40,
|
||||
overscan: 20,
|
||||
|
||||
pageSize: 30,
|
||||
defaultParams: {
|
||||
appId: appDetail._id
|
||||
}
|
||||
}
|
||||
);
|
||||
const {
|
||||
ScrollData,
|
||||
data: scrollDataList,
|
||||
setData,
|
||||
isLoading
|
||||
} = useScrollPagination(getWorkflowVersionList, {
|
||||
pageSize: 30,
|
||||
params: {
|
||||
appId: appDetail._id
|
||||
},
|
||||
refreshDeps: [appDetail._id]
|
||||
});
|
||||
const [editIndex, setEditIndex] = useState<number | undefined>(undefined);
|
||||
const [hoveredIndex, setHoveredIndex] = useState<number | undefined>(undefined);
|
||||
|
||||
@@ -231,14 +230,13 @@ const TeamCloud = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<ScrollList isLoading={isLoading || isLoadingVersion} flex={'1 0 0'} px={5}>
|
||||
{scrollDataList.map((data, index) => {
|
||||
const item = data.data;
|
||||
const firstPublishedIndex = scrollDataList.findIndex((data) => data.data.isPublish);
|
||||
<ScrollData isLoading={isLoading || isLoadingVersion} flex={'1 0 0'} px={5}>
|
||||
{scrollDataList.map((item, index) => {
|
||||
const firstPublishedIndex = scrollDataList.findIndex((data) => data.isPublish);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
key={data.index}
|
||||
key={item._id}
|
||||
alignItems={'center'}
|
||||
py={editIndex !== index ? 2 : 1}
|
||||
px={3}
|
||||
@@ -260,7 +258,7 @@ const TeamCloud = ({
|
||||
Trigger={
|
||||
<Box>
|
||||
<Avatar
|
||||
src={data.data.sourceMember.avatar}
|
||||
src={item.sourceMember.avatar}
|
||||
borderRadius={'50%'}
|
||||
w={'24px'}
|
||||
h={'24px'}
|
||||
@@ -269,10 +267,25 @@ const TeamCloud = ({
|
||||
}
|
||||
>
|
||||
{() => (
|
||||
<Flex alignItems={'center'} h={'full'} pl={5} gap={3}>
|
||||
<UserBox sourceMember={data.data.sourceMember} avatarSize="36px" fontSize="sm" />
|
||||
<Box fontSize={'12px'} color={'myGray.500'}>
|
||||
{formatTime2YMDHMS(item.time)}
|
||||
<Flex alignItems={'center'} h={'full'} pl={5} gap={2}>
|
||||
<Box>
|
||||
<Avatar
|
||||
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>
|
||||
</Flex>
|
||||
)}
|
||||
@@ -340,6 +353,6 @@ const TeamCloud = ({
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
</ScrollList>
|
||||
</ScrollData>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user