最近比較忙,前期忙公司手機端接口項目,各種開發+調試+發布現在幾乎上線無問題了;雖然公司項目忙不過在期間抽空做了兩件個人覺得有意義的事情,一者使用aspnetcore開發了個人線上項目(要說線上其實只能ip訪問,沒有域名哈哈),其架構組成由:aspnetcore1.0.0+redis+ postgressql+TaskMainForm服務,這個項目在後期會開源出來供大家分享學習,站點地址點這裡心聲網;一者是目前正在做的後台管理框架一葉子,現目前剛好吧js分頁插件shenniu.pager.js寫完,個人覺得還是可以的,這也是本章將要和大家分享的內容;那麼開始今天的分享內容,希望各位多多掃碼支持:
. 為什麼采用js分頁及效果圖
. 在view中如何使用及分享個後台方法
. 開發者視角閱讀shenniu.pager.js代碼
下面一步一個腳印的來分享:
. 為什麼采用js分頁及效果圖
首先,咋們來了解下市面上mvc兩種常用的分頁方式:跳轉分頁和ajax分頁;跳轉分頁意思就是頁面重定向到指定的頁面並通過傳遞分頁需要的參數,從而獲取數據後通過Modal來綁定數據,這個每次都會刷下頁面體驗上不是很好;ajax分頁通過異步js請求某個接口,然後從接口獲取到數據後,再賦值到展示的界面上,這種方式是不會刷新頁面,從而保證了用戶體驗;
下面來看下這次分享的js分頁插件效果圖:
圖一:
圖二:
圖三:
看效果圖好像看不出來什麼東西,我只能說沒辦法,以後爭取弄個gif動態圖片吧,後面代碼才是關鍵
. 在view中如何使用及分享個後台方法
首先,為了頁面樣式好看我使用了bootstrap+ace樣式框架,樣式效果就是如上面幾張圖所示(這裡是樣式和js文件);由於該插件是采用jquery格式書寫的所以需要引用jquery.js,如上面圖所示使用到了日期選擇框,因為我采用的樣式都是基於h5的設計所以這裡引用的日期選擇插件bootstrap-datepicker.min.js和她的樣式bootstrap-datepicker3.min.css;該實例需要的引用文件都好了,下面看下截圖:
再來,咋們就開始使用shenniu.pager.js,我們需要在點擊“查詢”按鈕的時候去調用這個插件,然後通過插件去獲取後台接口返回的數據,並綁定到頁面展示出來,所以有了如下代碼:
1 var snTool = new shenniuTool(); 2 3 $("button[id='btnSearch']").on("click", function () { 4 5 var data = { 6 txtName: $("input[name='txtName']").val(), 7 nStatus: $("select option:selected").val(), 8 dOperateTime: $("input[name='dOperateTime']").val() 9 }; 10 11 snTool.listFun({ 12 showId: "divShowResult", //內容顯示的div的Id 13 url: "/Menu/Search", 14 data: data, 15 pageSize: 2, //每頁N條 16 headText: [ 17 { nickName: "全選", name: "Id", colType: "checkbox" }, 18 { nickName: "名稱", name: "Name", colType: "label", isModalHeadText: true }, 19 { nickName: "鏈接", name: "Link" }, 20 { nickName: "狀態", name: "EnableDes" }, 21 { nickName: "操作人", name: "OperatorDes" }, 22 { nickName: "操作時間", name: "OperateTime", format: "yyyy-MM-dd" }, 23 { nickName: "操作", name: "Id", colType: "operate" } 24 ], 25 editeOption: { 26 url: "/Menu/Edit", 27 title: "編輯", 28 height: 500 29 }, 30 viewOption: { 31 url: "/Menu/Details", 32 title: "查看", 33 height: 500 34 }, 35 delOption: { 36 url: "/Menu/Delete", 37 title: "刪除", 38 height: 500 39 }, 40 modalExt: modalExt 41 }); 42 });
注意參數url: "/Menu/Search",這個指向的就是後台接口,那麼咋們來看下我後台咋們寫的:
1 [HttpGet] 2 public JsonResult Search() 3 { 4 var moPageResult = new StageModel.MoPageResult<dynamic>(); 5 6 try 7 { 8 9 var txtName = Request.Params["txtName"]; 10 var nStatus = string.IsNullOrWhiteSpace(Request.Params["nStatus"]) ? -1 : Convert.ToInt32(Request.Params["nStatus"]); 11 var dOperateTime = string.IsNullOrWhiteSpace(Request.Params["dOperateTime"]) ? Convert.ToDateTime("1991-01-01") : Convert.ToDateTime(Request.Params["dOperateTime"]); 12 var data = db.MoMenus.AsQueryable(); 13 if (!string.IsNullOrWhiteSpace(txtName)) 14 { 15 data = data.Where(b => b.Name.Contains(txtName)); 16 } 17 if (nStatus >= 0) 18 { 19 data = data.Where(b => b.IsEnable == (nStatus == (int)StageEnumHelper.ComStatus.啟用)); 20 } 21 if (dOperateTime > Convert.ToDateTime("1991-01-01")) 22 { 23 data = data.Where(b => b.OperateTime >= dOperateTime && b.OperateTime < dOperateTime.AddDays(1)); 24 } 25 26 moPageResult.MoPageContent( 27 data, 28 b => b.OperateTime, 29 b => new 30 { 31 Id = b.Id, 32 Name = b.Name, 33 Link = b.Link, 34 Des = b.Des, 35 IsEnable = b.IsEnable, 36 Operator = b.Operator, 37 38 OperatorDes = b.MoUserInfo.NickName, 39 EnableDes = b.IsEnable ? "啟用" : "禁用", 40 OperateTime = b.OperateTime 41 }); 42 43 } 44 catch (Exception ex) 45 { 46 } 47 return Json(moPageResult, JsonRequestBehavior.AllowGet); 48 }
後台接口Request.Params獲取的幾個參數就是從前端
var data = { txtName: $("input[name='txtName']").val(), nStatus: $("select option:selected").val(), dOperateTime: $("input[name='dOperateTime']").val() };
傳遞過來的,分別代碼了視圖中的名稱,狀態,操作時間等查詢條件;下面來看下,後台這兒沒有看到獲取類似分頁的當前頁數和分頁記錄數的操作,是封裝到了MoPageResult類中的MoPageContent()中,來看下MoPageResult類代碼如:
1 #region 分頁數據返回 2 3 public class MoPageResult<TResult> where TResult : class, new() 4 { 5 6 public MoPageResult() 7 { 8 9 } 10 11 public IQueryable<TResult> MoResult; 12 13 /// <summary> 14 /// 總頁數 15 /// </summary> 16 public int PageTotal { get; set; } 17 18 19 /// <summary> 20 /// 當前頁數 21 /// </summary> 22 public int CurrentPage { get; set; } 23 24 /// <summary> 25 /// 分頁記錄數 26 /// </summary> 27 public int PageSize { get; set; } 28 29 /// <summary> 30 /// 分頁方法 31 /// </summary> 32 /// <typeparam name="TKey"></typeparam> 33 /// <param name="query"></param> 34 /// <param name="order_desc"></param> 35 public void MoPageContent<T, TKey>(IQueryable<T> query, Expression<Func<T, TKey>> desc, Expression<Func<T, TResult>> selector = null, bool isDesc = true) where T : class,new() 36 { 37 38 if (HttpContext.Current == null) { return; } 39 var Request = HttpContext.Current.Request; 40 41 this.PageSize = string.IsNullOrWhiteSpace(Request.Params["pageSize"]) ? 15 : Convert.ToInt32(Request.Params["pageSize"]); 42 this.CurrentPage = string.IsNullOrWhiteSpace(Request.Params["currentPage"]) ? 1 : Convert.ToInt32(Request.Params["currentPage"]); 43 44 var nTotal = query.Count(); 45 this.PageTotal = nTotal / this.PageSize + (nTotal % this.PageSize > 0 ? 1 : 0); 46 47 if (selector != null) 48 { 49 if (isDesc) 50 { 51 query = query.OrderByDescending(desc); 52 } 53 else 54 { 55 query = query.OrderBy(desc); 56 } 57 this.MoResult = query. 58 Skip((this.CurrentPage - 1) * this.PageSize). 59 Take(this.PageSize). 60 Select(selector); 61 } 62 } 63 64 } 65 66 #endregion
MoPageContent()中默認是獲取了pagesize,currentpage參數,這樣減少了用戶操作性,並且此方法承擔了計算總頁數和執行分頁語句的角色,注意最後查詢語句Select(selector),selector是Expression<Func<T, TResult>>類型,這個T有條件約束where T : class,new();我在調用該分頁類的使用傳遞的T是dynamic,因為賴人如我覺得匿名類更方便;唯一遺憾的是select輸出暫時無法直接對某個屬性直接使用方法;
最後,插件使用還需要注意一個地方,就是時間,如果後台不處理時間直接DateTime的json格式化,那麼在插件獲取出來的時間格式如:
這個時候就需要在使用shenniu.pager.js插件時候在屬性headText中,指定時間列的格式如:
{ nickName: "操作時間", name: "OperateTime", format: "yyyy-MM-dd" }
使用format格式化時間格式,這個插件兼容的給有:yyyy,MM,dd,HH,mm,ss,相信滿足大家需要了;
. 開發者視角閱讀shenniu.pager.js代碼
首先,我們從上而下,映入眼簾的是插件屬性:
var defOption = { showId: "divShowResult", //內容顯示的div url: "", //ajax數據來源地址 headText: [ { nickName: "A", colType: "checkbox", name: "Id" }, { nickName: "B", colType: "label", name: "Name", isModalHeadText: true } //isModalHeadText:是否是模式窗體頭部顯示的信息 ], data: {}, //查詢條件 editeOption: { url: "", title: "編輯", width: 500, height: 500 }, //編輯地址,不包括id viewOption: { url: "", title: "查看", width: 500, height: 500 }, //查看地址 delOption: { url: "", title: "刪除", width: 500, height: 500 }, //刪除地址 currentPage: 1, //當前頁數 pageSize: 15, //分頁記錄數 showPageTab: 6, //展示6個頁數 modalExt: null, //模式窗體對象 //可忽略 callback: function () { }, //回調函數 tabId: "tab001", loading: "努力加載中,等會吧...", //可以直接寫出<img src=''/> sucFun: function (data) { }, befFun: function () { }, errFun: function () { }, comFun: function () { }, timeout: 60000 //超時60S }; $.extend(defOption, option); View Code裡面已經包括了注釋說明,看起來應該不是問題; $.extend(defOption, option); 這段代碼意思是吧用戶傳遞進來的參數和插件裡面默認的參數合並,用戶大於插件直接可以覆蓋相同屬性的值;
再來,看請求後台的方法:
1 //請求後台 2 function ajaxFun(option) { 3 4 if (option) { 5 $.extend(defOption, option); 6 } 7 8 //獲取分頁參數 9 var hidPageSize = defOption.pageSize; 10 var hidCurrentPage = defOption.currentPage; 11 12 if ($("form input[name='pageSize']").val()) { 13 hidPageSize = $("form input[name='pageSize']").val(); 14 } 15 if ($("form input[name='currentPage']").val()) { 16 hidCurrentPage = $("form input[name='currentPage']").val(); 17 } 18 19 //合並用戶查詢條件和分頁參數條件 20 var searchData = { 21 pageSize: hidPageSize, 22 currentPage: hidCurrentPage 23 }; 24 $.extend(searchData, defOption.data); 25 //請求後台數據 26 $.ajax({ 27 28 url: defOption.url, 29 type: "get", 30 data: searchData, 31 dataType: "json", 32 timeout: defOption.timeout, 33 34 async: true, 35 beforeSend: defOption.befFun, 36 success: defOption.sucFun, 37 }); 38 }
這個方法就是請求接口獲取數據的方法,裡面默認獲取了頁面中的pageSize,currentPage兩個分頁所需要的參數,這裡采用的是get方式來請求,當然可以寫成post,不過需要後台支持post就行了;
我們再看查詢列表方法:
1 //查詢列表 2 listFun: function (option) { 3 4 if (option) { 5 $.extend(defOption, option); 6 } 7 8 //默認格式 9 var tab = []; 10 tab.push('<table id="' + defOption.tabId + '" class="table table-bordered table-hover">'); 11 tab.push('<thead><tr role="row">'); 12 13 for (var i in defOption.headText) { 14 15 var head = defOption.headText[i]; 16 17 if (head.colType == "label") { 18 tab.push('<th class="center" tabindex="0" rowspan="1" colspan="1">' + head.nickName + '</th>'); 19 } else if (head.colType == "checkbox") { 20 tab.push('<th class="center " rowspan="1" colspan="1" aria-label="">'); 21 tab.push(' <label class="pos-rel">'); 22 tab.push(' <input type="checkbox" name="cbAll" class="ace">'); 23 tab.push(' <span class="lbl">' + head.nickName + '</span>'); 24 tab.push(' </label>'); 25 tab.push('</th>'); 26 } else { 27 tab.push('<th class="center" tabindex="0" rowspan="1" colspan="1">' + head.nickName + '</th>'); 28 } 29 } 30 tab.push('</tr></thead>'); 31 tab.push('<tbody><tr><td class="text-center" colspan="' + defOption.headText.length + '">' + defOption.loading + '</td></tr></tbody>'); 32 tab.push('</table>'); 33 tab.push('<div id="divPager" class="text-center"></div>'); 34 $("#" + defOption.showId).html(tab.join('')); 35 //全選事件 36 $("input[type='checkbox'][name='cbAll']").on("click", function () { 37 38 var cbStatus = $(this).is(":checked"); 39 if (cbStatus) { 40 $("input[name='cb']:checkbox").prop("checked", true); 41 } else { 42 $("input[name='cb']:checkbox").prop("checked", false); 43 } 44 }); 45 46 //數據返回成功處理 47 defOption.sucFun = function (data) { 48 49 var head = $("table[id='" + defOption.tabId + "'] tbody"); 50 if (data) { 51 if (data.MoResult) { 52 //遍歷table展示的數據 53 var rows = []; 54 $.each(data.MoResult, function (i, item) { 55 rows.push('<tr>'); 56 57 var modalHeadText = ""; 58 for (var h_i in defOption.headText) { 59 var head = defOption.headText[h_i]; 60 var item_val = item[head.name]; 61 if (item_val && typeof (item_val) != "undefined") { } else { item_val = ""; } 62 63 //時間格式化 64 if (head.format && item_val.length > 0) { 65 console.log(item_val); 66 var date = new Date(parseInt(item_val.replace("/Date(", "").replace(")/", ""), 10)); 67 item_val = head.format. 68 replace("yyyy", date.getFullYear()). 69 replace("MM", date.getMonth() + 1). 70 replace("dd", date.getDate()). 71 replace("HH", date.getHours()). 72 replace("mm", date.getMinutes()). 73 replace("ss", date.getMilliseconds()); 74 } 75 76 //獲取模式窗體頭部信息 77 if (modalHeadText.length <= 0) { modalHeadText = head.isModalHeadText ? item_val : "" }; 78 if (head.colType == "label") { 79 80 81 rows.push('<td class="center">' + item_val + '</td>'); 82 } else if (head.colType == "checkbox") { 83 rows.push('<td class="center">'); 84 rows.push(' <label class="pos-rel">'); 85 rows.push(' <input type="checkbox" name="cb" value="' + item_val + '" class="ace">'); 86 rows.push(' <span class="lbl"></span>'); 87 rows.push(' </label>'); 88 rows.push('</td>'); 89 } else if (head.colType == "operate") { 90 91 rows.push('<td class="center"><div class="hidden-sm hidden-xs action-buttons">'); 92 if (defOption.editeOption.url.length > 0) { 93 var editOption = $.extend({}, defOption.editeOption); 94 editOption.url += "/" + item_val; 95 editOption.title += modalHeadText.length > 0 ? "-" + modalHeadText : ""; 96 97 var op = JSON.stringify(editOption); 98 rows.push('<a class="blue" data-item=\'' + op + '\' href="javascript:;"><i class="ace-icon fa fa-pencil bigger-130"></i></a>'); 99 } 100 if (defOption.viewOption.url.length > 0) { 101 102 var viewOption = $.extend({}, defOption.viewOption); 103 viewOption.url += "/" + item_val; 104 viewOption.title += modalHeadText.length > 0 ? "-" + modalHeadText : ""; 105 106 var op = JSON.stringify(viewOption); 107 rows.push('<a class="blue" data-item=\'' + op + '\' href="javascript:;"><i class="ace-icon fa fa-search-plus bigger-130"></i></a>'); 108 } 109 if (defOption.delOption.url.length > 0) { 110 111 var delOption = $.extend({}, defOption.delOption); 112 delOption.url += "/" + item_val; 113 delOption.title += modalHeadText.length > 0 ? "-" + modalHeadText : ""; 114 115 var op = JSON.stringify(delOption); 116 rows.push('<a class="blue" data-item=\'' + op + '\' href="javascript:;"><i class="ace-icon fa fa-trash-o bigger-130"></i></a>'); 117 } 118 rows.push('</div></td>'); 119 } 120 else { 121 122 rows.push('<td class="center">' + item_val + '</td>'); 123 } 124 } 125 rows.push('</tr>'); 126 }); 127 128 //頁數展示 129 if (data.MoResult.length > 0) { 130 var pager = []; 131 pager.push('<div class="text-center">'); 132 pager.push(' <ul class="pagination">'); 133 var nPager = defOption.showPageTab;//每次展示6個分頁 134 //上一頁 135 var nprev = (data.CurrentPage - 1 >= 1 ? data.CurrentPage - 1 : 1); 136 pager.push(' <li class="paginate_button previous" aria-controls="dynamic-table" tabindex="0" id="dynamic-table_previous">'); 137 pager.push(' <a href="javascript:;" name="npager" data-page="' + nprev + '">上一頁</a>'); 138 pager.push(' </li>'); 139 140 //當前頁之前頁碼 141 var preTotal = data.CurrentPage - nPager >= 1 ? data.CurrentPage - nPager : 1; 142 for (var i = preTotal; i < data.CurrentPage ; i++) { 143 144 pager.push(' <li class="paginate_button ' + (i == data.CurrentPage ? "active disabled" : "") + '" aria-controls="dynamic-table">'); 145 pager.push(' <a href="javascript:;" name="npager" data-page="' + i + '">' + i + '</a>'); 146 pager.push(' </li>'); 147 } 148 149 //當前頁 150 pager.push(' <li class="paginate_button active disabled" aria-controls="dynamic-table">'); 151 pager.push(' <a href="javascript:;" name="npager" data-page="' + data.CurrentPage + '">' + data.CurrentPage + '</a>'); 152 pager.push(' </li>'); 153 154 //當前頁以後頁碼 155 var nextTotal = data.CurrentPage + nPager > data.PageTotal ? data.PageTotal : data.CurrentPage + nPager; 156 for (var i = data.CurrentPage + 1; i <= nextTotal; i++) { 157 158 pager.push(' <li class="paginate_button ' + (i == data.CurrentPage ? "active disabled" : "") + '" aria-controls="dynamic-table">'); 159 pager.push(' <a href="javascript:;" name="npager" data-page="' + i + '">' + i + '</a>'); 160 pager.push(' </li>'); 161 } 162 //下一頁 163 var nnext = (data.PageTotal < data.CurrentPage + 1 ? data.PageTotal : data.CurrentPage + 1); 164 pager.push(' <li class="paginate_button next" aria-controls="dynamic-table" tabindex="0" id="dynamic-table_next">'); 165 pager.push(' <a href="javascript:;" name="npager" data-page="' + nnext + '">下一頁</a>'); 166 pager.push(' </li>'); 167 168 pager.push(' </ul>'); 169 //分頁查詢條件 170 pager.push('<div>'); 171 pager.push(' <form>'); 172 pager.push(' <input type="hidden" name="pageSize" value="' + defOption.pageSize + '"/>'); 173 pager.push(' <input type="hidden" name="currentPage" value="' + defOption.currentPage + '"/>'); 174 pager.push(' </form>'); 175 pager.push('</div>'); 176 177 pager.push('</div>'); 178 179 //移除加載中 180 //head.html(""); 181 //添加結果 182 head.html(rows.join('')); 183 $("div[id='divPager']").html(pager.join('')); 184 //操作按鈕事件 185 $("a[data-item]").on("click", function () { 186 187 var data_Item = $(this).attr("data-item"); 188 if (data_Item) { 189 var data_Item_Obj = JSON.parse(data_Item); 190 191 defOption.modalExt.modalFun({ 192 width: data_Item_Obj.width, 193 height: data_Item_Obj.height, 194 url: data_Item_Obj.url, 195 title: data_Item_Obj.title, 196 callback: defOption.callback 197 }); 198 } 199 }); 200 //綁定分頁按鈕事件 201 $("a[name='npager']").on("click", function () { 202 203 var nPager = $(this).attr("data-page"); 204 if (nPager.length <= 0) { return; } 205 206 $("form input[name='currentPage']").val(nPager); 207 208 //執行請求後台 209 ajaxFun(defOption); 210 }); 211 } else { 212 head.html("<tr><td class=\"text-center\" colspan=\"" + defOption.headText.length + "\">未能查詢到數據!</td></tr>"); 213 $("div[id='divPager']").html(""); 214 } 215 } else { 216 head.html("<tr><td class=\"text-center\" colspan=\"" + defOption.headText.length + "\">未能查詢到數據。</td></tr>"); 217 $("div[id='divPager']").html(""); 218 } 219 } else { 220 head.html("<tr><td class=\"text-center\" colspan=\"" + defOption.headText.length + "\">未能查詢到數據</td></tr>"); 221 $("div[id='divPager']").html(""); 222 } 223 }; 224 if (option) { 225 $.extend(defOption, option); 226 } 227 228 //執行請求後台 229 ajaxFun(defOption); 230 } View Code這個方法體挺長的,主要操作是:
默認格式展示列表頭部並呈現出加載中的提示=》綁定復選框全選事件=》創建數據返回成功函數sucFun()=》調用請求後台方法ajaxFun();
再來看函數sucFun()等到數據返回後執行的操作是:
遍歷json返回數據展示到table中(其中包括了時間格式化的處理,復選框,label及操作按鈕類型operate的初始化)=》頁數展示及事件綁定(目前只有上一頁,當前頁之前頁碼,當前頁,當前頁以後頁碼,下一頁的效果展示,分頁查詢條件(生成pagesize和currentPage隱藏控件),綁定分頁按鈕事件)
以上就是shenniu.pager.js整個插件內容,不多且清晰,感覺分享給大家挺高興,下面貼出示例中用到的js文件和css文件可以在這裡下載