nankai-cms-website-gov/components/common/Pagination.vue
2025-03-13 10:14:57 +08:00

297 lines
8.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div v-if="pageCount > 1" class="pagination">
<div class="pagination-content">
<!-- 总数 -->
<span class="pagination-info">共 <em>{{ modelValue.total }}</em> 条</span>
<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>
<!-- 跳转 -->
<div class="pagination-info pagination-jump">
前往
<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}`
}
},
// 最多一次性展示多少个页码
limitPageCount: {
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>