新增模块: - 友链(friendlink)后端处理 + 前端管理页与友链板 - 书库(library):导入、章节、封面、阅读页、横竖版自适应 AdaptiveCoverSlot - 顶栏导航(header_nav)配置与 MobileTabBar/MobileRailDrawers 移动端抽屉 - 排行榜 service 测试、站点页面测试、时间线发布(timeline_release) 其它改动: - 后端 handlers/services 全量小幅调整 - 前端组件、库函数、URL/品牌/站点 URL 工具更新 - Lightbox 图片、MdEntries 条目卡、coverColor 派生色相等前端能力
94 lines
2.5 KiB
TypeScript
94 lines
2.5 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
|
|
interface PaginationProps {
|
|
total: number;
|
|
page: number;
|
|
size: number;
|
|
basePath: string;
|
|
query?: Record<string, string>;
|
|
onChange?: (page: number) => void;
|
|
}
|
|
|
|
export default function Pagination({ total, page, size, basePath, query = {}, onChange }: PaginationProps) {
|
|
const totalPages = Math.max(1, Math.ceil(total / size));
|
|
if (totalPages <= 1) return null;
|
|
|
|
const buildHref = (p: number) => {
|
|
const params = new URLSearchParams(query);
|
|
if (p > 1) params.set("page", String(p));
|
|
else params.delete("page");
|
|
const qs = params.toString();
|
|
return qs ? `${basePath}?${qs}` : basePath;
|
|
};
|
|
|
|
const handleClick = (p: number, e: React.MouseEvent) => {
|
|
if (onChange) {
|
|
e.preventDefault();
|
|
onChange(p);
|
|
}
|
|
};
|
|
|
|
const pages: (number | "...")[] = [];
|
|
const add = (p: number | "...") => pages.push(p);
|
|
|
|
if (totalPages <= 7) {
|
|
for (let i = 1; i <= totalPages; i++) add(i);
|
|
} else {
|
|
add(1);
|
|
if (page > 4) add("...");
|
|
const start = Math.max(2, page - 1);
|
|
const end = Math.min(totalPages - 1, page + 1);
|
|
for (let i = start; i <= end; i++) add(i);
|
|
if (page < totalPages - 3) add("...");
|
|
add(totalPages);
|
|
}
|
|
|
|
const cls =
|
|
"pager-btn min-w-[38px] h-9 px-1 flex items-center justify-center font-semibold text-[13px] rounded-full border transition-colors duration-150";
|
|
|
|
return (
|
|
<nav className="flex items-center justify-center gap-1.5 mt-6 flex-wrap" aria-label="分页">
|
|
{page > 1 && (
|
|
<Link
|
|
href={buildHref(page - 1)}
|
|
onClick={(e) => handleClick(page - 1, e)}
|
|
className={`${cls} px-3 gap-1`}
|
|
>
|
|
<ChevronLeft size={14} /> 上一页
|
|
</Link>
|
|
)}
|
|
|
|
{pages.map((p, i) =>
|
|
p === "..." ? (
|
|
<span key={`e${i}`} className="px-1 meta">
|
|
—
|
|
</span>
|
|
) : (
|
|
<Link
|
|
key={p}
|
|
href={buildHref(p)}
|
|
onClick={(e) => handleClick(p, e)}
|
|
aria-current={p === page ? "page" : undefined}
|
|
className={`${cls} ${p === page ? "is-current" : ""}`}
|
|
>
|
|
{p}
|
|
</Link>
|
|
)
|
|
)}
|
|
|
|
{page < totalPages && (
|
|
<Link
|
|
href={buildHref(page + 1)}
|
|
onClick={(e) => handleClick(page + 1, e)}
|
|
className={`${cls} px-3 gap-1`}
|
|
>
|
|
下一页 <ChevronRight size={14} />
|
|
</Link>
|
|
)}
|
|
</nav>
|
|
);
|
|
}
|