更新布局和微调整页面样式
This commit is contained in:
@@ -1,20 +1,188 @@
|
||||
import * as React from "react"
|
||||
'use client'
|
||||
import * as React from 'react'
|
||||
import {useState, useEffect} from 'react'
|
||||
|
||||
import {
|
||||
ChevronLeftIcon,
|
||||
ChevronRightIcon,
|
||||
MoreHorizontalIcon,
|
||||
} from "lucide-react"
|
||||
} from 'lucide-react'
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button, buttonVariants } from "@/components/ui/button"
|
||||
import {cn} from '@/lib/utils'
|
||||
|
||||
function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from './select'
|
||||
|
||||
export interface PaginationProps {
|
||||
page: number
|
||||
size: number
|
||||
total: number
|
||||
sizeOptions?: number[]
|
||||
onPageChange?: (page: number) => void
|
||||
onSizeChange?: (size: number) => void
|
||||
className?: string
|
||||
}
|
||||
|
||||
function Pagination({
|
||||
page,
|
||||
size,
|
||||
total,
|
||||
sizeOptions = [10, 20, 50, 100],
|
||||
onPageChange,
|
||||
onSizeChange,
|
||||
className,
|
||||
}: PaginationProps) {
|
||||
const [currentPage, setCurrentPage] = useState(page)
|
||||
const totalPages = Math.ceil(total / size)
|
||||
|
||||
// 同步外部 page 变化
|
||||
useEffect(() => {
|
||||
setCurrentPage(page)
|
||||
}, [page])
|
||||
|
||||
// 分页器逻辑
|
||||
const generatePaginationItems = () => {
|
||||
// 最多显示7个页码,其余用省略号
|
||||
const SIBLINGS = 1 // 当前页左右各显示的页码数
|
||||
const DOTS = -1 // 省略号标记
|
||||
|
||||
if (totalPages <= 7) {
|
||||
// 总页数少于7,全部显示
|
||||
return Array.from({length: totalPages}, (_, i) => i + 1)
|
||||
}
|
||||
|
||||
// 是否需要显示左边的省略号
|
||||
const showLeftDots = currentPage > 2 + SIBLINGS
|
||||
|
||||
// 是否需要显示右边的省略号
|
||||
const showRightDots = currentPage < totalPages - (2 + SIBLINGS)
|
||||
|
||||
if (showLeftDots && showRightDots) {
|
||||
// 两边都有省略号
|
||||
const leftSiblingIndex = Math.max(currentPage - SIBLINGS, 1)
|
||||
const rightSiblingIndex = Math.min(currentPage + SIBLINGS, totalPages)
|
||||
|
||||
return [1, DOTS, ...Array.from(
|
||||
{length: rightSiblingIndex - leftSiblingIndex + 1},
|
||||
(_, i) => leftSiblingIndex + i,
|
||||
), DOTS, totalPages]
|
||||
}
|
||||
|
||||
if (!showLeftDots && showRightDots) {
|
||||
// 只有右边有省略号
|
||||
return [...Array.from({length: 3 + SIBLINGS * 2}, (_, i) => i + 1), DOTS, totalPages]
|
||||
}
|
||||
|
||||
if (showLeftDots && !showRightDots) {
|
||||
// 只有左边有省略号
|
||||
return [1, DOTS, ...Array.from(
|
||||
{length: 3 + SIBLINGS * 2},
|
||||
(_, i) => totalPages - (3 + SIBLINGS * 2) + i + 1,
|
||||
)]
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
const handlePageChange = (newPage: number) => {
|
||||
if (newPage < 1 || newPage > totalPages || newPage === currentPage) {
|
||||
return
|
||||
}
|
||||
setCurrentPage(newPage)
|
||||
onPageChange?.(newPage)
|
||||
}
|
||||
|
||||
const handlePageSizeChange = (newSize: string) => {
|
||||
const parsedSize = parseInt(newSize, 10)
|
||||
if (onSizeChange) {
|
||||
onSizeChange(parsedSize)
|
||||
}
|
||||
}
|
||||
|
||||
const paginationItems = generatePaginationItems()
|
||||
|
||||
return (
|
||||
<div className={`flex flex-wrap items-center justify-end gap-4 ${className || ''}`}>
|
||||
<div className="flex-none flex items-center gap-2 text-sm text-muted-foreground">
|
||||
共
|
||||
{' '}
|
||||
{total}
|
||||
{' '}
|
||||
条记录,每页
|
||||
<Select
|
||||
value={size.toString()}
|
||||
onValueChange={handlePageSizeChange}
|
||||
>
|
||||
<SelectTrigger className="h-8 w-20">
|
||||
<SelectValue/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{sizeOptions.map(option => (
|
||||
<SelectItem key={option} value={option.toString()}>
|
||||
{option}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
条
|
||||
</div>
|
||||
|
||||
<PaginationLayout>
|
||||
<PaginationContent>
|
||||
|
||||
<PaginationItem>
|
||||
<PaginationPrevious
|
||||
onClick={() => handlePageChange(currentPage - 1)}
|
||||
className={currentPage === 1 ? 'opacity-50 pointer-events-none' : ''}
|
||||
/>
|
||||
</PaginationItem>
|
||||
|
||||
{paginationItems.map((pageNum, index) => {
|
||||
if (pageNum === -1) {
|
||||
return (
|
||||
<PaginationItem key={`dots-${index}`}>
|
||||
<PaginationEllipsis/>
|
||||
</PaginationItem>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<PaginationItem key={pageNum}>
|
||||
<PaginationLink
|
||||
isActive={pageNum === currentPage}
|
||||
onClick={() => handlePageChange(pageNum)}
|
||||
>
|
||||
{pageNum}
|
||||
</PaginationLink>
|
||||
</PaginationItem>
|
||||
)
|
||||
})}
|
||||
|
||||
<PaginationItem>
|
||||
<PaginationNext
|
||||
onClick={() => handlePageChange(currentPage + 1)}
|
||||
className={currentPage === totalPages ? 'opacity-50 pointer-events-none' : ''}
|
||||
/>
|
||||
</PaginationItem>
|
||||
|
||||
</PaginationContent>
|
||||
</PaginationLayout>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function PaginationLayout({className, ...props}: React.ComponentProps<'nav'>) {
|
||||
return (
|
||||
<nav
|
||||
role="navigation"
|
||||
aria-label="pagination"
|
||||
data-slot="pagination"
|
||||
className={cn("mx-auto flex w-full justify-center", className)}
|
||||
className={cn('flex-none', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
@@ -23,42 +191,39 @@ function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
|
||||
function PaginationContent({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"ul">) {
|
||||
}: React.ComponentProps<'ul'>) {
|
||||
return (
|
||||
<ul
|
||||
data-slot="pagination-content"
|
||||
className={cn("flex flex-row items-center gap-1", className)}
|
||||
className={cn('flex flex-row items-center gap-1', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function PaginationItem({ ...props }: React.ComponentProps<"li">) {
|
||||
return <li data-slot="pagination-item" {...props} />
|
||||
function PaginationItem({...props}: React.ComponentProps<'li'>) {
|
||||
return <li data-slot="pagination-item" {...props}/>
|
||||
}
|
||||
|
||||
type PaginationLinkProps = {
|
||||
isActive?: boolean
|
||||
} & Pick<React.ComponentProps<typeof Button>, "size"> &
|
||||
React.ComponentProps<"a">
|
||||
} & React.ComponentProps<'a'>
|
||||
|
||||
function PaginationLink({
|
||||
className,
|
||||
isActive,
|
||||
size = "icon",
|
||||
...props
|
||||
}: PaginationLinkProps) {
|
||||
return (
|
||||
<a
|
||||
aria-current={isActive ? "page" : undefined}
|
||||
aria-current={isActive ? 'page' : undefined}
|
||||
data-slot="pagination-link"
|
||||
data-active={isActive}
|
||||
className={cn(
|
||||
buttonVariants({
|
||||
variant: isActive ? "outline" : "ghost",
|
||||
size,
|
||||
}),
|
||||
className
|
||||
'inline-flex items-center justify-center text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 h-9 w-9 rounded-md border border-input hover:bg-secondary hover:text-secondary-foreground',
|
||||
`bg-card`,
|
||||
isActive && 'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
@@ -72,12 +237,10 @@ function PaginationPrevious({
|
||||
return (
|
||||
<PaginationLink
|
||||
aria-label="Go to previous page"
|
||||
size="default"
|
||||
className={cn("gap-1 px-2.5 sm:pl-2.5", className)}
|
||||
className={cn('gap-1 px-2.5 sm:pl-2.5', className)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronLeftIcon />
|
||||
<span className="hidden sm:block">Previous</span>
|
||||
<ChevronLeftIcon/>
|
||||
</PaginationLink>
|
||||
)
|
||||
}
|
||||
@@ -89,12 +252,10 @@ function PaginationNext({
|
||||
return (
|
||||
<PaginationLink
|
||||
aria-label="Go to next page"
|
||||
size="default"
|
||||
className={cn("gap-1 px-2.5 sm:pr-2.5", className)}
|
||||
className={cn('gap-1 px-2.5 sm:pr-2.5', className)}
|
||||
{...props}
|
||||
>
|
||||
<span className="hidden sm:block">Next</span>
|
||||
<ChevronRightIcon />
|
||||
<ChevronRightIcon/>
|
||||
</PaginationLink>
|
||||
)
|
||||
}
|
||||
@@ -102,15 +263,15 @@ function PaginationNext({
|
||||
function PaginationEllipsis({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"span">) {
|
||||
}: React.ComponentProps<'span'>) {
|
||||
return (
|
||||
<span
|
||||
aria-hidden
|
||||
data-slot="pagination-ellipsis"
|
||||
className={cn("flex size-9 items-center justify-center", className)}
|
||||
className={cn('flex size-9 items-center justify-center', className)}
|
||||
{...props}
|
||||
>
|
||||
<MoreHorizontalIcon className="size-4" />
|
||||
<MoreHorizontalIcon className="size-4"/>
|
||||
<span className="sr-only">More pages</span>
|
||||
</span>
|
||||
)
|
||||
@@ -118,6 +279,7 @@ function PaginationEllipsis({
|
||||
|
||||
export {
|
||||
Pagination,
|
||||
PaginationLayout,
|
||||
PaginationContent,
|
||||
PaginationLink,
|
||||
PaginationItem,
|
||||
|
||||
Reference in New Issue
Block a user