树形操作-树形拖拽选择

时间:2019-01-09 20:05:08 来源:互联网 作者: 神秘的大神 字体:

树形操作数据,做个整理总结。本篇是关于树节点拖拽选择,重新生成一棵新树,并支持删除节点。demo 是基于 jquery 及 easy-ui 库实现的。

前言:

  • demo 预览

  • 实现功能点:

    • 树形展示、筛选

    • 左侧节点支持拖拽到右侧,并且重组为树形展示

    • 右侧已选树形节点支持删除,并统计选择的子节点个数

    • 支持默认有子节点数据

  • 截图:

具体实现-loading:

利用 css3 实现。主要运用了 :before:after 选择器‘画’了两个圆,然后利用border进行圆的调整,最后利用animationtransform:rotate()实现旋转动画。

html:

<div class="loading"></div>

css:

    .loading {         height: 100%;         line-height: 100%;         position: relative;     }     .loading:before {         position: absolute;         top: calc(50% - 30px);         left: calc(50% - 30px);         content: "";         width: 60px;         height: 60px;         border-radius: 100%;         border: 5px solid skyblue;         border-left-color: transparent;         border-right-color: transparent;         animation: loading 1s linear infinite;      }     .loading:after {         position: absolute;         top: calc(50% - 30px);         left: calc(50% - 30px);         content: "";         width: 60px;         height: 60px;         border-radius: 100%;         border: 5px solid yellow;         border-top-color: transparent;         border-bottom-color: transparent;         animation: loading 2s linear infinite;      }     @keyframes loading {         from {             transform: rotate(0deg);         }         to {             transform: rotate(360deg);         }     }

具体实现-树形选择:

主要 html 结构如下,css 就不展示,比较简单。

<div class="tree-select">     <div class="tree-left">         <div class="search tree-header" id="search">             <input class="search-input" placeholder="搜索标签" value="" />         </div>         <ul class="tree-con loading" id="tree_select"></ul>     </div>     <div class='tree-right'>         <div class="tip-wrap tree-header">             <span class="tip-num">已选标签(<span class="num" id="tree_selected_num">0</span>)</span>             <span class="clear-all" id="tree_selected_clear">清空</span>         </div>         <ul class="tree-con" id="tree_selected">         </ul>     </div> </div>

js 代码比较多,先放个 js 代码组织结构图:

其中 event 中实现的是筛选和清空功能。

1. 数据获取,并渲染左侧树:renderSelectTree

treeSelectMod.$treeTarget.addClass('loading');  setTimeout(function () { // 实际中,通过 ajax 获取数据,这儿用 延时 模拟     treeSelectMod.$treeTarget.removeClass('loading');      if (result && result.flag && result.data && result.data.length) {         //实例化树形菜单         treeSelectMod.$treeTarget.tree({             data: result.data,             dnd: true, // 允许拖拽             formatter: function(node) {                 // 统计子节点个数                 var text = '<span class="node-name">' + node.text + '</span>';                 if (node.children && node.children.length > 0) {                     text += '<i class="tip">(' + node.children.length + ')</i>';                 }                 return text;             },             onLoadSuccess: function(e, node) {                 // 折叠树                 treeSelectMod.$treeTarget.tree('collapseAll');                                  // 节点上禁止放置                 $.each($('#tree_select .tree-node'), function(i, item) {                     $(item).droppable("disable");                 });             }         });         treeSelectMod.setDragAndDrop();          // 编辑回填:需要有已选数据         if (isEdit) {             treeSelectMod.editInit(result.data);         }     } else {         treeSelectMod.$treeTarget.html(treeSelectMod.noDataHtml);     } }, 1000);

2. 拖拽操作:setDragAndDrop

// 设置被拖元素 $("#tree_select .tree-node").draggable({     proxy: 'clone',     revert: true, // 拖动结束后节点将返回它的开始位置     cursor: 'pointer',     onStartDrag: function() {         $(this).draggable('proxy').css({ 'background': '#fff', 'opacity': '0.5' });     },     onStopDrag: function(e) {         // 拖拽置放位置,不是目标不进行操作         if (e.target.id != 'tree_selected') {             return true         }         var node = treeSelectMod.$treeTarget.tree('getNode', e.data.target); // 获取被拖动的节点数据          // 过滤         var selectedData = treeSelectMod.selectedData;         if (node.children && node.children.length) {             // 被拖拽节点是父节点:判断选中数据中是否有当前节点,没有,就加入,有就替换(保证子节点都正确)             var parentNode = {                 id: node.id,                 text: node.text,                 children: [],                 state: node.state || 'closed'             };             node.children.forEach(function(item) {                 parentNode['children'].push({                     id: item.id,                     text: item.text                 });             })             var hasSameParentNode = false;             for (var i = 0; i < selectedData.length; i++) {                 if (selectedData[i].id == node.id) {                     hasSameParentNode = true;                     selectedData[i] = parentNode;                     break;                 }             }             if (!hasSameParentNode) {                 selectedData.push(parentNode);             }         } else {             // 被拖拽节点为子节点:需要子节点带着其父节点,便于数据正确             // 通过被拖拽节点找其父节点,在选中的数据中进行同级比较,先找到相等的父节点,再在选中的子节点中判断是否有当前拖拽节点,没有就加入子节点,有不进行操作。如果连父节点都没有相等的,就连父节点一起加入             var parent = treeSelectMod.$treeTarget.tree('getParent', e.data.target);             var childNode = {                 id: node.id,                 text: node.text             };             if (parent) {                 var parentNode = {                     id: parent.id,                     text: parent.text,                     state: parent.state || 'closed'                 };                 parentNode['children'] = [];                 parentNode['children'].push(childNode);                                  var hasSameParentNode = false;                 for (var i = 0; i < selectedData.length; i++) {                     if (selectedData[i].id == parent.id) {                         hasSameParentNode = true;                         var children = selectedData[i]['children'] || [];                         var arr = children.filter(function(item) {                             return item.id == node.id;                         });                         if (!arr.length) { // 不存在,加入子节点                             children.push(childNode);                         }                         break;                     }                 }                 if (!hasSameParentNode) { // 没有相等父节点,连父节点一起加入                     selectedData.push(parentNode);                 }             }         }                  // 最后,渲染 DOM         treeSelectMod.renderSelectedTree(treeSelectMod.selectedData);     } });  //设置目标对象允许放置被拖元素 $("#tree_selected").droppable();

3. 渲染已选择树,及树节点删除:renderSelectedTree

$('#tree_selected').tree({     data: treeData,     formatter: function(node) {         var text = '<span class="node-name">' + node.text + '</span>';         if (node.children && node.children.length > 0) {             text += '<i class="tip">(' + node.children.length + ')</i>';         }         text += '<span class="tree-node-del">x</span>';         return text;     },     onClick: function(node) {         // 删除节点         var selectedData = treeSelectMod.selectedData;         if (node.children && node.children.length) {             // 删除节点是父节点:从已选择的中找到删除节点,进行删除             for (var i = 0; i < selectedData.length; i++) {                 if (selectedData[i].id == node.id) {                     selectedData.splice(i, 1);                 }             }         } else {             // 删除节点是子节点:从已选择的子级中找到删除子节点,进行删除,并展开其父节点,方便查看是否删除             for (var i = 0; i < selectedData.length; i++) {                 for (var j = 0; j < selectedData[i].children.length; j++) {                     if (selectedData[i].children[j].id == node.id) {                         selectedData[i].children.splice(j, 1);                         selectedData[i].state = 'open';                     }                 }                 if (!selectedData[i].children.length) { // 最后判断,如果父节点下没有子节点了,就把父节点删除                     selectedData.splice(i, 1);                 }             }         }         // 重新根据过滤后的数据进行渲染 DOM         treeSelectMod.renderSelectedTree(treeSelectMod.selectedData);     },     onLoadSuccess: function() {         // 统计选择的标签:子节点参与统计         $('#tree_selected_num').html(treeSelectMod.getTreeChildNum(treeSelectMod.selectedData));     } });

其他操作比较简单,就不赘述了,具体实现可看demo。

实现结果,只要每次右侧节点拖拽放置了以及右侧节点进行了节点删除挫折,左侧 DOM 就要进行一遍渲染。可能会存在性能问题,但目前没更好的实现方案。(欢迎有更好方案的提供建议,~~)

最后就是,目前实现的方案,只支持二级树结构,多级是不支持的。