BootStrap-Table-Treegrid 树形表格使用指南,mysql 连接查询原理
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;
/**
分页的一个工具类,接收分页信息
*/
public class TablePageable{
private Integer limit; //分页
private Integer offset;//首记录号(从 0 开始)
private String sort; //排序字段
private String order; //顺序,逆序
public Integer getLimit() {
return limit;
}
public void setLimit(Integer limit) {
this.limit = limit;
}
public Integer getOffset() {
return offset;
}
public void setOffset(Integer offset) {
this.offset = offset;
}
public String getSort() {
return sort;
}
public void setSort(String sort) {
this.sort = sort;
}
public String getOrder() {
return order;
}
public void setOrder(String order) {
this.order = order;
}
public PageRequest bulidPageRequest() {
int page=(offset!=null&&limit!=null)?offset/limit:0;
int size=limit!=null?limit:10;
if(sort==null) {
return PageRequest.of(page, size);
}else {
Order order2=new Order(Direction.fromString(order), sort);
Sort sort2= Sort.by(order2);
return PageRequest.of(page,size,sort2 );
}
}
public PageRequest bulidPageable(Sort sort) {
int page=(offset!=null&&limit!=null)?offset/limit:0;
int size=limit!=null?limit:10;
return PageRequest.of(page, size, sort);
}
public Sort bulidSort() {
Order order2=new Order(Direction.fromString(order), sort);
Sort sort2= Sort.by(order2);
return sort2;
}
}
这里持久层查询是用的 Spring-Data-Jpa,如果是其他持久层框架,只要查询后返回的 JSON 数据格式一致就可以了。
JSON 数据格式如下,parentId 为父节点 Id,和前面 js 中设置的一定要对应一样,不然树形结构显示不出来。
**注意:**如果父节点为空,parentId 的值需要为 null,不能为""空串,一个 parentId 类型为 Integer 类型,为空串的话,树形结构显示不出来。

加载完成后数据显示的效果如下。

实体类代码。
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.mcy.springbootsecurity.custom.BaseEntity;
import org.springframework.data.annotation.CreatedBy;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
/**
菜单表
@author
*/
@Entity
public class TbMenu {
private Integer id;
private String name;
private String url;
private Integer idx;
@JsonIgnore
private TbMenu parent;
@JsonIgnore
private List<TbMenu> children=new ArrayList<>();
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Column(unique=true)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public Integer getIdx() {
return idx;
}
public void setIdx(Integer idx) {
this.idx = idx;
}
@ManyToOne
@CreatedBy
public TbMenu getParent() {
return parent;
}
public void setParent(TbMenu parent) {
this.parent = parent;
}
@OneToMany(cascade=CascadeType.ALL,mappedBy="parent")
@OrderBy(value="idx")
public List<TbMenu> getChildren() {
return children;
}
public void setChildren(List<TbMenu> children) {
this.children = children;
}
public TbMenu(Integer id) {
super(id);
}
public TbMenu(){
super();
}
public TbMenu(String name, String url, Integer idx, TbMenu parent, List<TbMenu> children) {
this.name = name;
this.url = url;
this.idx = idx;
this.parent = parent;
this.children = children;
}
public TbMenu(Integer integer, String name, String url, Integer idx, TbMenu parent, List<TbMenu> children) {
super(integer);
this.name = name;
this.url = url;
this.idx = idx;
this.parent = parent;
this.children = children;
}
@Transient
public Integer getParentId() {
return parent==null?null:parent.getId();
}
}
新增和修改方法,前台 JS 代码(因为新增和修改是共用的一个页面和一个方法,所以这里放一起写了)。
//新增
function btn_add(){
var selectRows = $table.bootstrapTable('getSelections');
var dialog = $('<div class="modal fade" id="myModal" tabindex="-1" aria-labelledby="myModalLabel"></div>');
if(selectRows&&selectRows.length==1){
var parentId=selectRows[0].id;
dialog.load('menu/edit',{parentId:parentId});
}else{
dialog.load('menu/edit');
}
$("body").append(dialog);
dialog.modal().on('hidden.bs.modal', function () {
dialog.remove();
$table.bootstrapTable('refresh');
});
}
//修改
function btn_edit(){
var str = $("#menu_table").bootstrapTable('getSelections');
if(str.length != 1){
bootbox.alert("请选中一行进行编辑");
return ;
}
var dialog = $('<div class="modal fade" id="myModal" tabindex="-1" aria-labelledby="myModalLabel"></div>');
var id = str[0].id;
dialog.load("menu/edit?id="+id);
/添加到 body 中/
$("body").append(dialog);
/弹出模态框,绑定关闭后的事件/
dialog.modal().on('hidden.bs.modal', function () {
//删除模态框
dialog.remove();
$table.bootstrapTable('refresh');
});
}
新增和修改的后台请求方法,跳转到新增修改页面,通过 id 来判断是修改还是新增,parentId 判断新增时是否勾选行,勾选后为默认父级。
@Override
public void edit(TbMenuForm form, ModelMap map) throws InstantiationException, IllegalAccessException {
TbMenu model = new TbMenu();
if(form.getId() != null) {
model = menuService.findById(form.getId());
}
if(form.getParentId() != null) {
model.setParent(new TbMenu(form.getParentId()));
}
map.put("model", model);
}
新增修改页面代码
<body>
<form id="myForm" class="form-horizontal" role="form" >
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="myModalLabel">操作</h4>
</div>
<div class="modal-body">
<input type="hidden" name="id" th:value="${model.id}" id="modelId">
<div class="form-group">
<label for="txt_parent" class="col-sm-2 control-label">上级菜单</label>
<div class="col-sm-9">
<input name="parentId" data-th-value="${model.parent==null?null:model.parent.id}" class="form-control" id="txt_parent">
</div>
</div>
<div class="form-group">
<label for="txt_name" class="col-sm-2 control-label">菜单名称</label>
<div class="col-sm-9">
<input type="text" name="name" data-th-value="${model.name}" class="form-control" id="txt_name">
</div>
</div>
<div class="form-group">
<label for="txt_url" class="col-sm-2 control-label">访问地址</label>
<div class="col-sm-9">
<input type="text" name="url" data-th-value=${model.url} class="form-control" id="txt_url" placeholder="">
</div>
</div>
<div class="form-group">
<label for="txt_sort" class="col-sm-2 control-label">排序</label>
<div class="col-sm-9">
<input type="text" name="idx" data-th-value="${model.idx}" class="form-control" id="txt_sort">
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" onclick="btnSubmit()" class="btn btn-primary"><span class="glyphicon glyphicon-floppy-disk" aria-hidden="true"></span>保存</button>
<button type="button" class="btn btn-default" data-dismiss="modal"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span>关闭</button>
</div>
</div>
</div>
</form>
</body>
JS 部分代码。
<script th:inline="javascript">
$(function(){
$('#txt_parent').bootstrapCombotree({
url:'menu/treedata?id='+$('#modelId').val()
});
$('.selectpicker').selectpicker({});
});
function btnSubmit() {
var form= new FormData($("#myForm")[0]);
$.ajax({
url: 'menu/save',
type: 'post',
data: form,
processData: false, //不处理数据
contentType: false, //不设置内容类型
success: function(result){
if(result.success){
$("#myModal").modal("hide");
bootbox.alert("数据保存成功");
}else{
bootbox.alert(result.msg);
}
},
error: function(result){
bootbox.alert("数据保存失败!");
}
})
}
</script>
页面初始化后,加载 Combotree 下拉树的数据,并初始化,后台 Combotree 数据请求方法,如果是修改,就把当前修改项的 id 传递到后台,代码如下。
/***
combotree 树形加载
@return
*/
@RequestMapping(value="/treedata")
@ResponseBody
public Object treedata(Integer id) {
Sort sort = Sort.by("idx");
Specification<TbMenu> spec = buildSpec1();
List<TbMenu> list = menuService.findAll(spec,sort);
return buildTree(list,id);
}
《一线大厂 Java 面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》
【docs.qq.com/doc/DSmxTbFJ1cmN1R2dB】 完整内容开源分享
private Object buildTree(List<TbMenu> list, Integer id) {
List<HashMap<String, Object>> result=new ArrayList<>();
for (TbMenu dept : list) {
//判断如果是修改,修改节点在下拉树中不显示
if(dept.getId()!=id) {
//因为实体类中的字段名和需要返回的 JSON 字段名不同,这里需要转换一下
HashMap<String, Object> node=new HashMap<>();
node.put("id", dept.getId());
node.put("text",dept.getName());
node.put("pid", dept.getParentId());
node.put("nodes",buildTree(dept.getChildren(), id));
result.add(node);
}
}
return result;
}
//条件查询,先只显示最上级的菜单,即 parent 父级节点为空的数据
public Specification<TbMenu> buildSpec1() {
Specification<TbMenu> specification = new Specification<TbMenu>() {
private static final long serialVersionUID = 1L;
@Override
public Predicate toPredicate(Root<TbMenu> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
HashSet<Predicate> rules=new HashSet<>();
Predicate parent = cb.isNull(root.get("parent"));
rules.add(parent);
return cb.and(rules.toArray(new Predicate[rules.size()]));
}
};
return specification;
}
这里持久层查询是用的 Spring-Data-Jpa,如果是其他持久层框架,只要查询后返回的 JSON 数据格式一致就可以了。
响应请求数据返回的 JSON 格式数据,pid 为父节点 id。

Combotree 下拉树,在前面还引入了一个自己写的 combotree.js 文件,代码如下。
(function ($) {
function getTree(url){
var result=$.ajax({
type : 'post',
url : url ,
dataType : 'json' ,
async : false
}).responseText;
return eval('('+result+')');
}
$.fn.extend({
bootstrapCombotree: function(options, param){
var self=this;
this.onBodyDown=function(event){
var $options=undefined;
try{
.data($(event.target).prev()[0],'bootstrapCombotree');
}catch (e) {
$options=undefined;
}
if (!((event.target).parents("#comboDiv").length>0)) {
//if (!(event.target.id == 'comboDiv' || $(event.target).parents("#comboDiv").length>0)) {
self.hideMenu();
}
}
this.hideMenu=function(){
$('#comboDiv').fadeOut('fast');
$("#comboDiv").remove();
$("body").unbind("mousedown", self.onBodyDown);
}
this.getNode=function(root,id){
for(var i=0;i<root.length;i++){
var node=root[i];
if(node.id==id){
return node;
}
var x=self.getNode(node.nodes?node.nodes:[],id);
if(x){
return x;
}
}
return null;
},
this.create=function(target){
var (target);
var .data(target,'bootstrapCombotree').options;
$self.attr("type","hidden");
var ('<input class="form-control" />');
self.attr('placeholder'));
$this.attr('readonly',true);
this[0]);
//取出 options
var options=option,{
onNodeSelected:function(event,node){
$self.val(node.id);
$this.val(node.text);
$('#comboDiv').fadeOut('fast');
$("#comboDiv").remove();
$("body").unbind("mousedown", self.onBodyDown);
}
});
//在显示的框中写入默认值对应的 text 的内容
var self.val();
if($value){
var value);
if($node){
node.text);
}
}
$this.focus(function(){
if($('#comboDiv').length>0){
return;
}
var ('<div class="panel panel-default combotree" id="comboDiv"><div class="panel-body"><div id="bootstrapCombotree"></div></div></div>');
this.parent());
$('#bootstrapCombotree').treeview(options);
var thisObj=$(this);
var thisOffset=$(this).position();
var $dialogOffset=thisObj.closest('.modal-dialog').offset();
var $left=thisOffset.left;
var $top=thisOffset.top+thisObj.outerHeight();
$div.css({
left : $left+"px",
top: $top+"px",
zIndex: 1100
}).slideDown('fast');
$('body').bind("mousedown",self.onBodyDown);
});
};
if (typeof options == 'string'){
var method = $.fn.bootstrapCombotree.methods[options];
if (method){
return method(this, param);
}
return;
}
options = options || {};
return this.each(function(){
var state = $.data(this, 'bootstrapCombotree');
if (state){
$.extend(state.options, options);
} else {
.extend({}, $.fn.bootstrapCombotree.defaults, options);
$options.thisObj=this;
if($options.url){
options.url);
}
state = $.data(this, 'bootstrapCombotree', {
options: $options,
});
}
self.create(this);
});
}
});
$.fn.bootstrapCombotree.methods={
options:function(jq){
var .data(jq[0],'bootstrapCombotree').options;
return $option;
}
}
$.fn.bootstrapCombotree.defaults = {
expandIcon: "glyphicon glyphicon-plus-sign",
collapseIcon: "glyphicon glyphicon-minus-sign",
emptyIcon:"glyphicon glyphicon-file",
showBorder:false,
showTags: true,
url:'combotree',
data:[]
};
})(jQuery)
保存 JS 中的 btnSubmit()方法用于保存数据方法,后台保存响应方法。
@Override
public Object save(TbMenuForm form) {
try {
TbMenu model = new TbMenu();
Integer id = form.getId();
if(id != null) {
model = menuService.findById(id);
}
//父级菜单 id
Integer parentId = form.getParentId();
if(parentId == null) {
model.setParent(null);
}else {
model.setParent(new TbMenu(parentId));
}
BeanUtils.copyProperties(form, model,"id", "parent");
menuService.save(model);
return new AjaxResult("数据保存成功!");
} catch (Exception e) {
return new AjaxResult(false,"数据保存失败");
}
}
方法中 TbMenuForm 接收类,中字段和实体类差不多。代码如下。
import com.mcy.springbootsecurity.custom.BaseForm;
import com.mcy.springbootsecurity.entity.TbMenu;
public class TbMenuForm {
private Integer id;
private String name;
private String url;
private Integer idx;
private TbMenu parent;
private Integer parentId;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
架构学习资料





由于篇幅限制小编,pdf 文档的详解资料太全面,细节内容实在太多啦,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!
评论