website-01/components/admin/PaginationTable.tsx
2026-06-22 14:43:46 +08:00

134 lines
3.8 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
import { ReactNode, useState, useEffect } from 'react';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import { Button } from '@/components/ui/button';
import { ChevronLeft, ChevronRight, Loader2 } from 'lucide-react';
import { cn } from '@/lib/utils';
export interface Column<T> {
key: string;
title: string;
width?: number | string;
render?: (row: T, index: number) => ReactNode;
className?: string;
}
export interface PaginationTableProps<T> {
columns: Column<T>[];
rows: T[];
total: number;
page: number;
pageSize: number;
loading?: boolean;
onPageChange: (page: number) => void;
rowKey?: (row: T, index: number) => string;
emptyText?: string;
className?: string;
}
export function PaginationTable<T>({
columns,
rows,
total,
page,
pageSize,
loading,
onPageChange,
rowKey,
emptyText = '暂无数据',
className,
}: PaginationTableProps<T>) {
const totalPages = Math.max(1, Math.ceil(total / pageSize));
const [cur, setCur] = useState(page);
useEffect(() => setCur(page), [page]);
const go = (p: number) => {
const target = Math.min(Math.max(1, p), totalPages);
setCur(target);
onPageChange(target);
};
const start = total === 0 ? 0 : (cur - 1) * pageSize + 1;
const end = Math.min(cur * pageSize, total);
return (
<div className={cn('w-full', className)}>
<div className="rounded-md border border-gray-200">
<Table>
<TableHeader>
<TableRow className="bg-gray-50">
{columns.map((c) => (
<TableHead key={c.key} style={{ width: c.width }} className={c.className}>
{c.title}
</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{loading ? (
<TableRow>
<TableCell colSpan={columns.length} className="h-32">
<div className="flex items-center justify-center gap-2 text-gray-400">
<Loader2 className="h-5 w-5 animate-spin" />
<span className="text-sm"></span>
</div>
</TableCell>
</TableRow>
) : rows.length === 0 ? (
<TableRow>
<TableCell colSpan={columns.length} className="h-32 text-center text-gray-400">
{emptyText}
</TableCell>
</TableRow>
) : (
rows.map((row, idx) => (
<TableRow key={rowKey ? rowKey(row, idx) : String(idx)}>
{columns.map((c) => (
<TableCell key={c.key} className={c.className}>
{c.render ? c.render(row, idx) : String((row as Record<string, unknown>)[c.key] ?? '')}
</TableCell>
))}
</TableRow>
))
)}
</TableBody>
</Table>
</div>
<div className="mt-3 flex flex-col items-center justify-between gap-2 sm:flex-row">
<p className="text-xs text-gray-500">
{total} {start}-{end}
</p>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
disabled={cur <= 1}
onClick={() => go(cur - 1)}
>
<ChevronLeft className="h-4 w-4" />
</Button>
<span className="text-sm text-gray-600">
{cur} / {totalPages}
</span>
<Button
variant="outline"
size="sm"
disabled={cur >= totalPages}
onClick={() => go(cur + 1)}
>
<ChevronRight className="h-4 w-4" />
</Button>
</div>
</div>
</div>
);
}