zoukankan      html  css  js  c++  java
  • 树形操作1-树形拖拽选择

    树形操作数据,做个整理总结。本篇是关于树节点拖拽选择,重新生成一棵新树,并支持删除节点。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: "";
             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: "";
             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 就要进行一遍渲染。可能会存在性能问题,但目前没更好的实现方案。(欢迎有更好方案的提供建议,~~)

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

  • 相关阅读:
    换个角度看Salesforce之基础配置学习笔记(二)
    换个角度看Salesforce之基础配置学习笔记(一)
    C# LINQ学习笔记
    Oracle使用总结
    UML图及Visio 2010使用总结
    常见的DOS命令
    ansible笔记
    jsoncpp1.9.4源码解析
    fabric链码
    fabric数据结构
  • 原文地址:https://www.cnblogs.com/EnSnail/p/10246377.html
Copyright © 2011-2022 走看看