本文记录的只是我自己当时的代码,每行的注释很清楚了,你自己可以做相应变通
一、使用前提:
1、下载jstree依赖包
2、相关页面引入样式["jstree/themes/default/style.css"]、js["jstree/jstree.js"]
3、页面声明一个空div <div id="sortTree"></div>
二、对应的代码
1、树的初始化及相关的操作
$(function () { /** * 该树分为三层为 --项目 * --项目文档分类 * --分类下具体文档 * 即项目在1级、文档分类在2级、文档在3级 */ //项目的上下文,在上面提到的页面中 var contextPath = $('#contextPath').val(); //树的当前操作节点级别(实际上就是树的1级或2级或其他) var beforeLevel = null; //树的目标操作节点级别 var targetLevel = null; //节点拖动的目标地址位置下标 var targetPosition = null; //页面中树div的dom var tree = $('#sortTree'); //树的初始化 tree.on('move_node.jstree', function (event, data) { //以下是节点的移动事件要触发的操作 var targetId = data.parent; var sourceId = data.node.id; targetPosition = data.position; //分类移动 if(beforeLevel == 2) { $.ajax({ type: "POST", url: contextPath + "/docclassify/move", data: {"docClassifyId": sourceId, "helpDocSystemId": targetId, "targetPosition": targetPosition}, dataType: "json", success: function (res) { layer.msg(res.message); } }) } else if(beforeLevel == 3) { //文档移动 $.ajax({ type: "POST", url: contextPath + "/helpDoc/move", data: {"helpDocId": sourceId, "docClassifyId": targetId, "targetPosition": targetPosition}, dataType: "json", success: function (res) { layer.msg(res.message); } }) } }).jstree({ "core":{ //树的事件方法回调 "check_callback": function (op, node, parent) { beforeLevel = $('#'+node.id).attr("aria-level"); targetLevel = $('#'+parent.id).attr("aria-level"); if(beforeLevel == 3 && targetLevel == 1) { //文档不能直接移到项目下 layer.msg("文档需要移动到分类"); return false; } else if(beforeLevel == 3 && targetLevel == 3) { // 文档不能移到文档 return false; } else if(beforeLevel == 2 && targetLevel == 2) { // 分类不能移到分类 return false; } else if(beforeLevel == 2 && targetLevel == 3) { // 分类不能移到文档 return false; } else if(beforeLevel == 1 ) { //我这边遇到的问题就是第1级节点,也就是所有项目节点不能拖动,所以我在下面把项目的排序变为右键点击出现上移下移菜单来处理 return false; } }, data: function (obj, callback) { //初始化树的第1级数据,即所有有序排列的项目 var jsonStr = "[]"; var jsonArray = eval(jsonStr); $.ajax({ type: "GET", url: contextPath + "/helpdocsystem/initTree", dataType: "json", success: function (res) { var arrays = res.result; for (var i = 0; i < arrays.length; i++) { var arr = { "id": arrays[i].id, "parent": "#", "text": arrays[i].name, "icon": "icon iconfont icon-report-type", "index": arrays[i].index //数据库中的数据排序字段 } jsonArray.push(arr); } callback.call(this, jsonArray); } }); } }, "contexmenu": { //右键菜单 "item": function (node) { var temp = { "上移": { "lable": "上移", "action": function () { $.ajax({ type: "POST", url: contextPath + "/helpdocsystem/order", data: {"docSystemId": node.id, "moveFlag": 1}, dataType: "json", success: function (res) { layer.msg(res.message); //刷新树 tree.jstree(true).refresh(); } }) } }, "下移": { "lable": "下移", "action": function () { $.ajax({ type: "POST", url: contextPath + "/helpdocsystem/order", data: {"docSystemId": node.id, "moveFlag": 0}, dataType: "json", success: function (res) { layer.msg(res.message); //刷新树 tree.jstree(true).refresh(); } }) } } }; var menu = {}; var level = $('#'+node.id).attr("aria-level"); //此处限制节点为1级时才允许出现右键菜单 (level == 1) && (menu = temp); return menu; } }, "plugins": ["sort", "contextmenu", "dnd", "state"], //依次为排序、右键菜单、拖拽移动、状态组件 "sort": function (a, b) { return a['index'] - b['index']; } }).bind("select_node.jstree", function (event, data) { var instant = data.instance; var selectedNode = instant.get_node(data.selected); var level = $('#'+selectNode.id).attr("aria-level"); if(parseInt(level) == 2) { //点击2级节点,加载分类下的文档 loadHelpDoc(instant, selectedNode); } if(parseInt(level) == 1) { //点击1级节点,加载项目下的文档分类 loadDocClassify(instant, selectedNode); } }); /** * 加载分类下文档 * @param instant * @param selectNode */ function loadHelpDoc(instant, selectedNode) { $.ajax({ type: "GET", url: contextPath + "/helpdoc/initTree", data: {"docClassifyId": selectedNode.id}, dataType: "json", success: function (res) { var data = res.result; if(null != data && data.length > 0) { selectedNode.children = []; $.each(data, function (i, item) { var obj = { "id": item.id, "parent": item.parentId, "text": item.name, "icon": "icon iconfont icon-over-task" }; instant.create_node(selectedNode, obj, "last"); //创建子节点 if($("#"+selectedNode.id).hasClass("jstree-open")){ //打开关闭节点 instant.close_node(selectedNode); } else if($("#"+selectedNode.id).hasClass("jstree-closed")) { instant.open_node(selectedNode); } }); } } }) } /** * 加载项目下文档分类 * @param instant * @param selectNode */ function loadDocClassify(instant, selectedNode) { $.ajax({ type: "GET", url: contextPath + "/docclassify/initTree", data: {"helpDocSystemId": selectedNode.id}, dataType: "json", success: function (res) { var data = res.result; if(null != data && data.length > 0) { selectedNode.children = []; $.each(data, function (i, item) { var obj = { "id": item.id, "parent": item.parentId, "text": item.name, "icon": "icon iconfont icon-report-type" }; instant.create_node(selectedNode, obj, "last"); //创建子节点 if($("#"+selectedNode.id).hasClass("jstree-open")){ //打开关闭节点 instant.close_node(selectedNode); } else if($("#"+selectedNode.id).hasClass("jstree-closed")) { instant.open_node(selectedNode); } }); } } }); } });
还是贴一下一个接口排序的例子,当时写的确实有点烦,以免以后会忘;initTree相关接口就是关联查询出的有序集合,拼接成需要格式数据即可。
在这把排序可以理解成往一个有序的list中的某个位置插入元素,假如插入元素a,i作为目标位置,那么a元素的实际orderNo为list(i-1).getOrderNo() + 1,那么i位置以下的节点orderNo依次为i+1
需要注意的地方:1、目标位置为0时做了处理,a元素实际位置从1开始;2、目标元素的所有下级元素orderNo增加时需要排除目标元素
1、/docclassify/move
@PostMapping("/docclassify/move")
public String move(DocClassifyVo docClassifyVo, int targetPosition, Model model) {
try {
if (StringUtils.isNotEmpty(docClassifyVo.getHelpDocSystemId)) {
this.docClassifyManager.move(docClassifyVo, targetPosition);
model.addAttribute("message", "移动成功");
}
} catch (Exception e) {
e.printStackTrace();
model.addAttribute("message", "移动失败");
}
return "json";
}
public void move(DocClassifyVo docClassifyVo, int targetPosition) {
DocClassify docClassify = this.docClassifyService.findById(docClassifyVo.getDocClassifyId());
HelpDocSystem helpDocSystem = new HelpDocSystem();
helpDocSystem.setHelpDocSystemId(docClassifyVo.getHelpDocSystemId());
docClassify.setHelpDocSystem(helpDocSystem);
docClassify.setOrderNo(this.docClassifyService.getMaxOrderByHelpDocSystemId(docClassifyVo.getHelpDocSystemId) + 1);
this.docClassifyService.update(docClassify);
//按orderNo升序查询该项目下的所有分类
DocClassifyQo docClassifyQo = new DocClassifyQo();
docClassifyQo.setHelpDocSystemId(docClassifyVo.getHelpDocSystemId());
List<DocClassify> docClassifies = this.docClassifyService.queryPageList(docClassifyQo);
if(CollectionUtils.isNotEmpty(docClassifies)) {
for (int i = 0; i < docClassifies.size(); i++) {
if(docClassifies.get(i).getDocClassifyId().equals(docClassifyVo.getDocClassifyId())) {
List<DocClassify> batchUpdateList = new ArrayList<>();
//目标节点上级
DocClassify upDocClassify = null;
//目标节点
DocClassify targetDocClassify = docClassifies.get(i);
int realTargetPosition = 0;
int tempTargetPosition = 0;
if(targetPosition != 0) {
upDocClassify = docClassifies.get(targetPosition - 1);
tempTargetPosition = upDocClassify.getOrderNo() + 1;
realTargetPosition = upDocClassify.getOrderNo() + 1;
targetDocClassify.setOrderNo(realTargetPosition);
} else {
upDocClassify = docClassifies.get(targetPosition);
//移动到第一个时从1开始计算
tempTargetPosition = 1;
realTargetPosition = 1;
targetDocClassify.setOrderNo(realTargetPosition);
}
this.docClassifyService.update(targetDocClassify);
//目标节点所有下级
List<DocClassify> afterDocClassifies = this.docClassifyService.queryPageList(docClassifyQo);
for (int j = targetPosition; j < afterDocClassifies.size(); j++) {
if(!afterDocClassifies.get(i).getDocClassifyId().equals(docClassifyVo.getDocClassifyId())) {
DocClassify downDocClassify = afterDocClassifies.get(j);
downDocClassify.setOrderNo(++tempTargetPosition);
batchUpdateList.add(downDocClassify);
}
}
batchUpdateList.add(targetDocClassify);
this.docClassifyService.batchUpdate(batchUpdateList);
break;
}
}
}
//重新设置orderNo,避免排序次数多了后orderNo不连续
List<DocClassify> beforeDocClassifies = this.docClassifyService.queryPageList(docClassifyQo);
List<DocClassify> batchUpdateList = new ArrayList<>();
if(CollectionUtils.isNotEmpty(beforeDocClassifies)) {
for (int i = targetPosition; i < beforeDocClassifies.size(); i++) {
DocClassify sortDocClassify = beforeDocClassifies.get(i);
downDocClassify.setOrderNo(i+1);
batchUpdateList.add(sortDocClassify);
}
this.docClassifyService.batchUpdate(batchUpdateList);
}
}
2、/helpdocsystem/order
关于项目排序由于我们采用的是右键上移下移的操作,所以排序的算法也就没那么复杂,上移时moveFlag为1,下移时moveFlag为0。上移时取移动节点的前一个节点(i-1)的orderNo,下移时orderNo则为(i+1)
,互相交换orderNo更新即可
public void order(String docSystemId, int moveFlag) { //按orderNo升序查询该所有项目 HelpDocSystemQo helpDocSystemQo = new HelpDocSystemQo(); PageList<HelpDocSystemQo> helpDocSystems = this.helpDocSystemService.queryPageList(helpDocSystemQo); if(CollectionUtils.isNotEmpty(helpDocSystems)) { List<HelpDocSystem> batchUpdateList = new ArrayList<>(); for (int i = 0; i < helpDocSystems.size(); i++) { if(helpDocSystems.get(i).getHelpDocSystemId().equals(docSystemId)) { if(i == 0 && moveFlag == 1) { throw new RuntimeException("到顶了,不能再移动了"); } if(i == helpDocSystems.size() - 1 && moveFlag == 0) { throw new RuntimeException("到底了,不能再移动了"); } HelpDocSystem sourceHelpDocSystem = helpDocSystems.get(i); HelpDocSystem targetHelpDocSystem = null; if(moveFlag == 1) { targetHelpDocSystem = helpDocSystems.get(i - 1); } else { targetHelpDocSystem = helpDocSystems.get(i + 1); } int sourceOrderNo = sourceHelpDocSystem.getOrderNo(); int targetOrderNo = targetHelpDocSystem.getOrderNo(); sourceHelpDocSystem.setOrderNo(targetOrderNo); targetHelpDocSystem.setOrderNo(sourceOrderNo); batchUpdateList.add(sourceHelpDocSystem); batchUpdateList.add(targetHelpDocSystem); this.helpDocSystemService.batchUpdate(batchUpdateList); } } } }
