Write By Monkeyfly
以下内容均为原创,如需转载请注明出处。
该博客是去年写的,当时只写了一部分,截至目前还未完全写完。
分页的实现效果可以参考: Ant Design 实现的分页组件
一定要注意:思路很重要,如果是第一次写,先看一看标准示例是如何实现分页的,在示例上用鼠标多点几下,多操作几下,熟悉一下分页的用法,多动脑思考一下,然后就知道怎么去实现了。
前提
- 现阶段要对以前的代码进行重构,再加上我之前也没有写过分页的功能,于是就在原有代码的基础上进行重写。
- 刚开始我也不会写,我也只能是参考以前的写法,在自己完全理解的基础上进行重写。
- 那如何进行下手呢?
原理分析
我用点击地址簿图标,获取收寄件人地址信息的案例
进行分析一下:
整个弹窗的结构:分为四部分:标题、内容、页脚以及 loading 部分。
底部分页的结构:左右结构,分别是:左侧的快速跳转以及总页数;右侧的翻页以及页码列表
左侧的快速跳转部分:
左侧的分页大小列表选择部分:
- 点击地址簿图标后,向后台发起请求,获取收件人或寄件人的所有保存的地址信息。
注:在每次点击地址簿图标获取数据前,记得要清空数据列表,以及将所有有变动的地方恢复默认(可以称之为初始化操作)
。
//要清空地址列表信息、页码选择数字框、以及总页数(这些都需要重新渲染)
$(".addrbook-list").empty();
$(".page-num-list").empty();
$(".total-pages").text("");
$(".layer-shade-addrbook").show();
//最后,还要重置分页大小的值(默认值为2)
$("#ipt_pageSize").val("2");
- 其实在第一次获取数据时,就需要对数据进行 分页的处理。因为当前端拿到后台返回的所有地址信息时,就会默认展示第一页的数据。
// 第一次向后台请求数据时,前台传递给后台的请求参数如下:
"data": {
"currentPage":1, // 当前页数(要请求第几页的数据)
"pageSize":2, // 分页大小(即每页要展示多少条数据)
"type":"S", // 要查询的收寄件人类型
"refreshTime":"2019-08-04 17:56:11" // 当前时间戳
}
-
我们向后台发送分页的数据请求后,后台除了会给我们返回相应的数据列表之外,返回的数据还应该包括什么呢?肯定会包含分页相关的信息:数据的总条数
record(或者 total,总之都是自定义的命名)
、总页数、分页大小以及当前页数。至于总页数(所有的数据总共能分多少页) 完全是由 分页大小pageSize
(即每页展示几条数据) 决定的,即totalPages = Math.ceil(record / pageSize)
。 -
所以,在我们第一次向后台请求数据时,就要和后端约定好第一次请求数据要传递的参数有哪些。:
- 当前页数
currentPage
(即要获取哪一页的数据)【如果不传的话,后台会默认返回第一页的数据;或者第一次获取时向后台传个常量:1
,用于表示获取第一页的数据】 - 分页大小
pageSize
(即一页展示几条数据)【如果不传的话,后台会有自己设定的默认值返回给前端;这个参数是后面计算总页数要用的。】注:
1.毕竟后台是采用100%不信赖前端的原则。前端传递给后台的数据的,无论传的什么值,后台都会在原有基础上加上一层逻辑判断。这也是为了安全考虑。
2.总页数一般后台都会返回给前台,如果未返回需要前台自己计算。
- 人物类型
type
(是获取寄件人还是收件人的地址信息)【必须项】 - 当前时间戳
timeStamp
(传递什么类型或者什么格式的时间,这个前后端协商好就行)
综上所述,每次获取数据进行分页时,需要传递4个参数,分别是:
//4个参数都不是写死的,都是每次传递时动态变化的
var data = {
"currentPage":currentPage,
"pageSize":pageSize,
"type":type,
"timeStamp":time
};
- 那么,我们试着想一想,后台会给我们返回什么样的数据呢?
- 当前页的所有数据
dataList
- 分页大小
pageSize
- 总的数据条数
record
- 当前页数
currentPage
- 总页数
totalPages
(若后台未返回需要前端自行计算,当然计算方法也非常简单)
后台返回给前台的数据如下图所示:
- 接下来,就要写第一次获取数据之后的逻辑部分了。从后台拿到数据之后,前端要做哪些事情呢?【根据自己项目需求的实现来写,不同的实现,逻辑处理也不一样。此处我按照我们项目中的需求来写】
- 从后台拿到数据之后,要做
3
件事情:
- 根据获取到的数据
dataList
来 动态的渲染当前页的数据列表。 - 根据获取到的 总记录条数
record
和 分页大小pageSize
,计算出 总页数totalPages
。
//总页数 = 总条数 / 分页大小(向上取整)
var totalPages = Math.ceil(total/pageSize);
$(".total-pages").text(totalPages);
具体如下图所示:
- 根据总页数
totalPages
生成分页条(可以设定分页条中最多生成几个分页数字框,最大数量可以自己进行控制,我们这里规定最多不能超过5
个)【所以,此处需要进行判断,限制一下分页条中数字框的个数】
//注:总页数 = 分页数字框个数,且最多不超过5个
if(totalPages > 5){
for(var j=1;j<=5;j++){
$('<li class="item-page-num">'+j+'</li>').appendTo($('.page-num-list'));
}
}else{
for(var j=1;j<=totalPages;j++){
$('<li class="item-page-num">'+j+'</li>').appendTo($('.page-num-list'));
}
}
- 然后给每个分页数字框注册点击事件。
$('.item-page-num').on('click',getInfoByPageNum);
- 现在首页(第一页)要展示的数据已经获取到了,接下来要做的就是切换页码的功能:当点击每个分页数字框中的页码时,如何获取对应页码的相关地址数据信息。
- 和第一次获取数据的接口一样,只是传的参数不同罢了。【这次主要是当前页数
currentPage
和分页大小pageSize
】
传递的参数如下图所示:
//4个参数都不是写死的,都是每次传递时动态变化的
var data = {
"currentPage":currentPage,//当前页需要点击时动态获取,既然发生变化了,就要动态传递。
"pageSize":pageSize,//分页大小同样也要传给后台,既然没有发生变化就传递默认值。
"type":type,
"timeStamp":time
};
- 先改变所选数字框的样式
设置类名为 .current 的样式
(即取消之前页码的选中状态,设置当前已选页码的选中状态) - 然后获取当前所点击分页数字框中的页数
currentPage
并保存(当前点击第几页,该数据要传给后台) - 再获取当前的分页大小
pageSize
(每页放几条数据,也要传给后台) - 然后根据后台返回的数据动态渲染地址数据信息
addrbook-list
- 注:切换页码时只需要重新渲染地址数据信息并更新页码的选中状态即可,其他地方不用改动(即分页数字框
page-num-list
不用像第一次获取数据时重新创建一遍,只改变页面的选中状态即可)。
- 然后要做的事情就是:分页大小列表的点击事件(快速跳转页码的功能)。即点击切换不同的分页大小
pageSize
,再获取相应的数据,进行地址簿列表的渲染和分页信息的重建。
- 问:为什么此处要重建分页信息?
- 答:因为总的数据条数
record
不变,而分页大小pageSize
改变了,所以总页数也改变了。
注意下面的区别:
- 如果只是点击切换页数,则不需要重建分页数字框。
- 如果是点击切换分页大小,或者第一次获取地址信息时,则必须清空分页大小数字框,然后根据计算出来的总页数进行重建。
- 最后要实现的就是翻页操作了。【难点】
- 我们要知道翻页分为两部分:前翻页和后翻页,即平时所说的:切换至上一页、切换至下一页。
- 注意:这里的上一页和下一页,说明一次性只能上翻一页或者下翻一页。如果我们想一次性上翻或者下翻多页呢,应该怎么去做呢?
- 其实道理都是一样的,只是传递的参数不同罢了。假设我们定义一个专门用于点击翻页的方法
pageTurning()
, 在每次进行翻页操作时,通过传递参数值的不同来控制上翻页还是下翻页,以及一次翻多少页。 - 我们可以在每次翻页时,给
pageTurning()
翻页方法传递当前需要翻页的页码值(这里用 n 表示),即pageTurning(n)
来控制一次要翻的页数。 - 那么,上翻页和下翻页如何体现呢?我们只需要控制
参数 n
的数值类型即可,即 正数 表示下翻一页,负数表示上翻一页。 这里需要特别说明一下:刚开始实现翻页操作的时候,这块的想法有问题:我可能被之前的写法误导了,误认为翻页操作只是纯上下翻页的动作,即在点击翻页按钮后前后切换页码,仅此而已,而并没有页码值选中状态的改变与数据的重新渲染。
翻页操作想法描述:
// *1* 表示当前页的选中状态
// |...| 表示当前展示的页码列表范围,即最多只展示5页的数据
// () 表示翻页之后不在页码列表范围的页码,也就是被翻动过的页码
翻页前: |*1* 2 3 4 5 |
下翻一页后: (*1*) | 2 3 4 5 6 |
下翻一页后: (*1*) (2) | 3 4 5 6 7 |
// 由上可见:执行翻页操作后,此时并未做点击切换页码的操作,那么最初的选中页状态依然保留,翻页操作只是单纯的前后翻动页码列表(整个页面列表中的页码值都 +1 或者 -1),即在当前所展示的页码列表范围内页码值不断进行增减变化。
// 如果在执行翻页操作后,做了切换页码的操作,即点击了某页码,状态会变成如下这样:
点击选中第5页:(1) (2) | 3 4 *5* 6 7 |
// 此时,页码的选中状态才会发生变化。也就是说,触发页码选中状态发生变化的操作只有在点击某个页码值时,而上下翻页并不会直接改变页码的选中状态。
以上就是刚开始时做翻页的想法,当时也不知道,但现在看来这种想法很明显是不对的。而且错的太离谱了,没有和实际相结合,而且自己写出来的翻页操作比较反人类,还是要参考标准的实现效果来写,不要像我一样凭空想象应该怎么去实现,这么做是不对的。
正确的实现效果应该是下面这样的:
- 标准基础分页的实现:
// *1* 表示当前页的选中状态
// |...| 表示当前展示的页码列表范围,即最多只展示5页的数据
翻页前: | *1* 2 3 4 5 |
下翻一页后: | 1 *2* 3 4 5 |
下翻一页后: | 1 2 *3* 4 5 |
// 每翻动一页,页码的选中状态就应该设置为当前翻页后的页码。
// 也就是说,页码值的选中状态从 最初选中的 1 变为 2 再变为 3。
// 注意:此时的页码值并不会发生变化,也就是说向后一页真的只是改变了页码的选中状态并切换到了下一页,而非是整个页码的列表都发生变化,重新渲染,这与我刚开始的翻页想法确实有很大出入。
- 标准更多分页的实现:
而我的更多分页的实现,则是将向后5页
与向前5页
,放在了分页列表的两端。而且思路也与标准的分页思路相差甚远。
恍然大悟,这才是正确的打开方式!!!真不知道以前是怎么想的,现在看来是之前是真傻。
下面就说一说,之前翻页的实现思路吧:(别被我的思路误导了,感觉我这是剑走偏锋)
- 首先,无论是向前还是向后翻页,在我自己看来,当执行翻页操作后,会发生一件神奇的事情:当前页码列表中的每一个页码值都会因为翻页操作而发生数值上的变化,即每个页码值都会根据待翻动的页数执行
+N
和-N
操作。我将这个操作都统一为了 + 操作,即 N 可正可负。 - 即翻页数 n 为正数表示:向后翻页;n 为负数表示:向前翻页。
// *1* 表示当前页的选中状态
// |...| 表示当前展示的页码列表范围,即最多只展示5页的数据
// () 表示翻页之后不在页码列表范围的页码,也就是被翻动过的页码
翻页前: |*1* 2 3 4 5 |
下翻一页后: (*1*) | 2 3 4 5 6 |
下翻一页后: (*1*) (2) | 3 4 5 6 7 |
- 先拿到当前页码列表中左右两端的两个页码值,即当前页码列表展示范围内的最小页码值
(对应左边的页码值1)
和最大页码值(对应右边的页码值5)
。 - 这么做的目的是什么呢?
- 因为要计算出剩余可翻动的页数
remainPages
。 假设翻页前的页码列表为:4 5 6 7 8
,那么就需要分别计算出: - 上翻页的剩余可翻动页数: 如果执行上翻
N
页的操作,还剩余多少页可以被向上翻动;- 或者理解为:前面还剩多少页可以被翻动(被翻到底)。
remainPages_prev = 当前页码列表中的第一个页码值4 - 1 = 3
- 下翻页的剩余可翻动页数: 如果执行下翻
N
页的操作,还剩余多少页可以被向下翻动;- 或者理解为:后面还剩多少页可以被翻动(被翻到底)。
remainPages_next = 总页数 total - 当前页码列表中的最后一个页码值8
,假设总页数为 10, 则remainPages_next = 10 - 8 = 2
- 因为要计算出剩余可翻动的页数
- 为什么要计算剩余页数呢?
- 因为一旦得到了上翻页或者下翻页的剩余页数
remainPages
,就可以根据要翻动的指定页数(即向前或向后翻动N
页),来判断出剩余的页数remainPages
是否满足待上下翻动的页数N
。 - 在这里,我们假设要向前或者向后翻动 5 页,如果此时向前翻动的话,
本应该向前翻 5 页,但是因为向前只剩余 3 页可翻动
,所以实际向前翻动的页数 = 剩余可向前翻动的页数 3
。而如果此时向后翻动的话,因为向后只剩余 2 页可翻动,所以实际向后翻动的页数 = 剩余可向后翻动的页数 2。
- 因为一旦得到了上翻页或者下翻页的剩余页数
- 然后我们就可以根据剩余可翻动的页数
remainPages
与待翻页数N
的关系,计算出实际的翻页数actualPages
。- 也就是说,将剩余可翻动的页数
remainPages
和当前待翻的页数N
作比较,来计算出剩余的页数是否满足向前或者向后翻N
页的要求。 - 如果不满足就说明:当前无法继续按照指定的翻页数
N
来向前或向后翻页了,因为剩下的页数根本不足以翻动指定的页数N
了,所以只能是剩多少页就翻动多少页。- 如果
剩余页数 >= 待翻页数
,才可以翻页至指定的页数 - 否则,
剩几页就翻几页
(最小限度是第一页,最大限度是最后一页)
- 如果
- 也就是说,将剩余可翻动的页数
- 最后,渲染整个页码列表中的页码值
- 上翻,是对实际要翻动的页码值执行减
-
操作,即:当前页码列表中每一个页码值 - 实际的翻页数
- 下翻,则是对实际要翻动的页码值执行加
+
操作,即:当前页码列表中每一个页码值 + 实际的翻页数
- 上翻,是对实际要翻动的页码值执行减
代码实现如下:
/**
* 定义一个空对象 Page,用于进行翻页操作
* 该对象性有一个属性:curSelectedPageNum,用于保存翻页前页数列表中已选择的页码值
* 注:
* 1.对翻页而言,如果翻页之前点击选择了其他页码,那么进行翻页操作时该页码的选择状态必须一直保留。
* 2.不能说翻页前选中了第3页,翻页之后再回到第3页,发现它的选中状态没了,那就不对了。
* 3.只要是翻页前选中了某一页,无论是上翻还是下翻期间,该页的选中状态必须时刻保留着。除非选择切换了其他页。
* 故定义了该全局属性,用于保存翻页前已选择的页码值。
* 4.在每次打开地址簿弹窗时,就要将保存的页码值清除。以防止关闭弹窗后,再次打开弹窗进行翻页时,之前的页码选中状态依旧保留。
*/
var Page = {},
Page.curSelectedPageNum="";
/**
* 翻页操作:适用于上翻页和下翻页,区别在于第三个参数
* @param {总页数文本的类名} totalPagesEle
* @param {页码列表中每个页码选项的类名} PageNumItemEle
* @param {翻页数} n 正数:向后翻页;负数:向前翻页
*/
Page.pageTurning = function (totalPagesEle,PageNumItemEle,n){
var $pageNumItem = $(PageNumItemEle),
totalPages = $(totalPagesEle).text(),//获取当前总页数的值
curMinpageNum = $pageNumItem.first().text(),//页数列表中的首位页码值(上翻页用)
curMaxpageNum = $pageNumItem.last().text();//页数列表中的末位页码值(下翻页用)
//翻页之前首先判断一下,页码列表中是否存在已选页(是单纯的翻页还是选择某页之后进行的翻页操作)
var curSelectedPage = $pageNumItem.filter(".current");
if(curSelectedPage.length !==0 ){//如果存在所选页,则需要保存当前所选页的页码值,并重置其样式
Page.curSelectedPageNum = curSelectedPage.text();
curSelectedPage.removeClass("current");
}
//然后获取剩余页数
var remainPages;
if(n<0){//如果是上翻页:剩余页数 = 当前页数列表中的最小页码值 - 1
remainPages = curMinpageNum - 1;
}else{//如果是下翻页:剩余页数 = 总页数 - 当前页数列表中的最大页码值
remainPages = totalPages-curMaxpageNum;
}
//接下来要根据剩余页数计算出实际的翻页数;(注意:n取绝对值)
var actualPages;
if(remainPages >= Math.abs(n)){// 如果剩余页数 >= 待翻页数,才可以翻页至指定的页数
actualPages = Math.abs(n);
}else{//否则剩几页就翻几页(最小限度是第一页,最大限度是最后一页)
actualPages = remainPages;
}
//最后渲染页数列表中的页码值(上翻是减,下翻则是加)
$pageNumItem.each(function(){
var curPageNum = parseInt($(this).text());
if(n<0){//上翻页:减去相应的页码值
$(this).text(curPageNum - actualPages);
}else{//下翻页:加上相应的页码值
$(this).text(curPageNum + actualPages);
}
});
//渲染完页码值之后,要判断之前是否存在所选页。如果存在的话,则需要渲染翻页后的页码状态
if(Page.curSelectedPageNum){
$pageNumItem.each(function(){
if(Page.curSelectedPageNum == $(this).text()){//渲染后的页码列表,需保持渲染前的相同页码的样式
$(this).addClass("current");
}
});
}
}
/**
* 渲染地址簿列表及分页信息
* @param {当前页数} currentPage
* @param {分页大小} pageSize
* @param {收寄件人类型} type
* @param {判断是不是要渲染的批量寄件} many 是:Y 否:N
* 注:
* 1.切换页码只需要重新渲染地址信息即可,其他地方不用动。
*/
function renderAddrbookInfo(currentPage,pageSize,type){
if(many == "Y"){//如果是批量寄件
//渲染之前要清空全选复选框的选中状态
$("#batchSelectAll").removeClass("selected");
}
//清空分页大小数字框的内容
$(".page-num-list").empty();
//并清空地址列表信息
$(".addrbook-list").empty();
//显示加载等待动画
$(".layer-loading").show();
//获得当前的时间
var time = getFormatTime("yyyy-MM-dd HH:mm:ss");
//向后台传输的数据:当前页数(默认是第1页)、分页大小、收寄件人类型、当前时间
var data = {"currentPage":currentPage,"pageSize":pageSize,"type":type,"refreshTime":time};
$.ajax({ // 自己封装的 ajax 方法
action:"xxxx", // 请求接口名称
version:"V1.0",
data:data,
callback:function(res){
if(res.result){
//隐藏加载等待动画
$(".layer-loading").hide();
var data = res.data,
addrInfoList = data.list,
total = data.record,//获取到的地址信息的总条数
curPage = data.currentPage;//当前页数
result = "";
//首先,渲染发件人地址信息列表
var imgClass;
if(many == "Y"){//如果是批量寄件,存在多选操作,首先要在列表头部追加一个全选的复选框,其次列表中的每一项前都要有一个复选框
if($(".addrbook-head>.img-select").length<1){
$(".addrbook-head").prepend('<i class="img-select img-select-m" id="batchSelectAll"></i>');
}
$("#batchSelectAll").off("click",batchSelectAll).on("click",batchSelectAll);
imgClass = "img-select-m";
}else{//如果是预约寄件,每一项只能单选,所以这里去掉头部的全选复选框,将每一项前的复选框修改为单选按钮
imgClass = "img-select-s";
$(".addrbook-head .img-select-m").remove();
}
//渲染地址列表信息
for(var i=0;i<addrInfoList.length;i++){
result += '<li class="item-addr-info" οnclick="selectedAddrbookInfo(this,\''+many+'\')" data-code="' +addrInfoList[i].PROVINCE_CODE+','+addrInfoList[i].CITY_CODE+','+addrInfoList[i].AREA_CODE+ '">'+
'<i class="img-select '+imgClass+ '"></i>'+
'<span class="info-name">' + addrInfoList[i].CUSTOMER_NAME+ '</span>'+
'<span class="info-tel">' +addrInfoList[i].MOBILE+ '</span>'+
'<span class="info-address">' +addrInfoList[i].PROVINCE_NAME+' '+addrInfoList[i].CITY_NAME+' '+addrInfoList[i].COUNTY_NAME+' '+addrInfoList[i].ADDRESS+ '</span>'+
'</li>';
}
$(".addrbook-list").append(result);
//然后,计算出总页数(totalPages), 总页数 = 总条数 / 分页大小
var totalPages = Math.ceil(total/pageSize);
$(".total-pages").text(totalPages);
//接下来,需要根据总页数创建分页数字框(注:总页数 = 分页数字框个数,且最多不超过5个)
if(totalPages > 5){
if(curPage < 5){//如果当前所点击的页数 小于等于 5 ,从1开始创建页码数字框
for(var i=1;i<=5;i++){
if(i==curPage){//如果当前所渲染的数字框等于当前页数,则添加已选择状态
$('<li class="item-page-num current">'+i+'</li>').appendTo($('.page-num-list'));
}else{
$('<li class="item-page-num">'+i+'</li>').appendTo($('.page-num-list'));
}
}
}else{//如果当前所点击的页数 大于 5,则从...开始创建页码数字框
var remainder = curPage % 5;//当前页数整除5后的余数。可认为是多出来的页数。
if(remainder==0){//如果当前页数是5的倍数,如10,15,20...,则从 当前页-4 的页数开始渲染数字框
for(var i=curPage-4;i<=curPage;i++){
if(i==curPage){//如果当前所渲染的数字框等于当前页数,则添加已选择状态
$('<li class="item-page-num current">'+i+'</li>').appendTo($('.page-num-list'));
}else{
$('<li class="item-page-num">'+i+'</li>').appendTo($('.page-num-list'));
}
}
}else{//如果当前页数不是5的倍数,如6,22,56...则从 (当前页-多余页数)+1 的页码开始渲染,一直到 (当前页-多余页数)+5 的页码处
if((curPage-remainder+5)<totalPages){//如果要渲染的最大页码未超出总页数
for(var i=curPage-remainder+1;i<=curPage-remainder+5;i++){
if(i==curPage){//如果当前所渲染的数字框等于当前页数,则添加已选择状态
$('<li class="item-page-num current">'+i+'</li>').appendTo($('.page-num-list'));
}else{
$('<li class="item-page-num">'+i+'</li>').appendTo($('.page-num-list'));
}
}
}else{//如果要渲染的最大页码已超出总页数
for(var i=totalPages-4;i<=totalPages;i++){
if(i==curPage){//如果当前所渲染的数字框等于当前页数,则添加已选择状态
$('<li class="item-page-num current">'+i+'</li>').appendTo($('.page-num-list'));
}else{
$('<li class="item-page-num">'+i+'</li>').appendTo($('.page-num-list'));
}
}
}
}
}
}else{//如果总页数 小于 5,就创建个数 等于总页数 的数字框
for(var i=1;i<=totalPages;i++){
if(i==curPage){//如果当前所渲染的数字框等于当前页数,则添加已选择状态
$('<li class="item-page-num current">'+i+'</li>').appendTo($('.page-num-list'));
}else{
$('<li class="item-page-num">'+i+'</li>').appendTo($('.page-num-list'));
}
}
}
//最后,给分页数字框注册点击事件
$('.item-page-num').on('click',getInfoByPageNum);
}
}
});
}
未完待续…