vue + element-ui的分页问题实现
ma125120 人气:0背景介绍
最近比较空闲,公司的后台就想着把现在的后台管理系统给改版一下,说是以前的太难看了,用着也不好用,然后给我甩过来一个ant-design-pro的链接,说是他看这个就挺不错的。
我当时心里就想着,之前的那个项目混合在你们的java项目里,跟普通的jsp页面差不多,一下就是一大堆的css和js文件,看着我都害怕(好吧,我承认其实我都不敢看),这能加载的快了就奇了怪了。ant-design最初是为react设计的,ant-design-pro自然也是用react了,不得不说人家这个界面看着确实舒服。
对着ant-design-pro的官方文档看了一通,貌似看了跟没看也差不多???算了,还是直接看代码吧,整理了一下思路,大致上是看懂了,除了react + react-router外,状态管理用的是 dva, redux的异步问题算是解决了,要不就开始直接写页面吧?
等等,我好像漏掉了点什么?噢,对,先看看打包出来的文件大小,一打包我的心就凉了,最大的js居然有900多k,ant-design的源文件是真的大。react我还只是能写出代码,打包优化这个可就有点为难我了。这时的我再想到公司那1m的带宽,还有这几个后台的技术能力,要不然这个技术栈我还是放弃吧?不能指望连 请求头, CORS稍微高级一点的携带cookie, nginx静态服务器 都搞不懂的人去给我弄个静态服务器,再顺便开启一下gzip吧?算了算了,找找有没有vue + element-ui的后台模板,不用太费劲就找到了 vue-element-admin 。
vue-element-admin用着还行,就是界面不太符合我的理想情况,就对着ant-design-pro改造了一点,列表页大概就是下面这样了。列表的数据是要分页的,普通的列表页只有一个页面栈,也就是用户点击地址栏的回退地址栏时,会返回上一个页面栈,而不是上一页的数据,不太符合用户习惯吧?毕竟传统的网站都是可以回退到上一页的,嗯,话不多说,进入正题吧。
第一步:改变地址栏
假设列表页的路径是 /user/list,分页相关的参数为 { page: 1, pagesize: 10 }
,从其他页面跳转过来的时候,我们的路径通常是不包含任何参数的,之后的列表数据都是根据该页面的page和pagesize进行变化的,当未使用keep-alive缓存组件时,每次进入列表页都相当于第一次进入,也就是说每次都只能获取第一页的数据。
既然列表数据是用page和pagesize进行变化的,那直接从地址栏获取page和pagesize进行赋值不就好了?那么是改变地址栏的代码是直接写在当前页面还是 独立为分页组件 呢?从复用性方面来说,还是独立出来的好,毕竟其他页面可能也会使用到,总不能每次都复制粘贴吧,那组件化的意义何在?当然了,也不是说分页就必须用这个自定义的分页组件,只推荐在 主页面(非遮罩层 ,有的页面会在点击某一行数据时出现遮罩层显示子列表,此时使用element-ui的分页组件即可)需要分页时使用。
当改变地址栏的时候,我们是不希望不带分页参数的页面栈存在的,此时用replace直接替换即可。
MyPagination.vue的初始结构为:
<template> <div class = " flex all-center"> <template v-if="total > 0"> <el-pagination :page-size="pagesize" :total="total" :current-page="page" background layout="prev, pager, next, jumper, total" class="my-pagination" @current-change="changePage" /> </template> </div> </template> <script> export default { name: 'MyPagination', props: { total: { type: Number, default: 0, }, page: { type: Number, default: 1, }, pagesize: { type: Number, default: 10, }, totalPages: { type: Number, default: 1, }, }, created() { this.getCurrentPage(); }, methods: { changePage(val) { this.handlePage('push', val, this.pagesize); this.$emit('change', val, this.pagesize); }, getCurrentPage() { var { page, pagesize } = this.$route.query; if (!page || !pagesize) { this.handlePage('replace', page || 1, +pagesize || this.pagesize); return true; } return false; }, handlePage(type, page, pagesize) { this.$router[type]({ path: this.$route.path, query: { ...this.$route.query, page, pagesize }, }); } }, } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .my-pagination { padding-top: 24px; } </style>
父组件的关键代码:
<MyPagination :total = "total" :pagesize = "pagesize" :page="page" :totalPages = "totalPages" @change = "changePage" /> methods: { changePage(page, pagesize) { var _page = this.page, _pagesize = this.pagesize; this.page = page; this.pagesize = pagesize; if (page !== _page && pagesize || _pagesize !== pagesize) this.fetchData(); // 非首次进入页面时再获取分页数据,因为在created钩子中已经获取过一次了。 }, }
实现效果: 首次进入该页面时,如果不含有分页参数,就会先改变分页参数,然后再获取数据,之后点击分页组件的页码也会获取分页之后的数据。
第二步: 观察路由变化
上一步的实现效果乍一看好像没什么不对劲的地方,但是如果直接改变地址栏的话,显示的当前页和当前数据都不会变化。前端路由在页面的查询参数(指的是 router的查询参数 ,可不是普通页面的查询参数)变化时,默认是不会重新加载的,除非页面的key发生变化,这样是为了尽可能的防止页面重新渲染,所以就不用key的方式解决了,直接通过vue的watch检测 $route 的变化,从而改变当前页和当前数据的显示问题。
在MyPagination.vue中新增:
watch: { '$route'(to, from) { let { page, pagesize } = to.query; if (!this.getCurrentPage()) { this.$emit('change', +page || 1, +pagesize || 10); } } },
第三步: 控制pagesize的大小
在上一步的效果中,当改变地址栏的page和pagesize时,列表页的数据也会随之变化。既然是根据地址栏的参数变化,那么新的问题就产生了,
如果用户输入的page大于页面总数呢?
这个时候主要就看后台怎么设计了,
返回第一页的数据。
getCurrentPage() { var { page, pagesize } = this.$route.query; /* (totalPages > 0 && (page > totalPages));满足总页数大于0且当前页大于总页数时,跳转到第一页 */ if (!page || !pagesize || (totalPages > 0 && (page > totalPages))) { this.handlePage('replace', page || 1, this.pagesize); return true; } return false; },
返回最后一页的数据(我觉得这种操作应该是比较合理的)。
getCurrentPage() { var { page, pagesize } = this.$route.query, MAX_PAGESIZE = this.max, totalPages = this.totalPages; if (!page || !pagesize) { this.handlePage('replace', page || 1, +pagesize || this.pagesize); return true; } else if (totalPages > 0 && (page > totalPages)) { this.handlePage('replace', totalPages, +pagesize); return true; } return false; },
替换当前页面栈,return true的作用是阻止watch中的后续操作,取消本次请求。替换页面以后,请求远程数据,更新当前页和数据的显示。
返回空数组(可能大多数后台都是这么设计的,他们应该没想过page会大于总页数吧)。 代码与2中的一样。
上文都是建立在totalPages已确定的情况,如果是首次进入页面的话情况就会不一样了。
如果是首次进入页面的话,totalPages第一次是0,也就是地址栏的参数将不会发生变化,这时候就会出现地址栏和分页组件的显示不一致的情况。这时候可以在分页组件中watch totalPages的变化。
totalPages(newVal, oldVal) { if (+oldVal === 0 && newVal > 0) { this.handlePage('replace', this.page, +this.pagesize); } }
如果pagesize过大呢?
pagesize是必须要进行限制的,如果太大的话,后台查询数据就会非常慢,也可能会造成压力。 解决办法其实也简单,就是在props增加一个max属性,然后在getCurrentPage方法中进行限制,代码如下:
props: { max: { type: Number, default: 20, }, }, methods: { getCurrentPage() { var { page, pagesize } = this.$route.query, MAX_PAGESIZE = this.max, totalPages = this.totalPages; if (!page || !pagesize) { this.handlePage('replace', page || 1, +pagesize || this.pagesize); return true; } else if (pagesize > MAX_PAGESIZE) { this.handlePage('replace', page, MAX_PAGESIZE); return true; } else if (totalPages > 0 && (page > totalPages)) { this.handlePage('replace', totalPages, +pagesize); return true; } return false; }, },
第四步: 优化代码
点击分页组件的页码时产生两次请求
点击分页组件时,1. 会监听current-change事件并改变地址栏,同时emit change事件至父组件,2. 但是地址栏改变后,在watch $route也会emit change事件至父组件,那么只需要合并emit change事件,即current-change事件中只改变地址栏。
changePage(val) { this.handlePage('push', val, this.pagesize); },
结果
至此,一个自定义的分页组件就已经实现了,改变地址栏的参数就可以看到分页数据的变化了,点击页码时地址栏也会随之而改变,请求数量已经尽可能的减少了。
自定义的分页组件: MyPagination.vue
列表页: list.vue
完整demo: front_end
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
加载全部内容