Springboot整合Thymeleaf使用Datatable利用AJAX进行页面的查询功能完整实现

接这上一个帖子,最近这几天一直研究题目所说的如何利用Datatable实现一个相对完整的查询功能,这些天苦逼读源码,加上网上看一些资料以及使用AI工具,终于做出了一个相对完美的结果。

效果图如下(个人审美有限,毕竟后端出身,所以美观度大家见谅):

画面说明:

1.考虑到实际使用的时候,操作按钮可能比较多,所以采用下拉式按钮方式实现

2.默认搜索的Search栏追加了PlaceHolder的属性"sSearchPlaceholder":"输入用户名"

3.列排序功能参考代码可以自定义

4.追加了对后端传过来的值为了前端显示自定义的逻辑(下边为部分的datatable配置代码)

5.所有逻辑都是ajax异步操作(调查如何使用ajax废了很多时间。。。根据网上资料各式各样。。)

 {"data": "userType", "width": "10%","orderable": false,
                 'render': function (data, type, row) {
                  if(data == 1){
                      return "管理员";
                  }else{
                      return "部门用户";
                  }
              }
             }

6.无需多余配置,即可实现分页,每列显示数据等设置(列排序暂时没实现,稍后补充,休息一下),所有逻辑都是会跟后台通讯,并不是前台的js操作。

7.Datatable的奇数偶数行设置颜色设置

接下来上代码:

userList.html:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<!--引入公共的html -->
<head th:replace="/common/base :: common_header(~{::title},~{::link})">
    <title>User List</title>
    <!-- Select2 -->
    <link rel="stylesheet" th:href="@{../../static/plugins/select2/css/select2.min.css}">
</head>
<style media="screen">
    /*** 奇数行颜色设置 ***/
    table.dataTable tbody tr.odd {
      background-color: #f9f9f9;
    }

    /*** 偶数行颜色设置 ***/
    table.dataTable tbody tr.even {
      background-color: #ffffff;
    }
</style>
<script th:src="@{../../static/customize/userList.js}"></script>
<!-- Select2 -->
<script th:src="@{../../static/plugins/select2/js/select2.full.min.js}"></script>
<body>

<!-- general form elements -->
<div class="card card-success" style="width:100%">
    <div class="card-header">
        <h3 class="card-title">检索条件</h3>
    </div>
    <div class="card-body">
        <div class="row">
            <div class="col-3">
                <div class="form-group">
                    <label for="userName">用户名</label>
                    <input type="text" class="form-control" id="userName">
                </div>
            </div>
            <div class="col-3">
                <div class="form-group">
                    <label for="mobile">移动电话</label>
                    <input type="text" class="form-control" id="mobile">
                </div>
            </div>
            <!--<div class="col-3">
                <div class="form-group">
                    <label>Order By:</label>
                    <select class="select2" style="width: 100%;">
                        <option selected></option>
                        <option value="0">正常</option>
                        <option value="1">锁住</option>
                    </select>
                </div>
            </div>-->
        </div>
        <div class="row">
            <div class="card-footer" style="width:100%; text-align: right;">
                <button  class="btn btn-primary" id="search">查询</button>
            </div>
        </div>
    </div>
</div>
<div class="card-header">
    <table id="userList" class="table table-bordered table-responsive" style="width:100%">
        <thead>
        <tr>
            <th>id</th>
            <th>用户名</th>
            <th>类型</th>
            <th>昵称</th>
            <th>密码</th>
            <th>移动电话</th>
            <th>状态</th>
            <th>邮箱</th>
            <th>操作</th>
        </tr>
        </thead>
    </table>
</div>
</div>
</body>
</html>

userList.js

  var tableConfig;
  $(function () {
      //提示信息  初始化设置 一般不需要改
      var lang = {
          "sProcessing": "处理中...",
          "sLengthMenu": "每页 _MENU_ 项",
          "sZeroRecords": "没有匹配结果",
          "sInfo": "当前显示第 _START_ 至 _END_ 项,共 _TOTAL_ 项。",
          "sInfoEmpty": "当前显示第 0 至 0 项,共 0 项",
          "sInfoFiltered": "(由 _MAX_ 项结果过滤)",
          "sInfoPostFix": "",
          "sSearch": "搜索:",
          "sSearchPlaceholder":"输入用户名",
          "sUrl": "",
          "sEmptyTable": "表中数据为空",
          "sLoadingRecords": "载入中...",
          "sInfoThousands": ",",
          "oPaginate": {
              "sFirst": "首页",
              "sPrevious": "上页",
              "sNext": "下页",
              "sLast": "末页",
              "sJump": "跳转"
          },
          "oAria": {
              "sSortAscending": ": 以升序排列此列",
              "sSortDescending": ": 以降序排列此列"
          }
      };

      //重要修改 表格内容的自定义,需要根据业务定制
      var tableConfig = $("#userList").dataTable({
          "language": lang,  //提示信息
          "autoWidth": false,  //禁用自动调整列宽
          "stripeClasses": ["odd", "even"],  //为奇偶行加上样式,兼容不支持CSS伪类的场合
          "processing": true,  //隐藏加载提示,自行处理
          "serverSide": true,  //启用服务器端分页
          "searching": true,  //禁用原生搜索
          "orderMulti": false,  //启用多列排序
          "order": [],  //取消默认排序查询,否则复选框一列会出现小箭头
          "renderer": "bootstrap",  //渲染样式:Bootstrap和jquery-ui
          "pagingType": "simple_numbers",  //分页样式:simple,simple_numbers,full,full_numbers
          "ajax": function (data, callback, settings) {
             data.userName = $("#userName").val();
             data.mobile = $("#mobile").val();
             data.status = $("#status").val();
             console.log(data);
             $.ajax({
                      "type": "POST",
                      "url": "/list.do",//url请求的地址
                      "data": JSON.stringify(data),
                      "dataType": "json",
                      "async": true,
                      "contentType" : 'application/json',
                      "success": function (result) {
                          callback(result);
                      }
                  });
          },
          //列表表头字段
          "columns": [
             {"data": "id", "width": "5%","visible":false},
             {"data": "userName", "width": "10%"},
             {"data": "userType", "width": "10%","orderable": false,
             'render': function (data, type, row) {
                  if(data == 1){
                      return "管理员";
                  }else{
                      return "部门用户";
                  }
              }
             },
             {"data": "nickName","width": "15%"},
             {"data": "password", "width": "10%","orderable": false},
             {"data": "mobile", "width": "10%"},
             {"data": "status", "width": "10%","orderable": false,
             'render': function (data, type, row) {
                     if(data == 1){
                         return "启用";
                     }else{
                         return "停用";
                     }
                 }
             },
             {"data": "email", "width": "20%","orderable": false},
             {
                 "sTitle": '操作',
                 "orderable": false,
                 "width": "10%",
                 'render': function (data, type, row) {
                   var statusHtml = '<a class="dropdown-item" href="#" class="text-success" onclick="status('+row.id+')">启用</a>';
                   if(row.status=="0"){
                      statusHtml = '<a class="dropdown-item" href="#" class="text-danger" onclick="status('+row.id+')">停用</a>';
                   }
                   return '<div class="btn-group">'+
                          	'<button type="button" class="btn btn-default">详细</button>'+
                          	'<button type="button" class="btn btn-default dropdown-toggle dropdown-icon" data-toggle="dropdown" aria-expanded="false">'+
                          		'<span class="sr-only">Toggle Dropdown</span>'+
                          	'</button>'+
                          	'<div class="dropdown-menu" role="menu" style="">'+
                          	  '<a class="dropdown-item" href="#" onclick="details('+row.id+')">详细</a>'+
                          	  '<a class="dropdown-item" href="#" onclick="modify('+row.id+')">编辑</a>'+
                          	  '<div class="dropdown-divider"></div>'+
                          	   '<a class="dropdown-item" href="#" onclick="remove('+row.id+')">删除</a>'+
                          	   statusHtml
                          	'</div>'+
                          '</div>';
                 },
               }

          ]
      }).api();
    //此处需调用api()方法,否则返回的是JQuery对象而不是DataTables的API对象

    // 绑定检索按钮的点击事件
    $('#search').click(function(event) {
        event.preventDefault();
        tableConfig.draw();
    });
 })

 function details(userId){
 console.log(userId);
 }
 function modify(userId){
  console.log(userId);
  }
  function remove(userId){
   console.log(userId);
   }
 function status(userId){
   console.log(userId);
   }

在介绍后端代码之前,大家要了解Datatable给后端传参数的json是什么样的。userName为画面的传参,其他配置都是Datatable默认的配置,此处注意,不同的Datatable的版本属性名称可能不一样,大家要注意不然的话,调查起来很辛苦,要细心。

{
    "draw": 1,
    "columns": [
        {
            "data": "userName",
            "name": "",
            "searchable": true,
            "orderable": true,
            "search": {
                "value": "",
                "regex": false
            }
        }
    ],
    "order": [],
    "start": 0,
    "length": 10,
    "search": {
        "value": "",
        "regex": false
    },
    "userName": "a"
}
PagingRequest为分页用的公共类,其内容参照上边的Json,主要属性要跟上边的json一样不然报错。
package com.kaoutyou.biz.smart.bean.request;

import com.kaoutyou.biz.smart.bean.model.Column;
import com.kaoutyou.biz.smart.bean.model.Order;
import com.kaoutyou.biz.smart.bean.model.Search;
import lombok.Data;

import java.io.Serializable;
import java.util.List;

@Data
public class PagingRequest implements Serializable {
    private int start;
    private int length;
    private int draw;
    private Order[] order;
    private Column[] columns;
    private Search search;
}

Order类使用与排序的配置类,其内容参照上边的Json,主要属性要跟上边的json一样不然报错。

package com.kaoutyou.biz.smart.bean.model;

import com.kaoutyou.biz.smart.enums.DirectionEnum;
import lombok.Data;

import java.io.Serializable;

@Data
public class Order implements Serializable {
    private Integer column;
    private DirectionEnum dir;
}
Column类是所有类的配置信息,也要参考上边的json,其内容参照上边的Json,主要属性要跟上边的json一样不然报错。
package com.kaoutyou.biz.smart.bean.model;

import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@NoArgsConstructor
public class Column implements Serializable {
    private String data;
    private String name;
    private Boolean searchable;
    private Boolean orderable;
    private Search search;
    public Column(String data) {
        this.data = data;
    }
}

Search类是Datatable的search框的一些配置,也要参考上边的json,其内容参照上边的Json,主要属性要跟上边的json一样不然报错。

package com.kaoutyou.biz.smart.bean.model;

import lombok.Data;

import java.io.Serializable;

@Data
public class Search implements Serializable {
    private String value;
    private Boolean regex;
}

我的检索用的UserListRequest要继承PagingRequest。测试用的检索类,有的设置为不是必须。

package com.kaoutyou.biz.smart.bean.request;

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.io.Serializable;

@Data
public class UserListRequest extends PagingRequest implements Serializable {

    private static final long serialVersionUID = -3720714032174930387L;

    @ApiModelProperty(value = "用户名",required = false)
    private String userName;

    @ApiModelProperty(value = "移动电话" ,required = false)
    private String mobile;

    @ApiModelProperty(value = "昵称",required = false)
    private String nickName;

    @ApiModelProperty(value = "状态")
    private Integer status;

    @ApiModelProperty(value = "邮箱",required = false)
    private String email;

    @ApiModelProperty(value = "用户类型",required = false)
    private Integer userType;
}

Controller层

  @ResponseBody
    @PostMapping("/list.do")
    public Page<UserListResponse> list(@RequestBody UserListRequest request) {
        return userFacade.getUsers(request);
    }
Page类是根据Datatable的返回值设置的一个共通类,这样返回以后不需要再给画面设值了,直接mapping。
package com.kaoutyou.biz.smart.bean.model;

import lombok.Data;

import java.util.List;

@Data
public class Page<T> {

    private List<T> data;
    private int recordsFiltered;
    private int recordsTotal;
    private int draw;

    public Page(List<T> userListResponses) {
        this.data = userListResponses;
    }

    public Page() {

    }
}
UserFacade层的代码是将检索结果,赋值给Page<UserListResponse>
public Page<UserListResponse> getUsers(UserListRequest pagingRequest) {
        Page<BizAdminUserEntity> pageUsers = iUserService.getUsers(pagingRequest);
        Page<UserListResponse> pageResponse = new Page<UserListResponse>();
        BeanUtils.copyProperties(pageUsers,pageResponse);
        pageResponse.setData(SmartWebUtils.copyList2List(pageUsers.getData(),UserListResponse.class));
        return pageResponse;
    }

copyList2List的方法其实就是用JSON的方式实现List的Copy

package com.kaoutyou.biz.smart.utils;

import com.alibaba.fastjson.JSON;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * SmartWebUtils常用工具类
 *
 * @author aaron.yingchao.he
 */
@Component
public class SmartWebUtils {
    private SmartWebUtils() {
    }

    /**
     * 利用json实现 list的深复制
     *
     * @param fromList
     * @param clazz
     * @param <T>
     * @param <E>
     * @return
     */
    public static <T, E> List<T> copyList2List(List<E> fromList, Class<T> clazz) {
        return JSON.parseArray(JSON.toJSONString(fromList), clazz);
    }
}

最主要的是UserServiceImpl的实现代码如下,这里边封装了针对Datatable的检索而自定义的一些实现。

package com.kaoutyou.biz.smart.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.kaoutyou.biz.smart.bean.comparators.UsersComparators;
import com.kaoutyou.biz.smart.bean.model.Column;
import com.kaoutyou.biz.smart.bean.model.Order;
import com.kaoutyou.biz.smart.bean.model.Page;
import com.kaoutyou.biz.smart.bean.model.PageArray;
import com.kaoutyou.biz.smart.bean.request.UserListRequest;
import com.kaoutyou.biz.smart.bean.request.UserSignInRequest;
import com.kaoutyou.biz.smart.domain.BizAdminUserEntity;
import com.kaoutyou.biz.smart.repository.BizAdminUserMapper;
import com.kaoutyou.biz.smart.service.IUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.util.Comparator;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;


@Service("userService")
@Slf4j
public class UserServiceImpl extends ServiceImpl<BizAdminUserMapper, BizAdminUserEntity> implements IUserService {

    private static final Comparator<BizAdminUserEntity> EMPTY_COMPARATOR = (e1, e2) -> 0;

    @Override
    public BizAdminUserEntity signIn(UserSignInRequest request) {
        LambdaQueryWrapper<BizAdminUserEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(BizAdminUserEntity::getUserName, request.getUserName());
        wrapper.eq(BizAdminUserEntity::getPassword, request.getPassword());
        List<BizAdminUserEntity> list = this.list(wrapper);
        if (!CollectionUtils.isEmpty(list)) {
            return this.list(wrapper).get(0);
        }
        return null;
    }

    @Override
    public Page<BizAdminUserEntity> getUsers(UserListRequest userListRequest) {
        LambdaQueryWrapper<BizAdminUserEntity> wrapper = new LambdaQueryWrapper<>();
        //检索条件设定
        if (!StringUtils.isEmpty(userListRequest.getUserName())) {
            wrapper.like(BizAdminUserEntity::getUserName, userListRequest.getUserName());
        }
        if (!StringUtils.isEmpty(userListRequest.getMobile())) {
            wrapper.eq(BizAdminUserEntity::getMobile, userListRequest.getMobile());
        }
        if (!StringUtils.isEmpty(userListRequest.getNickName())) {
            wrapper.like(BizAdminUserEntity::getNickName, userListRequest.getNickName());
        }
        if (!ObjectUtils.isEmpty(userListRequest.getStatus())) {
            wrapper.eq(BizAdminUserEntity::getStatus, userListRequest.getStatus());
        }
        if (!StringUtils.isEmpty(userListRequest.getEmail())) {
            wrapper.eq(BizAdminUserEntity::getEmail, userListRequest.getEmail());
        }
        if (!ObjectUtils.isEmpty(userListRequest.getUserType())) {
            wrapper.eq(BizAdminUserEntity::getUserType, userListRequest.getUserType());
        }
        //设定检索条件
        List<BizAdminUserEntity> list = this.list(wrapper);
        return this.getPage(list, userListRequest);
    }

    @Override
    public PageArray getUsersArray(UserListRequest pagingRequest) {
        return null;
    }

    private Page<BizAdminUserEntity> getPage(List<BizAdminUserEntity> list, UserListRequest userListRequest) {
        List<BizAdminUserEntity> filtered = list.stream()
                .sorted(sortUsers(userListRequest))
                .filter(filterList(userListRequest))
                .skip(userListRequest.getStart())
                .limit(userListRequest.getLength())
                .collect(Collectors.toList());
        //去的search结果的count数
        long count = list.stream().filter(filterList(userListRequest)).count();
        Page<BizAdminUserEntity> page = new Page<>(filtered);
        page.setRecordsFiltered((int) count);
        page.setRecordsTotal((int) count);
        page.setDraw(userListRequest.getDraw());
        page.setData(filtered);
        return page;
    }

    private Predicate<BizAdminUserEntity> filterList(UserListRequest userListRequest) {
        if (userListRequest.getSearch() == null || StringUtils.isEmpty(userListRequest.getSearch()
                .getValue())) {
            return user -> true;
        }
        String value = userListRequest.getSearch().getValue();
        return user -> user.getUserName().toLowerCase().contains(value)
                || user.getEmail().toLowerCase().contains(value);
    }

    private Comparator<BizAdminUserEntity> sortUsers(UserListRequest userListRequest) {
        if (userListRequest.getOrder() == null) {
            return EMPTY_COMPARATOR;
        }
        if (userListRequest.getOrder().length > 0) {
            Order order = userListRequest.getOrder()[0];
            int columnIndex = order.getColumn();
            Column column = userListRequest.getColumns()[columnIndex];
            log.debug("排序的列名:{}", column.getName());
            Comparator<BizAdminUserEntity> comparator = UsersComparators.getComparator(column.getName(), order.getDir());
            if (comparator == null) {
                return EMPTY_COMPARATOR;
            }
        }
        return EMPTY_COMPARATOR;
    }
}
UsersComparators类是一个比较器的封装,没时间调试先这么写吧。
package com.kaoutyou.biz.smart.bean.comparators;

import com.kaoutyou.biz.smart.domain.BizAdminUserEntity;
import com.kaoutyou.biz.smart.enums.DirectionEnum;
import lombok.Data;

import java.util.Comparator;

@Data
public class UsersComparators {


    public static Comparator<BizAdminUserEntity> getComparator(String columnName, DirectionEnum dir) {
        // 创建比较器
        Comparator<BizAdminUserEntity> comparator = null;

        if ("nickName".equals(columnName)) {
            comparator = Comparator.comparing(BizAdminUserEntity::getUserName);
        } else if ("mobile".equals(columnName)) {
            comparator = Comparator.comparing(BizAdminUserEntity::getMobile);
        } else if ("userName".equals(columnName)) {
            comparator = Comparator.comparing(BizAdminUserEntity::getUserName);
        }

        if (DirectionEnum.DESC.equals(dir)) {
            comparator.reversed();
        }
        return comparator;
    }
}

 到这里为止所有的实现基本结束,花了2天时间研究的成果给大家分享一下,好辛苦的,给个赞吧^_^,我会持续发帖的。
PS:一些检索组件还在看以后会将画面持续更新,尽量做得更完美。

特别感谢抖音的AI工具:豆包

相关的一些配置信息也借鉴了这个哥们的帖子:SpringBoot结合dataTables(Ajax,分页,新增列)-CSDN博客

写文章不易,辛苦给个赞吧 ^_^ 

  • 20
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值