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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容