总结 vue 实现分页器的基本思路
简介
本文介绍基于 vue2 框架实现基本的分页器,包括页码前进/后退、页码点击跳转、显示 ... 、显示总页数、显示总数据条数 等功能。
效果预览
快速跳转
实现思路
视图部分
视图方面比较简单,将各功能部分依次搭建即可,包括前进键(>>)、后退键(<<)、中间以及首尾(1和页码上限)的页码、...、以及页码总计和数据总计。
代码示例
<template> <div class="pagination"> <div class="pagination_wrap"> <div class="pagination_total-data pagination_info"> <span>共{{ total }}条数据</span> </div> <div class="pagination_action-group"> <div class="prev pagination_btn" :class="{ disabled: isPrevDisabled }" :disabled="isPrevDisabled" @click="handlePrev" > <a>«上一页</a> </div> <div class="pagination_btn-group" @click="handlePageSelect"> <template v-if="isShowPrevDot"> <li class="pagination_btn"> <a data-page="1">1</a> </li> <li class="pagination_dotted"> <span>...</span> </li> </template> <li class="pagination_btn" :class="{ active: currentPage === i }" v-for="i in pageList" :key="i" > <a :data-page="i">{{ i }}</a> </li> <template v-if="isShowNextDot"> <li class="pagination_dotted"> <span>...</span> </li> <li class="pagination_btn"> <a :data-page="totalPages">{{ totalPages }}</a> </li> </template> </div> <div class="next pagination_btn" :class="{ disabled: isNextDisabled }" :disabled="isNextDisabled" @click="handleNext" > <a>下一页»</a> </div> </div> <div class="pagination_total-pages pagination_info"> <span>共{{ totalPages }}页</span> </div> </div> </div> </template>
样式部分
样式部分(scss),通过 Flex 布局使元素水平排列,并赋予基本样式
// 全局 css 变量 :root { --gap-small: 5px; --gap-primary: 15px; } // 覆盖浏览器默认样式 body { li { box-sizing: border-box; } // 注意添加下面这条样式,否则部分浏览器在 ... 旁边会显示一个额外的 · [class*='dotted']::marker { content: ''; } } .pagination { width: 100%; overflow: hidden; .pagination_wrap { margin: 18px 0; display: flex; justify-content: center; .pagination_action-group { margin-left: 0; margin-bottom: 0; vertical-align: middle; display: flex; .pagination_btn { line-height: 18px; display: inline-block; a { position: relative; line-height: 18px; text-decoration: none; background-color: #fff; border: 1px solid #e0e9ee; font-size: 14px; padding: 9px 18px; color: #333; } &.active { a { background-color: #fff; color: #e1251b; border-color: #fff; cursor: default; } } &.prev, &.next { a { background-color: #fafafa; cursor: pointer; } } &.disabled { a { color: #999; cursor: default; } } } .pagination_dotted { span { position: relative; line-height: 18px; text-decoration: none; background-color: #fff; font-size: 14px; border: 0; padding: 9px 18px; color: #333; } } .pagination_btn-group { display: flex; } } .pagination_info { color: #333; font-size: 14px; } .pagination_total-data { margin-right: var(--gap-primary); } .pagination_total-pages { margin-left: var(--gap-primary); } } }
数据和逻辑
接下里是关键的逻辑实现,包括前进、后退、页码点选跳转、显示 ... 等功能。
数据部分
props 中定义的 4 个变量与数据显示相关,通常从服务端获取,且不支持内部修改,因此由父组件传递进来;data 和 computed 中定义的 8 个变量与视图构建或更新相关,属于组件内私有,仅支持内部修改。
props: { total: Number, // 数据总数 currentPage: Number, // 当前页码数 pagerCount: Number, // 允许同时显示的页码数 totalPages: Number, // 页码总数 }, data() { return { pageList: [], // 中间区域显示的页码范围 start: 0, // 中间区域显示的开始页码指针 end: 0, // 中间区域显示的结束页码指针 isShowPrevDot: false, isShowNextDot: false, }; }, computed: { resourceData() { return { total: this.total, currentPage: this.currentPage }; }, isPrevDisabled() { return this.currentPage === 1; }, isNextDisabled() { return this.currentPage === this.totalPages; }, // 总页数超过当前允许的同时显示页数,才有可能需要显示 ... isDotNeeded() { return this.totalPages > this.pagerCount; }, },
逻辑处理部分
逻辑处理部分包括:
- 两个内部变量更新方法:setupPageList(start, end) 和 setupPagination(currentPage, totalPages, pagerCount)
- 一个 emit 通知方法:updateCurrentPage(newPage)
- 三个事件处理方法:handlePrev() 、handleNext() 、handlePageSelect(e)
- 一个侦听器:侦听 currentPage 并初始化
methods: { // 根据开始和结束指针,重新生成中间区域的页码 setupPageList(start, end) { let pageList = []; for (let i = start; i <= end; i++) { pageList.push(i); } return pageList; }, // 根据操作后的当前页码,重新判定是否显示两侧的 ... ,以及更新中间区域的页码范围 setupPagination(currentPage, totalPages, pagerCount) { // 初始化 start 和 end let start = 1, end = totalPages; if (this.isDotNeeded) { // 从当前页码开始,向左右两侧延伸 pagerCount 一半的距离 let halfOffset= Math.floor(pagerCount / 2, 0); start = Math.max(1, currentPage - halfOffset); end = Math.min(totalPages, currentPage + halfOffset); // 若中间页码数量仍小于 pagerCount,先拉长到 pagerCount if (end - start + 1 < pagerCount) { if (end - pagerCount > -1) { // 左侧仍有剩余页码 start = end - pagerCount + 1; } else { end = start + pagerCount - 1; } } // 根据延伸后的左右指针,判定是否需要显示左右两侧的 ... this.isShowPrevDot = start > 1; this.isShowNextDot = totalPages - end > 0; } else { this.isShowPrevDot = false; this.isShowNextDot = false; } // 根据 ... 的判定结果,赋值两侧指针的最终值 this.start = Math.max(1, start + (this.isShowPrevDot ? 1 : 0)); this.end = Math.min(totalPages, end - (this.isShowNextDot ? 1 : 0)); // 根据最新的指针,生成中间区域的页码 this.pageList = [].concat(this.setupPageList(this.start, this.end)); }, handlePrev() { const oldPage = this.currentPage; const newPage = Math.max(1, oldPage - 1); if (oldPage !== newPage) { this.updateCurrentPage(newPage); // 由于侦听器的存在,不需要手动触发分页器更新;next 与 select 同理 // this.setupPagination(newPage); } }, handleNext() { const oldPage = this.currentPage; const newPage = Math.min(this.totalPages, oldPage + 1); if (oldPage !== newPage) { this.updateCurrentPage(newPage); } }, handlePageSelect(e) { const newPage = +e.target.dataset.page; this.updateCurrentPage(newPage); }, // 通知父组件修改当前页 updateCurrentPage(newPage) { this.$emit('updateCurrentPage', newPage); }, }, // 由于数据从服务端获取,需要在当前组件或作为父组件的业务组件中监听初始数据的变化,并触发分页器的初始化 // 同时,用户改变当前页码时,也会触发分页器更新 watch: { resourceData: { // 若只侦听当前页码变化,则数据源变化但当前页未变时无法自动更新 // 因此,改为侦听一个计算属性,该计算属性包含了需要侦听的多个属性: total 和 currentPage // currentPage: { immediate: true, handler() { this.$nextTick(() => { this.setupPagination( this.currentPage, this.totalPages, this.pagerCount ); }); }, }, },
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!