文章目录
- 前言
- Pagination组件
- 1. 功能分析
- 2. 代码+详细注释
- 3. 使用方式
- 4. 效果展示 [PC端&手机端]
- 总结
前言
在上篇文章中,我们封装了表格组件Table,本文则继续封装配套使用的分页器组件。想看Table表格组件的,可自行查看全局常用组件Table封装
Pagination组件
1. 功能分析
(1)渲染一个带有分页功能的用户界面,包括导航到第一页、上一页、下一页和最后一页的按钮,并实现响应式布局
(2)提供一个输入框,允许用户手动输入页码,并提供一个按钮用于跳转到指定页码
(3)通过调用onChange prop来更新页码,并根据当前页数更新UI显示
(4)通过自定义的useIsMobile hook来判断设备类型,根据设备类型显示不同效果
(5)使用国际化语言显示文案
2. 代码+详细注释
// @/components/Pagination/index.tsx
import { useState, FC } from 'react'
import { useTranslation } from 'react-i18next'
import { PaginationLeft, PaginationRight, PaginationContainer } from './styled'
import { useIsMobile } from '@/hooks'
import Button from '@/components/Button'
import LeftArrow from './left_arrow.png'
import RightArrow from './right_arrow.png'
import DisLeftArrow from './disabled_left_arrow.png'
import DisRightArrow from './disabled_right_arrow.png'
// 组件的属性类型
type Props = {
currentPage: number // 当前页码
total: number // 总页数
gotoPage?: number // 跳转页码,默认为当前页码加一
onChange: (page: number, size?: number) => void // 页码改变时的回调函数
className?: string // 组件额外的class名
pageSize?: number // 每页显示条目数
pageSizes?: number[] // 每页显示条目数的可选项
}
/**
* 分页组件
* @param currentPage 当前页码
* @param total 总页数
* @param gotoPage 跳转页码,默认为当前页码加一
* @param onChange 页码改变时的回调函数
* @param className 组件额外的class名
* @param pageSize 每页显示条目数
* @param pageSizes 每页显示条目数的可选项
*/
const Pagination: FC<Props> = ({
currentPage,
total,
gotoPage = currentPage === total ? total : currentPage + 1,
onChange,
className,
pageSize,
pageSizes,
}) => {
// 判断是否是移动端
const isMobile = useIsMobile()
// 获取i18n翻译函数
const { t } = useTranslation()
// 创建一个state,用于存储输入框的值
const [inputVal, setInputVal] = useState(gotoPage)
// 计算总页数,并确保总页数大于0
const totalCount = Math.max(total, 1)
// 计算当前页数,并确保当前页数在范围内
const current = Math.min(Math.max(currentPage, 1), total)
// 移动端中间描述文本
const mobileMiddleDescrip = `${t('pagination.total_page')} ${totalCount} ${t('pagination.end_page')}`
// PC端中间描述文本
const pcMiddleDescrip = `${t('pagination.current_page')} ${current} ${t('pagination.of_page')} ${totalCount} ${t(
'pagination.end_page',
)}`
// 页码更新
const changePage = (page: number) => {
if (page && page >= 1 && page <= totalCount) {
setInputVal(Math.min(page + 1, totalCount))
onChange(page)
}
}
return (
<PaginationContainer className={className}>
<PaginationLeft isFirstPage={current === 1} isLastPage={current === totalCount}>
<Button className="first-button" onClick={() => changePage(1)}>
{t('pagination.first')}
</Button>
<Button className="left-button" onClick={() => changePage(current - 1)}>
<img src={current === 1 ? DisLeftArrow : LeftArrow} alt="左侧按钮" />
</Button>
{!isMobile && <span className="middle-discrip">{pcMiddleDescrip}</span>}
<Button className="right-button" onClick={() => changePage(current + 1)}>
<img src={current === totalCount ? DisRightArrow : RightArrow} alt="右侧按钮" />
</Button>
{isMobile && <span className="middle-discrip">{mobileMiddleDescrip}</span>}
<Button className="last-button" onClick={() => changePage(totalCount)}>
{t('pagination.last')}
</Button>
</PaginationLeft>
<PaginationRight>
<span className="page-label">{t('pagination.page')}</span>
<input
type="text"
className="page-input"
pattern="[0-9]*"
value={inputVal}
onChange={event => {
const pageNo = parseInt(event.target.value, 10)
setInputVal(Number.isNaN(pageNo) ? 0 : Math.min(pageNo, totalCount))
}}
/>
<Button className="page-go-to" onClick={() => changePage(inputVal)}>
{t('pagination.goto')}
</Button>
</PaginationRight>
</PaginationContainer>
)
}
export default Pagination
--------------------------------------------------------------------------------------------------------------
// @/components/Pagination/styled.tsx
import styled from 'styled-components'
import variables from '../../styles/variables.module.scss'
export const PaginationContainer = styled.div`
display: flex;
flex-direction: row;
padding: 10px 20px;
background: #fff;
`
export const PaginationLeft = styled.div`
display: flex;
align-items: center;
justify-content: center;
flex: 3;
font-size: 14px;
@media (max-width: ${variables.mobileBreakPoint}) {
padding-left: 0;
justify-content: flex-start;
}
.first-button,
.last-button {
padding: 4px 8px;
border-radius: 5px;
background: #f5f5f5;
cursor: pointer;
color: ${({ isFirstPage }: { isFirstPage: boolean }) => (isFirstPage ? '#969696' : '#000000')};
cursor: ${({ isFirstPage }: { isFirstPage: boolean }) => (isFirstPage ? 'pointer' : 'auto')};
&:hover {
background: #999;
}
@media (max-width: ${variables.mobileBreakPoint}) {
display: none;
}
}
.left-button,
.right-button {
margin-left: 20px;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 5px;
background: #f5f5f5;
cursor: ${({ isFirstPage }: { isFirstPage: boolean }) => (isFirstPage ? 'pointer' : 'auto')};
&:hover {
background: #999;
}
img {
width: 8px;
}
@media (max-width: ${variables.mobileBreakPoint}) {
margin-left: 5px;
}
}
.right-button {
cursor: ${({ isLastPage }: { isLastPage: boolean }) => (isLastPage ? 'pointer' : 'auto')};
}
.last-button {
margin-left: 20px;
color: ${({ isLastPage }: { isLastPage: boolean }) => (isLastPage ? '#969696' : '#000000')};
cursor: ${(props: { isFirstPage: boolean; isLastPage: boolean }) => (props.isLastPage ? 'none' : 'auto')};
}
.middle-discrip {
display: flex;
align-items: center;
justify-content: center;
height: 30px;
background: #f5f5f5;
border-radius: 5px;
font-size: 12px;
padding: 0 12px;
margin-left: 20px;
white-space: nowrap;
@media (max-width: ${variables.mobileBreakPoint}) {
background: #fff;
border-radius: 0;
margin: 0 5px;
padding: 0;
}
}
`
export const PaginationRight = styled.div`
display: flex;
align-items: center;
flex: 2;
font-size: 14px;
@media (max-width: ${variables.mobileBreakPoint}) {
justify-content: flex-end;
}
.page-input {
width: 120px;
height: 30px;
border-radius: 5px;
background-color: rgb(245, 245, 245);
color: rgb(204, 204, 204);
margin-right: 40px;
outline: none;
border: none;
padding-left: 10px;
@media (max-width: ${variables.mobileBreakPoint}) {
width: 60px;
margin-right: 20px;
font-size: 12px;
}
}
.page-label {
margin-right: 20px;
@media (max-width: ${variables.mobileBreakPoint}) {
display: none;
}
}
.page-go-to {
height: 30px;
line-height: 30px;
padding: 0 10px;
background: #f5f5f5;
border-radius: 6px;
border: none;
outline: none;
cursor: pointer;
&:hover {
background: #999;
}
@media (max-width: ${variables.mobileBreakPoint}) {
font-size: 12px;
}
}
`
3. 使用方式
// 引入组件
import Pagination from "@/components/Pagination";
// 使用
<Pagination
currentPage={1}
PageSize={10}
PageSizes={[10, 20, 30]}
total={data?.total ?? 1}
onChange={handlePageChange}
/>
const handlePageChange = (page: number) => {
console.log('handlePageChange', page)
}
4. 效果展示 [PC端&手机端]
(1)PC端
(2)手机端
总结
下一篇讲【开始首页编码教学】。关注本栏目,将实时更新。