在前面的隨筆中,已經介紹了ABP的增刪改查的操作,但是對於查詢的數據並沒有進行分頁,只是進行粗糙的展示,今天的隨筆中將摸索進行分頁展示。這里打算使用的分頁插件是DataTables,這是一款比較強大的表格插件。
在以前我們后台手動分頁的時候,需要前台傳入兩個重要的分頁參數:PageIndex和PageSize(顯示第幾頁的數據和每頁顯示的數量),這是必須的量的參數。分頁作為一個頁面展示的基礎功能,ABP框架已經對分頁功能進行了一些方便性的操作,為我們提供了一些有助於分頁的接口和Dto,Dto是什么?這個在前面的隨筆中已經研究過了,這里就不再重復。
一 .ABP中的分頁接口
在ABP中總共為我們提供了三個分頁的接口:IPagedResultRequest、ISortedResultRequest、ILimitedResultRequest三個接口
從上面的三個接口中我們看到了三個重要的變量,這就是我們分頁和排序中經常用到的量。
二. 實現分頁的Dto
在我們免費的ABP中模板中,也就是只能找到這么三個接口,對我們分頁來說確實並沒有提供了多大的方便,但是在ABP Zero中已經對三個接口進行了相應的實現,只是zero是收費的。在這里我們可以模仿zero在我們ABP模板中添加上分頁的Dto,並且為DataTables這個插件定制分頁Dto。
詳細的代碼
1 1.PagedInputDto 2 public class PagedInputDto : IPagedResultRequest 3 { 4 /// <summary> 5 /// 每頁顯示的行數 6 /// </summary> 7 [Range(1, AppConsts.MaxPageSize)] 8 public int MaxResultCount { get; set; } 9 /// <summary> 10 /// 跳過數量=MaxResultCount*頁數 11 /// </summary> 12 [Range(0, int.MaxValue)] 13 public int SkipCount { get; set; } 14 15 public PagedInputDto() 16 { 17 MaxResultCount = AppConsts.DefaultPageSize; 18 } 19 } 20 2. PagedAndFilteredInputDto 21 public class PagedAndFilteredInputDto : IPagedResultRequest 22 { 23 [Range(1, AppConsts.MaxPageSize)] 24 public int MaxResultCount { get; set; } 25 26 [Range(0, int.MaxValue)] 27 public int SkipCount { get; set; } 28 29 public string Filter { get; set; } 30 31 public PagedAndFilteredInputDto() 32 { 33 MaxResultCount = AppConsts.DefaultPageSize; 34 } 35 } 36 3. PageAndSortedInputDto 37 public class PagedAndSortedInputDto : PagedInputDto, ISortedResultRequest 38 { 39 public string Sorting { get; set; } 40 41 public PagedAndSortedInputDto() 42 { 43 MaxResultCount = AppConsts.DefaultPageSize; 44 } 45 } 46 4.PagedSortedAndFilteredInputDto 47 public class PagedSortedAndFilteredInputDto : PagedAndSortedInputDto 48 { 49 public string Filter { get; set; } 50 //接收DataTables的參數 51 public int Draw { get; set; } 52 public int Length 53 { 54 get 55 { 56 return this.MaxResultCount; 57 } 58 59 set 60 { 61 this.MaxResultCount = value; 62 } 63 } 64 public int Start 65 { 66 get 67 { 68 return this.SkipCount; 69 } 70 71 set 72 { 73 this.SkipCount = value; 74 } 75 } 76 } 77 5.DataTablesPageOutPutDto 78 [Serializable] 79 public class DataTablesPagedOutputDto<T>:PagedResultDto<T> 80 { 81 public int Draw { get; set; } 82 83 /// <summary> 84 /// 過濾后的記錄數(沒有就是全部),這個是必須的參數 85 /// </summary> 86 public int RecordsFiltered { get; set; } 87 88 public int RecordsTotal { get { return this.TotalCount; } } 89 90 public DataTablesPagedOutputDto(int totalCount, IReadOnlyList<T> items) 91 : base(totalCount, items) 92 { 93 this.RecordsFiltered = totalCount; 94 } 95 }
其中PagedSortedAndFilteredInputDto和DataTablesPageOutPutDto分別是為了適應DataTables的需求定制的兩個類,Input的類中Start、Length、Draw、Filter都是為了接收DataTables傳遞過來的參數,在OutPut類中定義了RecordsFiltered和recordsTotal和Draw這些都是DataTables需要的參數。說了這么多,還是先看一下DataTables這插件再說。
三.DataTables分頁
在這里我們使用的服務端分頁,詳細的內容可查看官網的具體介紹:http://www.datatables.club/manual/server-side.html
(1)Dto的請求參數
當然參數還有許多,但是主要的參數也就是上面的那幾個,尤其是已經圈出來的這三個,就可以完成分頁功能了,如果需要進行排序或者添加按照字段的搜索的功能,那么就需要用到下面的字段了,我們這里只是使用分頁功能。
(2)Dto的返回參數
通過了上面DataTables官網的介紹,我們已經清楚了我們Dto中定義的參數的作用了,不知道大家有沒有一點困惑,就是Draw參數到底是干什么的???哈哈哈,我們在DataTables中已經找到了答案,他是防止跨站腳本攻擊的,關於他的賦值,只要給他賦值一個整數就可以了。
三.在ABP中使用DataTables實現分頁
View
Index的具體代碼
@using Abp.Authorization.Users @using StudyABPProject.Web.Startup @model IList<StudyABPProject.Movie.Dto.MovieTicketDto> @{ ViewBag.CurrentPageName = PageNames.Movies; // The menu item will be active for this page. } @section scripts { <script src="~/lib/jquery-daterangepicker/daterangepicker.js" asp-append-version="true"></script> <script src="~/view-resources/Views/Movie/Index.js" asp-append-version="true"></script> <link href="~/lib/jquery-daterangepicker/daterangepicker.css" rel="stylesheet" /> <link href="~/lib/datatables/jquery.dataTables.min.css" rel="stylesheet" /> <script src="~/lib/datatables/jquery.dataTables.min.js"></script> } <div class="row clearfix"> <div class="col-lg-12 col-md-12 col-sm-12 col-xs-12"> <div class="card"> <div class="header"> <h2> @L("Movie") </h2> <ul class="header-dropdown m-r--5"> <li class="dropdown"> <a href="javascript:void(0);" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"> <i class="material-icons">more_vert</i> </a> <ul class="dropdown-menu pull-right"> <li><a id="RefreshButton" href="javascript:void(0);" class="waves-effect waves-block"><i class="material-icons">refresh</i>Refresh</a></li> </ul> </li> </ul> </div> <div class="body table-responsive"> <button type="button" class="btn btn-primary waves-effect waves-float pull-right" data-toggle="modal" data-target="#MovieTicketCreateModal"> <i class="material-icons">添加</i> </button> <table id="MovieTable" name="MovieTable"></table> </div> </div> </div> </div> <div class="modal fade" id="MovieTicketCreateModal" tabindex="-1" role="dialog" aria-labelledby="UserCreateModalLabel" data-backdrop="static"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <h4 class="modal-title"> <span>@L("CreateMovieTicket")</span> </h4> </div> <div class="modal-body"> <form name="movieCreateForm" role="form" novalidate class="form-validation"> <div class="tab-content"> <div role="tabpanel" class="tab-pane animated fadeIn active" id="create-user-details"> <div class="row clearfix" style="margin-top:10px;"> <div class="col-sm-12"> <div class="form-group form-float"> <div class="form-line"> <input class="form-control" type="text" name="MovieName" required maxlength="256" minlength="2"> <label class="form-label">@L("MovieName")</label> </div> </div> </div> </div> <div class="row clearfix"> <div class="col-sm-12"> <div class="form-group form-float"> <div class="form-line"> <input type="text" name="MovieActor" class="form-control" required maxlength="256"> <label class="form-label">@L("MovieActor")</label> </div> </div> </div> </div> <div class="row clearfix"> <div class="col-sm-12"> <div class="form-group form-float"> <div class="form-line"> <input type="datetime" name="StartTime" class="form-control" required> @*<label class="form-label">@L("StartTime")</label>*@ </div> </div> </div> </div> <div class="row clearfix"> <div class="col-sm-12"> <div class="form-group form-float"> <div class="form-line"> <input type="datetime" id="EndTime" name="EndTime" class="form-control"> @*<label class="form-label">@L("EndTime")</label>*@ </div> </div> </div> </div> <div class="row clearfix"> <div class="col-sm-12"> <div class="form-group form-float"> <div class="form-line"> <input type="number" id="Money" name="Money" class="form-control"> <label class="form-label">@L("Money")</label> </div> </div> </div> </div> </div> </div> <div class="modal-footer"> <button type="button" class="btn btn-default waves-effect" data-dismiss="modal">取消</button> <button type="submit" id="btnSave" class="btn btn-primary waves-effect">保存</button> </div> </form> </div> </div> </div> </div> <div class="modal fade" id="MovieTicketEditModal" tabindex="-1" role="dialog" data-backdrop="static"> <div class="modal-dialog" role="document"> <div class="modal-content"> </div> </div> </div>
Js
關於js代碼的位置我也按照框架中的位置放在了view-resources,話說Js代碼離View有點遠~~~
Index.Js中的主要代碼
(function () { $(function () { var _movieService = abp.services.app.movieTicket; var _$modal = $('#MovieTicketCreateModal'); var _$form = _$modal.find('form[name="movieCreateForm"]'); _$form.validate({ rules: { MovieName: { required:true }, StartTime: "required", EndtTime: { required: true }, MovieActor: "required" ,Money:"required" }, messages: { MovieName: { required:"電影名稱不能為空" }, MovieActor: { required: "演員名稱不能為空" }, StartTime: { required: "開始時間不能為空" }, EndTime: { required: "結束時間不能為空" }, Money: { required: "票價不能為空" } } }); var dateOption = { locale: { format: 'YYYY-MM-DD HH:mm:ss', applyLabel: '確認', cancelLabel: '取消' }, singleDatePicker: true, startDate: moment().format("YYYY-MM-DD HH:mm:ss"), timePicker24Hour: true, timePicker: true, autoApply: true, autoUpdateInput: true }; $('input[name=StartTime]').daterangepicker(dateOption); $('input[name=EndTime]').daterangepicker(dateOption); $('#RefreshButton').click(function () { refreshUserList(); }); $('.delete-movie').click(function () { var movieId = $(this).attr("data-movie-id"); var movieName = $(this).attr("data-movie-name"); abp.message.confirm( "刪除電影 '" + movieName + "'?", function (isConfirmed) { if (isConfirmed) { _movieService.deleteMovie({ "id": movieId, "movieName": movieName, }).done(function () { refreshMovieList(); }); } } ); }); $('.edit-movie').click(function (e) { var movieId = $(this).attr("data-movie-id"); e.preventDefault(); $.ajax({ url: abp.appPath + 'MovieTicket/EditMovieTicketModal?movieId=' + movieId, type: 'POST', contentType: 'application/html', success: function (content) { $('#MovieTicketEditModal div.modal-content').html(content); }, error: function (e) { } }); }); _$form.find('button[type="submit"]').click(function (e) { e.preventDefault(); if (!_$form.valid()) { return; } var movie = _$form.serializeFormToObject(); abp.ui.setBusy(_$modal); _movieService.createMovie(movie).done(function (response) { if (response == "No") { abp.message.error("創建失敗"); } else { _$modal.modal('hide'); location.reload(true); } }).always(function () { abp.ui.clearBusy(_$modal); }); }); _$modal.on('shown.bs.modal', function () { _$modal.find('input:not([type=hidden]):first').focus(); }); function refreshMovieList() { location.reload(true); //reload page to see new user! } function deleteUser(userId, userName) { } var CONSTANT = { DATA_TABLES: { DEFAULT_OPTION: { //DataTables初始化選項 language: { "sProcessing": "處理中...", "sLengthMenu": "每頁 _MENU_ 項", "sZeroRecords": "沒有匹配結果", "sInfo": "當前顯示第 _START_ 至 _END_ 項,共 _TOTAL_ 項。", "sInfoEmpty": "當前顯示第 0 至 0 項,共 0 項", "sInfoFiltered": "(由 _MAX_ 項結果過濾)", "sInfoPostFix": "", "sSearch": "搜索:", "sUrl": "", "sEmptyTable": "表中數據為空", "sLoadingRecords": "載入中...", "sInfoThousands": ",", "oPaginate": { "sFirst": "首頁", "sPrevious": "上頁", "sNext": "下頁", "sLast": "末頁", "sJump": "跳轉" }, "oAria": { "sSortAscending": ": 以升序排列此列", "sSortDescending": ": 以降序排列此列" } }, autoWidth: false, //禁用自動調整列寬 stripeClasses: ["odd", "even"],//為奇偶行加上樣式,兼容不支持CSS偽類的場合 order: [], //取消默認排序查詢,否則復選框一列會出現小箭頭 processing: false, //隱藏加載提示,自行處理 serverSide: true, //啟用服務器端分頁 searching: false //禁用原生搜索 }, COLUMN: { CHECKBOX: { //復選框單元格 className: "td-checkbox", orderable: false, width: "30px", data: null, render: function (data, type, row, meta) { return '<input type="checkbox" class="iCheck">'; } } }, RENDER: { //常用render可以抽取出來,如日期時間、頭像等 ELLIPSIS: function (data, type, row, meta) { data = data || ""; return '<span title="' + data + '">' + data + '</span>'; } } } }; var getQueryCondition=function(data) { var param = {}; //組裝排序參數 if(data.order&&data.order.length && data.order[0]) { //組裝分頁參數 param.start = data.start; param.length = data.length; param.draw = data.draw; return param; } var page = { $table: $("#MovieTable"), $dataTable: null, initDataPicker: function () { var dataOption = { startDate: moment().startOf("month"), "maxDate": null, singleDatePicker: true }; var dataOption1 = { startDate: moment().endOf("month"), "maxDate": null, singleDatePicker: true }; $("#StartTime").WIMIDaterangepicker(dataOption); $("#EndTime").WIMIDaterangepicker(dataOption1); }, initTable: function () { if (!$.fn.DataTable.isDataTable("#MovieTable")) { page.$datatable = page.$table.DataTable($.extend(true, {}, CONSTANT.DATA_TABLES.DEFAULT_OPTION, { ajax: function (data, callback, settings) { //封裝請求參數 var param = getQueryCondition(data); $.ajax({ type: "GET", url: "/api/services/app/" + "movieTicket/getAllMovieTicketPage", cache: false, //禁用緩存 data: param, //傳入已封裝的參數 dataType: "json", success: function (response) { //封裝返回數據 var returnData = {}; returnData.draw = response.result.draw;//這里直接自行返回了draw計數器,應該由后台返回 returnData.recordsTotal = response.result.recordsFiltered;//總記錄數 returnData.recordsFiltered = response.result.recordsFiltered;//后台不實現過濾功能,每次查詢均視作全部結果 returnData.data = response.result.items; //調用DataTables提供的callback方法,代表數據已封裝完成並傳回DataTables進行渲染 //此時的數據需確保正確無誤,異常判斷應在執行此回調前自行處理完畢 callback(returnData); }, error: function (XMLHttpRequest, textStatus, errorThrown) { alert("查詢失敗"); } }); }, "paging": true, //綁定數據 "columns": [ { "defaultContent": "", "title": "操作", "orderable": false, "width": "150px", "className": "text-center not-mobile", "createdCell": function (td, cellData, rowData, row, col) { var $actionContent = $("<div class='action-content'>"); $('<button class="btn btn-xs">修改</button>') .appendTo($actionContent) .click(function () { alert(rowData.startTime); console.log(rowData); }); $('<button class="btn btn-xs"> 刪除 </button>') .appendTo($actionContent) .click(function () { alert(rowData.id); }); $(td).append($actionContent); }
},
{
"data": "movieName",
"title": "電影名稱"
},
{
"data": "movieActor",
"title": "演員名稱",
"width": "120px",
},
{
"data": "startTime",
"title": "開始時間",
"render": function (data, type, full, meta) {
return moment(data).format("YYYY-MM-DD HH:mm:ss");
}
},
{
"data": "endTime",
"title": "結束時間",
"render": function (data, type, full, meta) {
return moment(data).format("YYYY-MM-DD HH:mm:ss");
}
},
{
"data": "money",
"title": "票價",
}
], }));//此處需調用api()方法,否則返回的是JQuery對象而不是DataTables的API對象 } else { page.$datatable.ajax.reload(); } }, init: function () { page.initTable(); } } page.init(); }); })();
在這里需要感謝https://blog.csdn.net/u011072139/article/details/54312414?locationnum=10&fps=1,從這篇博客類借鑒了一些Jscript代碼。
注意的問題:
(1)在使用DataTables的時候,經常出現一個錯誤,錯誤的提示:沒有“length”,其實出現這個錯誤的原因是沒有為Data賦值,DataTables需要返回Data,然后它會自動計算length,所以只要將Data賦值並返回即可。
(2)returnData.data = response.result.items;從這行代碼中可以看出,我們返回的數據並不是一個簡單的對象,不能直接訪問我們在后台傳出的屬性,多封裝了一層。
上面的代碼中只是實現了分頁的功能,關於刪除和修改並沒有重新實現。需要注意的是在DataTables盡心后台訪問的時候的請求路徑url,url: "/api/services/app/" + "movieTicket/getAllMovieTicketPage",這的路徑並不是具體的控制器中的方法,而是直接訪問的Application層的服務方法,這里就涉及了ABP中動態的Js代理,還一個需要重點關注的是type必須是Get類型,否則是無法找到訪問路徑的,這是因為在ABP中動態Js代理默認的請求方式是get。關於動態的Js代理在ABP中的應用這個將會在后面的隨筆去研究。
后台主要的代碼
public async Task<PagedResultDto<MovieTicketDto>> GetAllMovieTicketPage(MovieInputDto input) { var query = movieTicketRepository.GetAll() ; var totalCount =await query.CountAsync(); var models =await query.OrderBy(input.Sorting).AsNoTracking().PageBy(input).ToListAsync(); if (models.Count()==0) { return new DataTablesPagedOutputDto<MovieTicketDto>(0, new List<MovieTicketDto>()); } var items = models.MapTo<List<MovieTicketDto>>(); return new DataTablesPagedOutputDto<MovieTicketDto>(totalCount,items); }
到此為止,基本的分頁功能已經實現,下面看一下運行的效果吧
請求的數據:
返回的數據: