欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

使用Vue构建可重用的分页组件

程序员文章站 2022-05-14 15:12:22
web应用程序中资源分页不仅对性能很有帮助,而且从用户体验的角度来说也是非常有用的。在这篇文章中,将了解如何使用vue创建动态和可用的分页组件。 基本结构 分页组件应该...

web应用程序中资源分页不仅对性能很有帮助,而且从用户体验的角度来说也是非常有用的。在这篇文章中,将了解如何使用vue创建动态和可用的分页组件。

基本结构

分页组件应该允许用户访问第一个和最后一个页面,向前和向后移动,并直接切换到近距离的页面。

大多数应用程序在用户每次更改页面时都会发出api请求。我们需要确保组件允许这样做,但是我们不希望在组件内发出这样的请求。这样,我们将确保组件在整个应用程序中是可重用的,并且请求都是在操作或服务层中进行的。我们可以通过使用用户单击的页面的数字触发事件来实现此目的。

有几种可能的方法来实现api端点上的分页。对于这个例子,我们假设api告诉我们每个页面的结果数、页面总数和当前页面。这些将是我们的动态 props 。

相反,如果api只告诉记录的总数,那么我们可以通过将结果的数量除以每一页的结果数来计算页数: totalresults / resultsperpage 。

我们想要渲染一个按钮到 第一页 、 上一页 、 页面数量范围 、 下一页 和 最后一页 :

[first] [next] [1] [2] [3] [previous] [last]

比如像下图这样的一个效果:

使用Vue构建可重用的分页组件 

尽管我们希望渲染一个系列的页面,但并不希望渲染所有可用页面。让我们允许在我们的组件中设置一个最多可见按钮的 props 。

既然我们知道了我们想要的组件要做成什么,需要哪些数据,我们就可以设置html结构和所需要的 props 。

<template id="pagination">
  <ul class="pagination">
    <li>
      <button type="button">« first</button>
    </li>
    <li>
      <button type="button">«</button>
    </li>
    <!-- 页数的范围 -->
    <li>
      <button type="button">next »</button>
    </li>
    <li>
      <button type="button">»</button>
    </li>
  </ul>
</template>
vue.component('pagination', {
  template: '#pagination',
  props: {
    maxvisiblebuttons: {
      type: number,
      required: false,
      default: 3
    },
    totalpages: {
      type: number,
      required: true
    },
    total: {
      type: number,
      required: true
    },
    currentpage: {
      type: number,
      required: true
    }
  }
})

上面的代码注册了一个 pagination 组件,如果调用这个组件:

<div id="app">
  <pagination></pagination>
</div>

这个时候看到的效果如下:

使用Vue构建可重用的分页组件 

注意,为了能让组件看上去好看一点,给组件添加了一点样式。

事件监听

现在我们需要通知父组件,当用户单击按钮时,用户点击了哪个按钮。

我们需要为每个按钮添加一个事件监听器。 v-on 指令 允许侦听dom事件。在本例中,我将使用 v-on 的快捷键 来侦听单击事件。

为了通知父节点,我们将使用 $emit 方法 来发出一个带有页面点击的事件。

我们还要确保分页按钮只有在页面可用时才唯一一个当前状态。为了这样做,将使用 v-bind 将 disabled 属性的值与当前页面绑定。我们还是使用 :v-bind 的快捷键 : 。

为了保持我们的 template 干净,将使用 computed 属性 来检查按钮是否被禁用。使用 computed 也会被缓存,这意味着只要 currentpage 不会更改,对相同计算属性的几个访问将返回先前计算的结果,而不必再次运行该函数。

<template id="pagination">
  <ul class="pagination">
    <li>
      <button type="button" @click="onclickfirstpage" :disabled="isinfirstpage">« first</button>
    </li>
    <li>
      <button type="button" @click="onclickpreviouspage" :disabled="isinfirstpage">«</button>
    </li>
    <li v-for="page in pages">
      <button type="button" @click="onclickpage(page.name)" :disabled="page.isdisabled"> {{ page.name }}</button>
    </li>
    <li>
      <button type="button" @click="onclicknextpage" :disabled="isinlastpage">next »</button>
    </li>
    <li>
      <button type="button" @click="onclicklastpage" :disabled="isinlastpage">»</button>
    </li>
  </ul>
</template>

vue.component('pagination', {
  template: '#pagination',
  props: {
    maxvisiblebuttons: {
      type: number,
      required: false,
      default: 3
    },
    totalpages: {
      type: number,
      required: true
    },
    total: {
      type: number,
      required: true
    },
    currentpage: {
      type: number,
      required: true
    }
  },
  computed: {
    isinfirstpage: function () {
      return this.currentpage === 1
    },
    isinlastpage: function () {
      return this.currentpage === this.totalpages
    }
  },
  methods: {
    onclickfirstpage: function () {
      this.$emit('pagechanged', 1)
    },
    onclickpreviouspage: function () {
      this.$emit('pagechanged', this.currentpage - 1)
    },
    onclickpage: function (page) {
      this.$emit('pagechanged', page)
    },
    onclicknextpage: function () {
      this.$emit('pagechanged', this.currentpage + 1)
    },
    onclicklastpage: function () {
      this.$emit('pagechanged', this.totalpages)
    }
  }
})

在调用 pagination 组件时,将 totalpages 和 total 以及 currentpage 传到组件中:

<div id="app">
  <pagination :total-pages="11" :total="120" :current-page="currentpage"></pagination>
</div>

let app = new vue({
  el: '#app',
  data () {
    return {
      currentpage: 2
    }
  }
})

运行上面的代码,将会报错:

使用Vue构建可重用的分页组件 

不难发现,在 pagination 组件中,咱们还少了 pages 。从前面介绍的内容,我们不难发现,需要计算出 pages 的值。

vue.component('pagination', {
  template: '#pagination',
  props: {
    maxvisiblebuttons: {
      type: number,
      required: false,
      default: 3
    },
    totalpages: {
      type: number,
      required: true
    },
    total: {
      type: number,
      required: true
    },
    currentpage: {
      type: number,
      required: true
    }
  },
  computed: {
    isinfirstpage: function () {
      return this.currentpage === 1
    },
    isinlastpage: function () {
      return this.currentpage === this.totalpages
    },
    startpage: function () {
      if (this.currentpage === 1) {
        return 1
      }
      if (this.currentpage === this.totalpages) {
        return this.totalpages - this.maxvisiblebuttons + 1
      }
      return this.currentpage - 1
    },
    endpage: function () {
      return math.min(this.startpage + this.maxvisiblebuttons - 1, this.totalpages)
    },
    pages: function () {
      const range = []
      for (let i = this.startpage; i <= this.endpage; i+=1) {
        range.push({
          name: i,
          isdisabled: i === this.currentpage
        })
      }
      return range
    }
  },
  methods: {
    onclickfirstpage: function () {
      this.$emit('pagechanged', 1)
    },
    onclickpreviouspage: function () {
      this.$emit('pagechanged', this.currentpage - 1)
    },
    onclickpage: function (page) {
      this.$emit('pagechanged', page)
    },
    onclicknextpage: function () {
      this.$emit('pagechanged', this.currentpage + 1)
    },
    onclicklastpage: function () {
      this.$emit('pagechanged', this.totalpages)
    }
  }
})

这个时候得到的结果不再报错,你在浏览器中将看到下图这样的效果:

使用Vue构建可重用的分页组件 

添加样式

现在我们的组件实现了最初想要的所有功能,而且添加了一些样式,让它看起来更像一个分页组件,而不仅像是一个列表。

我们还希望用户能够清楚地识别他们所在的页面。让我们改变表示当前页面的按钮的颜色。

为此,我们可以使用对象语法将html类绑定到当前页面按钮上。当使用对象语法绑定类名时,vue将在值发生变化时自动切换类。

虽然 v-for 中的每个块都可以访问父作用域范围,但是我们将使用 method 来检查页面是否处于 active 状态,以便保持我们的 templage 干净。

vue.component('pagination', {
  template: '#pagination',
  props: {
    maxvisiblebuttons: {
      type: number,
      required: false,
      default: 3
    },
    totalpages: {
      type: number,
      required: true
    },
    total: {
      type: number,
      required: true
    },
    currentpage: {
      type: number,
      required: true
    }
  },
  computed: {
    isinfirstpage: function () {
      return this.currentpage === 1
    },
    isinlastpage: function () {
      return this.currentpage === this.totalpages
    },
    startpage: function () {
      if (this.currentpage === 1) {
        return 1
      }
      if (this.currentpage === this.totalpages) {
        return this.totalpages - this.maxvisiblebuttons + 1
      }
      return this.currentpage - 1
    },
    endpage: function () {
      return math.min(this.startpage + this.maxvisiblebuttons - 1, this.totalpages)
    },
    pages: function () {
      const range = []
      for (let i = this.startpage; i <= this.endpage; i+=1) {
        range.push({
          name: i,
          isdisabled: i === this.currentpage
        })
      }
      return range
    }
  },
  methods: {
    onclickfirstpage: function () {
      this.$emit('pagechanged', 1)
    },
    onclickpreviouspage: function () {
      this.$emit('pagechanged', this.currentpage - 1)
    },
    onclickpage: function (page) {
      this.$emit('pagechanged', page)
    },
    onclicknextpage: function () {
      this.$emit('pagechanged', this.currentpage + 1)
    },
    onclicklastpage: function () {
      this.$emit('pagechanged', this.totalpages)
    },
    ispageactive: function (page) {
      return this.currentpage === page;
    }
  }
})

接下来,在 pages 中添加当前状态:

<li v-for="page in pages">
  <button type="button" @click="onclickpage(page.name)" :disabled="page.isdisabled" :class="{active: ispageactive(page.name)}"> {{ page.name }}</button>
</li>

这个时候你看到效果如下:

使用Vue构建可重用的分页组件 

但依然还存在一点点小问题,当你在点击别的按钮时, active 状态并不会随着切换:

使用Vue构建可重用的分页组件 

继续添加代码改变其中的效果:

let app = new vue({
  el: '#app',
  data () {
    return {
      currentpage: 2
    }
  },
  methods: {
    onpagechange: function (page) {
      console.log(page)
      this.currentpage = page;
    }
  }
})

在调用组件时:

<div id="app">
  <pagination :total-pages="11" :total="120" :current-page="currentpage" @pagechanged="onpagechange"></pagination>
</div>

这个时候的效果如下了:

使用Vue构建可重用的分页组件 

到这里,基本上实现了咱想要的分页组件效果。

无障碍化处理

熟悉bootstrap的同学都应该知道,bootstrap中的组件都做了无障碍化的处理,就是在组件中添加了wai-aria相关的设计。比如在分页按钮上添加 aria-label 相关属性:

使用Vue构建可重用的分页组件 

在我们这个组件中,也相应的添加有关于wai-aria相关的处理:

<template id="pagination">
  <ul class="pagination" aria-label="page navigation">
    <li>
      <button type="button" @click="onclickfirstpage" :disabled="isinfirstpage" aria-label="go to the first page">« first</button>
    </li>
    <li>
      <button type="button" @click="onclickpreviouspage" :disabled="isinfirstpage" aria-label="previous">«</button>
    </li>
    <li v-for="page in pages">
      <button type="button" @click="onclickpage(page.name)" :disabled="page.isdisabled" :aria-label="`go to page number ${page.name}`"> {{ page.name }}</button>
    </li>
    <li>
      <button type="button" @click="onclicknextpage" :disabled="isinlastpage" aria-label="next">next »</button>
    </li>
    <li>
      <button type="button" @click="onclicklastpage" :disabled="isinlastpage" aria-label="go to the last page">»</button>
    </li>
  </ul>
</template>

这样有关于 aria 相关的属性就加上了:

使用Vue构建可重用的分页组件 

最终的效果如下,可以点击下面的连接访问:

https://codepen.io/airen/pen/mxmlrg