接这上一个帖子,最近这几天一直研究题目所说的如何利用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博客
写文章不易,辛苦给个赞吧 ^_^