Bootstrap

Vue3分页(Pagination)

Vue2分页(Pagination)

可自定义设置以下属性:

  • 当前页数(page),类型:number,默认 1

  • 每页条数(pageSize),类型:number,默认 10

  • 每页可以显示多少条(pageSizeOptions),类型:string[]|number[],默认 [10, 20, 50, 100]

  • 数据总数(total),类型:number,默认 0

  • 显示的页码数组长度(pageListNum),类型:number,默认 5

  • 只有一页时是否隐藏分页(hideOnSinglePage),类型:boolean,默认 false

  • 是否可以快速跳转至某页(showQuickJumper),类型:boolean,默认 false

  • 是否展示 pageSize 切换器(showSizeChanger),类型:boolean,默认 undefined,当 total 大于 50 时默认为 true

  • 是否显示当前页数和数据总量(showTotal),类型:boolean,默认 false

  • 分页展示位置,靠左left,居中center,靠右right(placement),类型:'left'|'center'|'right',默认 'center'

效果如下图:在线预览

 鼠标悬浮时切换为箭头:

①创建自定义分页组件Pagination.vue:

其中引入使用了 Vue3选择器(Select)组件 

<script setup lang="ts">
import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
import Select from '../select'
interface Props {
  page?: number // 当前页数
  pageSize?: number // 每页条数
  pageSizeOptions?: string[] | number[] // 每页可以显示多少条
  total?: number // 数据总数
  pageListNum?: number // 显示的页码数组长度
  hideOnSinglePage?: boolean // 只有一页时是否隐藏分页
  showQuickJumper?: boolean // 是否可以快速跳转至某页
  showSizeChanger?: boolean // 是否展示 pageSize 切换器,当 total 大于 50 时默认为 true
  showTotal?: boolean // 是否显示当前页数和数据总量
  placement?: 'left' | 'center' | 'right' // 分页展示位置,靠左left,居中center,靠右right
}
const props = withDefaults(defineProps<Props>(), {
  // 基于类型的声明
  page: 1,
  pageSize: 10,
  pageSizeOptions: () => [10, 20, 50, 100],
  total: 0,
  pageListNum: 5,
  hideOnSinglePage: false,
  showQuickJumper: false,
  showSizeChanger: undefined,
  showTotal: false,
  placement: 'center'
})
const currentPage = ref(props.page) // 当前页数
const currentPageSize = ref(Number(props.pageSizeOptions[0])) // 当前 pageSize
const jumpNumber = ref('') // 跳转的页码
const forwardMore = ref(false) // 左省略号展示
const backwardMore = ref(false) // 右省略号展示
const forwardArrow = ref(false) // 左箭头展示
const backwardArrow = ref(false) // 右箭头展示
const totalPage = computed(() => {
  // 总页数
  return Math.ceil(props.total / currentPageSize.value) // 向上取整
})
const pageList = computed(() => {
  // 获取显示的页码数组
  return dealPageList(currentPage.value).filter((n) => n !== 1 && n !== totalPage.value)
})
const sizeChanger = computed(() => {
  if (typeof props.showSizeChanger === 'boolean') {
    return props.showSizeChanger
  } else {
    // undefined
    return props.total > 50
  }
})
const options = computed(() => {
  return props.pageSizeOptions.map((pageSize: string | number) => {
    return {
      label: pageSize + ' 条/页',
      value: Number(pageSize)
    }
  })
})
const emits = defineEmits(['change', 'pageSizeChange'])
watch(currentPage, (to: number): void => {
  // 通过更改当前页码,修改分页数据
  // loading,value = true
  console.log('change:', to)
  emits('change', to, currentPageSize.value)
})
onMounted(() => {
  // 监听键盘Enter按键
  document.onkeydown = (e: KeyboardEvent) => {
    if (e.key === 'Enter') {
      jumpPage()
    }
  }
})
onUnmounted(() => {
  document.onkeydown = null
})
function dealPageList(curPage: number): number[] {
  var resList = []
  var offset = Math.floor(props.pageListNum / 2) // 向下取整
  var pager = {
    start: curPage - offset,
    end: curPage + offset
  }
  if (pager.start < 1) {
    pager.end = pager.end + (1 - pager.start)
    pager.start = 1
  }
  if (pager.end > totalPage.value) {
    pager.start = pager.start - (pager.end - totalPage.value)
    pager.end = totalPage.value
  }
  if (pager.start < 1) {
    pager.start = 1
  }
  if (pager.start > 1) {
    forwardMore.value = true
  } else {
    forwardMore.value = false
  }
  if (pager.end < totalPage.value) {
    backwardMore.value = true
  } else {
    backwardMore.value = false
  }
  // 生成要显示的页码数组
  for (let i = pager.start; i <= pager.end; i++) {
    resList.push(i)
  }
  return resList
}
function onForward(): void {
  currentPage.value = currentPage.value - props.pageListNum > 0 ? currentPage.value - props.pageListNum : 1
}
function onBackward(): void {
  currentPage.value =
    currentPage.value + props.pageListNum < totalPage.value ? currentPage.value + props.pageListNum : totalPage.value
}
function jumpPage(): void {
  var num = Number(jumpNumber.value)
  if (Number.isInteger(num)) {
    if (num < 1) {
      num = 1
    }
    if (num > totalPage.value) {
      num = totalPage.value
    }
    changePage(num)
  }
  jumpNumber.value = '' // 清空跳转输入框
}
function onKeyboard(e: KeyboardEvent, pageNum: number) {
  if (e.key === 'Enter') {
    changePage(pageNum)
  }
}
function changePage(pageNum: number): boolean | void {
  if (pageNum === 0 || pageNum === totalPage.value + 1) {
    return false
  }
  if (currentPage.value !== pageNum) {
    // 点击的页码不是当前页码
    currentPage.value = pageNum
  }
}
function onSizeChange(pageSize: number) {
  const maxPage = Math.ceil(props.total / pageSize)
  if (currentPage.value > maxPage) {
    currentPage.value = maxPage
    emits('pageSizeChange', currentPage.value, pageSize)
  } else {
    emits('pageSizeChange', currentPage.value, pageSize)
    emits('change', currentPage.value, pageSize)
  }
}
</script>
<template>
  <div :class="[`m-pagination ${placement}`, { hidden: hideOnSinglePage && total <= pageSize }]">
    <div class="m-pagination-wrap">
      <span class="mr8" v-if="showTotal">共 {{ totalPage }} 页 / {{ total }} 条</span>
      <span class="u-item" :class="{ disabled: currentPage === 1 }" tabindex="-1" @keydown="onKeyboard($event, currentPage - 1)" @click="changePage(currentPage - 1)">
        <svg class="u-arrow" viewBox="64 64 896 896" data-icon="left" aria-hidden="true" focusable="false">
          <path
            d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 0 0 0 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"
          ></path>
        </svg>
      </span>
      <span :class="['u-item', { active: currentPage === 1 }]" @click="changePage(1)">1</span>
      <span
        class="m-arrow"
        ref="forward"
        v-show="forwardMore && pageList[0] - 1 > 1"
        @click="onForward"
        @mouseenter="forwardArrow = true"
        @mouseleave="forwardArrow = false"
      >
        <span class="u-ellipsis">•••</span>
        <svg class="u-icon" viewBox="64 64 896 896" data-icon="double-left" aria-hidden="true" focusable="false">
          <path
            d="M272.9 512l265.4-339.1c4.1-5.2.4-12.9-6.3-12.9h-77.3c-4.9 0-9.6 2.3-12.6 6.1L186.8 492.3a31.99 31.99 0 0 0 0 39.5l255.3 326.1c3 3.9 7.7 6.1 12.6 6.1H532c6.7 0 10.4-7.7 6.3-12.9L272.9 512zm304 0l265.4-339.1c4.1-5.2.4-12.9-6.3-12.9h-77.3c-4.9 0-9.6 2.3-12.6 6.1L490.8 492.3a31.99 31.99 0 0 0 0 39.5l255.3 326.1c3 3.9 7.7 6.1 12.6 6.1H836c6.7 0 10.4-7.7 6.3-12.9L576.9 512z"
          ></path>
        </svg>
      </span>
      <span
        :class="['u-item', { active: currentPage === page }]"
        v-for="(page, index) in pageList"
        :key="index"
        @click="changePage(page)"
      >
        {{ page }}
      </span>
      <span
        class="m-arrow"
        ref="backward"
        v-show="backwardMore && pageList[pageList.length - 1] + 1 < totalPage"
        @click="onBackward"
        @mouseenter="backwardArrow = true"
        @mouseleave="backwardArrow = false"
      >
        <span class="u-ellipsis">•••</span>
        <svg class="u-icon" viewBox="64 64 896 896" data-icon="double-right" aria-hidden="true" focusable="false">
          <path
            d="M533.2 492.3L277.9 166.1c-3-3.9-7.7-6.1-12.6-6.1H188c-6.7 0-10.4 7.7-6.3 12.9L447.1 512 181.7 851.1A7.98 7.98 0 0 0 188 864h77.3c4.9 0 9.6-2.3 12.6-6.1l255.3-326.1c9.1-11.7 9.1-27.9 0-39.5zm304 0L581.9 166.1c-3-3.9-7.7-6.1-12.6-6.1H492c-6.7 0-10.4 7.7-6.3 12.9L751.1 512 485.7 851.1A7.98 7.98 0 0 0 492 864h77.3c4.9 0 9.6-2.3 12.6-6.1l255.3-326.1c9.1-11.7 9.1-27.9 0-39.5z"
          ></path>
        </svg>
      </span>
      <span
        v-show="totalPage !== 1"
        :class="['u-item', { active: currentPage === totalPage }]"
        @click="changePage(totalPage)"
      >
        {{ totalPage }}
      </span>
      <span class="u-item" :class="{ disabled: currentPage === totalPage }" tabindex="-1" @keydown="onKeyboard($event, currentPage + 1)" @click="changePage(currentPage + 1)">
        <svg class="u-arrow" viewBox="64 64 896 896" data-icon="right" aria-hidden="true" focusable="false">
          <path
            d="M765.7 486.8L314.9 134.7A7.97 7.97 0 0 0 302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 0 0 0-50.4z"
          ></path>
        </svg>
      </span>
      <Select
        v-if="sizeChanger"
        class="u-pagesize-select"
        :options="options"
        @change="onSizeChange"
        v-model="currentPageSize"
      />
      <span class="u-jump-page" v-if="showQuickJumper">
        跳至
        <input type="text" v-model="jumpNumber" />
        页
      </span>
    </div>
  </div>
</template>
<style lang="less" scoped>
.hidden {
  display: none;
}
.left {
  text-align: left;
}
.center {
  text-align: center;
}
.right {
  text-align: right;
}
.m-pagination {
  margin: 16px 0;
  .m-pagination-wrap {
    display: inline-block;
    height: 32px;
    line-height: 30px;
    font-size: 14px;
    color: rgba(0, 0, 0, 0.88);
    text-align: center;
    .mr8 {
      margin-right: 8px;
    }
    .u-item {
      display: inline-block;
      min-width: 32px;
      height: 32px;
      line-height: 30px;
      user-select: none; // 禁止选取文本
      border: 1px solid #d9d9d9;
      border-radius: 6px;
      background: #fff;
      cursor: pointer;
      outline: none;
      transition: all 0.3s;
      &:hover {
        .active();
      }
      .u-arrow {
        display: inline-block;
        fill: rgba(0, 0, 0, 0.65);
        width: 12px;
        height: 12px;
        transition: all 0.3s;
      }
      &:not(:last-child) {
        margin-right: 8px;
      }
    }
    .active {
      // 悬浮/选中样式
      font-weight: 600;
      color: @themeColor;
      border-color: @themeColor;
      .u-arrow {
        fill: @themeColor;
      }
    }
    .disabled {
      // pointer-events: none; // 禁用鼠标事件
      color: rgba(0, 0, 0, 0.25);
      background: #fff;
      border-color: #d9d9d9;
      cursor: not-allowed;
      &:hover {
        font-weight: 400;
        color: rgba(0, 0, 0, 0.65);
        border-color: #d9d9d9;
        .u-arrow {
          fill: rgba(0, 0, 0, 0.25);
        }
      }
      .u-arrow {
        fill: rgba(0, 0, 0, 0.25);
      }
    }
    .m-arrow {
      position: relative;
      display: inline-block;
      vertical-align: top;
      margin-right: 8px;
      width: 32px;
      height: 32px;
      transition: all 0.3s;
      cursor: pointer;
      &:hover {
        .u-ellipsis {
          opacity: 0;
          pointer-events: none;
        }
        .u-icon {
          opacity: 1;
          pointer-events: auto;
        }
      }
      .u-ellipsis {
        box-sizing: border-box;
        position: absolute;
        top: 0;
        left: 0;
        bottom: 0;
        right: 0;
        display: block;
        margin: auto;
        color: rgba(0, 0, 0, 0.25);
        font-family: Arial, Helvetica, sans-serif;
        line-height: 32px;
        letter-spacing: 2px;
        text-align: center;
        text-indent: 0.13em;
        opacity: 1;
        transition: all 0.2s;
      }
      .u-icon {
        display: inline-flex;
        align-items: center;
        text-align: center;
        vertical-align: -0.125em;
        fill: @themeColor;
        width: 12px;
        height: 12px;
        opacity: 0;
        pointer-events: none;
        transition: all 0.2s;
      }
    }
    .u-pagesize-select {
      margin-left: 8px;
      line-height: 30px;
      vertical-align: top;
    }
    .u-jump-page {
      margin-left: 8px;
      line-height: 30px;
      font-size: 14px;
      input {
        vertical-align: top;
        width: 50px;
        height: 32px;
        padding: 4px 11px;
        margin: 0 8px;
        border: 1px solid #d9d9d9;
        border-radius: 6px;
        background: #fff;
        text-align: left;
        outline: none;
        transition: all 0.3s;
        &:hover {
          border-color: @themeColor;
        }
        &:focus {
          border-color: @themeColor;
          box-shadow: 0 0 0 2px fade(@themeColor, 20%);
        }
      }
    }
  }
}
</style>

②在要使用的页面引入分页器:

<script setup lang="ts">
import Pagination from './Pagination.vue'
import { ref } from 'vue'

const total = ref(100)
function changePage (page: number, pageSize: number) { // 分页回调
  console.log('page:', page)
  console.log('pageSize:', pageSize)
}
function pageSizeChange (page: number, pageSize: number) { // pageSize 变化的回调
  console.log('change page:', page)
  console.log('change pageSize:', pageSize)
}
</script>
<template>
  <div>
    <h1>{{ $route.name }} {{ $route.meta.title }}</h1>
    <h2 class="mt30 mb10">基本使用</h2>
    <Pagination :total="total" @change="changePage" />
    <h2 class="mt30 mb10">靠左展示</h2>
    <Pagination :total="total" placement="left" @change="changePage" />
    <h2 class="mt30 mb10">靠右展示</h2>
    <Pagination :total="total" placement="right" @change="changePage" />
    <h2 class="mt30 mb10">快速跳转 & 数据总量</h2>
    <Pagination
      :total="total"
      show-quick-jumper
      show-total
      @change="changePage"
      @pageSizeChange="pageSizeChange" />
    <h2 class="mt30 mb10">Ant Design 分页</h2>
    <a-pagination
      :total="total"
      show-quick-jumper
      @change="changePage"
      @showSizeChange="pageSizeChange" />
  </div>
</template>
;