pref: useScrollPagination support debounce and throttle. (#4355)

* pref: useScrollPagination support debounce and throttle.

* fix: useScrollPagination loading

* fix: isloading

* fix: org search path hide
This commit is contained in:
Finley Ge
2025-03-27 15:58:13 +08:00
committed by GitHub
parent 8b29aae238
commit e9f75c7e66
9 changed files with 68 additions and 87 deletions

View File

@@ -14,6 +14,7 @@ import {
} from 'ahooks'; } from 'ahooks';
import MyBox from '../components/common/MyBox'; import MyBox from '../components/common/MyBox';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useRequest2 } from './useRequest';
type ItemHeight<T> = (index: number, data: T) => number; type ItemHeight<T> = (index: number, data: T) => number;
const thresholdVal = 100; const thresholdVal = 100;
@@ -183,22 +184,21 @@ export function useScrollPagination<
>( >(
api: (data: TParams) => Promise<TData>, api: (data: TParams) => Promise<TData>,
{ {
refreshDeps,
scrollLoadType = 'bottom', scrollLoadType = 'bottom',
pageSize = 10, pageSize = 10,
params = {}, params = {},
EmptyTip, EmptyTip,
showErrorToast = true showErrorToast = true,
...props
}: { }: {
refreshDeps?: any[];
scrollLoadType?: 'top' | 'bottom'; scrollLoadType?: 'top' | 'bottom';
pageSize?: number; pageSize?: number;
params?: Record<string, any>; params?: Record<string, any>;
EmptyTip?: React.JSX.Element; EmptyTip?: React.JSX.Element;
showErrorToast?: boolean; showErrorToast?: boolean;
} } & Parameters<typeof useRequest2>[1]
) { ) {
const { t } = useTranslation(); const { t } = useTranslation();
const { toast } = useToast(); const { toast } = useToast();
@@ -213,6 +213,7 @@ export function useScrollPagination<
const loadData = useLockFn( const loadData = useLockFn(
async (init = false, ScrollContainerRef?: RefObject<HTMLDivElement>) => { async (init = false, ScrollContainerRef?: RefObject<HTMLDivElement>) => {
if (noMore && !init) return; if (noMore && !init) return;
setTrue();
if (init) { if (init) {
setData([]); setData([]);
@@ -221,8 +222,6 @@ export function useScrollPagination<
const offset = init ? 0 : data.length; const offset = init ? 0 : data.length;
setTrue();
try { try {
const res = await api({ const res = await api({
offset, offset,
@@ -274,7 +273,7 @@ export function useScrollPagination<
({ ({
children, children,
ScrollContainerRef, ScrollContainerRef,
isLoading, isLoading: isLoadingProp,
...props ...props
}: { }: {
isLoading?: boolean; isLoading?: boolean;
@@ -283,7 +282,7 @@ export function useScrollPagination<
} & BoxProps) => { } & BoxProps) => {
const ref = ScrollContainerRef || ScrollRef; const ref = ScrollContainerRef || ScrollRef;
const loadText = useMemo(() => { const loadText = useMemo(() => {
if (isLoading) return t('common:common.is_requesting'); if (isLoading || isLoadingProp) return t('common:common.is_requesting');
if (noMore) return t('common:common.request_end'); if (noMore) return t('common:common.request_end');
return t('common:common.request_more'); return t('common:common.request_more');
}, [isLoading, noMore]); }, [isLoading, noMore]);
@@ -338,13 +337,13 @@ export function useScrollPagination<
); );
// Reload data // Reload data
useRequest( useRequest2(
async () => { async () => {
loadData(true); loadData(true);
}, },
{ {
manual: false, manual: false,
refreshDeps ...props
} }
); );

View File

@@ -79,7 +79,10 @@ function MemberModal({
withOrgs: true, withOrgs: true,
status: 'active', status: 'active',
searchKey searchKey
} },
throttleWait: 500,
debounceWait: 200,
refreshDeps: [searchKey]
}); });
const { const {
@@ -100,13 +103,6 @@ function MemberModal({
} }
); );
const search = _.debounce(() => {
refreshList();
refreshGroups();
}, 200);
useEffect(search, [searchKey]);
const [selectedOrgList, setSelectedOrgIdList] = useState<OrgListItemType[]>([]); const [selectedOrgList, setSelectedOrgIdList] = useState<OrgListItemType[]>([]);
const [selectedMemberList, setSelectedMemberList] = useState< const [selectedMemberList, setSelectedMemberList] = useState<

View File

@@ -62,11 +62,11 @@ function GroupEditModal({
status: 'active', status: 'active',
withOrgs: true, withOrgs: true,
searchKey searchKey
} },
throttleWait: 500,
debounceWait: 200,
refreshDeps: [searchKey]
}); });
const refetchMemberList = _.debounce(refreshList, 200);
useEffect(() => refetchMemberList, [searchKey]);
const groupId = useMemo(() => String(group._id), [group._id]); const groupId = useMemo(() => String(group._id), [group._id]);

View File

@@ -48,14 +48,14 @@ export function ChangeOwnerModal({
{ {
pageSize: 20, pageSize: 20,
params: { params: {
searchKey: searchKey searchKey
} },
refreshDeps: [searchKey],
debounceWait: 200,
throttleWait: 500
} }
); );
const search = _.debounce(refreshList, 500);
useEffect(() => search, [searchKey]);
const { const {
isOpen: isOpenMemberListMenu, isOpen: isOpenMemberListMenu,
onClose: onCloseMemberListMenu, onClose: onCloseMemberListMenu,

View File

@@ -102,17 +102,12 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
withPermission: true, withPermission: true,
withOrgs: true, withOrgs: true,
searchKey searchKey
} },
refreshDeps: [searchKey, status],
throttleWait: 500,
debounceWait: 200
}); });
const refreshList = _.debounce(() => {
refetchMemberList();
}, 200);
useEffect(() => {
refreshList();
}, [searchKey, status]);
const onRefreshMembers = useCallback(() => { const onRefreshMembers = useCallback(() => {
refetchMemberList(); refetchMemberList();
}, [refetchMemberList]); }, [refetchMemberList]);

View File

@@ -3,7 +3,6 @@ import { Box, Button, Flex, Grid, HStack, ModalBody, ModalFooter } from '@chakra
import type { GroupMemberRole } from '@fastgpt/global/support/permission/memberGroup/constant'; import type { GroupMemberRole } from '@fastgpt/global/support/permission/memberGroup/constant';
import Avatar from '@fastgpt/web/components/common/Avatar'; import Avatar from '@fastgpt/web/components/common/Avatar';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
import type { IconNameType } from '@fastgpt/web/components/common/Icon/type';
import SearchInput from '@fastgpt/web/components/common/Input/SearchInput'; import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
import MyModal from '@fastgpt/web/components/common/MyModal'; import MyModal from '@fastgpt/web/components/common/MyModal';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
@@ -31,32 +30,32 @@ function OrgMemberManageModal({
onClose: () => void; onClose: () => void;
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [searchKey, setSearchKey] = useState('');
const { const { data: allMembers, ScrollData: MemberScrollData } = useScrollPagination(getTeamMembers, {
data: allMembers,
ScrollData: MemberScrollData,
isLoading: isLoadingMembers
} = useScrollPagination(getTeamMembers, {
pageSize: 20, pageSize: 20,
params: { params: {
withOrgs: true, withOrgs: true,
withPermission: false, withPermission: false,
status: 'active' status: 'active',
} searchKey
},
throttleWait: 500,
debounceWait: 200,
refreshDeps: [searchKey]
}); });
const { const { data: orgMembers, ScrollData: OrgMemberScrollData } = useScrollPagination(
data: orgMembers, getTeamMembers,
ScrollData: OrgMemberScrollData, {
isLoading: isLoadingOrgMembers pageSize: 100000,
} = useScrollPagination(getTeamMembers, { params: {
pageSize: 100000, orgId: currentOrg._id,
params: { withOrgs: false,
orgId: currentOrg._id, withPermission: false
withOrgs: false, }
withPermission: false
} }
}); );
const [selected, setSelected] = useState<{ name: string; tmbId: string; avatar: string }[]>([]); const [selected, setSelected] = useState<{ name: string; tmbId: string; avatar: string }[]>([]);
@@ -70,8 +69,6 @@ function OrgMemberManageModal({
); );
}, [orgMembers]); }, [orgMembers]);
const [searchKey, setSearchKey] = useState('');
const { run: onUpdate, loading: isLoadingUpdate } = useRequest2( const { run: onUpdate, loading: isLoadingUpdate } = useRequest2(
() => { () => {
return putUpdateOrgMembers({ return putUpdateOrgMembers({
@@ -147,7 +144,7 @@ function OrgMemberManageModal({
setSearchKey(e.target.value); setSearchKey(e.target.value);
}} }}
/> />
<MemberScrollData mt={3} flexGrow="1" overflow={'auto'} isLoading={isLoadingMembers}> <MemberScrollData mt={3} flexGrow="1" overflow={'auto'}>
{allMembers.map((member) => { {allMembers.map((member) => {
return ( return (
<MemberItemCard <MemberItemCard
@@ -163,12 +160,7 @@ function OrgMemberManageModal({
</MemberScrollData> </MemberScrollData>
</Flex> </Flex>
<Flex flexDirection="column" p="4" overflowY="auto" overflowX="hidden"> <Flex flexDirection="column" p="4" overflowY="auto" overflowX="hidden">
<OrgMemberScrollData <OrgMemberScrollData mt={3} flexGrow="1" overflow={'auto'}>
mt={3}
flexGrow="1"
overflow={'auto'}
isLoading={isLoadingOrgMembers}
>
<Box mt={2}>{`${t('common:chosen')}:${selected.length}`}</Box> <Box mt={2}>{`${t('common:chosen')}:${selected.length}`}</Box>
{selected.map((member) => { {selected.map((member) => {
return ( return (

View File

@@ -81,7 +81,7 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
const { const {
currentOrg, currentOrg,
orgs, orgs,
isLoadingOrgs, isLoading,
paths, paths,
onClickOrg, onClickOrg,
members, members,
@@ -134,18 +134,14 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
/> />
</Box> </Box>
</Flex> </Flex>
<MyBox <MyBox flex={'1 0 0'} h={0} display={'flex'} flexDirection={'column'}>
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={onPathClick} /> {!searchKey && (
<Path paths={paths} rootName={userInfo?.team?.teamName} onClick={onPathClick} />
)}
</Box> </Box>
<Flex flex={'1 0 0'} h={0} w={'100%'} gap={'4'}> <Flex flex={'1 0 0'} h={0} w={'100%'} gap={'4'}>
<MemberScrollData flex="1"> <MemberScrollData flex="1" isLoading={isLoading}>
<TableContainer> <TableContainer>
<Table> <Table>
<Thead> <Thead>

View File

@@ -185,8 +185,7 @@ const TeamCloud = ({
const { const {
ScrollData, ScrollData,
data: scrollDataList, data: scrollDataList,
setData, setData
isLoading
} = useScrollPagination(getWorkflowVersionList, { } = useScrollPagination(getWorkflowVersionList, {
pageSize: 30, pageSize: 30,
params: { params: {
@@ -230,7 +229,7 @@ const TeamCloud = ({
); );
return ( return (
<ScrollData isLoading={isLoading || isLoadingVersion} flex={'1 0 0'} px={5}> <ScrollData flex={'1 0 0'} px={5} isLoading={isLoadingVersion}>
{scrollDataList.map((item, index) => { {scrollDataList.map((item, index) => {
const firstPublishedIndex = scrollDataList.findIndex((data) => data.isPublish); const firstPublishedIndex = scrollDataList.findIndex((data) => data.isPublish);

View File

@@ -41,14 +41,11 @@ function useOrg({ withPermission = true }: { withPermission?: boolean } = {}) {
() => getOrgList({ orgId: currentOrg._id, withPermission: withPermission, searchKey }), () => getOrgList({ orgId: currentOrg._id, withPermission: withPermission, searchKey }),
{ {
manual: false, manual: false,
refreshDeps: [userInfo?.team?.teamId, path, currentOrg._id] refreshDeps: [userInfo?.team?.teamId, path, currentOrg._id, searchKey],
debounceWait: 200,
throttleWait: 500
} }
); );
const search = _.debounce(() => {
if (!searchKey) return;
refetchOrgs();
}, 200);
useEffect(() => search, [searchKey]);
const paths = useMemo(() => { const paths = useMemo(() => {
if (!currentOrg) return []; if (!currentOrg) return [];
@@ -63,14 +60,19 @@ function useOrg({ withPermission = true }: { withPermission?: boolean } = {}) {
}, [currentOrg, orgStack]); }, [currentOrg, orgStack]);
const onClickOrg = (org: OrgListItemType) => { const onClickOrg = (org: OrgListItemType) => {
setOrgStack([...orgStack, org]); if (searchKey) {
setSearchKey(''); setOrgStack([org]);
setSearchKey('');
} else {
setOrgStack([...orgStack, org]);
}
}; };
const { const {
data: members = [], data: members = [],
ScrollData: MemberScrollData, ScrollData: MemberScrollData,
refreshList: refetchMembers refreshList: refetchMembers,
isLoading: isLoadingMembers
} = useScrollPagination(getTeamMembers, { } = useScrollPagination(getTeamMembers, {
pageSize: 20, pageSize: 20,
params: { params: {
@@ -106,11 +108,13 @@ function useOrg({ withPermission = true }: { withPermission?: boolean } = {}) {
]); ]);
}; };
const isLoading = isLoadingOrgs || isLoadingMembers;
return { return {
orgStack, orgStack,
currentOrg, currentOrg,
orgs, orgs,
isLoadingOrgs, isLoading,
paths, paths,
onClickOrg, onClickOrg,
members, members,