在前面的随笔中,已经介绍了ABP的增删改查的操作,但是对于查询的数据并没有进行分页,只是进行粗糙的展示,今天的随笔中将摸索进行分页展示。这里打算使用的分页插件是DataTables,这是一款比较强大的表格插件。
在以前我们后台手动分页的时候,需要前台传入两个重要的分页参数:PageIndex和PageSize(显示第几页的数据和每页显示的数量),这是必须的量的参数。分页作为一个页面展示的基础功能,ABP框架已经对分页功能进行了一些方便性的操作,为我们提供了一些有助于分页的接口和Dto,Dto是什么?这个在前面的随笔中已经研究过了,这里就不再重复。
一 .ABP中的分页接口
在ABP中总共为我们提供了三个分页的接口:IPagedResultRequest、ISortedResultRequest、ILimitedResultRequest三个接口
从上面的三个接口中我们看到了三个重要的变量,这就是我们分页和排序中经常用到的量。
二. 实现分页的Dto
在我们免费的ABP中模板中,也就是只能找到这么三个接口,对我们分页来说确实并没有提供了多大的方便,但是在ABP Zero中已经对三个接口进行了相应的实现,只是zero是收费的。在这里我们可以模仿zero在我们ABP模板中添加上分页的Dto,并且为DataTables这个插件定制分页Dto。
详细的代码
.PagedInputDto
public class PagedInputDto : IPagedResultRequest
{
/// <summary>
/// 每页显示的行数
/// </summary>
[Range(, AppConsts.MaxPageSize)]
public int MaxResultCount { get; set; }
/// <summary>
/// 跳过数量=MaxResultCount*页数
/// </summary>
[Range(, int.MaxValue)]
public int SkipCount { get; set; } public PagedInputDto()
{
MaxResultCount = AppConsts.DefaultPageSize;
}
}
. PagedAndFilteredInputDto
public class PagedAndFilteredInputDto : IPagedResultRequest
{
[Range(, AppConsts.MaxPageSize)]
public int MaxResultCount { get; set; } [Range(, int.MaxValue)]
public int SkipCount { get; set; } public string Filter { get; set; } public PagedAndFilteredInputDto()
{
MaxResultCount = AppConsts.DefaultPageSize;
}
}
. PageAndSortedInputDto
public class PagedAndSortedInputDto : PagedInputDto, ISortedResultRequest
{
public string Sorting { get; set; } public PagedAndSortedInputDto()
{
MaxResultCount = AppConsts.DefaultPageSize;
}
}
.PagedSortedAndFilteredInputDto
public class PagedSortedAndFilteredInputDto : PagedAndSortedInputDto
{
public string Filter { get; set; }
//接收DataTables的参数
public int Draw { get; set; }
public int Length
{
get
{
return this.MaxResultCount;
} set
{
this.MaxResultCount = value;
}
}
public int Start
{
get
{
return this.SkipCount;
} set
{
this.SkipCount = value;
}
}
}
.DataTablesPageOutPutDto
[Serializable]
public class DataTablesPagedOutputDto<T>:PagedResultDto<T>
{
public int Draw { get; set; } /// <summary>
/// 过滤后的记录数(没有就是全部),这个是必须的参数
/// </summary>
public int RecordsFiltered { get; set; } public int RecordsTotal { get { return this.TotalCount; } } public DataTablesPagedOutputDto(int totalCount, IReadOnlyList<T> items)
: base(totalCount, items)
{
this.RecordsFiltered = totalCount;
}
}
其中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="" minlength="">
<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="">
<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[]) {
//组装分页参数
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()==)
{
return new DataTablesPagedOutputDto<MovieTicketDto>(, new List<MovieTicketDto>());
}
var items = models.MapTo<List<MovieTicketDto>>();
return new DataTablesPagedOutputDto<MovieTicketDto>(totalCount,items);
}
到此为止,基本的分页功能已经实现,下面看一下运行的效果吧
请求的数据:
返回的数据: