Vue手写分页器

本文介绍如何封装一个Vue分页器组件,详细阐述了分页需求的分析,包括计算连续页码范围、渲染逻辑,并提供了完整的源码实现。通过组件化实现,提升前端页面加载效率,改善用户体验。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

如何封装一个分页器组件

文章最后会提供源码

分析与代码部分

需要分页器的原因:从服务器中获取的数据很多,如果全部一次性加载会导致卡顿,用户体验差

我们写一个分页器需要的外部数据(即使用该分页器组件时需要通过props传入的数据):

  1. 当前是第几页 pageNo

  2. 每一页展示多少数据 pageSize

  3. 整个分页器一共有多少数据 total

  4. 分页器连续页码个数 continues(通常是奇数5 / 7,一般要求对称)

ps:其中暗含一个隐藏数据:整个分页器一共有多少页 Math.ceil(total / pageSize)




下面开始写代码:

计算连续页码的起始数字与结束数字

连续页码就是分页器中按数字顺序连续显示的页码,个数为 continues

在这里插入图片描述
例如上图中的 3、4、5、6、7 这5个页码,我们要先计算出这几个连续页码的开始页码和结束页码

逻辑:当前页码为第n页,假设continues = 5,那么起始数字为n-2,结束数字为n+2

// computed中
totalPage() {
	return Math.ceil(this.total / this.pageSize);
},
// 连续页码起始数字与结束数字
startNumAndEndNum() {
    const { pageNo, totalPage, continues } = this;
    let start = 0;
    let end = 0;
    // 如果总页数少于连续页码数,只展示 1~最后页码数 即可
    if (totalPage < continues) {
        start = 1;
        end = totalPage;
    // 如果总页数大于等于连续页码数
    } else {
        start = pageNo - Math.floor(continues / 2);
        end = pageNo + Math.floor(continues / 2);
        // start为 0 / 负数
        if (start < 1) {
            start = 1;
            end = continues;
        }
        // end大于总页数
        if (end > totalPage) {
            start = totalPage - continues + 1;
            end = totalPage;
    	}
}
	return {start, end};
},
连续页码数组
// computed中
pagesArr() {
    let arr = [];
    let pageNum = this.startNumAndEndNum.start;
    arr.length = (this.totalPage < this.continues ? this.totalPage : this.continues);
    for (let i = 0; i < arr.length; i++) {
        arr[i] = pageNum;
        pageNum++;
    }
    return arr
}
渲染数据
<ul>
    <li class="prev disabled">
        <a href="#">« 上一页</a>
    </li>
    <li v-show="startNumAndEndNum.start > 1">
        <a href="#">1</a>
    </li>
    <li class="dotted" v-show="startNumAndEndNum.start > 2"><span>...</span></li>

    <!-- 中间部分 -->
    <li v-for="(p, index) in pagesArr" :key="index">
        <a href="#">{{p}}</a>
    </li>

    <li class="dotted" v-show="startNumAndEndNum.end < totalPage - 1"><span>...</span></li>
    <li v-show="startNumAndEndNum.end < totalPage">
        <a href="#">{{ totalPage }}</a>
    </li>
    <li class="next">
        <a href="#">下一页 »</a>
    </li>
</ul>

完整代码:

<template>
  <div class="fr page">
    <div class="sui-pagination clearfix">
      <ul>
        <li
          class="prev"
          :class="{ disabled: pageNo === 1 }"
          @click="pageNo > 1 ? $emit('getPageNo', pageNo - 1) : null"
        >
          <a>« 上一页</a>
        </li>
        <li
          v-show="startNumAndEndNum.start > 1"
          :class="{ active: pageNo === 1 }"
          @click="$emit('getPageNo', 1)"
        >
          <a>1</a>
        </li>
        <li class="dotted" v-show="startNumAndEndNum.start > 2">
          <span>...</span>
        </li>

        <!-- 中间部分 -->
        <li
          v-for="(page, index) in pagesArr"
          :key="index"
          :class="{ active: pageNo === page }"
          @click="$emit('getPageNo', page)"
        >
          <a>{{ page }}</a>
        </li>

        <li class="dotted" v-show="startNumAndEndNum.end < totalPage - 1">
          <span>...</span>
        </li>
        <li
          v-show="startNumAndEndNum.end < totalPage"
          :class="{ active: pageNo === totalPage }"
          @click="$emit('getPageNo', totalPage)"
        >
          <a>{{ totalPage }}</a>
        </li>
        <li
          class="next"
          :class="{ disabled: pageNo === totalPage }"
          @click="pageNo < totalPage ? $emit('getPageNo', pageNo + 1) : null"
        >
          <a>下一页 »</a>
        </li>
      </ul>
      <div>
        <span>{{ total }}</span>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "Pagination",
  props: ["pageNo", "pageSize", "total", "continues"],
  computed: {
    totalPage() {
      return Math.ceil(this.total / this.pageSize);
    },
    // 连续页码起始数字与结束数字
    startNumAndEndNum() {
      const { pageNo, continues, totalPage } = this;
      let start = 0;
      let end = 0;
      // 如果总页数少于连续页数
      if (totalPage < continues) {
        start = 1;
        end = totalPage;
      } else {
        start = pageNo - Math.floor(continues / 2);
        end = pageNo + Math.floor(continues / 2);
        // start出现0/负数
        if (start < 1) {
          start = 1;
          end = continues;
        }
        // end大于总页数
        if (end > totalPage) {
          start = totalPage - continues + 1;
          end = totalPage;
        }
      }
      return { start, end };
    },
    pagesArr() {
      let arr = [];
      // 连续页码个数
      arr.length =
        this.totalPage < this.continues ? this.totalPage : this.continues;
      // 第一个连续页码
      let pageNum = this.startNumAndEndNum.start;
      for (let i = 0; i < arr.length; i++) {
        arr[i] = pageNum;
        pageNum++;
      }
      return arr;
    },
  },
};
</script>

<style scoped lang="less">
.page {
  display: flex;
  justify-content: center;
  overflow: hidden;

  .sui-pagination {
    margin: 18px 0;

    ul {
      margin-left: 0;
      margin-bottom: 0;
      vertical-align: middle;
      float: left;

      li {
        line-height: 18px;
        display: inline-block;

        a {
          position: relative;
          float: left;
          background-color: #fff;
          padding: 9px 18px;
          border: 1px solid #e0e9ee;
          margin-left: -1px;
          line-height: 18px;
          text-decoration: none;
          font-size: 14px;
          color: #333;
          cursor: pointer;
        }

        &.active {
          a {
            background-color: #e1251b;
            color: #fff;
            cursor: default;
          }
        }

        &.prev {
          a {
            margin-left: 0px;
            background-color: #fafafa;
          }
        }

        &.disabled {
          a {
            color: #999;
            cursor: default;
          }
        }

        &.dotted {
          span {
            position: relative;
            float: left;
            padding: 9px 18px;
            border: 1px solid #e0e9ee;
            margin-left: -1px;
            background-color: #fff;
            font-size: 14px;
            line-height: 18px;
            text-decoration: none;
            color: #333;
          }
        }

        &.next {
          a {
            background-color: #fafafa;
          }
        }
      }
    }

    div {
      margin-top: 10px;
      margin-left: 20px;
      color: #333;
      font-size: 14px;
      float: left;
      width: 80px;
    }
  }
}
</style>

在其他组件中使用:

<!-- 分页器 -->
<Pagination
	// 引号内写入想传入的数据
	:pageNo=""
	:pageSize=""
	:total=""
	:continues=""
	@getPageNo=""
/>
### Vue3 中实现分页的方法 在 Vue3 中,可以通过多种方式来实现分页功能。以下是两种常见的方法:一种是通过手动编写逻辑代码实现分页;另一种则是利用现有的第三方库或组件。 #### 方法一:手写分页逻辑 可以创建一个简单的分页器组件,该组件接收数据列表作为属性并显示指定数量的数据项。以下是一个基本的手动分页示例: ```vue <template> <div> <!-- 显示当前页面的内容 --> <ul> <li v-for="item in paginatedData" :key="item.id">{{ item.name }}</li> </ul> <!-- 分页按钮 --> <button @click="prevPage">上一页</button> <span>第 {{ currentPage }} / {{ totalPages }} 页</span> <button @click="nextPage">下一页</button> </div> </template> <script> import { ref, computed } from 'vue'; export default { props: { data: { type: Array, required: true }, pageSize: { type: Number, default: 10 } }, setup(props) { const currentPage = ref(1); // 计算总页数 const totalPages = computed(() => Math.ceil(props.data.length / props.pageSize)); // 获取当前页码对应的数据子集 const paginatedData = computed(() => { const start = (currentPage.value - 1) * props.pageSize; const end = start + props.pageSize; return props.data.slice(start, end); }); // 上一页操作 function prevPage() { if (currentPage.value > 1) { currentPage.value--; } } // 下一页操作 function nextPage() { if (currentPage.value < totalPages.value) { currentPage.value++; } } return { currentPage, totalPages, paginatedData, prevPage, nextPage }; } }; </script> ``` 上述代码展示了如何基于 `props` 和响应式变量构建一个基础的分页组件[^1]。 --- #### 方法二:使用第三方分页插件 Vue 社区提供了许多成熟的分页解决方案,比如 Element Plus 或 Vuetify 的分页组件。这些工具通常已经内置了样式和交互逻辑,能够快速集成到项目中。 ##### 使用 Element Plus 实现分页 Element Plus 是 Vue3 生态中最流行的 UI 库之一,它提供了一个名为 `<el-pagination>` 的分页组件。下面是如何将其用于分页的一个简单例子: ```vue <template> <div> <!-- 数据展示区域 --> <ul> <li v-for="item in paginatedData" :key="item.id">{{ item.name }}</li> </ul> <!-- 分页控件 --> <el-pagination background layout="prev, pager, next" :total="data.length" :page-size="pageSize" :current-page="currentPage" @current-change="handlePageChange"> </el-pagination> </div> </template> <script> import { ref, computed } from 'vue'; import { ElPagination } from 'element-plus'; // 导入分页组件 export default { components: { ElPagination }, // 注册分页组件 props: { data: { type: Array, required: true } }, setup(props) { const currentPage = ref(1); // 当前页码 const pageSize = ref(10); // 每页条目数 // 总页数计算 const totalPages = computed(() => Math.ceil(props.data.length / pageSize.value)); // 当前页码对应的数据显示范围 const paginatedData = computed(() => { const start = (currentPage.value - 1) * pageSize.value; const end = start + pageSize.value; return props.data.slice(start, end); }); // 处理页码变化事件 function handlePageChange(pageNumber) { currentPage.value = pageNumber; // 更新当前页码 } return { currentPage, pageSize, totalPages, paginatedData, handlePageChange }; } }; </script> ``` 此代码片段演示了如何结合自定义逻辑与 Element Plus 提供的功能完成更复杂的分页需求[^2]。 --- #### 注意事项 无论是采用手工编码还是依赖于现有框架,在实际开发过程中都需要考虑以下几个方面: - **性能优化**:当处理大量数据时,应避免一次性加载全部内容至内存中。 - **用户体验设计**:合理设置每页大小以及导航按钮布局以提升易用性。 - **兼容性和可扩展性**:确保所选方案能够在不同浏览器环境下正常工作,并支持未来可能新增的需求变更。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值