在前面的随笔中,已经介绍了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代码。
上面的代码中只是实现了分页的功能,关于删除和修改并没有重新实现。需要注意的是在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); }
到此为止,基本的分页功能已经实现,下面看一下运行的效果吧