美文网首页
Vue2自定义分页组件

Vue2自定义分页组件

作者: 我是上帝可爱多 | 来源:发表于2017-08-15 11:21 被阅读496次

    分页是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,就不需要这个了。。。

    相关文章

      网友评论

          本文标题:Vue2自定义分页组件

          本文链接:https://www.haomeiwen.com/subject/hmsmrxtx.html