refactor: The PageScrollList component supports two generic parameters

This commit is contained in:
zhaoying
2026-01-29 15:57:52 +08:00
parent 9c3e0b5541
commit 4fe32b7dbc
4 changed files with 31 additions and 29 deletions

View File

@@ -1,13 +1,14 @@
import React, { useEffect, useState, useRef, forwardRef, useImperativeHandle } from 'react';
import { List, Skeleton} from 'antd';
import { List } from 'antd';
import InfiniteScroll from 'react-infinite-scroll-component';
import { request } from '@/utils/request';
import Empty from '@/components/Empty';
import PageEmpty from '@/components/Empty/PageEmpty'
import PageLoading from '@/components/Empty/PageLoading'
const PAGE_SIZE = 20;
interface ApiResponse {
items?: Record<string, unknown>[];
interface ApiResponse<T> {
items?: T[];
page: {
page: number;
pagesize: number;
@@ -19,26 +20,25 @@ export interface PageScrollListRef {
refresh: () => void;
}
interface PageScrollListProps {
interface PageScrollListProps<T, Q = Record<string, unknown>> {
url: string;
renderItem: (item: Record<string, unknown>) => React.ReactNode;
query?: Record<string, unknown>;
renderItem: (item: T) => React.ReactNode;
query?: Q;
column?: number;
className?: string;
}
const PageScrollList = forwardRef<PageScrollListRef, PageScrollListProps>(({
const PageScrollList = forwardRef(<T, Q = Record<string, unknown>>({
renderItem,
query,
url,
column = 4,
className = '',
}, ref) => {
}: PageScrollListProps<T, Q>, ref: React.Ref<PageScrollListRef>) => {
useImperativeHandle(ref, () => ({
refresh,
}));
const [loading, setLoading] = useState(false);
const [data, setData] = useState<Record<string, unknown>[]>([]);
const [data, setData] = useState<T[]>([]);
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
const scrollRef = useRef<HTMLDivElement>(null);
@@ -54,8 +54,8 @@ const PageScrollList = forwardRef<PageScrollListRef, PageScrollListProps>(({
...(query||{}),
})
.then((res) => {
const response = res as ApiResponse;
const results = Array.isArray(response.items) ? response.items : Array.isArray(response) ? response : [];
const response = res as ApiResponse<T>;
const results = Array.isArray(response.items) ? response.items : Array.isArray(response) ? response as T[] : [];
if (flag) {
setData(results);
} else {
@@ -104,7 +104,7 @@ const PageScrollList = forwardRef<PageScrollListRef, PageScrollListProps>(({
dataLength={data.length}
next={loadMoreData}
hasMore={hasMore}
loader={<Skeleton active />}
loader={<PageLoading />}
// endMessage={<Divider plain>It is all, nothing more 🤐</Divider>}
scrollableTarget="scrollableDiv"
>
@@ -118,11 +118,11 @@ const PageScrollList = forwardRef<PageScrollListRef, PageScrollListProps>(({
</List.Item>
)}
/>
) : !loading ? <Empty /> : null}
) : !loading ? <PageEmpty /> : null}
</InfiniteScroll>
</div>
</>
);
});
}) as <T = Record<string, unknown>, Q = Record<string, unknown>>(props: PageScrollListProps<T, Q> & { ref?: React.Ref<PageScrollListRef> }) => React.ReactElement;
export default PageScrollList;

View File

@@ -58,13 +58,12 @@ const ApiKeyManagement: React.FC = () => {
</Button>
</div>
<PageScrollList
<PageScrollList<ApiKey, { is_active: boolean; type: string }>
ref={scrollListRef}
url={getApiKeyListUrl}
query={{ is_active: true, type: 'service' }}
column={2}
renderItem={(item: Record<string, unknown>) => {
let apiKeyItem = item as unknown as ApiKey
renderItem={(apiKeyItem) => {
return (
<RbCard
title={apiKeyItem.name}

View File

@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
import { Button, Row, Col, App } from 'antd';
import clsx from 'clsx';
import { DeleteOutlined } from '@ant-design/icons';
import type { Application, ApplicationModalRef } from './types';
import type { Application, ApplicationModalRef, Query } from './types';
import ApplicationModal from './components/ApplicationModal';
import SearchInput from '@/components/SearchInput'
import RbCard from '@/components/RbCard/Card'
@@ -14,7 +14,7 @@ import { formatDateTime } from '@/utils/format';
const ApplicationManagement: React.FC = () => {
const { t } = useTranslation();
const { modal } = App.useApp();
const [query, setQuery] = useState({});
const [query, setQuery] = useState<Query>({} as Query);
const applicationModalRef = useRef<ApplicationModalRef>(null);
const scrollListRef = useRef<PageScrollListRef>(null)
@@ -47,7 +47,7 @@ const ApplicationManagement: React.FC = () => {
}
return (
<>
<Row gutter={16} className="rb:mb-[16px]">
<Row gutter={16} className="rb:mb-4">
<Col span={12}>
<SearchInput
placeholder={t('application.searchPlaceholder')}
@@ -62,22 +62,22 @@ const ApplicationManagement: React.FC = () => {
</Col>
</Row>
<PageScrollList
<PageScrollList<Application, Query>
ref={scrollListRef}
url={getApplicationListUrl}
query={query}
renderItem={(item: Application) => (
renderItem={(item) => (
<RbCard
title={item.name}
avatar={
<div className="rb:w-[48px] rb:h-[48px] rb:rounded-[8px] rb:mr-[13px] rb:bg-[#155eef] rb:flex rb:items-center rb:justify-center rb:text-[28px] rb:text-[#ffffff]">
<div className="rb:w-12 rb:h-12 rb:rounded-lg rb:mr-3.25 rb:bg-[#155eef] rb:flex rb:items-center rb:justify-center rb:text-[28px] rb:text-[#ffffff]">
{item.name[0]}
</div>
}
>
{['type', 'source', 'created_at'].map((key, index) => (
<div key={key} className={clsx("rb:flex rb:justify-between rb:gap-[20px] rb:font-regular rb:text-[14px]", {
'rb:mt-[12px]': index !== 0
<div key={key} className={clsx("rb:flex rb:justify-between rb:gap-5 rb:font-regular rb:text-[14px]", {
'rb:mt-3': index !== 0
})}>
<span className="rb:text-[#5B6167]">{t(`application.${key}`)}</span>
<span className={clsx({
@@ -89,14 +89,14 @@ const ApplicationManagement: React.FC = () => {
: key === 'source' && !item.is_shared
? t('application.configuration')
: key === 'created_at'
? formatDateTime(item[key as keyof Application], 'YYYY-MM-DD HH:mm:ss')
? formatDateTime(item.created_at, 'YYYY-MM-DD HH:mm:ss')
: t(`application.${item[key as keyof Application]}`)
}
</span>
</div>
))}
<div className="rb:mt-[20px] rb:flex rb:justify-between rb:gap-[10px]">
<div className="rb:mt-5 rb:flex rb:justify-between rb:gap-2.5">
<Button type="primary" ghost className="rb:w-[calc(100%-46px)]" onClick={() => handleEdit(item)}>{t('application.configuration')}</Button>
<Button icon={<DeleteOutlined />} onClick={() => handleDelete(item)}></Button>
</div>

View File

@@ -1,4 +1,7 @@
// 应用数据类型
export interface Query {
search: string;
}
export interface Application {
id: string;
workspace_id: string;