Files

118 lines
3.3 KiB
TypeScript

import { ChevronLeft, ChevronRight } from "lucide-react";
import { Button } from "~/components/ui/button";
/** Build a compact page number list with ellipsis, e.g. 1 … 4 5 6 … 20 */
function visiblePages(current: number, total: number): Array<number | "ellipsis"> {
if (total <= 7) {
return Array.from({ length: total }, (_, index) => index + 1);
}
const pages = new Set<number>([1, total, current, current - 1, current + 1]);
if (current <= 3) {
pages.add(2);
pages.add(3);
pages.add(4);
}
if (current >= total - 2) {
pages.add(total - 1);
pages.add(total - 2);
pages.add(total - 3);
}
const sorted = [...pages].filter((p) => p >= 1 && p <= total).sort((a, b) => a - b);
const result: Array<number | "ellipsis"> = [];
for (const page of sorted) {
const prev = result[result.length - 1];
if (typeof prev === "number" && page - prev > 1) {
result.push("ellipsis");
}
result.push(page);
}
return result;
}
export function AdminPagination({
page,
totalPages,
totalCount,
loading,
hasMore,
canGoToPage,
onPrev,
onNext,
onGoToPage,
}: {
page: number;
totalPages: number;
totalCount: number;
loading?: boolean;
hasMore: boolean;
canGoToPage: (page: number) => boolean;
onPrev: () => void;
onNext: () => void;
onGoToPage: (page: number) => void;
}) {
if (totalCount === 0) return null;
const pages = visiblePages(page, totalPages);
return (
<div className="mt-3 flex flex-wrap items-center justify-between gap-2 text-[11px] text-[#5d7186]">
<span>
<strong className="text-[#27394d]">{totalCount}</strong> 条,第 {page}/{totalPages}
</span>
<div className="flex items-center gap-1">
<Button
type="button"
variant="outline"
size="xs"
disabled={loading || page <= 1}
onClick={onPrev}
className="h-7 gap-0.5 rounded-[5px] border-[#d9e3ec] bg-white px-2 text-[11px] text-[#4c6c88]"
>
<ChevronLeft size={14} />
上一页
</Button>
{pages.map((item, index) =>
item === "ellipsis" ? (
<span key={`e-${index}`} className="px-1 text-[#94a2b3]">
</span>
) : (
<Button
key={item}
type="button"
variant="outline"
size="xs"
disabled={loading || !canGoToPage(item)}
onClick={() => onGoToPage(item)}
className={`h-7 min-w-7 rounded-[5px] px-2 text-[11px] ${
item === page
? "border-brand bg-brand-soft font-semibold text-brand"
: "border-[#d9e3ec] bg-white text-[#4c6c88] disabled:opacity-40"
}`}
title={
canGoToPage(item)
? undefined
: "请先通过上一页/下一页依次访问该页"
}
>
{item}
</Button>
),
)}
<Button
type="button"
variant="outline"
size="xs"
disabled={loading || !hasMore}
onClick={onNext}
className="h-7 gap-0.5 rounded-[5px] border-[#d9e3ec] bg-white px-2 text-[11px] text-[#4c6c88]"
>
下一页
<ChevronRight size={14} />
</Button>
</div>
</div>
);
}