vue + element-ui的分页问题

背景介绍

最近比较空闲,公司的后台就想着把如今的后台管理系统给改版一下,说是之前的太难看了,用着也很差用,而后给我甩过来一个ant-design-pro的连接,说是他看这个就挺不错的。css

我当时内心就想着,以前的那个项目混合在大家的java项目里,跟普通的jsp页面差很少,一下就是一大堆的css和js文件,看着我都惧怕(好吧,我认可其实我都不敢看),这能加载的快了就奇了怪了。ant-design最初是为react设计的,ant-design-pro天然也是用react了,不得不说人家这个界面看着确实舒服。前端

对着ant-design-pro的官方文档看了一通,貌似看了跟没看也差很少???算了,仍是直接看代码吧,整理了一下思路,大体上是看懂了,除了react + react-router外,状态管理用的是 dva, redux的异步问题算是解决了,要不就开始直接写页面吧?vue

等等,我好像漏掉了点什么?噢,对,先看看打包出来的文件大小,一打包个人心就凉了,最大的js竟然有900多k,ant-design的源文件是真的大。react我还只是能写出代码,打包优化这个可就有点为难我了。这时的我再想到公司那1m的带宽,还有这几个后台的技术能力,要否则这个技术栈我仍是放弃吧?不能期望连 请求头, CORS稍微高级一点的携带cookie, nginx静态服务器 都搞不懂的人去给我弄个静态服务器,再顺便开启一下gzip吧?算了算了,找找有没有vue + element-ui的后台模板,不用太费劲就找到了vue-element-adminjava

vue-element-admin用着还行,就是界面不太符合个人理想状况,就对着ant-design-pro改造了一点,列表页大概就是下面这样了。列表的数据是要分页的,普通的列表页只有一个页面栈,也就是用户点击地址栏的回退地址栏时,会返回上一个页面栈,而不是上一页的数据,不太符合用户习惯吧?毕竟传统的网站都是能够回退到上一页的,嗯,话很少说,进入正题吧。react

列表页

第一步:改变地址栏

假设列表页的路径是 /user/list,分页相关的参数为{ page: 1, pagesize: 10 },从其余页面跳转过来的时候,咱们的路径一般是不包含任何参数的,以后的列表数据都是根据该页面的page和pagesize进行变化的,当未使用keep-alive缓存组件时,每次进入列表页都至关于第一次进入,也就是说每次都只能获取第一页的数据。nginx

既然列表数据是用page和pagesize进行变化的,那直接从地址栏获取page和pagesize进行赋值不就行了?那么是改变地址栏的代码是直接写在当前页面仍是 独立为分页组件 呢?从复用性方面来讲,仍是独立出来的好,毕竟其余页面可能也会使用到,总不能每次都复制粘贴吧,那组件化的意义何在?固然了,也不是说分页就必须用这个自定义的分页组件,只推荐在主页面(非遮罩层,有的页面会在点击某一行数据时出现遮罩层显示子列表,此时使用element-ui的分页组件便可)须要分页时使用。git

当改变地址栏的时候,咱们是不但愿不带分页参数的页面栈存在的,此时用replace直接替换便可。github

MyPagination.vue的初始结构为:element-ui

<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>

复制代码

父组件的关键代码:redux

<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大于页面总数呢?

这个时候主要就看后台怎么设计了,

  1. 返回第一页的数据。
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;
},
复制代码
  1. 返回最后一页的数据(我以为这种操做应该是比较合理的)。
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中的后续操做,取消本次请求。替换页面之后,请求远程数据,更新当前页和数据的显示。

  1. 返回空数组(可能大多数后台都是这么设计的,他们应该没想过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

若是有什么问题,欢迎你们留言进行探讨。

相关文章
相关标签/搜索