app / src /components /common /Pagination.tsx
AZILS's picture
Upload 323 files
a21c316 verified
import { ChevronLeft, ChevronRight } from 'lucide-react';
import { useTranslation } from 'react-i18next';
interface PaginationProps {
currentPage: number;
totalPages: number;
onPageChange: (page: number) => void;
totalItems: number;
itemsPerPage: number;
onPageSizeChange?: (pageSize: number) => void; // 新增:分页大小变更回调
pageSizeOptions?: number[]; // 新增:可选的分页大小选项
}
function Pagination({
currentPage,
totalPages,
onPageChange,
totalItems,
itemsPerPage,
onPageSizeChange,
pageSizeOptions = [10, 20, 50, 100]
}: PaginationProps) {
const { t } = useTranslation();
if (totalPages <= 1 && !onPageSizeChange) return null;
// 计算显示的页码范围 (最多显示 5 个页码)
let startPage = Math.max(1, currentPage - 2);
let endPage = Math.min(totalPages, startPage + 4);
if (endPage - startPage < 4) {
startPage = Math.max(1, endPage - 4);
}
const pages = [];
for (let i = startPage; i <= endPage; i++) {
pages.push(i);
}
const startIndex = (currentPage - 1) * itemsPerPage + 1;
const endIndex = Math.min(currentPage * itemsPerPage, totalItems);
return (
<div className="flex items-center justify-between px-6 py-3">
{/* Mobile View */}
<div className="flex flex-1 justify-between sm:hidden">
<button
onClick={() => onPageChange(currentPage - 1)}
disabled={currentPage === 1}
className={`relative inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 px-4 py-2 text-sm font-medium ${currentPage === 1
? 'bg-gray-100 dark:bg-gray-800 text-gray-400 cursor-not-allowed'
: 'bg-white dark:bg-base-100 text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-base-200'
}`}
>
{t('common.prev_page')}
</button>
<button
onClick={() => onPageChange(currentPage + 1)}
disabled={currentPage === totalPages}
className={`relative ml-3 inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 px-4 py-2 text-sm font-medium ${currentPage === totalPages
? 'bg-gray-100 dark:bg-gray-800 text-gray-400 cursor-not-allowed'
: 'bg-white dark:bg-base-100 text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-base-200'
}`}
>
{t('common.next_page')}
</button>
</div>
{/* Desktop View */}
<div className="hidden sm:flex sm:flex-1 sm:items-center sm:justify-between">
<div className="flex items-center gap-4">
<p className="text-sm text-gray-700 dark:text-gray-400">
{t('common.pagination_info', { start: startIndex, end: endIndex, total: totalItems })}
</p>
{/* 分页大小选择器 */}
{onPageSizeChange && (
<div className="flex items-center gap-2">
<span className="text-sm text-gray-600 dark:text-gray-400">{t('common.per_page')}</span>
<select
value={itemsPerPage}
onChange={(e) => onPageSizeChange(parseInt(e.target.value))}
className="px-2 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-base-100 text-gray-900 dark:text-base-content focus:outline-none focus:ring-2 focus:ring-blue-500"
>
{pageSizeOptions.map(size => (
<option key={size} value={size}>{size} {t('common.items')}</option>
))}
</select>
</div>
)}
</div>
<div>
<nav className="isolate inline-flex -space-x-px rounded-md shadow-sm" aria-label="Pagination">
<button
onClick={() => onPageChange(currentPage - 1)}
disabled={currentPage === 1}
className={`relative inline-flex items-center rounded-l-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 dark:ring-gray-600 hover:bg-gray-50 dark:hover:bg-base-200 focus:z-20 focus:outline-offset-0 ${currentPage === 1 ? 'cursor-not-allowed opacity-50' : ''
}`}
>
<span className="sr-only">{t('common.prev_page')}</span>
<ChevronLeft className="h-4 w-4" aria-hidden="true" />
</button>
{/* First Page Link if needed */}
{startPage > 1 && (
<>
<button
onClick={() => onPageChange(1)}
className="relative inline-flex items-center px-4 py-2 text-sm font-semibold text-gray-900 dark:text-gray-200 ring-1 ring-inset ring-gray-300 dark:ring-gray-600 hover:bg-gray-50 dark:hover:bg-base-200 focus:z-20 focus:outline-offset-0"
>
1
</button>
{startPage > 2 && (
<span className="relative inline-flex items-center px-4 py-2 text-sm font-semibold text-gray-700 dark:text-gray-400 ring-1 ring-inset ring-gray-300 dark:ring-gray-600 focus:outline-offset-0">
...
</span>
)}
</>
)}
{pages.map(page => (
<button
key={page}
onClick={() => onPageChange(page)}
aria-current={page === currentPage ? 'page' : undefined}
className={`relative inline-flex items-center px-4 py-2 text-sm font-semibold focus:z-20 focus:outline-offset-0 ${page === currentPage
? 'z-10 bg-blue-600 text-white focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600'
: 'text-gray-900 dark:text-gray-200 ring-1 ring-inset ring-gray-300 dark:ring-gray-600 hover:bg-gray-50 dark:hover:bg-base-200'
}`}
>
{page}
</button>
))}
{/* Last Page Link if needed */}
{endPage < totalPages && (
<>
{endPage < totalPages - 1 && (
<span className="relative inline-flex items-center px-4 py-2 text-sm font-semibold text-gray-700 dark:text-gray-400 ring-1 ring-inset ring-gray-300 dark:ring-gray-600 focus:outline-offset-0">
...
</span>
)}
<button
onClick={() => onPageChange(totalPages)}
className="relative inline-flex items-center px-4 py-2 text-sm font-semibold text-gray-900 dark:text-gray-200 ring-1 ring-inset ring-gray-300 dark:ring-gray-600 hover:bg-gray-50 dark:hover:bg-base-200 focus:z-20 focus:outline-offset-0"
>
{totalPages}
</button>
</>
)}
<button
onClick={() => onPageChange(currentPage + 1)}
disabled={currentPage === totalPages}
className={`relative inline-flex items-center rounded-r-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 dark:ring-gray-600 hover:bg-gray-50 dark:hover:bg-base-200 focus:z-20 focus:outline-offset-0 ${currentPage === totalPages ? 'cursor-not-allowed opacity-50' : ''
}`}
>
<span className="sr-only">{t('common.next_page')}</span>
<ChevronRight className="h-4 w-4" aria-hidden="true" />
</button>
</nav>
</div>
</div>
</div>
);
}
export default Pagination;