React+TS前台项目实战(十六)-- 全局常用组件Pagination封装

文章目录

  • 前言
  • Pagination组件
    • [1. 功能分析](#1. 功能分析)
    • [2. 代码+详细注释](#2. 代码+详细注释)
    • [3. 使用方式](#3. 使用方式)
    • [4. 效果展示 [PC端&手机端]](#4. 效果展示 [PC端&手机端])
  • 总结

前言

在上篇文章中,我们封装了表格组件Table,本文则继续封装配套使用的分页器组件。想看Table表格组件的,可自行查看全局常用组件Table封装


Pagination组件

1. 功能分析

(1)渲染一个带有分页功能的用户界面,包括导航到第一页、上一页、下一页和最后一页的按钮,并实现响应式布局

(2)提供一个输入框,允许用户手动输入页码,并提供一个按钮用于跳转到指定页码

(3)通过调用onChange prop来更新页码,并根据当前页数更新UI显示

(4)通过自定义的useIsMobile hook来判断设备类型,根据设备类型显示不同效果

(5)使用国际化语言显示文案

2. 代码+详细注释

c 复制代码
// @/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. 使用方式

c 复制代码
// 引入组件
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)手机端


总结

下一篇讲【开始首页编码教学】。关注本栏目,将实时更新。

相关推荐
正小安1 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch2 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光2 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   2 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   2 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web3 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常3 小时前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式
莹雨潇潇4 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
Jiaberrr4 小时前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
Tiffany_Ho4 小时前
【TypeScript】知识点梳理(三)
前端·typescript