dataTables.bootstrap.js+PageHelper实现后端分页

如果是做一些管理系统,分页是最常见的场景,本文指导如何使用前端的dataTables.bootstrap.js插件和后端的PageHelper分页插件实现后端分页。

技术栈:

  • 服务端使用SpringBoot开发
  • 使用Mybatis作为ORM框架
  • 使用PageHelper作为后端分页插件
  • 使用Bootstrap作为前端UI框架
  • 使用dataTables.bootstrap.js作为前端分页插件

我们需要实现一个展示用户操作日志的分页列表。

后端数据实现

首先,我们实现根据条件全量查询日志列表

 List<UserLog> listUserLog(Integer userId,Integer type);
  @Override
    public List<UserLog> listUserLog(Integer userId,Integer type) {
        return userLogMapper.selectAll(userId,type);
    }

PageHelper提供了PageInfo类,但是却与前端所需字段不太符合,所以,我们自己实现一个类DataTablePageUtil`:

public class DataTablePageUtil<T> {
    /*------------------DT自动请求的参数(Sent parameters) begin--------------------*/
    /*
     * 绘制计数器。这个是用来确保Ajax从服务器返回的是对应的(Ajax是异步的,因此返回的顺序是不确定的)。 要求在服务器接收到此参数后再返回
     */
    private int draw; // 第几次请求
    /*
     * 第一条数据的起始位置,比如0代表第一条数据
     */
    private int start = 0;// 起止位置
    /*
     * 告诉服务器每页显示的条数,这个数字会等于返回的 data集合的记录数,可能会大于因为服务器可能没有那么多数据。
     * 这个也可能是-1,代表需要返回全部数据(尽管这个和服务器处理的理念有点违背)
     */
    private int length = 100; // 数据长度

    /*
     * 全局的搜索条件,条件会应用到每一列( searchable需要设置为 true )
     */
    private String search;

    /*
     * 如果为 true代表全局搜索的值是作为正则表达式处理,为 false则不是。
     * 注意:通常在服务器模式下对于大数据不执行这样的正则表达式,但这都是自己决定的
     */
    private boolean is_search;

    /*
     * 告诉后台那些列是需要排序的。 i是一个数组索引,对应的是 columns配置的数组,从0开始
     */
    private int[] order;

    /*
     * 告诉后台列排序的方式, desc 降序 asc升序
     */
    private String order_dir;

    /*
     * columns 绑定的数据源,由 columns.dataOption 定义。
     */
    private String columns_data;

    /*
     * columns 的名字,由 columns.nameOption 定义。
     */
    private String columns_name;

    /*
     * 标记列是否能被搜索,为true代表可以,否则不可以,这个是由 columns.searchableOption 控制
     */
    private String columns_searchable;

    /*
     * 标记列是否能排序,为 true代表可以,否则不可以,这个是由 columns.orderableOption 控制
     */
    private boolean is_orderable;

    /*
     * 标记具体列的搜索条件
     */
    private String columns_search_value;

    /*
     * 特定列的搜索条件是否视为正则表达式, 如果为 true代表搜索的值是作为正则表达式处理,为 false则不是。
     * 注意:通常在服务器模式下对于大数据不执行这样的正则表达式,但这都是自己决定的
     */
    private boolean is_search_regex;

    /*------------------DT自动请求的参数(Sent parameters) end--------------------*/

    /*------------------服务器需要返回的数据(Returned data) begin--------------------*/

    /*
     * 必要。上面提到了,Datatables发送的draw是多少那么服务器就返回多少。
     * 这里注意,作者出于安全的考虑,强烈要求把这个转换为整形,即数字后再返回,而不是纯粹的接受然后返回,这是 为了防止跨站脚本(XSS)攻击。
     */
    // private int draw;

    /*
     * 必要。即没有过滤的记录数(数据库里总共记录数)
     */
    private int recordsTotal;

    /*
     * 必要。过滤后的记录数(如果有接收到前台的过滤条件,则返回的是过滤后的记录数)
     */
    private int recordsFiltered;

    /*
     * 必要。表中中需要显示的数据。这是一个对象数组,也可以只是数组, 区别在于 纯数组前台就不需要用 columns绑定数据,会自动按照顺序去显示
     * ,而对象数组则需要使用 columns绑定数据才能正常显示。 注意这个 data的名称可以由 ajaxOption 的
     * ajax.dataSrcOption 控制
     */
    private List<T> data;

    /*
     * 可选。你可以定义一个错误来描述服务器出了问题后的友好提示
     */
    private String error;

    /*-------------可选参数-----------------*/

    /*
     * 自动绑定到 tr节点上
     */
    private String dt_rowId;

    /*
     * 自动把这个类名添加到 tr
     */
    private String dt_rowClass;

    /*
     * 使用 jQuery.data() 方法把数据绑定到row中,方便之后用来检索(比如加入一个点击事件)
     */
    private Object dt_rowData;

    /*
     * 自动绑定数据到 tr上,使用 jQuery.attr() 方法,对象的键用作属性,值用作属性的值。 注意这个 需要 Datatables
     * 1.10.5+的版本才支持
     */
    private Object dt_rowAttr;

    /*-------------可选参数-----------------*/
    /*------------------服务器需要返回的数据(Returned data) end--------------------*/


    /*
     * 当前页码
     */
    private int page_num = 1;

    /*
     * 每页数据
     */
    private int page_size = 100;


    public DataTablePageUtil() {

    }

    public DataTablePageUtil(HttpServletRequest request) {
        //开始的数据行数
        String start = request.getParameter("start");
        //每页的数据数
        String length = request.getParameter("length");
        //DT传递的draw:
        String draw = request.getParameter("draw");

        this.setStart(Integer.parseInt(start));
        this.setLength(Integer.parseInt(length));
        this.setDraw(Integer.parseInt(draw));
        //计算页码
        this.page_num = (Integer.parseInt(start) / Integer.parseInt(length)) + 1;

    }

    public DataTablePageUtil(int draw, int start, int length, PageInfo pageInfo){
        this.draw = draw;
        this.start = start;
        this.length = length;
        this.data = pageInfo.getList();
        this.page_num = start / length + 1;
        this.page_size = length;
        this.recordsTotal = (int)pageInfo.getTotal();
        this.recordsFiltered = this.recordsTotal;
    }

    // Getter & Setter
}

该类核心是这个构造方法,需要把PageInfo的数据重新赋值给DataTablePageUtil

    public DataTablePageUtil(int draw, int start, int length, PageInfo pageInfo){
        this.draw = draw;
        this.start = start;
        this.length = length;
        this.data = pageInfo.getList();
        this.page_num = start / length + 1;
        this.page_size = length;
        this.recordsTotal = (int)pageInfo.getTotal();
        this.recordsFiltered = this.recordsTotal;
    }

Controller的实现:


@RequestMapping(value = "/user/list",method = RequestMethod.POST)
    @ResponseBody
    public DataTablePageUtil<UserLog> userOperateLogList(@RequestParam(required = false,defaultValue = "1") int draw,
                                                         @RequestParam(required = false,defaultValue = "0") int start,
                                                         @RequestParam(required = false,defaultValue = "10") int length,
                                                         @RequestParam(required = false,defaultValue = "0") Integer userId,
                                                         @RequestParam(required = false,defaultValue = "0") Integer type){
        if(userId == 0){
            userId = null;
        }
        if(type == 0){
            type = null;
        }
        PageHelper.startPage(start, length);

        return new DataTablePageUtil<>(draw,start,length,new PageInfo<>(logService.listUserLog(userId,type),length));
    }

当我们请求/admin/log/user/list的时候,获取到的数据:

{
    "draw":1,
    "start":0,
    "length":10,
    "search":null,
    "is_search":false,
    "order":null,
    "order_dir":null,
    "columns_data":null,
    "columns_name":null,
    "columns_searchable":null,
    "is_orderable":false,
    "columns_search_value":null,
    "is_search_regex":false,
    "recordsTotal":42,
    "recordsFiltered":42,
    "data":[
        {
            "id":43,
            "userId":1,
            "userDisplayName":"FunGa黄佳",
            "operateType":6,
            "remark":"上传文件:http://7xq0ls.com1.z0.glb.clouddn.com/FvponGiCwOeQ7qfZGrh7brYMGHj3",
            "logTime":"2018-09-10T02:44:22.000+0000"
        },
        {
            "id":42,
            "userId":1,
            "userDisplayName":"FunGa黄佳",
            "operateType":2,
            "remark":"新建文章:Myabtis批量插入返回自增主键",
            "logTime":"2018-09-08T10:16:14.000+0000"
        },
        {
            "id":41,
            "userId":1,
            "userDisplayName":"FunGa黄佳",
            "operateType":6,
            "remark":"上传文件:http://7xq0ls.com1.z0.glb.clouddn.com/Fv22YIygUx-MEuPVSUUCeAOD1YaG",
            "logTime":"2018-09-08T09:56:35.000+0000"
        },
        {
            "id":40,
            "userId":1,
            "userDisplayName":"FunGa黄佳",
            "operateType":2,
            "remark":"新建文章:为什么需要泛型?",
            "logTime":"2018-09-08T06:20:53.000+0000"
        },
        {
            "id":39,
            "userId":1,
            "userDisplayName":"FunGa黄佳",
            "operateType":6,
            "remark":"上传文件:http://7xq0ls.com1.z0.glb.clouddn.com/FlG--a1GubDWL3ghKzYlHxVJ3S4j",
            "logTime":"2018-09-08T06:03:00.000+0000"
        },
        {
            "id":38,
            "userId":1,
            "userDisplayName":"FunGa黄佳",
            "operateType":2,
            "remark":"新建文章:SpringBoot实现注解对请求进行权限验证",
            "logTime":"2018-09-07T13:05:42.000+0000"
        },
        {
            "id":37,
            "userId":1,
            "userDisplayName":"FunGa黄佳",
            "operateType":6,
            "remark":"上传文件:http://7xq0ls.com1.z0.glb.clouddn.com/FtoXMR8I_F79a5-VtYS08U91tV99",
            "logTime":"2018-09-07T12:57:05.000+0000"
        },
        {
            "id":36,
            "userId":1,
            "userDisplayName":"FunGa黄佳",
            "operateType":3,
            "remark":"编辑专题:搞定面试,内容:面试题其实有时候很恶心的,因为如果不是有所准备,真的可能答不上来,或者说一个资深的老程序员也未必答得上来,但是如果能答上来,那必然是加分的呀。",
            "logTime":"2018-09-07T12:42:48.000+0000"
        },
        {
            "id":35,
            "userId":1,
            "userDisplayName":"FunGa黄佳",
            "operateType":3,
            "remark":"创建专题:搞定面试,内容:面试题其实有时候很恶习的,因为如果不是有所准备,真的可能答不上来,或者说一个资深的老程序员也未必答得上来,但是如果能答上来,那必然是加分的呀。",
            "logTime":"2018-09-07T12:41:36.000+0000"
        },
        {
            "id":34,
            "userId":1,
            "userDisplayName":"FunGa黄佳",
            "operateType":6,
            "remark":"上传文件:http://7xq0ls.com1.z0.glb.clouddn.com/FutQ0kGzuwmKPLtWWzPo_RXx6r1n",
            "logTime":"2018-09-07T12:39:16.000+0000"
        }
    ],
    "error":null,
    "dt_rowId":null,
    "dt_rowClass":null,
    "dt_rowData":null,
    "dt_rowAttr":null,
    "page_num":1,
    "page_size":10
}

前端分页

展示页面和查询条件:

<div class="box">
                <div class="box-header with-border">
                    <h3 class="box-title"></h3>
                </div>
                <form class="form-inline pull-right">
                    <div class="box-body">
                        <div class="form-group">
                            <label for="user-id" class="control-label">操作人:</label>
                                <select class="form-control" id="user-id" style="width:160px" onchange="redraw()">
                                    <option value="0">请选择...</option>
                                    <option th:each="user,userStat : ${userList}" th:value="${user.id}" th:text="${user.displayName}"></option>
                                </select>
                        </div>
                        &nbsp;&nbsp;&nbsp;&nbsp;
                        <div class="form-group">
                            <label for="type" class="control-label">操作类型:</label>
                                <select class="form-control" id="type" style="width:160px" onchange="redraw()">
                                    <option value="0">请选择...</option>
                                    <option value="1">操作用户</option>
                                    <option value="2">操作文章</option>
                                    <option value="3">操作专题</option>
                                    <option value="4">操作课程</option>
                                    <option value="5">操作评论</option>
                                    <option value="6">操作图片</option>
                                    <option value="7">作者回复</option>
                                </select>
                        </div>
                    </div>
                </form>

                <!-- /.box-header -->
                <div class="box-body">
                    <table width="100%" id="user-log-table" class="table table-bordered">
                        <thead>
                        <tr>
                            <th style="width: 10px">#</th>
                            <th style="width: 60px">操作人</th>
                            <th style="width: 60px">操作类型</th>
                            <th style="width: 240px">操作内容</th>
                            <th style="width: 120px">时间</th>
                        </tr>
                        </thead>
                        <tbody>

                        </tbody>
                    </table>
                </div>
                <!-- /.box-body -->
            </div>

我们定义了一个数据table,和一个简单的数据过滤form表单。

jquery生成分页:

<script>

  var dt;


  $(function(){
      drawTable();
  })

  function drawTable() {
      dt = $('#user-log-table').DataTable( {
          processing: true,
          serverSide: true,
          lengthChange: false,//是否允许用户改变表格每页显示的记录数
          ordering: false,//是否允许用户排序
          paging: true,//是否分页
          async: false,
          pagingType: "full_numbers",//除首页、上一页、下一页、末页四个按钮还有页数按钮
        /* scrollX: true,//允许水平滚动
         scrollY: "200px",
         scrollCollapse: true, */
          searching: false,//是否开始本地搜索
          stateSave: false,//刷新时是否保存状态
          autoWidth: true,//自动计算宽度
          //deferRender : true,//延迟渲染
          language: {
              "lengthMenu": "每页 _MENU_ 条记录",
              "zeroRecords": " ",
              "info": "当前 _START_ 条到 _END_ 条 共 _TOTAL_ 条",
              "infoEmpty": "无记录",
              "infoFiltered": "(从 _MAX_ 条记录过滤)",
              // "search": "用户",
              // "processing": "载入中",
              "paginate": {
                  "first": "首页",
                  "previous": "上一页",
                  "next": "下一页",
                  "last": "尾页"
              }
          },
          fnDrawCallback: function(){
              var api = this.api();
              var startIndex = api.context[0]._iDisplayStart;
              api.column(0).nodes().each(function(cell, i) {
                  cell.innerHTML = startIndex + i + 1;
              });
          },
          ajax: {
              url: '/admin/log/user/list',
              type: 'POST',
              data:{userId:$('#user-id').val(),type:$('#type').val()}
          },
          columns: [
              {
                  data: null
              },
              {
                  data: "userDisplayName"
              },
              {
                  data: "operateType",
                  render : function (data) {
                      switch (data){
                          case 1:
                              return "操作用户";
                          case 2:
                              return "操作文章";
                          case 3:
                              return "操作专题";
                          case 4:
                              return "操作课程";
                          case 5:
                              return "操作评论";
                          case 6:
                              return "操作文件";
                          case 7:
                              return "作者回复";
                          default:
                              return "未知类型";
                      }
                  }
              },
              {data: "remark"},
              {
                  data: "logTime",
                  render: function (data) {
                      return new Date(data).Format("yyyy-MM-dd hh:mm:ss");
                  }
              }
          ]
      } );
  }

  function redraw() {
      if(dt){
          dt.clear();
          dt.destroy();
      }
      drawTable();
  }
</script>

当页面加载的时候,按照默认的参数请求接口获取分页数据。当选择了条件,把原先分页销毁,重新带参数请求接口生成新的分页对象。

© 2019 FunGa技术札记 All Rights Reserved. 本站访客数人次 本站总访问量
Theme by hiero