需求分析#
- 自定义显示的页码按钮的数量
- 自定义分页组件的颜色主题
- 可以选择显示文本内容或者图标来进行翻页操作
实现#
- 分页组件需要显示的页码按钮数量,可以通过 pagesToDisplay () 这个计算属性定义
- 颜色主题可以通过绑定一个
paginationClass
的 class,返回颜色type
实现 - 文本或图标的选择可以通过嵌套条件判断 template 实现
分页原理#
实现分页主要依靠这两个参数,total (总条目数),perPage (每页显示的条目数量)。后端可以通过这两个参数,返回相应的数据给前端。
整个分页组件中,相对来说比较麻烦的地方在于页码列表的显示逻辑。页码列表是一个返回 [最小页码,最大页码] 的数组。进而,问题被切分为如何求解页码列表数组中页码值的最大值和最小值。这里定义两个计算属性 maxPage (),minPage () 来返回页码数组中页码值的最大值,和最小值。
页面组件#
<template>
<ul class="pagination" :class="paginationClass">
<!-- 前一页 -->
<li
class="page-item prev-page"
:class="{ disabled: value === 1,'no-arrows': noArrows }"
>
<a class="page-link" arial-label="Previous" @click="prevPage">
<!-- 是否有文本 -->
<template v-if="withText">
perv
</template>
<!-- 箭头图标 -->
<i class="arrow-left" v-else></i>
</a>
</li>
<!-- 当前显示的分页列表 -->
<li
class="page-item"
v-for="item in range(minPage, maxPage)"
:key="item"
:class="{active: value === item}"
>
<a class="page-link" @click="changePage(item)">{{item}}</a>
</li>
<!-- 后一页 -->
<li
class="paga-item next-page"
:class="{disabled: value === totalPage, 'no-arrows': noArrows}"
>
<a class="page-link" arial-label="Next" @click="nextPage">
<!-- 文本内容 -->
<template v-if="withText">
Next
</template>
<i class="arrow-right" v-else></i>
</a>
</li>
</ul>
</template>
props#
props: {
// 颜色主题
type: {
type: String,
default: "primary",
// 验证`type`的值
validator: value => {
return [
"default",
"primary",
"info",
"success",
"danger"
].includes(value);
}
},
// 是否带有文本内容
withText: Boolean,
// 箭头图标
noArrows: Boolean,
// 页码数
pageCount: {
type: Number,
default: 0
},
// 每页显示的项数
perPage: {
type: Number,
default: 0
},
// 总项数
total: {
type: Number,
default: 0
},
// 当前页码的值
value: {
type: Number,
default: 1
}
}
data#
data: {
// 页码列表中显示的页码数量
defaultPagesToDisplay: 5
}
computed#
computed: {
// 颜色主题
paginationClass() {
return `pagination-${this.type}`
},
// 总页码数
toatalPages() {
if (this.pageCount > 0) return this.pageCount;
if (this.total > 0 ) {
return Math.ceil(this.total / this.perPage);
}
},
// 显示的页码按钮数量
pagesToDisplay() {
if (this.totalPages > 0 && this.totalPages < this.defaultPagesToDisplay) {
return this.totalPages;
} else {
return this.defaultPagesToDisplay;
}
},
// 页码列表中最小的页码数
minPage() {
if (this.value >= this.pagesToDisplay) {
// 定义一个偏移量
const pagesToAdd = Math.floor(this.pagesToDisplay / 2);
// 假定的最大页码数 = 当前页码值 + 偏移量
const newMaxPage = this.value + pagesToAdd;
if (newMaxPage > this.totalPages) {
return totalPages - this.pagesToDisplay + 1;
} else {
// 当前页码值始位处于页码列表的中间位置
return this.value - pagesToAdd;
}
} else {
// 当前页码值小于默认页码列表按钮数量时,最小页码数返回1
return 1;
}
},
// 页码列表中最大的页码数
maxPage() {
if (this.value >= this.pagesToDisplay) {
const pagesToAdd = Math.floor(this.pagesToDisplay / 2);
const newMaxPage = this.value + pagesToAdd;
if (newMaxPage < this.totalPages) {
return newMaxPage;
} else {
return totalPages;
}
} else {
// 当前页码值小于默认页码列表按钮数量时,最大页码数返回显示的页码列表按钮数
return this.pageToDisplay;
}
}
}
methods#
methods: {
// 页码列表数组
range(min, max) {
let arr =[];
for (let i = min; i <= max; i++) {
arr.push(i);
}
return arr;
},
// 跳转页码
changePage(item) {
this.$emit("input", item);
},
// 下一页
nextPage() {
if (this.value < this.totalPages) {
this.$emit("input", this.value + 1);
}
},
// 上一页
prevPage() {
if (this.value > 1) {
this.$emit("input", this.value - 1);
}
}
}
最后#
想要不断提高自己的技术水平,就不能只满足于写业务组件。在写像分页这种独立组件的过程中,也让我思考API的设计
,以及功能复杂性
的问题。无论是多么复杂的组件,大体上都是由 prop,event,slot 三部分组成。具体的实现,主要还是依靠基础的 javascript 的能力。
此前,我也仿照 element 写了 radio 和 input 组件。希望自己能坚持去写这些独立组件,去发现更大的世界。本文的分页组件源码在这儿。