原文地址:https://doterlin.github.io/blog/2016/12/24/vue-pager/
所有源码和示例在这里。
Demo演示请点这里。
1.下载示例源码
为了更好的理解代码,建议通过以下方式将源码下载下来:
- 使用git下载(推荐):
git clone git@github.com:doterlin/vue-pagination.git
- 使用npm安装:
npm i vue-pagination-bs
- 或者点击下载zip
2.运行
直接打开indexl.html
3.修改代码
组件是通过webpack
打包的,我们修改了代码是需要输入webpack
命令进行编译生成最终的js文件才能看到修改后效果。要使用webpack
命令需要在控制台安装好依赖:
npm install
等待依赖安装需要一段时间,我们可以先看看以下几节理解代码。
4.子组件
组件功能是vue.js
的一个重要部分,除了使用Vue.component(tagName, options)
方法注册一个组件外,我们可以写一个.vue
文件,把组件的样式(style)
,逻辑(script)
,模板(template)
存放在一个独立的文件,使得组件的维护更加容易,代码耦合度更小。但是浏览器是无法识别.vue
文件的,我们需要借助webpack
或者browserify
这样的工具把文件打包成可运行的js文件就ok了。关于如何使用webpack
配置vue
可参考官方示例webpack-simple。
如果你还不熟悉webpack
,你可以暂时使用本例子的配置(webpack.config.js
)不进行重新不配置。
以下是代码,代码的解说放在注释里面,方便查看:
//pagination.vue bootstrap风格分页组件
//author: doterlin
//2016-12-16
<template>
<div class="pagination-wrap" v-cloak v-if="totalPage!=0">
<ul class="pagination">
<li :class="currentPage==1?'disabled':''"><a href="javascript:;" @click="turnToPage(1)">首页</a></li>
<li :class="currentPage==1?'disabled':''"><a @click="turnToPage(currentPage-1)" href="javascript:;">上一页</a></li>
<li><a href="javascript:;" @click="turnToPage(currentPage-3)" v-text="currentPage-3" v-if="currentPage-3>0"></a></li>
<li><a href="javascript:;" @click="turnToPage(currentPage-2)" v-text="currentPage-2" v-if="currentPage-2>0"></a></li>
<li><a href="javascript:;" @click="turnToPage(currentPage-1)" v-text="currentPage-1" v-if="currentPage-1>0"></a></li>
<li class="active"><a href="javascript:;" @click="turnToPage(currentPage)" v-text="currentPage">3</a></li>
<li><a href="javascript:;" @click="turnToPage(currentPage+1)" v-text="currentPage+1" v-if="currentPage+1<totalPage"></a></li>
<li><a href="javascript:;" @click="turnToPage(currentPage+2)" v-text="currentPage+2" v-if="currentPage+2<totalPage"></a></li>
<li><a href="javascript:;" @click="turnToPage(currentPage+3)" v-text="currentPage+3" v-if="currentPage+3<totalPage"></a></li>
<li :class="currentPage==totalPage?'disabled':''"><a href="javascript:;" @click="turnToPage(currentPage+1)" >下一页</a></li>
<li :class="currentPage==totalPage?'disabled':''"><a href="javascript:;" @click="turnToPage(totalPage)">尾页</a></li>
</ul>
<small class="small nowrap"> 当前第 <span class="text-primary" v-text="currentPage"></span> 页,共有 <span class="text-primary" v-text="totalPage"></span> 页</small>
<div class="go">
<div :class="isPageNumberError?'input-group error':'input-group'">
<input class="form-control" type="number" v-model="goToPage"><a href="javascript:;" class="input-group-addon" @click="turnToPage(goToPage)">Go</a>
</div>
</div>
</div>
</template>
<script type="text/javascript">
export default {
props: {
//传入总页数,默认100
totalPage: {
type: Number,
default: 1,
required: true,
validator(value) {
return value >= 0
}
},
//传入当前页,默认1
currentPage:{
type: Number,
default: 1,
validator(value) {
return value >= 0
}
},
//传入页面改变时的回调,用于更新你的数据
//回调默认是打印当前页
//请根据需要在传入的回调函数里更改函数体
changeCallback: {
type: Function,
default(cPage) {
console.log("默认回调,显示页码:" + cPage);
}
}
},
data(){
return {
myCurrentPage : 1,
isPageNumberError: false
}
},
computed:{
// prop不应该在组件内部做改变
// 所以我们这里设置一个内部计算属性myCurrentPage来代替props中的currentPage
// 为什么要这么做?参考:https://cn.vuejs.org/v2/guide/components.html#单向数据流
currentPage(){
return this.myCurrentPage;
}
},
methods:{
//turnToPage为跳转到某页
//传入参数pageNum为要跳转的页数
turnToPage( pageNum ){
var ts = this;
var pageNum = parseInt(pageNum);
//页数不合法则退出
if (!pageNum || pageNum > ts.totalPage || pageNum < 1) {
console.log('页码输入有误!');
ts.isPageNumberError = true;
return false;
}else{
ts.isPageNumberError = false;
}
//更新当前页
ts.myCurrentPage = pageNum;
//页数变化时的回调
ts.changeCallback(pageNum);
}
}
}
</script>
<style type="text/css">
.pagination-wrap{
margin: 0 auto;
text-align: center;
}
.pagination {
display: inline-block;
padding-left: 0;
margin: 20px 0;
border-radius: 4px;
}
.small {
margin: 0 10px;
position: relative;
top: -32px;
}
.nowrap {
white-space: nowrap;
}
.input-group {
position: relative;
display: table;
border-collapse: separate;
}
.input-group-addon {
padding: 6px 12px;
font-size: 14px;
font-weight: 400;
line-height: 1;
color: #555;
text-align: center;
background-color: #eee;
border: 1px solid #ccc;
border-radius: 0 4px 4px 0;
}
.input-group-addon, .input-group-btn {
width: 1%;
white-space: nowrap;
vertical-align: middle;
}
.input-group-addon, .input-group-btn, .input-group .form-control {
box-sizing: border-box;
display: table-cell;
}
.input-group .form-control:first-child, .input-group-addon:first-child, .input-group-btn:first-child>.btn, .input-group-btn:first-child>.btn-group>.btn, .input-group-btn:first-child>.dropdown-toggle, .input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle), .input-group-btn:last-child>.btn-group:not(:last-child)>.btn {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.input-group-addon, .input-group-btn, .input-group .form-control {
display: table-cell;
}
.input-group .form-control {
position: relative;
z-index: 2;
float: left;
width: 100%;
margin-bottom: 0;
}
.go .error .form-control{
border: 1px solid #d95656;
}
.form-control {
display: block;
width: 100%;
height: 34px;
padding: 6px 12px;
font-size: 14px;
line-height: 1.42857143;
color: #555;
background-color: #fff;
background-image: none;
border: 1px solid #ccc;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
-webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;
-o-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
}
.text-primary {
color: #428bca;
}
.pagination>li:first-child>a, .pagination>li:first-child>span {
margin-left: 0;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
.go {
display: inline-block;
max-width: 140px;
top: -21px;
position: relative;
}
.input-group-addon:last-child {
display: table-cell;
text-decoration: none;
border-left: 0;
}
.pagination>.disabled>span, .pagination>.disabled>span:hover, .pagination>.disabled>span:focus, .pagination>.disabled>a, .pagination>.disabled>a:hover, .pagination>.disabled>a:focus {
color: #777;
cursor: not-allowed;
background-color: #fff;
border-color: #ddd;
}
.pagination>li:last-child>a, .pagination>li:last-child>span {
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
.pagination>.active>a, .pagination>.active>span, .pagination>.active>a:hover, .pagination>.active>span:hover, .pagination>.active>a:focus, .pagination>.active>span:focus {
z-index: 2;
color: #fff;
cursor: default;
background-color: #428bca;
border-color: #428bca;
}
.pagination>li>a, .pagination>li>span {
position: relative;
float: left;
padding: 6px 12px;
margin-left: -1px;
line-height: 1.42857143;
color: #428bca;
text-decoration: none;
background-color: #fff;
border: 1px solid #ddd;
}
.pagination>li {
display: inline;
}
</style>
5.使用组件
写好了分页组件之后,就可以引入了。我把一个简单应用结构的分析成下图:
当然,一般应用都不会只是一层父子关系,而是几层甚至更多。所以实际的应用的结构应该如下图所示:
现在我们要做的就是写一个最大单元组件用于规划app,比如我们只做了存放分页组件,并对分页组件进行初始化(传入总页数,当前页数,页码变化回调):
//App.vue
//这是父组件示例
//author: doterlin
//2016-12-16
<template>
<div id="app">
<h1 class="title" v-text="msg"></h1>
<pagination :totalPage="parentTotalPage" :currentPage="parentCurrentpage" :changeCallback="parentCallback"></pagination>
</div>
</template>
<script>
//引入pagination组件
import pagination from './pagination.vue';
export default {
name: 'app',
data () {
return {
msg : "Update your data here.",
//在data中初始化总页数totalPage,和当前页currentPage
parentTotalPage: 100,
parentCurrentpage: 1
}
},
//使用pagination组件
components: { pagination },
methods:{
//在这里传入pagination的跳转页码回调方法
//cPage参数是已跳转的当前页码
parentCallback( cPage ) {
//这里是页码变化后要做的事
this.msg = 'Update your data here. Page: ' + cPage;
}
}
}
</script>
<style>
body,html{margin:0;padding:0;}
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.title{
margin: 200px 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
}
a {
color: #42b983;
}
</style>
这样的话,如果有除了分页组件之外的其他组件,我们的编码入口也很明确是app.vue
。
6.应用入口
我们把所有的组件放在根目录下的component
目录下,用于存放所有组件。就此应用的组件已经布置完成,然后需要写一个vue实例用于渲染我们的组件大佬(app.vue
):
import Vue from 'vue'
import App from '../component/app.vue'
//一个vue实例
var app = new Vue({
data: {},
el: '#app',
render: h => h(App),
mounted() {
}
})
7.编译
输入命令并回车:
npm run build
当webpack
提示了打包成功信息后就会发现多个一个文件./dist/build.js
,这就是编译后的代码。
有了可运行的js
,我们就可以写一个html
进行引入(刚才在./src/app.js
需要渲染的元素是id="app"
):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no" name="viewport">
<title>vue-pagination-bs</title>
</head>
<body>
<div id="app"></div>
<script src="./dist/build.js"></script>
</body>
</html>
最后运行index.html
就可以把应用跑起来了。
总结
- 组件化是前端不可或缺的一部分。
- vue将前端组件化做的更加明朗了,我们需要做的不只是他如何进行组织,更需要理解这种别样的组件化途径的思想。
- 实践永远是检验真理的唯一标准,与其看着别人用了高大上的东西在装逼,还不如自己去尝试并装逼。
- 做一个负责人的程序员和一个有专业精神的前端。