2025-03-13 09:52:18 +08:00
|
|
|
|
<template>
|
|
|
|
|
<div v-if="pageCount > 1" class="pagination">
|
|
|
|
|
<div class="pagination-content">
|
|
|
|
|
<!-- 总数 -->
|
2025-03-13 10:14:57 +08:00
|
|
|
|
<span class="pagination-info">共 <em>{{ modelValue.total }}</em> 条</span>
|
2025-03-13 09:52:18 +08:00
|
|
|
|
<ul>
|
|
|
|
|
<!-- 上一页 -->
|
|
|
|
|
<li :class="{ disabled: modelValue.page <= 1 }">
|
|
|
|
|
<el-icon v-if="modelValue.page <= 1"><ArrowLeft/></el-icon>
|
|
|
|
|
<nuxt-link v-else :to="getPageUrl(modelValue.page - 1)">
|
|
|
|
|
<el-icon><ArrowLeft/></el-icon>
|
|
|
|
|
</nuxt-link>
|
|
|
|
|
</li>
|
|
|
|
|
<!-- 前置分页 -->
|
|
|
|
|
<li
|
|
|
|
|
v-for="pageIndex in prevPages"
|
|
|
|
|
:key="pageIndex"
|
|
|
|
|
:class="{ selected: pageIndex === modelValue.page }"
|
|
|
|
|
>
|
|
|
|
|
<nuxt-link :to="getPageUrl(pageIndex)">{{ pageIndex }}</nuxt-link>
|
|
|
|
|
</li>
|
|
|
|
|
<!-- 前置翻页器:存在中间页码 || 完全展示了后置页码 -->
|
|
|
|
|
<li v-if="centerPages.length > 0 || afterPages.length === limitPageCount" class="page-control">
|
|
|
|
|
<nuxt-link :to="getPageUrl(modelValue.page-limitPageCount < 1 ? 1 : modelValue.page-limitPageCount)">
|
|
|
|
|
<span>...</span>
|
|
|
|
|
<el-icon><DArrowLeft /></el-icon>
|
|
|
|
|
</nuxt-link>
|
|
|
|
|
</li>
|
|
|
|
|
<!-- 中间分页 -->
|
|
|
|
|
<li
|
|
|
|
|
v-for="pageIndex in centerPages"
|
|
|
|
|
:key="pageIndex"
|
|
|
|
|
:class="{ selected: pageIndex === modelValue.page }"
|
|
|
|
|
>
|
|
|
|
|
<nuxt-link :to="getPageUrl(pageIndex)">{{ pageIndex }}</nuxt-link>
|
|
|
|
|
</li>
|
|
|
|
|
<!-- 后置翻页器:存在中间页码 || 完全展示了前面的页码但总页数要大于前方展示的页码数 -->
|
|
|
|
|
<li v-if="centerPages.length > 0 || (prevPages.length === limitPageCount && pageCount > limitPageCount)" class="page-control">
|
|
|
|
|
<nuxt-link :to="getPageUrl(modelValue.page+limitPageCount > pageCount ? pageCount : modelValue.page+limitPageCount)">
|
|
|
|
|
<span>...</span>
|
|
|
|
|
<el-icon><DArrowRight /></el-icon>
|
|
|
|
|
</nuxt-link>
|
|
|
|
|
</li>
|
|
|
|
|
<!-- 后置分页 -->
|
|
|
|
|
<li
|
|
|
|
|
v-for="pageIndex in afterPages"
|
|
|
|
|
:key="pageIndex"
|
|
|
|
|
:class="{ selected: pageIndex === modelValue.page }"
|
|
|
|
|
>
|
|
|
|
|
<nuxt-link :to="getPageUrl(pageIndex)">{{ pageIndex }}</nuxt-link>
|
|
|
|
|
</li>
|
|
|
|
|
<!-- 下一页 -->
|
|
|
|
|
<li :class="{ disabled: modelValue.page >= pageCount }">
|
|
|
|
|
<el-icon v-if="modelValue.page >= pageCount"><ArrowRight /></el-icon>
|
|
|
|
|
<nuxt-link v-else :to="getPageUrl(modelValue.page + 1)">
|
|
|
|
|
<el-icon><ArrowRight/></el-icon>
|
|
|
|
|
</nuxt-link>
|
|
|
|
|
</li>
|
|
|
|
|
</ul>
|
|
|
|
|
<!-- 跳转 -->
|
2025-03-13 10:14:57 +08:00
|
|
|
|
<div class="pagination-info pagination-jump">
|
2025-03-13 09:52:18 +08:00
|
|
|
|
前往
|
|
|
|
|
<el-input-number
|
|
|
|
|
v-model="targetPage"
|
|
|
|
|
:controls="false"
|
|
|
|
|
:max="pageCount"
|
|
|
|
|
@keydown.enter="jump"
|
|
|
|
|
/>
|
|
|
|
|
页
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
import { ArrowLeft, DArrowLeft, ArrowRight, DArrowRight } from '@element-plus/icons-vue'
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
name: 'Pagination',
|
|
|
|
|
components: { ArrowLeft, DArrowLeft, ArrowRight, DArrowRight },
|
|
|
|
|
emits: ['update:modelValue', 'page-change'],
|
|
|
|
|
props: {
|
|
|
|
|
// 分页值,e.g: { page: 1, capacity: 10, total: 100 }
|
|
|
|
|
modelValue: {
|
|
|
|
|
type: Object,
|
|
|
|
|
required: true,
|
|
|
|
|
default: function () {
|
|
|
|
|
return {}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
// 获取页码链接函数
|
|
|
|
|
getPageUrl: {
|
|
|
|
|
type: Function,
|
|
|
|
|
default: function (page) {
|
|
|
|
|
return `?page=${page}`
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
// 最多一次性展示多少个页码
|
2025-03-13 10:14:57 +08:00
|
|
|
|
limitPageCount: {
|
2025-03-13 09:52:18 +08:00
|
|
|
|
type: Number,
|
|
|
|
|
default: 5
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
data () {
|
|
|
|
|
return {
|
|
|
|
|
targetPage: 1,
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
computed: {
|
|
|
|
|
// 最大同时展示的页码数量,其中4 = 首页 + 页面控制按钮 + 尾页 + 页面控制按钮
|
|
|
|
|
maxPageCount () {
|
|
|
|
|
return this.limitPageCount + 4
|
|
|
|
|
},
|
|
|
|
|
// 总页数
|
|
|
|
|
pageCount () {
|
|
|
|
|
return Math.ceil(this.modelValue.total / this.modelValue.capacity)
|
|
|
|
|
},
|
|
|
|
|
// 中间数
|
|
|
|
|
limitPageCountHalf () {
|
|
|
|
|
return Math.floor(this.limitPageCount / 2)
|
|
|
|
|
},
|
|
|
|
|
// 前置分页
|
|
|
|
|
prevPages () {
|
|
|
|
|
// 总页不超过maxPageCount,全部展示
|
|
|
|
|
if (this.pageCount <= this.maxPageCount) {
|
|
|
|
|
return new Array(this.pageCount)
|
|
|
|
|
.fill(0)
|
|
|
|
|
.map((item, index) => index + 1)
|
|
|
|
|
}
|
|
|
|
|
// 当前页码在头部(即当前页码 <= limitPageCount)
|
|
|
|
|
if (this.modelValue.page < this.limitPageCount) {
|
|
|
|
|
return new Array(this.limitPageCount)
|
|
|
|
|
.fill(0)
|
|
|
|
|
.map((item, index) => index + 1)
|
|
|
|
|
}
|
|
|
|
|
return [1]
|
|
|
|
|
},
|
|
|
|
|
// 中间分页,展示5条
|
|
|
|
|
centerPages () {
|
|
|
|
|
if (this.pageCount <= this.maxPageCount) {
|
|
|
|
|
return []
|
|
|
|
|
}
|
|
|
|
|
// 当前页码不在头和尾部(即当前页码 > limitPageCount && 当前页码 < pageCount - limitPageCount)
|
|
|
|
|
if (this.modelValue.page >= this.limitPageCount && this.modelValue.page <= this.pageCount - this.limitPageCount + 1) {
|
|
|
|
|
const pages = []
|
|
|
|
|
for (let i = 0; i < this.limitPageCount; i++) {
|
|
|
|
|
// 中间页面
|
|
|
|
|
if (i === this.limitPageCountHalf) {
|
|
|
|
|
pages.push(this.modelValue.page)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
// 左右两侧页码
|
|
|
|
|
pages.push(this.modelValue.page - this.limitPageCountHalf + i)
|
|
|
|
|
}
|
|
|
|
|
return pages
|
|
|
|
|
}
|
|
|
|
|
return []
|
|
|
|
|
},
|
|
|
|
|
// 后置分页
|
|
|
|
|
afterPages () {
|
|
|
|
|
if (this.pageCount <= this.maxPageCount) {
|
|
|
|
|
return []
|
|
|
|
|
}
|
|
|
|
|
// 当前页码在尾部(当前页码 >= pageCount - limitPageCount + 1)
|
|
|
|
|
if (this.modelValue.page > this.pageCount - this.limitPageCount + 1) {
|
|
|
|
|
return new Array(this.limitPageCount)
|
|
|
|
|
.fill(0)
|
|
|
|
|
.map((item, index) => {
|
|
|
|
|
return this.pageCount - this.limitPageCount + index + 1
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return [this.pageCount]
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
watch: {
|
|
|
|
|
// 监听分页数据发生变化,刷新目标页码
|
|
|
|
|
'modelValue.page': {
|
|
|
|
|
immediate: true,
|
|
|
|
|
handler (newValue) {
|
|
|
|
|
if (this.targetPage !== newValue) {
|
|
|
|
|
this.targetPage = newValue
|
|
|
|
|
this.$router.replace(this.getPageUrl(newValue))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
methods: {
|
|
|
|
|
// 跳转指定页
|
|
|
|
|
jump () {
|
|
|
|
|
if (this.targetPage < 1) {
|
|
|
|
|
this.targetPage = 1
|
|
|
|
|
}
|
|
|
|
|
this.$router.push(this.getPageUrl(this.targetPage))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
|
|
.pagination {
|
|
|
|
|
--page-item-size: 35px;
|
|
|
|
|
width: 100%;
|
|
|
|
|
text-align: center;
|
|
|
|
|
.pagination-content {
|
|
|
|
|
width: 800px;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
margin: 0 auto;
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
align-items: center;
|
|
|
|
|
}
|
|
|
|
|
// 页码列表
|
|
|
|
|
ul {
|
|
|
|
|
display: flex;
|
|
|
|
|
margin: 0 30px;
|
|
|
|
|
li {
|
|
|
|
|
width: var(--page-item-size);
|
|
|
|
|
height: var(--page-item-size);
|
|
|
|
|
background: var(--background-color);
|
|
|
|
|
margin-right: 3px;
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
align-items: center;
|
|
|
|
|
a {
|
|
|
|
|
display: block;
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
line-height: var(--page-item-size);
|
|
|
|
|
text-align: center;
|
|
|
|
|
color: #333;
|
|
|
|
|
&:hover {
|
|
|
|
|
color: var(--primary-color);
|
|
|
|
|
}
|
|
|
|
|
.el-icon {
|
|
|
|
|
position: relative;
|
|
|
|
|
top: 2px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 选中状态
|
|
|
|
|
&.selected {
|
|
|
|
|
a {
|
|
|
|
|
color: var(--color-white);
|
|
|
|
|
background: var(--primary-color);
|
|
|
|
|
&:hover {
|
|
|
|
|
color: var(--color-white);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 禁用状态
|
|
|
|
|
&.disabled {
|
|
|
|
|
color: var(--color-gray);
|
|
|
|
|
cursor: not-allowed;
|
|
|
|
|
}
|
|
|
|
|
// 页面控制
|
|
|
|
|
&.page-control {
|
|
|
|
|
a {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
align-items: center;
|
|
|
|
|
}
|
|
|
|
|
.el-icon {
|
|
|
|
|
display: none;
|
|
|
|
|
position: relative;
|
|
|
|
|
top: 0;
|
|
|
|
|
}
|
|
|
|
|
&:hover {
|
|
|
|
|
span {
|
|
|
|
|
display: none;
|
|
|
|
|
}
|
|
|
|
|
.el-icon {
|
|
|
|
|
display: block;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 附加信息
|
|
|
|
|
.pagination-info {
|
|
|
|
|
color: var(--color-gray);
|
|
|
|
|
em {
|
|
|
|
|
color: var(--font-color);
|
|
|
|
|
font-style: normal;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 跳转
|
|
|
|
|
.pagination-jump {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
.el-input-number {
|
|
|
|
|
width: 70px;
|
|
|
|
|
text-align: center;
|
|
|
|
|
margin: 0 10px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|