Vue2自定义分页组件

分页是WEB开发中很常用的功能,尤其是在各种前后端分离的今天,后端API返回数据,前端根据数据的count以及当前页码pageIndex来计算分页页码并渲染到页面上已经是一个很普通很常见的功能了。博主之前的公司分页是用的一个jquery插件,使用起来真的是诸多不便。今天我们自己手写一个vue2的分页组件,供以后开发使用。

  • 请求API,返回第一屏数据(pageSize内)以及所有相关条件的数据总量count。
  • 将数据总量传递给page组件,来计算页码并渲染到页面上。
  • 点击页码,发送请求获取该页码的数据,返回数据总量count以及该页码下的数据条目。

简单处理,样式类似于bootstrap的分页组件,在第一页时,禁用上一页,以及首页按钮;在最后一页时,禁用下一页,以及尾页按钮;超出范围的页码以...来代替,效果图如下:


好吧,下面晒出我的分页组件模版:

<template>
     <ul class="mo-paging">
        <!-- prev -->
        <li
        :class="['paging-item', 'paging-item--prev', {'paging-item--disabled' : index === 1}]"
        @click="prev">prev</li>

       <!-- first -->
        <li
        :class="['paging-item', 'paging-item--first', {'paging-item--disabled' : index === 1}]"
        @click="first">first</li>

        <li
        :class="['paging-item', 'paging-item--more']"
        v-if="showPrevMore">...</li>

        <li
        :class="['paging-item', {'paging-item--current' : index === pager}]"  //index是当前页码
        v-for="pager in pagers"
        @click="go(pager)">{{ pager }}</li>

        <li
        :class="['paging-item', 'paging-item--more']"
        v-if="showNextMore">...</li>

        <!-- last -->
        <li
        :class="['paging-item', 'paging-item--last', {'paging-item--disabled' : index === pages}]"
        @click="last">last</li>

        <!-- next -->
        <li
        :class="['paging-item', 'paging-item--next', {'paging-item--disabled' : index === pages}]"
        @click="next">next</li>
     </ul>
</template>

模版写粗来了,是不是很一目了然的感觉,css就不在这里摆出来了。
好了,接下来就是比较复杂的js代码了,我们先思考一下,这个组件肯定是作为子组件被引用到一个父组件里面。

export default {
    name : 'MoPaging',
    //通过props来接受从父组件传递过来的值
    props : {
        //页面中的可见页码,其他的以...替代, 必须是奇数
        perPages : { 
            type : Number,
            default : 5 
        },

        //当前页码
        pageIndex : {
            type : Number,
            default : 1
        },

        //每页显示条数
        pageSize : {
            type : Number,
            default : 10
        },

        //总记录数
        total : {
            type : Number,
            default : 1
        }
 },

   methods : {
        prev(){
            if (this.index > 1) {
                this.go(this.index - 1)
            }
        },
        next(){
            if (this.index < this.pages) {
                this.go(this.index + 1)
            }
        },
        first(){
            if (this.index !== 1) {
                this.go(1)
            }
        },
        last(){
            if (this.index != this.pages) {
                this.go(this.pages)
            }
        },
        go (page) {
            if (this.index !== page) {  //点击的页码不是当前页码
                this.index = page
                //父组件通过change方法来接受当前的页码
                this.$emit('change', this.index)
            }
        }
    }

其实这些method里面的prev,next都还比较简单,如果对父子组件通信不了解的同学,文章结尾我会给一个例子,关键字$emit。

data () {
        return {
            index : this.pageIndex, //当前页码
            limit : this.pageSize, //每页显示条数
            size : this.total || 1, //总记录数
            showPrevMore : false,   //前后有2个 ... 根据这里判断是否显示
            showNextMore : false
        }
    },
computed : {

        //计算总页码
        pages(){
            return Math.ceil(this.size / this.limit)
        },

        //计算页码,当count等变化时自动计算
        pagers () {
            const array = []
            const perPages = this.perPages
            const pageCount = this.pages
            let current = this.index
            const _offset = (perPages - 1) / 2


            const offset = {
                start : current - _offset,
                end   : current + _offset
            }

            //-1, 3
            if (offset.start < 1) {
                offset.end = offset.end + (1 - offset.start)
                offset.start = 1
            }
            if (offset.end > pageCount) {
                offset.start = offset.start - (offset.end - pageCount)
                offset.end = pageCount
            }
            if (offset.start < 1) offset.start = 1

            this.showPrevMore = (offset.start > 1)
            this.showNextMore = (offset.end < pageCount)

            for (let i = offset.start; i <= offset.end; i++) {
                array.push(i)
            }

            return array
        }

        watch : {
            pageIndex(val) {
                this.index = val || 1
        },
            pageSize(val) {
                this.limit = val || 10
        },
            total(val) {
               this.size = val || 1
        }
    }
    }

大家可能看到pagers里面的代码被吓到了,其实也没什么,里面只是做了一些判断,提供左右2边的... 是否现实的依据。我们来分析一下:

  1. 中间显示的条目是5,左边的 ... 在当前页面大于3时显示,这个很好判断。
  2. 中间显示的条目是5,右边的 ... 在当前页面小于18时显示,这个很好判断。
  3. 当前页码index在pagers数组的中间,这是一个奇数个的数组。

其实到了这里也差不多了,我们来看一下父组件的写法:

<template>
    <div class="list">
        <template v-if="count">
            <ul>
                <li v-for="item in items">...</li>
            </ul>
            <mo-paging 
            :page-index="currentPage" 
            :total="count" 
            :page-size="pageSize" 
            @change="pageChange">
            </mo-paging>
        </template>
    </div>
</template>

这个父组件向下传递了一些数据给分页组件,如total,page-index,page-size。

<script>
    import MoPaging from './paging'
    export default {
        //显示的声明组件
        components : {
            MoPaging 
        },
        data () {
            return {
                pageSize : 20 , //每页显示20条数据
                currentPage : 1, //当前页码
                count : 0, //总记录数
                items : []
            }
        },
        methods : {
            //获取数据
            getList () {
                //模拟
                let url = `/api/list/?pageSize=${this.pageSize}&currentPage=${this.currentPage}`
                this.$http.get(url)
                .then(({body}) => {

                    //子组件监听到count变化会自动更新DOM
                    this.count = body.count
                    this.items = body.list
                })
            },

            //从page组件传递过来的当前page
            pageChange (page) {
                this.currentPage = page
                this.getList()
            }
        },
        mounted() {
            //请求第一页数据
            this.getList()
        } 
    }
</script>

mounted当页面挂载时,就去获取数据。当在分页组件点击了某个页数后,会触发父组件的pageChange事件,这个函数里面的page是通过emit传递过来的。

好了,最后讲一下父子组件通信的问题吧。。。
父组件:

<div id="counter-event-example">
  <p>{{ total }}</p>
  <button-counter :increment="incrementTotal"></button-counter>
  <button-counter :increment="incrementTotal"></button-counter>
</div>

new Vue({
  el: '#counter-event-example',
  data: {
    total: 0
  },
  methods: {
    incrementTotal() {
      this.total += 1
    }
  }
})

子组件:

Vue.component('button-counter', {
  template: '<button v-on:click="increment">{{ counter }}</button>',
  data: function () {
    return {
      counter: 0
    }
  },
  methods: {
    increment() {
      this.counter += 1
      this.$emit('increment')
    }
  },
})

上面讲的两种方法都父子组件之间的通信,有时候非父子关系的组件也需要通信。在 Vue1.0 时代,可以通过 $dispatch 和 $broadcast 来解决,首先 dispatch 到根组件,然后再 broadcast 到子组件。Vue2.0 中官方推荐用 event bus 或者 vuex 解决,event bus 的本质是一个发布者订阅者模式。

<div id="example">
    <Display></Display>
    <Increment></Increment>
</div>

var bus = new Vue()
Vue.component('Increment', {
  template: `<button @click="increment">+</button>`,
  data: function() {
   return {count: 0}
  },
  methods: {
    increment: function(){
      var increment = this.count++
      bus.$emit('inc', increment)
  }
 }
})
Vue.component('Display', {
  template: `<h3>Clicked: {{count}} times</h3>`,
  data: function(){
  return {count: 0}
  },
 created: function(){
   bus.$on('inc', function(num){
     this.count = num
   }.bind(this))
 }
})
new Vue({
 el: "#example",
})

相信大家一看就会懂,这个时候同级组件的沟通需要经过父组件bus,然而在做项目的时候我们有vuex,就不需要这个了。。。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容