分页是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边的... 是否现实的依据。我们来分析一下:
- 中间显示的条目是5,左边的 ... 在当前页面大于3时显示,这个很好判断。
- 中间显示的条目是5,右边的 ... 在当前页面小于18时显示,这个很好判断。
- 当前页码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}¤tPage=${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,就不需要这个了。。。