feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
.empty {
|
||||
overflow: visible;
|
||||
height: 100%;
|
||||
|
||||
|
||||
|
||||
.spin {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
:global {
|
||||
.semi-spin-wrapper {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
.semi-tabs-content {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.semi-spin-children {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
margin-left: 8px;
|
||||
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 22px;
|
||||
color: var(--semi-color-text-3, rgba(29, 28, 35, 35%));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { UIEmpty, Spin } from '@coze-arch/bot-semi';
|
||||
import { IllustrationFailure } from '@douyinfe/semi-illustrations';
|
||||
|
||||
import { type EmptyProps } from '../../type';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
/* Plugin header */
|
||||
|
||||
function Index(props: EmptyProps) {
|
||||
const {
|
||||
isLoading,
|
||||
isSearching,
|
||||
loadRetry,
|
||||
isError,
|
||||
renderEmpty,
|
||||
text,
|
||||
btn,
|
||||
icon,
|
||||
} = props;
|
||||
return (
|
||||
<div className={s.empty}>
|
||||
{renderEmpty?.(props) ||
|
||||
(!isError ? (
|
||||
isLoading ? (
|
||||
<Spin
|
||||
tip={
|
||||
<span className={s['loading-text']}>{I18n.t('Loading')}</span>
|
||||
}
|
||||
wrapperClassName={s.spin}
|
||||
size="middle"
|
||||
/>
|
||||
) : (
|
||||
<UIEmpty
|
||||
isNotFound={!!isSearching}
|
||||
empty={{
|
||||
title: text?.emptyTitle || I18n.t('inifinit_list_empty_title'),
|
||||
description: text?.emptyTitle ? text?.emptyDesc : '',
|
||||
btnText: btn?.emptyText,
|
||||
btnOnClick: btn?.emptyClick,
|
||||
icon,
|
||||
}}
|
||||
notFound={{
|
||||
title:
|
||||
text?.searchEmptyTitle || I18n.t('inifinit_search_not_found'),
|
||||
}}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<UIEmpty
|
||||
empty={{
|
||||
title: I18n.t('inifinit_list_load_fail'),
|
||||
icon: <IllustrationFailure />,
|
||||
btnText: loadRetry && I18n.t('inifinit_list_retry'),
|
||||
btnOnClick: () => {
|
||||
loadRetry?.();
|
||||
},
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Index;
|
||||
@@ -0,0 +1,55 @@
|
||||
.footer-container {
|
||||
padding: 12px 0 28px;
|
||||
text-align: center;
|
||||
|
||||
* {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.loading,
|
||||
.error-retry {
|
||||
margin-left: 10px;
|
||||
line-height: 20px;
|
||||
color: var(--semi-color-text-3, rgba(29, 28, 35, 35%));
|
||||
}
|
||||
|
||||
.error-retry {
|
||||
cursor: pointer;
|
||||
color: var(--semi-color-focus-border, #4d53e8);
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-spin-middle > .semi-spin-wrapper {
|
||||
height: 16px;
|
||||
|
||||
svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.load-more-btn {
|
||||
font-weight: 600;
|
||||
background: #fff;
|
||||
border-radius: 40px;
|
||||
|
||||
span {
|
||||
color: #1d1c23;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #fff;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.responsive-foot-container {
|
||||
padding: 0 0 16px;
|
||||
|
||||
.load-more-btn {
|
||||
height: 40px;
|
||||
padding: 16px 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Spin, UIButton } from '@coze-arch/bot-semi';
|
||||
import { useIsResponsive } from '@coze-arch/bot-hooks';
|
||||
|
||||
import { type FooterProps } from '../../type';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
/* Plugin header */
|
||||
|
||||
function Index(props: FooterProps) {
|
||||
const {
|
||||
isLoading,
|
||||
loadRetry,
|
||||
isError,
|
||||
renderFooter,
|
||||
isNeedBtnLoadMore,
|
||||
noMore,
|
||||
} = props;
|
||||
const isResponsive = useIsResponsive();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(s['footer-container'], {
|
||||
[s['responsive-foot-container']]: isResponsive,
|
||||
})}
|
||||
>
|
||||
{renderFooter?.(props) ||
|
||||
(isLoading ? (
|
||||
<>
|
||||
<Spin />
|
||||
<span className={s.loading}>{I18n.t('Loading')}</span>
|
||||
</>
|
||||
) : isError ? (
|
||||
<>
|
||||
<Spin />
|
||||
<span className={s['error-retry']} onClick={loadRetry}>
|
||||
{I18n.t('inifinit_list_retry')}
|
||||
</span>
|
||||
</>
|
||||
) : isNeedBtnLoadMore && !noMore ? (
|
||||
<UIButton
|
||||
onClick={loadRetry}
|
||||
className={s['load-more-btn']}
|
||||
theme="borderless"
|
||||
>
|
||||
{I18n.t('mkpl_load_btn')}
|
||||
</UIButton>
|
||||
) : null)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Index;
|
||||
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
useState,
|
||||
useRef,
|
||||
useEffect,
|
||||
type Dispatch,
|
||||
type SetStateAction,
|
||||
} from 'react';
|
||||
|
||||
import {
|
||||
useInfiniteScroll,
|
||||
useUpdateEffect,
|
||||
useMemoizedFn,
|
||||
useDebounceFn,
|
||||
} from 'ahooks';
|
||||
|
||||
import { type ScrollProps, type InfiniteListDataProps } from '../type';
|
||||
|
||||
/* 滚动Hooks */
|
||||
|
||||
function useForwardFunc<T>(
|
||||
dataInfo: InfiniteListDataProps<T>,
|
||||
mutate: Dispatch<SetStateAction<InfiniteListDataProps<T>>>,
|
||||
) {
|
||||
// 手动插入数据,不通过接口
|
||||
const insertData = (item, index) => {
|
||||
dataInfo.list.splice(index, 0, item);
|
||||
mutate({
|
||||
...dataInfo,
|
||||
list: [...(dataInfo?.list || [])],
|
||||
});
|
||||
};
|
||||
|
||||
// 手动删除数据,不通过接口
|
||||
const removeData = index => {
|
||||
dataInfo.list.splice(index, 1);
|
||||
mutate({
|
||||
...dataInfo,
|
||||
list: [...(dataInfo?.list || [])],
|
||||
});
|
||||
};
|
||||
|
||||
const getDataList = () => dataInfo?.list;
|
||||
|
||||
return { insertData, removeData, getDataList };
|
||||
}
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function, @coze-arch/max-line-per-function -- 看了下代码行数不太好优化
|
||||
function useScroll<T>(props: ScrollProps<T>) {
|
||||
const {
|
||||
targetRef,
|
||||
loadData,
|
||||
threshold,
|
||||
reloadDeps,
|
||||
isNeedBtnLoadMore,
|
||||
resetDataIfReload = true,
|
||||
} = props;
|
||||
const [isLoadingError, setIsLoadingError] = useState<boolean>(false);
|
||||
const refFetchNo = useRef<number>(0);
|
||||
const refResolve = useRef<(value) => void>();
|
||||
const {
|
||||
loading,
|
||||
data: dataInfo,
|
||||
loadingMore,
|
||||
loadMore,
|
||||
noMore,
|
||||
cancel,
|
||||
mutate,
|
||||
reload,
|
||||
} = useInfiniteScroll<InfiniteListDataProps<T>>(
|
||||
async current => {
|
||||
// 此处逻辑如此复杂,是解决Scroll中的bug。
|
||||
// useInfiniteScroll中的cancel只是取消了一次请求,但是数据会根据current重新设置一遍。
|
||||
const fetchNo = refFetchNo.current;
|
||||
if (refResolve.current) {
|
||||
// 保证顺序执行,如果有当前方法,就取消上一次的请求,防止出现由于网络原因导致数据覆盖问题
|
||||
// 同时发出A1,A2,三次请求,但是A1先到达,然后请求了B1, 但是A1过慢,导致了A1覆盖了B1的请求。
|
||||
refResolve.current({
|
||||
...(current || {}),
|
||||
list: [],
|
||||
});
|
||||
}
|
||||
|
||||
const result = await new Promise((resolve, reject) => {
|
||||
refResolve.current = resolve;
|
||||
loadData(current)
|
||||
.then(value => resolve(value))
|
||||
.catch(err => reject(err));
|
||||
});
|
||||
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
refResolve.current = null;
|
||||
|
||||
// 切换Tab的时候,如果此时正在请求,防止数据的残留界面显示
|
||||
if (refFetchNo.current !== fetchNo) {
|
||||
if (current) {
|
||||
current.list = [];
|
||||
}
|
||||
return {
|
||||
list: [],
|
||||
nextPage: 1,
|
||||
};
|
||||
}
|
||||
return result as InfiniteListDataProps<T>;
|
||||
},
|
||||
{
|
||||
target: isLoadingError || isNeedBtnLoadMore ? null : targetRef, //失败的时候,通过去掉target的事件绑定,禁止滚动加载。
|
||||
threshold,
|
||||
onBefore: () => {
|
||||
//setIsLoadingError(false);
|
||||
},
|
||||
isNoMore: data => data?.hasMore !== undefined && !data?.hasMore,
|
||||
onSuccess: () => {
|
||||
if (isLoadingError) {
|
||||
setIsLoadingError(false);
|
||||
}
|
||||
},
|
||||
onError: e => {
|
||||
// 如果在请求第一页数据时发生错误,并且当前列表不为空,则reset数据
|
||||
// 这个case只有当resetDataIfReload设置为false时才会发生
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
if (dataInfo.nextPage === 1 && (dataInfo?.list?.length ?? 0) > 0) {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
mutate({
|
||||
...dataInfo,
|
||||
list: [],
|
||||
});
|
||||
}
|
||||
setIsLoadingError(true);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const { insertData, removeData, getDataList } = useForwardFunc(
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
dataInfo,
|
||||
mutate,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isNeedBtnLoadMore && !(loading || loadingMore)) {
|
||||
reload();
|
||||
}
|
||||
}, []);
|
||||
|
||||
const reloadData = useMemoizedFn(() => {
|
||||
mutate({
|
||||
list: resetDataIfReload ? [] : (dataInfo?.list ?? []),
|
||||
hasMore: undefined,
|
||||
nextPage: 1,
|
||||
});
|
||||
cancel();
|
||||
setIsLoadingError(false);
|
||||
reload();
|
||||
});
|
||||
|
||||
useUpdateEffect(() => {
|
||||
refFetchNo.current++;
|
||||
reloadData();
|
||||
}, [...(reloadDeps || [])]);
|
||||
const isLoading = loading || loadingMore || props.isLoading;
|
||||
const { run: loadMoreDebounce } = useDebounceFn(
|
||||
() => {
|
||||
if (isLoading) {
|
||||
return;
|
||||
}
|
||||
if (!isNeedBtnLoadMore) {
|
||||
loadMore();
|
||||
}
|
||||
},
|
||||
{ wait: 500 },
|
||||
);
|
||||
useEffect(() => {
|
||||
const resize = () => {
|
||||
loadMoreDebounce();
|
||||
};
|
||||
window.addEventListener('resize', resize);
|
||||
return () => {
|
||||
window.removeEventListener('resize', resize);
|
||||
};
|
||||
}, []);
|
||||
const { list } = dataInfo || {};
|
||||
return {
|
||||
dataList: list,
|
||||
isLoading,
|
||||
loadMore: () => {
|
||||
if (!isLoading) {
|
||||
//如果已经有数据加载中了,需要禁止重复加载。
|
||||
loadMore();
|
||||
}
|
||||
},
|
||||
reload: reloadData,
|
||||
noMore,
|
||||
cancel,
|
||||
isLoadingError,
|
||||
mutate,
|
||||
insertData,
|
||||
removeData,
|
||||
getDataList,
|
||||
};
|
||||
}
|
||||
|
||||
export default useScroll;
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
.height-whole-100 {
|
||||
overflow: visible;
|
||||
height: 100%;
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
forwardRef,
|
||||
useImperativeHandle,
|
||||
type RefObject,
|
||||
useEffect,
|
||||
} from 'react';
|
||||
|
||||
import cls from 'classnames';
|
||||
import { ResponsiveList } from '@coze-arch/responsive-kit';
|
||||
import { List } from '@coze-arch/bot-semi';
|
||||
|
||||
import { type InfiniteListProps, type InfiniteListRef } from './type';
|
||||
import useScroll from './hooks/use-scroll';
|
||||
import Footer from './components/footer';
|
||||
import Empty from './components/empty';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
/* Plugin header */
|
||||
|
||||
function Index<T extends object>(props: InfiniteListProps<T>, ref) {
|
||||
const {
|
||||
isSearching,
|
||||
className,
|
||||
emptyContent,
|
||||
grid,
|
||||
renderItem,
|
||||
itemClassName,
|
||||
renderFooter,
|
||||
scrollConf,
|
||||
emptyConf,
|
||||
onChangeState,
|
||||
canShowData = true,
|
||||
isNeedBtnLoadMore = false,
|
||||
isResponsive,
|
||||
retryFunc,
|
||||
responsiveConf,
|
||||
containerClassName,
|
||||
} = props;
|
||||
|
||||
const {
|
||||
dataList,
|
||||
isLoading,
|
||||
loadMore,
|
||||
noMore,
|
||||
isLoadingError,
|
||||
mutate,
|
||||
reload,
|
||||
insertData,
|
||||
removeData,
|
||||
getDataList,
|
||||
} = useScroll<T>({ ...scrollConf, isNeedBtnLoadMore });
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({ mutate, reload, insertData, removeData, getDataList }),
|
||||
[mutate, reload, insertData, removeData, getDataList],
|
||||
);
|
||||
useEffect(() => {
|
||||
onChangeState?.(isLoading, dataList);
|
||||
}, [dataList, isLoading]);
|
||||
|
||||
// 根据白名单对列表移动端进行移动端适配
|
||||
|
||||
return (
|
||||
<div className={cls(s['height-whole-100'], containerClassName)}>
|
||||
{!dataList?.length || !canShowData ? (
|
||||
/** 数据为空的时候,操作如何显示空页面 */
|
||||
<Empty
|
||||
isError={canShowData ? isLoadingError : false}
|
||||
isSearching={isSearching}
|
||||
isLoading={canShowData ? isLoading : true}
|
||||
loadRetry={retryFunc || loadMore}
|
||||
{...emptyConf}
|
||||
/>
|
||||
) : isResponsive ? (
|
||||
<ResponsiveList<T>
|
||||
className={className}
|
||||
emptyContent={isLoading ? <></> : emptyContent}
|
||||
dataSource={dataList}
|
||||
renderItem={(item, number) => renderItem?.(item, number)}
|
||||
gridCols={responsiveConf?.gridCols}
|
||||
gridGapXs={{
|
||||
basic: 4,
|
||||
}}
|
||||
footer={
|
||||
<div className="text-sm px-6 py-3">
|
||||
<Footer
|
||||
isError={isLoadingError}
|
||||
noMore={noMore}
|
||||
isLoading={isLoading}
|
||||
loadRetry={retryFunc || loadMore}
|
||||
renderFooter={renderFooter}
|
||||
isNeedBtnLoadMore={isNeedBtnLoadMore}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<List
|
||||
{...{ className, emptyContent, grid }}
|
||||
emptyContent={isLoading ? <></> : emptyContent}
|
||||
dataSource={dataList}
|
||||
split={false}
|
||||
renderItem={(item, number) => (
|
||||
<List.Item
|
||||
className={
|
||||
typeof itemClassName === 'string'
|
||||
? itemClassName
|
||||
: itemClassName?.(item) // 支持动态行className
|
||||
}
|
||||
>
|
||||
{renderItem?.(item, number)}
|
||||
</List.Item>
|
||||
)}
|
||||
footer={
|
||||
<Footer
|
||||
isError={isLoadingError}
|
||||
noMore={noMore}
|
||||
isLoading={isLoading}
|
||||
loadRetry={retryFunc || loadMore}
|
||||
renderFooter={renderFooter}
|
||||
isNeedBtnLoadMore={isNeedBtnLoadMore}
|
||||
dataNum={dataList?.length}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const InfiniteList = forwardRef(Index) as <T>(
|
||||
props: InfiniteListProps<T> & { ref?: RefObject<InfiniteListRef> },
|
||||
) => JSX.Element;
|
||||
107
frontend/packages/community/component/src/infinite-list/type.ts
Normal file
107
frontend/packages/community/component/src/infinite-list/type.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type ReactElement, type RefObject } from 'react';
|
||||
|
||||
import {
|
||||
type ResponsiveTokenMap,
|
||||
type ScreenRange,
|
||||
} from '@coze-arch/responsive-kit';
|
||||
import { type ListProps } from '@coze-arch/bot-semi/List';
|
||||
|
||||
export interface EmptyProps {
|
||||
isError?: boolean;
|
||||
isLoading?: boolean;
|
||||
isSearching?: boolean;
|
||||
loadRetry?: () => void; //重试加载
|
||||
text?: {
|
||||
emptyTitle?: string;
|
||||
emptyDesc?: string;
|
||||
searchEmptyTitle?: string;
|
||||
};
|
||||
btn?: {
|
||||
emptyClick?: () => void; //
|
||||
emptyText?: string;
|
||||
};
|
||||
icon?: ReactElement;
|
||||
|
||||
renderEmpty?: (
|
||||
emptyProps: Omit<EmptyProps, 'renderEmpty'>,
|
||||
) => React.ReactNode | null;
|
||||
}
|
||||
export interface FooterProps {
|
||||
isError?: boolean; // 是否加载出错
|
||||
isLoading?: boolean; // 是否加载中
|
||||
noMore?: boolean; //没有更多数据
|
||||
isNeedBtnLoadMore?: boolean;
|
||||
dataNum?: number;
|
||||
loadRetry?: () => void; //重试加载
|
||||
renderFooter?: (
|
||||
footerProps: Omit<FooterProps, 'renderFooter'>,
|
||||
) => React.ReactNode | null;
|
||||
}
|
||||
|
||||
export interface InfiniteListDataProps<T> {
|
||||
list: T[];
|
||||
hasMore?: boolean;
|
||||
nextPage: number;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface ScrollProps<T> {
|
||||
threshold?: number; //距离下方多长距离,开始加载数据
|
||||
targetRef?: RefObject<HTMLDivElement>; // 监听滚动的Dom 引用
|
||||
loadData: (current) => Promise<InfiniteListDataProps<T>>; // 加载更多数据
|
||||
reloadDeps?: unknown[]; // 重新加载数据依赖
|
||||
isNeedBtnLoadMore?: boolean;
|
||||
isLoading?: boolean; // 是否加载中
|
||||
resetDataIfReload?: boolean; // 当reload时,是否先reset列表已存在数据,默认为true
|
||||
}
|
||||
|
||||
export interface InfiniteListProps<T>
|
||||
extends Pick<
|
||||
ListProps<T>,
|
||||
'className' | 'emptyContent' | 'grid' | 'renderItem'
|
||||
> {
|
||||
containerClassName?: string;
|
||||
canShowData?: boolean; //是否能够显示数据了
|
||||
isSearching?: boolean; // 是否搜索中,主要是用于错误显示的时候,选择文案使用
|
||||
itemClassName?: string | ((item: T) => string);
|
||||
isNeedBtnLoadMore?: boolean;
|
||||
isResponsive?: boolean;
|
||||
emptyConf: {
|
||||
renderEmpty?: EmptyProps['renderEmpty'];
|
||||
text?: EmptyProps['text'];
|
||||
btn?: EmptyProps['btn'];
|
||||
icon?: EmptyProps['icon'];
|
||||
};
|
||||
renderFooter?: FooterProps['renderFooter'];
|
||||
scrollConf: ScrollProps<T>;
|
||||
rowKey?: string;
|
||||
retryFunc?: () => void;
|
||||
onChangeState?: (loading, data) => void;
|
||||
responsiveConf?: {
|
||||
gridCols?: ResponsiveTokenMap<ScreenRange>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface InfiniteListRef {
|
||||
mutate: (data) => void;
|
||||
reload: () => void;
|
||||
insertData: (item, index) => void;
|
||||
removeData: (index) => void;
|
||||
getDataList: () => unknown[]; // 获取当前列表数据
|
||||
}
|
||||
Reference in New Issue
Block a user