297 lines
8.2 KiB
Vue
297 lines
8.2 KiB
Vue
<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>
|