Vue3手写分页器
在学习电商项目时,有一个手写分页器的功能模块,但是使用Vue2实现的,所以用Vue3尝试着写了一个。
首先,有四个点要了解
1)分页器展示,需要哪些数据(条件)?
-
当前是第几个: pageNo字段表示当前页数
-
每一页需要展示多少条数据:pageSize字段
-
整个分页器一共有多少条数据: total字段 —— 【一共多少页: total/pageSize,向上取整】
-
分页器连续页码个数,一般是5或者7: continues字段
2)自定义分页器,在开发的时候先自己传递假的数据进行调试,调试成功再用服务器数据
3)对于分页器而言,很重要的一个地方是 【算出:连续页码起始数字和结束数字】
当前如果是第8页且连续页码数为5,则为 6 7 8 9 10。但还需要兼顾到特殊情况(如起始页不能小于1,结束页不能超过总页数,以及连续页码数必须要小于总页数),否则就要特别的赋值
4)分页器动态展示
v-for: 可以遍历 数组|数字|字符串|对象
然后开始正式写代码。分页器的组件名为PaginationIndex,在搜索页SearchIndex使用该组件。
静态页面
首先,找一个分页器的静态界面,先把它展示出来,之后再做各功能的添加
<template>
<div class="pagination">
<button>1</button>
<button>上一页</button>
<button>···</button>
<button>3</button>
<button>4</button>
<button>5</button>
<button>6</button>
<button>7</button>
<button>···</button>
<button>9</button>
<button>上一页</button>
<button style="margin-left: 30px">共 60 条</button>
</div>
</template>
<script>
export default {
name: "PaginationIndex",
}
</script>
<style lang="less" scoped>
.pagination {
button {
margin: 0 5px;
background-color: #f4f4f5;
color: #606266;
outline: none;
border-radius: 2px;
padding: 0 4px;
vertical-align: top;
display: inline-block;
font-size: 13px;
min-width: 35.5px;
height: 28px;
line-height: 28px;
cursor: pointer;
box-sizing: border-box;
text-align: center;
border: 0;
&[disabled] {
color: #c0c4cc;
cursor: not-allowed;
}
&.active {
cursor: not-allowed;
background-color: #409eff;
color: #fff;
}
}
}
</style>
因为数据,页数都是自己写死的,所以接下来需要获得我们真正所需要的数据(需要从父组件中传过来)
SearchIndex中使用该组件时,传递相应的值
<!-- 分页器 测试分页器阶段-->
<PaginationIndex
:pageNo="1"
:pageSize="10"
:total="91"
:continues="5"
/>
父元素给子元素传递数据,这里采用props接收。 子组件中
export default {
name: "PaginationIndex",
props: ["pageNo", "pageSize", "total", "continues"],
分页器起始与结束——计算属性
同时,应该根据pageNo(当前所在页),计算出它的前后连续页(这里因为continues=5,也就是前后连续5页)。这里使用计算属性,分别用了两种方法。
这里的计算属性因为自己用的不是很熟练,可能写的有点繁琐或者不太对的地方,有问题的地方大佬看到了可以教教我。
setup(props, context) {
const PageInfo = reactive({});
// 总共多少页
(PageInfo.totalPage = computed({
get() {
return Math.ceil(props.total / props.pageSize);
},
})),
// 计算出连续的页码的起始数字和结束数字【最少5页】
(PageInfo.startNumAndEndNum = computed(() => {
const { pageNo, pageSize, total, continues } = props;
// 定义两个变量,存储起始与结束 数字
let start = 0;
let end = 0;
// 连续页码数字5 【至少为5页】,如果没有5页
if (continues > PageInfo.totalPage) {
start = 1;
end = PageInfo.totalPage;
} else {
// 总页数 > 连续页数
start = pageNo - Math.floor(continues / 2);
end = start + continues - 1;
// 把不正常的现象纠正,比如start小于1,或者end超过total了
if (start < 1) {
start = 1;
end = continues;
}
if (end > PageInfo.totalPage) {
end = PageInfo.totalPage;
start = PageInfo.totalPage - continues + 1;
}
}
return { start, end };
}));
计算连续页数这里要考虑算法健壮性,比如连续页码数比总页数还大的情况,或者通过计算后,连续起始页小于1,连续结束页大于总页数。 这里返回以对象形式返回
不仅如此,还要考虑到如果起始页为1,上一页功能应该禁用,结束页到底了,下一页按钮也要禁用。还有如果起始页为2,1后面的省略号就不需要了。
DOM结构的代码如下:
<template>
<div class="pagination">
<button :disabled="pageNo == 1">
上一页
</button>
<button
v-show="PageInfo.startNumAndEndNum.start > 1"
>
1
</button>
<button v-if="PageInfo.startNumAndEndNum.start > 2">···</button>
<button
v-for="(page, index) in continues"
:key="index"
>
{{ page + PageInfo.startNumAndEndNum.start - 1 }}
</button>
<button v-if="PageInfo.startNumAndEndNum.end < PageInfo.totalPage - 1">
···
</button>
<button
v-show="PageInfo.startNumAndEndNum.end < PageInfo.totalPage"
>
{{ PageInfo.totalPage }}
</button>
<button :disabled="pageNo == PageInfo.totalPage">下一页</button>
<button style="margin-left: 30px">共 {{ total }} 条</button>
</div>
</template>
这里和vue2里有个小区别就是,vue2中之前的代码里 v-for循环展示中间连续页时,v-for和v-if一起用的。v-for可以循环遍历数字,所以vue2里面是v-for的结束页,假设结束页是10,也就是连续页码为6 7 8 9 10,v-for="(page,index) in PageInfo.startNumAndEndNum.end",这里就会遍历10次,vue2中的做法是,加上v-if判断,只保留后面的5次。但是vue3里面v-for和v-if没法放一起用。所以我的想法是,要么外面包一层div,给外层div加上v-if。我这就是采用v-for 连续页数。也就是只遍历5次,page就是从1-5,然后展示的时候只需要page + 起始页 - 1即为对应的连续页。
分页器动态展示
将分页器按照需求改好后,页面部分就算是差不多了,接下来需要点击相应的页码,进行相应数据的改动以及重新发送请求。这里就涉及到子组件给父组件传递数据,所以采用自定义事件。
vue3中自定义事件相关的记录写在了下面这个博客里
https://blog.csdn.net/m0_56698268/article/details/126578810
中间等等过程就先不写了。最后放上分页器组件的代码
PaginationIndex.vue
<template>
<div class="pagination">
<button :disabled="pageNo == 1" @click="getPageNo(pageNo - 1)">
上一页
</button>
<button
v-show="PageInfo.startNumAndEndNum.start > 1"
@click="getPageNo(1)"
:class="{ active: pageNo == 1 }"
>
1
</button>
<button v-if="PageInfo.startNumAndEndNum.start > 2">···</button>
<button
v-for="(page, index) in continues"
:key="index"
@click="getPageNo(page + PageInfo.startNumAndEndNum.start - 1)"
:class="{ active: pageNo == page + PageInfo.startNumAndEndNum.start - 1 }"
>
{{ page + PageInfo.startNumAndEndNum.start - 1 }}
</button>
<button v-if="PageInfo.startNumAndEndNum.end < PageInfo.totalPage - 1">
···
</button>
<button
v-show="PageInfo.startNumAndEndNum.end < PageInfo.totalPage"
@click="getPageNo(PageInfo.totalPage)"
:class="{ active: pageNo == PageInfo.totalPage }"
>
{{ PageInfo.totalPage }}
</button>
<button :disabled="pageNo == PageInfo.totalPage">下一页</button>
<button style="margin-left: 30px">共 {{ total }} 条</button>
</div>
</template>
<script>
import { computed, reactive } from "vue";
export default {
name: "PaginationIndex",
props: ["pageNo", "pageSize", "total", "continues"],
emits: ["getPageNo"],
setup(props, context) {
const PageInfo = reactive({});
// 总共多少页
(PageInfo.totalPage = computed({
get() {
return Math.ceil(props.total / props.pageSize);
},
})),
// 计算出连续的页码的起始数字和结束数字【最少5页】
(PageInfo.startNumAndEndNum = computed(() => {
const { pageNo, pageSize, total, continues } = props;
// 定义两个变量,存储起始与结束 数字
let start = 0;
let end = 0;
// 连续页码数字5 【至少为5页】,如果没有5页
if (continues > PageInfo.totalPage) {
start = 1;
end = PageInfo.totalPage;
} else {
// 总页数 > 连续页数
start = pageNo - Math.floor(continues / 2);
end = start + continues - 1;
// 把不正常的现象纠正,比如start小于1,或者end超过total了
if (start < 1) {
start = 1;
end = continues;
}
if (end > PageInfo.totalPage) {
end = PageInfo.totalPage;
start = PageInfo.totalPage - continues + 1;
}
}
return { start, end };
}));
function getPageNo(pageNo) {
context.emit("getPageNo", pageNo);
}
return {
PageInfo,
getPageNo,
};
},
};
</script>
<style lang="less" scoped>
.pagination {
text-align: center;
button {
margin: 0 5px;
background-color: #f4f4f5;
color: #606266;
outline: none;
border-radius: 2px;
padding: 0 4px;
vertical-align: top;
display: inline-block;
font-size: 13px;
min-width: 35.5px;
height: 28px;
line-height: 28px;
cursor: pointer;
box-sizing: border-box;
text-align: center;
border: 0;
&[disabled] {
color: #c0c4cc;
cursor: not-allowed;
}
&.active {
cursor: not-allowed;
background-color: #409eff;
color: #fff;
}
}
}
.active {
background-color: skyblue;
}
</style>
父组件 SearchIndex.vue
DOM
<!-- 分页器 测试分页器阶段-->
<PaginationIndex
:pageNo="searchParams.pageNo"
:pageSize="searchParams.pageSize"
:total="total"
:continues="5"
@getPageNo="getPageNo"
/>
JS
export default {
name: "SearchIndex",
components: {
SearchSelector,
},
setup() {
const store = useStore();
const $route = useRoute();
const $router = useRouter();
const internalInstance = getCurrentInstance();
const $bus = internalInstance.appContext.config.globalProperties.$bus;
// 带给服务器的参数
const searchParams = reactive({
// 一级分类id
category1Id: "",
// 二级分类id
category2Id: "",
// 三级分类id
category3Id: "",
// 分类名字
categoryName: "",
// 关键字
keyword: "",
// 排序 : 初始状态应该是综合|降序
order: "1:desc",
// 分页器
pageNo: 1,
// 每页展示数据个数
pageSize: 10,
// 平台售卖属性操作带的参数
props: [],
// 品牌
trademark: "",
});
let total = computed({
get() {
return store.state.search.searchList.total;
},
});
// 自定义事件回调——获取当前第几页
function getPageNo(pageNo) {
// console.log("getPageNo", pageNo);
// 整理带给服务器参数
searchParams.pageNo = pageNo;
getData();
}