在学习电商项目时,有一个手写分页器的功能模块,但是使用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();
- }