118 lines
3.3 KiB
TypeScript
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>
|
|
);
|
|
}
|