业务背景:后端是从es查询的数据,每次返回400条,前端自己分页,每次以一个scrollId去查询下一个400条,这里就设计到如何控制分页,到了临界值时最大分页数应该增加所以采用改变total的方式来控制页码,不能直接跳转到最大的页数,只能慢慢向后点,页数递增
效果图:
ESPagination组件代码:
<template>
<div class="pagination-wrapper">
<div v-if="pageLayout.total" class="pager-count">共 {{virtualpagination.total}} 条</div>
<pagination
v-bind="[$props, $attrs]"
v-on="$listeners"
:total="virtualpagination.virTotal"
:pager-count="virtualpagination.pagerCount"
:page.sync="virtualpagination.page"
:limit.sync="virtualpagination.size"
:page-sizes="pageSizes"
:layout="pageLayout.layout"
:disabled="isDisabled"
@pagination="paginationChange" />
</div>
</template>
<script>
export default {
props: {
// 请求数据的函数
getDataFunc: {
type: Function,
required: true
},
// 传入上面的参数
getDataParams: {
type: Object,
default: () => {}
},
// 每次scrollId获取的数据量
eachScrollGetCount: {
type: Number
},
// limit 下拉框数据
pageSizes: {
type: Array,
default: () => [10, 20, 30, 40]
},
// layout 布局
layout: {
type: String,
default: 'total, sizes, prev, pager, next, jumper'
},
// 显示多少个页码
pagerCount: {
type: Number
},
// 最大的请求总数,如当数量超过10000时下一页第10001条请求会报错所以做限制
maxCount: {
type: Number
},
// 是否禁用
disabled: {
type: Boolean
}
},
computed: {
// 当前layout的total作单独处理
pageLayout() {
const layout = this.layout.split(',').map(item => item.trim())
const totalIndx = layout.findIndex(item => item === 'total')
if (totalIndx !== -1) {
layout.splice(totalIndx, 1)
}
return {
layout: layout.join(','),
total: totalIndx !== -1
}
},
// 是否是disabled
isDisabled() {
return this.disabled || this.virtualpagination.loading
}
},
data() {
return {
virtualpagination: {
loading: false,
// curPage
page: 1,
// curLimit
size: this.pageSizes[0] || 10,
// 假分页的传给组件的total值
virTotal: 0,
// 数据返回的真实total,可以用于显示
total: 0,
// 每次scroll请求的数据量,如每次scrollId请求400条前端分页处理
eachScrollGetCount: this.eachScrollGetCount || 400,
// 每次请求的scrollId,第一次请求为'',后面请求为返回的scrollId
scrollId: '',
// 当前分页显示的最大页码数量
pagerCount: this.pagerCount || 7,
// 请求回来的所有tableData数据,往后翻页增量的数据追加在后面,当前页数据从这里面截取
allTableData: [],
// 当前分页呈现的tableData
curPageData: [],
// 分页变动的前一页
prevPage: 1,
// 最大的请求总数,如当数量超过10000时下一页第10001条请求会报错所以做限制
maxCount: this.maxCount
}
}
},
watch: {
// 每次数据变动设置table的data
'virtualpagination.curPageData': function(data) {
this.$emit('setTableData', data)
}
},
methods: {
// 分页切换
paginationChange({ page: curPage, limit }) {
this.pageChangeIsGetData(curPage, limit)
this.setVirTotal(curPage, limit)
},
// 设置假分页total
setVirTotal(curPage, limit) {
const { virtualpagination: { pagerCount, virTotal, maxCount, total: trueTotal }, virtualpagination } = this
// 当前虚拟的页码总数 = 当前总条数 / 每页数
const curPageCount = virTotal / limit
// 分页跨度,如果是7页,那么跨度就是4
const splitNum = Math.ceil(pagerCount / 2)
// 最小值边界,小于这个值时需要维持最小页码数
const minTotalBoundary = limit * pagerCount
// 点击分页后,通过计算tempTotal来控制当前显示的页码数量,tempTotal 为之前的virtualTotal + (两边的跨度页 - 前总页码数与当前页码数的差值)*分页数
let temTotal = virtualpagination.virTotal + limit * (splitNum - (curPageCount - curPage))
// 分页往后走,如从第5页跳到第7页
if (curPageCount - curPage < splitNum) {
// console.log('next')
// 如果当前虚拟总数大于最大边界,以最大边界为准
if (maxCount && temTotal > maxCount) {
virtualpagination.virTotal = maxCount
} else if (temTotal > trueTotal) {
// 当前虚拟的页大于真实total时 以真实total为准
virtualpagination.virTotal = trueTotal
} else {
virtualpagination.virTotal = temTotal
}
}
// 分页往前走,如从第7页跳到第5页
if (curPageCount - curPage > splitNum) {
// console.log('prev')
// 如果当前页数>5,往前走到小于5的位置,按照上面直接赋值,页面的页码会减小为4,所以做限制
if (virtualpagination.virTotal >= minTotalBoundary && temTotal < minTotalBoundary) {
virtualpagination.virTotal = minTotalBoundary
} else {
virtualpagination.virTotal = temTotal
}
}
// 当limit change时如从10切为40,维持页码数量
if (trueTotal > minTotalBoundary && virtualpagination.virTotal < minTotalBoundary) {
virtualpagination.virTotal = minTotalBoundary
}
},
// 每次分页改变的时候判断是否去请求数据
async pageChangeIsGetData(curPage, limit) {
const { virtualpagination: { allTableData, prevPage, eachScrollGetCount }, virtualpagination } = this
// 根据每次请求的scroll固定数据量大小计算每次请求边界 400, 40页,limit:10
const eachScrollCountMaxPage = Math.floor(eachScrollGetCount / limit)
const rangeEnd = eachScrollCountMaxPage * Math.ceil(prevPage / eachScrollCountMaxPage)
// 目前把数据存入前端就不需要跳转到前面的也去请求数据了, 并且如果是已经请求到很后面了,然后突然回到前面再到临界点时
// 不应该再去请求数据(eg:curPage: 40, 现在跳到第1页或者往前走,30|31为临界点,到了30页,我再往后走点击31页这个临界值是不应该再次去请求数据的)
if (curPage > rangeEnd && curPage > prevPage && curPage >= Math.ceil(allTableData.length / limit)) {
// 往后走的分页,需要请求数据
await this.getTableList()
} else {
// 往前或者在范围内的直接截取数据不用请求
virtualpagination.curPageData = allTableData.slice((curPage - 1) * limit, curPage * limit)
}
// 将现在的的page赋予prevPage
virtualpagination.prevPage = curPage
},
// 请求数据后设置表格数据和total值
setTableData(data, condition) {
const { virtualpagination: { allTableData, page: curPage, size: limit, pagerCount, virTotal }, virtualpagination } = this
// 根据condition 判断是否直接清空数据赋值
if (condition) {
// 重新赋值
virtualpagination.allTableData = data.data
} else {
// 追加在所有数据后面
virtualpagination.allTableData.push(...data.data)
}
// scrollId 赋值(其实每次的scrollId都是一样的)
virtualpagination.scrollId = data.scrollId
// 当前页数据
virtualpagination.curPageData = allTableData.slice((curPage - 1) * limit, curPage * limit)
// 真实分页总数
const trueTotal = data.totalCounts
virtualpagination.total = trueTotal
if (virTotal === 0 || trueTotal < virTotal) {
// 第一次初始化的时候
// 初始值为默认limit*pagerCount
virtualpagination.virTotal = trueTotal <= pagerCount * limit ? trueTotal : pagerCount * limit
}
},
// 获取表格信息
async getTableList() {
// 设置loading
this.$emit('setLoading', true)
this.virtualpagination.loading = true
try {
const { data } = await this.getDataFunc({
scrollId: this.virtualpagination.scrollId,
pageSize: this.virtualpagination.eachScrollGetCount,
...this.getDataParams
})
// 设置数据
this.setTableData(data)
} catch (err) {
console.log(err)
}
// 设置loading
this.$emit('setLoading', false)
this.virtualpagination.loading = false
}
},
mounted() {
this.getTableList()
}
}
</script>
<style lang="scss" scoped>
.pagination-wrapper {
display: flex;
justify-content: flex-end;
align-items: center;
}
</style>
使用举例:
<template>
<div class="box">
<el-table
class="detail-table"
:data="tableData"
v-loading="loading"
height="380">
<el-table-column
v-for="column in tableColumns"
:key="column.prop"
show-overflow-tooltip
:prop="column.prop"
:label="column.label" />
</el-table>
<es-pagination
style="padding-top: 20px;"
:getDataFunc="getTaskPageInfo"
:getDataParams="params"
@setTableData="setTableData"
@setLoading="setLoading" />
</div>
</template>
<script>
import { getTaskPageInfo } from '@/api/modules/distributeCenter.js'
export default {
data() {
return {
loading: false,
tableData: [],
getTaskPageInfo,
// 请求的带上参数
params: {
taskId: this.$route.query.id,
keyword: this.searchWord
},
tableColumns: [
{
prop: 'aaa',
label: '企业aa'
},
{
prop: 'bbb',
label: '客户bb'
}
]
}
},
methods: {
// 更新table数据
setTableData(data) {
this.tableData = data
},
// 请求数据的loading
setLoading(loading) {
this.loading = loading
}
}
}
</script>
<style lang="scss" scoped>
.detail-table /deep/ .el-table__header .cell{
white-space:nowrap!important
}
</style>
上面是es数据集群的数据,现在又遇到一个不一样的需求,当前数据又三四十万左右,但是接口分页只能满足查前面 1 万条数据,超过了 1 万就会报错,所以还是采用上面的不能跳转的最大页数,分页页码一点点往后递增,也能支持数据量<1 万时正常分页
PaginationRange 组件:
<template>
<div class="pagination-wrapper">
<div v-if="pageLayout.total" class="pager-count">共 {{total}} 条</div>
<pagination
v-bind="[$props, $attrs]"
v-on="$listeners"
:total="virtualpagination.virTotal"
:pager-count="virtualpagination.pagerCount"
:page.sync="virtualpagination.page"
:limit.sync="virtualpagination.size"
:page-sizes="pageSizes"
:layout="pageLayout.layout"
@pagination="paginationChange" />
</div>
</template>
<script>
export default {
props: {
total: {
type: Number,
default: 0
},
// limit 下拉框数据
pageSizes: {
type: Array,
default: () => [10, 20, 30, 40]
},
// layout 布局
layout: {
type: String,
default: 'total, sizes, prev, pager, next, jumper'
},
// 显示多少个页码
pagerCount: {
type: Number
},
// 最大的请求总数,如当数量超过10000时下一页第10001条请求会报错所以做限制
maxCount: {
type: Number
}
},
computed: {
// 当前layout的total作单独处理
pageLayout: function() {
const layout = this.layout.split(',').map(item => item.trim())
const totalIndx = layout.findIndex(item => item === 'total')
if (totalIndx !== -1) {
layout.splice(totalIndx, 1)
}
return {
layout: layout.join(','),
total: totalIndx !== -1
}
}
},
watch: {
total(val) {
this.virtualpagination.total = this.total
this.setTotal()
}
},
data() {
return {
virtualpagination: {
// curPage
page: 1,
// curLimit
size: this.pageSizes[0] || 10,
// 假分页total
virTotal: 0,
// 真实total
total: this.total || 0,
// 当前分页显示的最大页码数量
pagerCount: this.pagerCount || 7,
// 最大的请求总数,如当数量超过10000时下一页第10001条请求会报错所以做限制
maxCount: this.maxCount
}
}
},
methods: {
paginationChange({ page: curPage, limit }) {
this.setVirTotal(curPage, limit)
this.$emit('pagination', { page: curPage, limit })
},
// 设置假分页total
setVirTotal(curPage, limit) {
const { virtualpagination: { pagerCount, virTotal, maxCount, total: trueTotal }, virtualpagination } = this
// 当前页码总数 = 当前总条数 / 每页数
const curPageCount = virTotal / limit
// 分页跨度,如果是5,那么跨度就是3
const splitNum = Math.ceil(pagerCount / 2)
// 最小值边界,小于这个值时需要维持最小页码数
const minTotalBoundary = limit * pagerCount
// 点击分页后,通过计算tempTotal来控制当前显示的页码数量,tempTotal 为之前的virtualTotal + (两边的跨度页 - 前总页码数与当前页码数的差值)*分页数
let temTotal = virtualpagination.virTotal + limit * (splitNum - (curPageCount - curPage))
// 分页往后走,如从第5页跳到第7页
if (curPageCount - curPage < splitNum) {
// console.log('next')
// 如果当前虚拟总数大于最大边界,以最大边界为准
if (maxCount && temTotal > maxCount) {
virtualpagination.virTotal = maxCount
} else if (temTotal > trueTotal) {
// 当前虚拟的页大于真实total时 以真实total为准
virtualpagination.virTotal = trueTotal
} else {
virtualpagination.virTotal = temTotal
}
}
// 分页往前走,如从第7页跳到第5页
if (curPageCount - curPage > splitNum) {
// console.log('prev')
// 如果当前页数>5,往前走到小于5的位置,按照上面直接赋值,页面的页码会减小为4,所以做限制
if (virtualpagination.virTotal >= minTotalBoundary && temTotal < minTotalBoundary) {
virtualpagination.virTotal = minTotalBoundary
} else {
virtualpagination.virTotal = temTotal
}
}
// 当limit change时如从10切为40,维持页码数量
if (trueTotal > minTotalBoundary && virtualpagination.virTotal < minTotalBoundary) {
virtualpagination.virTotal = minTotalBoundary
}
},
// 数据请求回来后设置total
setTotal() {
const { virtualpagination: { virTotal, pagerCount, size, total }, virtualpagination } = this
virtualpagination.total = total
if (virTotal === 0 || total < virTotal) {
// 第一次初始化的时候
// 初始值为默认limit*pagerCount
virtualpagination.virTotal = total <= pagerCount * size ? total : pagerCount * size
}
}
}
}
</script>
<style lang="scss" scoped>
.pagination-wrapper {
display: flex;
justify-content: flex-end;
align-items: center;
}
</style>
使用举例:
<template>
<div class="box">
<el-table :data="tableData">...</el-table>
<pagination-range
:total="virtualpagination.total"
:maxCount="virtualpagination.maxCount"
@pagination="paginationChange" />
</div>
</template>
<script>
import { getTableList } from '@/api/modules/aa.js'
export default {
data() {
return {
virtualpagination: {
page: 1,
size: 10,
// 真实total
total: 0,
// 能够翻页的最大数据范围
maxCount: 10000
},
tableData: []
}
},
methods: {
// 分页事件
paginationChange({ page, limit }) {
this.virtualpagination.page = page
this.virtualpagination.size = limit
// 参数是直接在 getList 里面写的,所以把page和size放到virtualpagination上
this.getData()
},
// 获取table列表数据
async getData() {
try {
const { data } = await getTableList()
// 设置当前tableData
this.tableData = data.data
// 设置真实total并传给分页组件
this.virtualpagination.total = data.totalCounts
} catch (err) {
console.log(err)
}
}
},
mounted() {
this.getData()
}
}
</script>