分页---Vue+.net+bootstrap实现
通过学习Vue,的确觉的Vue的双向绑定使用起来十分方便,因此研究了一下列表显示时分页的实现,这里我使用了bootstrap的样式,所以在页面中引用bootstrap的样式文件,后台提数据源使用.net的,数据库访问使用EF,如果库中存有大量数据,为提高显示速度还是建议使用更好的数据访问方式,比如dapper,下面看看我的实现 :
1、首先创建一个分页的类PageList,封装其分页所需的各值:
public class PageList { /// <summary> /// 初始化 /// </summary> /// <param name="curPage">当前页</param> /// <param name="total">记录数</param> /// <param name="pagesize">每页显示的记录数</param> /// <param name="showPageNum">页码显示数量</param> public PageList(int curPage, int total,int pagesize=10,int showPageNum=9) { this.total = total; this.pagesize = pagesize; this.curPage = curPage; this.showPageNum = showPageNum; this.firstNum = 1; this.lastNum = this.totalPages; this.pagelist = this.getPagelist(); } //前面的点,前面省略的页数用.来代表, public bool previousSpot { get; set; } //后面的点,后面省略的页数用.来代表, public bool nextSpot { get; set; } //第一个页数,一般都是1 public int firstNum { get; set; } //最后一个页数,也就是最大页数 public int lastNum { get; set; } //默认页面显示最多页号数目 public int showPageNum { get; set; } public int total { get; set; }//总记录数 //总页数 public int totalPages { get { return (int)Math.Ceiling((double)total / pagesize); } } public int curPage { get; set; }//当前页 public int pagesize { get; set; }//每页大小 //页数列表,此列表中不包含第一页和最后一页 public List<int> pagelist { get; set; } public List<int> getPagelist() { var p = new List<int>(); if (totalPages <= showPageNum)//全部显示 { for (int i = 2; i < totalPages; i++) { p.Add(i); } } else { var yiban = ((int)((showPageNum + 1) / 2)) - 1;//前后保留页数大小 if (curPage - yiban > 1 && curPage + yiban < totalPages) { //两头都可取值 this.previousSpot = this.nextSpot = true; for (int i = curPage - yiban+1; i < curPage + yiban; i++) { p.Add(i); } } else if (curPage - yiban > 1 ) { //右头值少 this.previousSpot = true; for (int i = totalPages - 1; i > totalPages - showPageNum + 2; i--) { p.Add(i); } } else if (curPage - yiban <= 1 ) { //左头值少 this.nextSpot = true; for (int i = 2; i < showPageNum; i++) { p.Add(i); } } } return p.OrderBy(x => x).ToList(); }
这里默认设定了显示页码时最多显示的页码数量为9,也就是页码数量超过9时,用...隐藏其超过的数,当前页始终显示在最中央。
2、后台提取数据的控制器:
int pagesize = 10;//设定每页显示的记录数量 //取 public ActionResult GetData(int curPage = 1) { var total = db.news.Count();//取记录数 var pages = new PageList(curPage, total, pagesize);//初始化分页类 var list = db.news.OrderBy(x => x.id).Skip((curPage - 1) * pagesize).Take(pagesize);//取页面记录列表 var data = new { list = list, pages = pages };//构造对象 return Json(data, JsonRequestBehavior.AllowGet); }
页面中的数据是通过异步方式提取,第一次是默认是第一页,这里的页面记录列表的提取使用了EF,当然还可以使用其它方式访问数据库,这里只是为了方便演示,建议在真正项目使用更加高效的方法。
这里提供dapper访问的方法,其中加入了查询语句。
public ActionResult getdata(news info, int page = 1) { var conn = db.Database.Connection; var sql = string.Format("select * ,row_number() over ( order by id desc ) as rownum from news where 1=1 "); if (!string.IsNullOrEmpty(info.title)) { sql = string.Format(sql + " and title like '%{0}%'", info.title); } if (!string.IsNullOrEmpty(info.writer)) { sql = string.Format(sql + " and writer like '%{0}%'", info.writer); } if (!string.IsNullOrEmpty(info.depname)) { sql = string.Format(sql + " and depname ='{0}'", info.depname); } var sql2 = string.Format("select top {0} * from (" + sql + ") as a where a.rownum>({1}-1)*{0} and a.rownum<={0}*{1}", pagesize, page); var sqlcount = string.Format("select count(rownum) from (" + sql + ")"); using (conn) { var list = conn.Query<news>(sql2); var total = conn.QuerySingle<int>("select count(id) from (" + sql + ") as aa"); var pages = new PageList(page, pagesize, total); var obj = new { list = list, pages = pages }; return Json(obj, JsonRequestBehavior.AllowGet); } }
3、页面中的分页实现如下:
@{ ViewBag.Title = "Index"; } <link href="~/Content/css/bootstrap-theme.min.css" rel="stylesheet" /> <link href="~/Content/css/bootstrap.min.css" rel="stylesheet" /> <link href="~/Content/css/font-awesome.min.css" rel="stylesheet" /> <script src="~/Content/js/jquery-1.8.2.min.js"></script> <script src="~/Content/js/bootstrap.min.js"></script> <script src="~/Scripts/vue.min.js"> </script> <script src="~/Scripts/axios.min.js"></script> <hr> <div id="app"> <ul> <li v-for="item in list">{{item.title}}</li> </ul> <nav aria-label="Page navigation"> <ul class="pagination"> <li v-bind:class="{disabled:pages.curPage==1}"> <a href="javascript:;" v-on:click="pages.curPage==1?'':go(pages.curPage-1)" aria-label="Previous"> <span aria-hidden="true">«</span> </a> </li> <li v-bind:class="{active:pages.curPage==1}"><a href="javascript:;" v-on:click="go(1)">{{pages.firstNum}}</a></li> <li v-if="pages.previousSpot" class="disabled"><span><span aria-hidden="true">...</span></span></li> <li v-for="item in pages.pagelist" v-bind:class="{active:pages.curPage==item}"><a href="javascript:;" v-on:click="go(item)">{{item}}</a></li> <li v-show="pages.nextSpot" class="disabled"><span><span aria-hidden="true">...</span></span></li> <li v-bind:class="{active:pages.curPage==pages.lastNum}"><a href="javascript:;" v-on:click="go(pages.lastNum)">{{pages.lastNum}}</a></li> <li v-bind:class="{disabled:pages.curPage==pages.lastNum}"> <a href="javascript:;" v-on:click="pages.curPage==pages.lastNum?'':go(pages.curPage+1)" aria-label="Next"> <span aria-hidden="true">»</span> </a> </li> </ul> </nav> </div> <script type="text/javascript"> var app = new Vue({ el: '#app', data:{ list: [], pages:[], }, mounted: function () { this.getData(); }, methods: { go: function (n) { this.getData(n); }, getData: function (n) { var _this = this; axios.post("/home/getData", { curPage: n }).then(function (res) { _this.pages = res.data.pages; _this.list = res.data.list; }); } } }); </script>
分页部分已经通过改造,使用了Vue的方式,使用时直接复制即可使用,使用的方法就有两个go,getData,每次在使用时可以在方法中加入这两个方法, ajax使用axios.js实现 。
简单写成组件,以后再完善
@{ ViewBag.Title = "Index"; } <link href="~/Content/css/bootstrap-theme.min.css" rel="stylesheet" /> <link href="~/Content/css/bootstrap.min.css" rel="stylesheet" /> <link href="~/Content/css/font-awesome.min.css" rel="stylesheet" /> <script src="~/Content/js/jquery-1.8.2.min.js"></script> <script src="~/Content/js/bootstrap.min.js"></script> <script src="~/Scripts/vue.min.js"> </script> <script src="~/Scripts/axios.min.js"></script> <hr> <div id="app"> <ul> <li v-for="item in list">{{item.title}}</li> </ul> <mypage v-bind:pages="pages" v-on:getdata="getData"></mypage> </div> <template id="myPage"> <nav aria-label="Page navigation"> <ul class="pagination"> <li v-bind:class="{disabled:pages.curPage==1}"> <a href="javascript:;" v-on:click="pages.curPage==1?'':go(pages.curPage-1)" aria-label="Previous"> <span aria-hidden="true">«</span> </a> </li> <li v-bind:class="{active:pages.curPage==1}"><a href="javascript:;" v-on:click="go(1)">{{pages.firstNum}}</a></li> <li v-if="pages.previousSpot" class="disabled"><span><span aria-hidden="true">...</span></span></li> <li v-for="item in pages.pagelist" v-bind:class="{active:pages.curPage==item}"><a href="javascript:;" v-on:click="go(item)">{{item}}</a></li> <li v-show="pages.nextSpot" class="disabled"><span><span aria-hidden="true">...</span></span></li> <li v-bind:class="{active:pages.curPage==pages.lastNum}"><a href="javascript:;" v-on:click="go(pages.lastNum)">{{pages.lastNum}}</a></li> <li v-bind:class="{disabled:pages.curPage==pages.lastNum}"> <a href="javascript:;" v-on:click="pages.curPage==pages.lastNum?'':go(pages.curPage+1)" aria-label="Next"> <span aria-hidden="true">»</span> </a> </li> </ul> </nav> </template> <script type="text/javascript"> Vue.component('mypage', { template: '#myPage', props: ['pages'], methods: { go: function (n) { this.$emit("getdata", n=n); }, } }); var app = new Vue({ el: '#app', data:{ list: [], pages:[], }, mounted: function () { this.getData(); }, methods: { go: function (n) { this.getData(n); }, getData: function (n) { n = n || 1; var _this = this; axios.post("/home/getData", { curPage: n }).then(function (res) { _this.pages = res.data.pages; _this.list = res.data.list; }); } } }); </script>
进一步改进组件,通用性更好,下面把访问地址等提取到组件中
@{ ViewBag.Title = "Index"; } <link href="~/Content/css/bootstrap-theme.min.css" rel="stylesheet" /> <link href="~/Content/css/bootstrap.min.css" rel="stylesheet" /> <link href="~/Content/css/font-awesome.min.css" rel="stylesheet" /> <script src="~/Content/js/jquery-1.8.2.min.js"></script> <script src="~/Content/js/bootstrap.min.js"></script> <script src="~/Scripts/vue.min.js"> </script> <script src="~/Scripts/axios.min.js"></script> <hr> <div id="app"> <ul> <li v-for="item in list">{{item.title}}</li> </ul> <mypage v-on:getdata="getdata" url="/home/getdata"></mypage> </div> <template id="myPage"> <nav aria-label="Page navigation"> <ul class="pagination"> <li v-bind:class="{disabled:pages.curPage==1}"> <a href="javascript:;" v-on:click="pages.curPage==1?'':go(pages.curPage-1)" aria-label="Previous"> <span aria-hidden="true">«</span> </a> </li> <li v-bind:class="{active:pages.curPage==1}"><a href="javascript:;" v-on:click="go(1)">{{pages.firstNum}}</a></li> <li v-if="pages.previousSpot" class="disabled"><span><span aria-hidden="true">...</span></span></li> <li v-for="item in pages.pagelist" v-bind:class="{active:pages.curPage==item}"><a href="javascript:;" v-on:click="go(item)">{{item}}</a></li> <li v-show="pages.nextSpot" class="disabled"><span><span aria-hidden="true">...</span></span></li> <li v-bind:class="{active:pages.curPage==pages.lastNum}"><a href="javascript:;" v-on:click="go(pages.lastNum)">{{pages.lastNum}}</a></li> <li v-bind:class="{disabled:pages.curPage==pages.lastNum}"> <a href="javascript:;" v-on:click="pages.curPage==pages.lastNum?'':go(pages.curPage+1)" aria-label="Next"> <span aria-hidden="true">»</span> </a> </li> </ul> </nav> </template> <script type="text/javascript"> Vue.component('mypage', { template: '#myPage', props: ['url'], data:function(){ return { pages: [], } }, mounted: function () { this.go(1); }, methods: { go: function (n) { this.getData(n); }, getdata: function (n) { var me = this; axios.post(this.url, { curPage: n }).then(function (res) { me.pages = res.data.pages; me.$emit("getdata", res.data.list); }); } } }); var app = new Vue({ el: '#app', data: { list: [], }, methods: { getData: function (data) { this.list = data; } } }); </script>
为了以后能重用,可以把组件放在一个页面中,其它地方法引用组件。在.netMVC4下封装此分页实现如下 :
分页实现
1、自定义一个分页类:WzhPaged,封装分页各参数
public class WzhPaged { /// <summary> /// 初始化 /// </summary> /// <param name="curPage">当前页</param> /// <param name="total">记录数</param> /// <param name="pagesize">每页显示的记录数</param> /// <param name="showPageNum">页码显示数量</param> public WzhPaged(int curPage, int total, int pagesize = 10, int showPageNum = 9) { this.total = total; this.pagesize = pagesize; this.curPage = curPage; this.showPageNum = showPageNum; this.firstNum = 1; this.lastNum = this.totalPages; this.pagelist = this.getPagelist(); } //前面的点,前面省略的页数用.来代表, public bool previousSpot { get; set; } //后面的点,后面省略的页数用.来代表, public bool nextSpot { get; set; } //第一个页数,一般都是1 public int firstNum { get; set; } //最后一个页数,也就是最大页数 public int lastNum { get; set; } //默认页面显示最多页号数目 public int showPageNum { get; set; } public int total { get; set; }//总记录数 //总页数 public int totalPages { get { return (int)Math.Ceiling((double)total / pagesize); } } public int curPage { get; set; }//当前页 public int pagesize { get; set; }//每页大小 //页数列表,此列表中不包含第一页和最后一页 public List<int> pagelist { get; set; } public List<int> getPagelist() { var p = new List<int>(); if (totalPages <= showPageNum)//全部显示 { for (int i = 2; i < totalPages; i++) { p.Add(i); } } else { var yiban = ((int)((showPageNum + 1) / 2)) - 1;//前后保留页数大小 if (curPage - yiban > 1 && curPage + yiban < totalPages) { //两头都可取值 this.previousSpot = this.nextSpot = true; for (int i = curPage - yiban + 1; i < curPage + yiban; i++) { p.Add(i); } } else if (curPage - yiban > 1) { //右头值少 this.previousSpot = true; for (int i = totalPages - 1; i > totalPages - showPageNum + 2; i--) { p.Add(i); } } else if (curPage - yiban <= 1) { //左头值少 this.nextSpot = true; for (int i = 2; i < showPageNum; i++) { p.Add(i); } } } return p.OrderBy(x => x).ToList(); } }
2、使用Vue封装好分页代码单独放在一个页面中,比如在Views/Shared下添加页面 _pagehelper.cshtml:
<template id="mypage"> <nav aria-label="Page navigation"> <ul class="pagination"> <li v-bind:class="{disabled:pages.curPage==1}"> <a href="javascript:;" v-on:click="pages.curPage==1?'':go(pages.curPage-1)" aria-label="Previous"> <span aria-hidden="true">«</span> </a> </li> <li v-bind:class="{active:pages.curPage==1}"><a href="javascript:;" v-on:click="go(1)">{{pages.firstNum}}</a></li> <li v-if="pages.previousSpot" class="disabled"><span><span aria-hidden="true">...</span></span></li> <li v-for="item in pages.pagelist" v-bind:class="{active:pages.curPage==item}"><a href="javascript:;" v-on:click="go(item)">{{item}}</a></li> <li v-show="pages.nextSpot" class="disabled"><span><span aria-hidden="true">...</span></span></li> <li v-if="pages.lastNum!=1&&pages.lastNum!=0" v-bind:class="{active:pages.curPage==pages.lastNum}"><a href="javascript:;" v-on:click="go(pages.lastNum)">{{pages.lastNum}}</a></li> <li v-bind:class="{disabled:pages.curPage==pages.lastNum}"> <a href="javascript:;" v-on:click="pages.curPage==pages.lastNum?'':go(pages.curPage+1)" aria-label="Next"> <span aria-hidden="true">»</span> </a> </li> </ul> </nav> </template> <script type="text/javascript"> Vue.component('mypage', { template: '#mypage', props: ['url', 'prop'], data: function () { return { pages: [], } }, mounted: function () { this.go(1); }, methods: { go: function (n) { this.getdata(n); }, getdata: function (n) { this.prop = this.prop || {}; this.prop.curPage = n; var me = this; axios.post(this.url, this.prop).then(function (res) { me.pages = res.data.pages; me.$emit("getdata", res.data.list); }); } } }); </script>
3、在每个模板页中引用上面的组件文件,比如模板页_Layout.cshtml中引用此页:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width" /> <title>@ViewBag.Title</title> <link href="~/Content/css/bootstrap-theme.min.css" rel="stylesheet" /> <link href="~/Content/css/bootstrap.min.css" rel="stylesheet" /> <link href="~/Content/css/font-awesome.min.css" rel="stylesheet" /> <script src="~/Content/js/jquery-1.8.2.min.js"></script> <script src="~/Content/js/bootstrap.min.js"></script> <script src="~/Scripts/vue.min.js"> </script> <script src="~/Scripts/axios.min.js"></script> </head> <body> @{Html.RenderPartial("_pagehelper");} @RenderBody() @RenderSection("scripts", required: false) </body> </html>
只要使用了此模板的网页就有了分页的组件,就可以方便快捷的使用分页组件了。
4、下面在需要分页功能的页面上使用组装好的组件,考虑到页面中可能会使用到查询功能,所以分两种情况,无查询搜索功能和有查询搜索功能。
(1).无查询搜索功能,比较简单一点:
<div id="app"> <ul> <li v-for="item in list">{{item.title}}</li> </ul> <div v-if="list.length==0"> <span> 无数据</span> </div> <mypage v-on:getdata="getData" url="/home/getlistData"></mypage> </div> <script type="text/javascript"> var app = new Vue({ el: '#app', data: { list: [], }, methods: { getData: function (data) { this.list = data; } } }); </script>
<mypage v-on:getdata="getData" url="/home/getlistData"></mypage>, getdata是设置要显示的列表集合赋值, url是页面在页码间跳转时获取数据来源的地址。
(2)有查询搜索功能,这个较复杂一些,根据自身需求自己来写查询条件,这里列出本人在项目使用的方法
<div id="app"> <div class="search"> <input placeholder="标题" style="width:200px;display:inline-block" class="form-control" v-model.trim="title" /> <input placeholder="撰稿人" style="width:200px;display:inline-block" class="form-control" v-model.trim="writer" /> 发部单位:<select v-model="depname" class="form-control" style="width:auto;display:inline"> <option></option> <option v-for="dep in deps">{{dep}}</option> </select> <button class="btn btn-info" v-on:click="btnSearch"><i class="fa fa-search fa-lg"></i> 查询</button> <button class="btn btn-warning" v-on:click="btnReset"><i class="fa fa-search fa-lg"></i> 重置</button> </div> <div> <table class="table table-responsive"> <tr> <th width="50%">标题</th> <th>时间</th> <th>一级栏目</th> <th>二级栏目</th> <th>发表者</th> <th>单位</th> </tr> <tr v-for="item in list"> <td><a v-bind:href="'/home/news/'+item.id" target="_blank"> {{item.title}}</a></td> <td> {{item.addtime|formatedate}} </td> <td>{{item.colfirstName}}</td> <td>{{item.colsecondName}}</td> <td>{{item.writer}}</td> <td>{{item.depname}}</td> </tr> </table> <div v-if="list.length==0"> <span> 无数据</span> </div> <mypage v-on:getdata="getData" url="/home/getlistData" v-bind:prop="prop" ref="myref" ></mypage> </div> </div> <script> var app = new Vue({ el: '#app', data: { list: [], deps: [], depname: "", title: "", writer: "", curpage: 1, type:'@ViewBag.type' }, filters: { formatedate: function (d) { if (d != "") { var date = new Date(parseInt(d.substring(6, 19))); return date.toLocaleDateString(); } } }, mounted: function () { this.$nextTick(function () { this.loadDep(); }); }, methods: { btnReset: function () { this.depname = this.title = this.writer = ""; }, getData: function (data) { this.list = data; }, btnSearch: function () { this.$refs.myref.go(1); }, loadDep: function () { var _this = this; axios.post("/home/getDepName").then(function (res) { _this.deps = res.data; }); } }, computed: { prop: function () { return { title: this.title, writer: this.writer, depname: this.depname, type: this.type } } }, }); </script>
<mypage v-on:getdata="getData" url="/home/getlistData" v-bind:prop="prop" ref="myref" ></mypage> ,多了两个属性, prop是查询和跳转时用到的参数,比如查询姓名等,但这里跳转的页码是不用管,因为我已经在组件中完成它了。
prop要在计算属性中设置好要传送的值。
6、.net后台,控制器
不需要查询的:
public ActionResult getData(int curPage = 1) { var total = db.news.Count();//取记录数 var pages = new PageList(curPage, total, pagesize);//初始化分页类 var list = db.news.OrderBy(x => x.id).Skip((curPage - 1) * pagesize).Take(pagesize);//取页面记录列表 var obj = new { list = list, pages = pages };//构造对象 return Json(obj, JsonRequestBehavior.AllowGet); }
需要查询的:
public ActionResult getlistdata(news info, int curPage = 1, string type = "") { var conn = db.Database.Connection; var sql = string.Format("select * ,row_number() over ( order by id desc ) as rownum from news where ispublish=1 and colsecondName='{0}' ", type); if (!string.IsNullOrEmpty(info.title)) { sql = string.Format(sql + " and title like '%{0}%'", info.title); } if (!string.IsNullOrEmpty(info.writer)) { sql = string.Format(sql + " and writer like '%{0}%'", info.writer); } if (!string.IsNullOrEmpty(info.depname)) { sql = string.Format(sql + " and depname ='{0}'", info.depname); } var sql2 = string.Format("select top {0} * from (" + sql + ") as a where a.rownum>({1}-1)*{0} and a.rownum<={0}*{1}", pagesize, curPage); var sqlcount = string.Format("select count(rownum) from (" + sql + ")"); using (conn) { var newslist = conn.Query<news>(sql2); var total = conn.QuerySingle<int>("select count(id) from (" + sql + ") as aa"); var pagelist = new WzhPaged(curPage, total, pagesize); var obj = new { list = newslist, pages = pagelist }; return Json(obj, JsonRequestBehavior.AllowGet); } }