书接上回,上一篇说到我们实现了咱内搜索,由于一次搜索的内容可能太多,因此这章将会将如何将内容进行分页。添加分页功能可让用户浏览不同的页面以查看所有发票。让我们看看如何使用 URL 参数实现分页功能,就像搜索一样。
添加分页
首先需要创建一个分页组件,用于承接这个功能,然后在发票页面上面进行引用
编辑/app/invoices/page.tsx
页面,将分页注释删除掉
import Pagination from "@/app/ui/invoices/pagination";
import Search from "@/app/ui/search";
import Table from "@/app/ui/invoices/table";
//import { CreateInvoice } from "@/app/ui/invoices/buttons";
import { lusitana } from "@/app/ui/fonts";
import { InvoicesTableSkeleton } from "@/app/ui/skeletons";
import { Suspense } from "react";
import { fetchInvoicesPages } from "@/app/lib/data";
export default async function Page({
searchParams,
}: {
searchParams?: {
query?: string;
page?: string;
};
}) {
const query = searchParams?.query || "";
const currentPage = Number(searchParams?.page) || 1;
const totalPages = await fetchInvoicesPages(query);
return (
<div className="w-full">
<div className="flex w-full items-center justify-between">
<h1 className={`${lusitana.className} text-2xl`}>最近发票</h1>
</div>
<div className="mt-4 flex items-center justify-between gap-2 md:mt-8">
<Search placeholder="搜索发票内容......" />
{/* <CreateInvoice /> */}
</div>
<Suspense key={query + currentPage} fallback={<InvoicesTableSkeleton />}>
<Table query={query} currentPage={currentPage} />
</Suspense>
<div className="mt-5 flex w-full justify-center">
<Pagination totalPages={totalPages} />
</div>
</div>
);
}
修改之后会报错,因为我们还没有实现/app/ui/invoices/pagination.tsx
组件,接下来需要实现分页的组件。
创建文件/app/ui/invoices/pagination.tsx
,编辑
"use client";
import { ArrowLeftIcon, ArrowRightIcon } from "@heroicons/react/24/outline";
import clsx from "clsx";
import Link from "next/link";
import { usePathname, useSearchParams } from "next/navigation";
export default function Pagination({ totalPages }: { totalPages: number }) {
const pathname = usePathname();
const searchParams = useSearchParams();
const currentPage = Number(searchParams.get("page")) || 1;
console.log(pathname);
console.log(searchParams);
console.log(currentPage);
return <div>Pagination</div>;
}
上面这段逻辑是用于获取路径、参数和当前页面数的,接下来,在组件中创建一个名为的新函数createPageURL。与搜索类似,您将使用它URLSearchParams来设置新页码并pathName创建 URL 字符串。
createPageURL创建当前搜索参数的实例。然后,它将“page”参数更新为提供的页码。最后,它使用路径名和更新的搜索参数构建完整的URL。组件的其余部分<Pagination>
处理样式和不同状态(第一个、最后一个、活动、禁用等)。我们不会在本课程中详细介绍,但您可以随意查看代码以了解createPageURL调用的位置。
最后,当用户输入新的搜索查询时,您需要将页码重置为1。您可以通过更新组件handleSearch
中的函数来执行此操作<Search>
,编辑 /app/ui/search.tsx
页面
完成这一切之后,我们切换到/app/ui/invoices/pagination.tsx
和 /app/lib/utils.ts
完善页面的UI功能
编辑/app/lib/utils.ts
,添加下面的代码
export const generatePagination = (currentPage: number, totalPages: number) => {
// If the total number of pages is 7 or less,
// display all pages without any ellipsis.
if (totalPages <= 7) {
return Array.from({ length: totalPages }, (_, i) => i + 1);
}
// If the current page is among the first 3 pages,
// show the first 3, an ellipsis, and the last 2 pages.
if (currentPage <= 3) {
return [1, 2, 3, "...", totalPages - 1, totalPages];
}
// If the current page is among the last 3 pages,
// show the first 2, an ellipsis, and the last 3 pages.
if (currentPage >= totalPages - 2) {
return [1, 2, "...", totalPages - 2, totalPages - 1, totalPages];
}
// If the current page is somewhere in the middle,
// show the first page, an ellipsis, the current page and its neighbors,
// another ellipsis, and the last page.
return [
1,
"...",
currentPage - 1,
currentPage,
currentPage + 1,
"...",
totalPages,
];
};
这个 generatePagination函数实现了一个智能的分页显示逻辑,旨在创建一个用户友好的分页导航系统。它的主要功能是根据当前页码和总页数生成一个页码数组,用于显示在分页控件中。让我们详细分析一下它的功能:
处理少量页面的情况
如果总页数小于或等于7,函数会返回所有页码的数组,不使用省略号,例如:[1, 2, 3, 4, 5, 6, 7]
处理当前页在前三页的情况
如果当前页是前三页之一,函数会显示前三页,然后是省略号,最后是最后两页。例如:[1, 2, 3, "...", 99, 100]
处理当前页在后三页的情况
如果当前页是后三页之一,函数会显示前两页,然后是省略号,最后是最后三页。例如:[1, 2, "...", 98, 99, 100]
处理当前页在中间的情况:
如果当前页在中间位置,函数会显示第一页,省略号,当前页及其前后各一页,另一个省略号,最后是最后一页。例如:[1, "...", 49, 50, 51, "...", 100]
编辑/app/lib/pagination.tsx
,修改为下面的代码,实现分页的左右按钮
"use client";
import { ArrowLeftIcon, ArrowRightIcon } from "@heroicons/react/24/outline";
import clsx from "clsx";
import Link from "next/link";
import { usePathname, useSearchParams } from "next/navigation";
import { generatePagination } from "@/app/lib/utils";
export default function Pagination({ totalPages }: { totalPages: number }) {
const pathname = usePathname();
const searchParams = useSearchParams();
const currentPage = Number(searchParams.get("page")) || 1;
const createPageURL = (pageNumber: number | string) => {
const params = new URLSearchParams(searchParams);
params.set("page", pageNumber.toString());
return `${pathname}?${params.toString()}`;
};
const allPages = generatePagination(currentPage, totalPages);
return (
<>
{/* NOTE: Uncomment this code in Chapter 11 */}
<div className="inline-flex">
<PaginationArrow
direction="left"
href={createPageURL(currentPage - 1)}
isDisabled={currentPage <= 1}
/>
<div className="flex -space-x-px">
{allPages.map((page, index) => {
let position: "first" | "last" | "single" | "middle" | undefined;
if (index === 0) position = "first";
if (index === allPages.length - 1) position = "last";
if (allPages.length === 1) position = "single";
if (page === "...") position = "middle";
return (
<PaginationNumber
key={page}
href={createPageURL(page)}
page={page}
position={position}
isActive={currentPage === page}
/>
);
})}
</div>
<PaginationArrow
direction="right"
href={createPageURL(currentPage + 1)}
isDisabled={currentPage >= totalPages}
/>
</div>
</>
);
}
function PaginationNumber({
page,
href,
isActive,
position,
}: {
page: number | string;
href: string;
position?: "first" | "last" | "middle" | "single";
isActive: boolean;
}) {
const className = clsx(
"flex h-10 w-10 items-center justify-center text-sm border",
{
"rounded-l-md": position === "first" || position === "single",
"rounded-r-md": position === "last" || position === "single",
"z-10 bg-blue-600 border-blue-600 text-white": isActive,
"hover:bg-gray-100": !isActive && position !== "middle",
"text-gray-300": position === "middle",
}
);
return isActive || position === "middle" ? (
<div className={className}>{page}</div>
) : (
<Link href={href} className={className}>
{page}
</Link>
);
}
function PaginationArrow({
href,
direction,
isDisabled,
}: {
href: string;
direction: "left" | "right";
isDisabled?: boolean;
}) {
const className = clsx(
"flex h-10 w-10 items-center justify-center rounded-md border",
{
"pointer-events-none text-gray-300": isDisabled,
"hover:bg-gray-100": !isDisabled,
"mr-2 md:mr-4": direction === "left",
"ml-2 md:ml-4": direction === "right",
}
);
const icon =
direction === "left" ? (
<ArrowLeftIcon className="w-4" />
) : (
<ArrowRightIcon className="w-4" />
);
return isDisabled ? (
<div className={className}>{icon}</div>
) : (
<Link className={className} href={href}>
{icon}
</Link>
);
}
以上内容为分页的详细技术实现步骤,如果你想看更多内容或者能够看到技术更新的内容,请百度搜索:曲速引擎 warp drive csdn
在首页找到我的地址访问即可,一线更新内容将会在我的个人博客上面更新,谢谢大家。
更详细内容查看
独立博客 https://www.dataeast.cn/
CSDN博客 https://blog.csdn.net/siberiaWarpDrive
B站视频空间 https://space.bilibili.com/25871614?spm_id_from=333.1007.0.0
关注 “曲速引擎 Warp Drive” 微信公众号