188 lines
5.5 KiB
TypeScript
188 lines
5.5 KiB
TypeScript
import React from 'react';
|
||
import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, MoreHorizontal } from 'lucide-react';
|
||
import { Button } from './button';
|
||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './select';
|
||
import { cn } from '@/lib/utils';
|
||
|
||
interface DataPaginationProps {
|
||
currentPage: number;
|
||
totalPages: number;
|
||
pageSize: number;
|
||
totalItems: number;
|
||
startIndex: number;
|
||
endIndex: number;
|
||
onPageChange: (page: number) => void;
|
||
onPageSizeChange: (size: number) => void;
|
||
canPreviousPage: boolean;
|
||
canNextPage: boolean;
|
||
pageSizeOptions?: number[];
|
||
}
|
||
|
||
export function DataPagination({
|
||
currentPage,
|
||
totalPages,
|
||
pageSize,
|
||
totalItems,
|
||
startIndex,
|
||
endIndex,
|
||
onPageChange,
|
||
onPageSizeChange,
|
||
canPreviousPage,
|
||
canNextPage,
|
||
pageSizeOptions = [10, 30, 50, 100],
|
||
}: DataPaginationProps) {
|
||
// 生成页码数组
|
||
const generatePageNumbers = () => {
|
||
const pages: (number | 'ellipsis')[] = [];
|
||
const maxVisiblePages = 5;
|
||
|
||
if (totalPages <= maxVisiblePages) {
|
||
// 如果总页数少,显示所有页码
|
||
for (let i = 1; i <= totalPages; i++) {
|
||
pages.push(i);
|
||
}
|
||
} else {
|
||
// 总是显示第一页
|
||
pages.push(1);
|
||
|
||
if (currentPage > 3) {
|
||
pages.push('ellipsis');
|
||
}
|
||
|
||
// 显示当前页附近的页码
|
||
const startPage = Math.max(2, currentPage - 1);
|
||
const endPage = Math.min(totalPages - 1, currentPage + 1);
|
||
|
||
for (let i = startPage; i <= endPage; i++) {
|
||
pages.push(i);
|
||
}
|
||
|
||
if (currentPage < totalPages - 2) {
|
||
pages.push('ellipsis');
|
||
}
|
||
|
||
// 总是显示最后一页
|
||
if (totalPages > 1) {
|
||
pages.push(totalPages);
|
||
}
|
||
}
|
||
|
||
return pages;
|
||
};
|
||
|
||
const pageNumbers = generatePageNumbers();
|
||
|
||
if (totalItems === 0) {
|
||
return null;
|
||
}
|
||
|
||
return (
|
||
<div className="border-t bg-background">
|
||
<div className="flex flex-col gap-4 px-4 py-4 sm:flex-row sm:items-center sm:justify-between">
|
||
{/* 左侧:每页显示数量 */}
|
||
<div className="flex items-center gap-2">
|
||
<span className="text-sm text-muted-foreground hidden sm:inline">每页显示</span>
|
||
<Select
|
||
value={pageSize.toString()}
|
||
onValueChange={(value) => onPageSizeChange(Number(value))}
|
||
>
|
||
<SelectTrigger className="h-9 w-[70px]">
|
||
<SelectValue />
|
||
</SelectTrigger>
|
||
<SelectContent side="top">
|
||
{pageSizeOptions.map((size) => (
|
||
<SelectItem key={size} value={size.toString()}>
|
||
{size}
|
||
</SelectItem>
|
||
))}
|
||
</SelectContent>
|
||
</Select>
|
||
<span className="text-sm text-muted-foreground">条</span>
|
||
</div>
|
||
|
||
{/* 中间:分页器 */}
|
||
<div className="flex items-center gap-2">
|
||
{/* 第一页 */}
|
||
<Button
|
||
variant="outline"
|
||
size="sm"
|
||
onClick={() => onPageChange(1)}
|
||
disabled={!canPreviousPage}
|
||
className="h-9 w-9 p-0 hidden sm:flex"
|
||
>
|
||
<ChevronsLeft className="h-4 w-4" />
|
||
</Button>
|
||
|
||
{/* 上一页 */}
|
||
<Button
|
||
variant="outline"
|
||
size="sm"
|
||
onClick={() => onPageChange(currentPage - 1)}
|
||
disabled={!canPreviousPage}
|
||
className="h-9 w-9 p-0"
|
||
>
|
||
<ChevronLeft className="h-4 w-4" />
|
||
</Button>
|
||
|
||
{/* 页码 */}
|
||
<div className="flex items-center gap-1">
|
||
{pageNumbers.map((page, index) => (
|
||
<React.Fragment key={index}>
|
||
{page === 'ellipsis' ? (
|
||
<div className="flex h-9 w-9 items-center justify-center">
|
||
<MoreHorizontal className="h-4 w-4 text-muted-foreground" />
|
||
</div>
|
||
) : (
|
||
<Button
|
||
variant={currentPage === page ? 'default' : 'outline'}
|
||
size="sm"
|
||
onClick={() => onPageChange(page)}
|
||
className={cn(
|
||
'h-9 w-9 p-0',
|
||
currentPage === page && 'bg-green-600 hover:bg-green-700 text-white'
|
||
)}
|
||
>
|
||
{page}
|
||
</Button>
|
||
)}
|
||
</React.Fragment>
|
||
))}
|
||
</div>
|
||
|
||
{/* 下一页 */}
|
||
<Button
|
||
variant="outline"
|
||
size="sm"
|
||
onClick={() => onPageChange(currentPage + 1)}
|
||
disabled={!canNextPage}
|
||
className="h-9 w-9 p-0"
|
||
>
|
||
<ChevronRight className="h-4 w-4" />
|
||
</Button>
|
||
|
||
{/* 最后一页 */}
|
||
<Button
|
||
variant="outline"
|
||
size="sm"
|
||
onClick={() => onPageChange(totalPages)}
|
||
disabled={!canNextPage}
|
||
className="h-9 w-9 p-0 hidden sm:flex"
|
||
>
|
||
<ChevronsRight className="h-4 w-4" />
|
||
</Button>
|
||
</div>
|
||
|
||
{/* 右侧:统计信息 */}
|
||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||
<span className="hidden sm:inline">
|
||
显示 <span className="font-medium text-foreground">{startIndex}</span> 至{' '}
|
||
<span className="font-medium text-foreground">{endIndex}</span> 条,
|
||
</span>
|
||
<span>
|
||
共 <span className="font-medium text-foreground">{totalItems}</span> 条
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
} |