使用vue+zTree实现穿梭框嵌套树形结构

先来看看实现效果:

起因:

该项目是一个脱敏产品,简单地说就是把数据库或者其他大量数据中的敏感数据筛选或处理。而此处是选择文件部分,它由elementUI的卡片组件和zTree的树形图插件组合而成。

至于为什么不使用elementUI的树形图组件呢?诚然elementUI的树形图比zTree好看多了,不过elemntUI的树形图功能太少了,获取想要的数据很困难,NNwanzi最开始是使用的elementUI来完成这一功能,但浪费了不少时间最后决定用的zTree。

环境搭建:

除了elementUI,vue的基本环境外,还需要一个zTree的环境,这里主要讲zTree的环境

        1.官方下载地址:zTree_v3: jQuery 树插件 zTree v3 系列 (gitee.com)

        官方API:API 文档 [zTree -- jQuery 树插件] (treejs.cn)

        2.导入项目

        这里没有用到脚手架,直接git下载资源,使用代码导入到index

    <script src="js/plugin/zTree_3.5.48/jquery.ztree.all.js" type="text/javascript"></script>
    <script src="js/plugin/zTree_3.5.48/jquery.ztree.exhide.js" type="text/javascript"></script>

 自定义cardTree组件源码:

(要想实现全部穿梭树效果还需调用cardTree和按钮一起组合完成)

<!--cardTree组件,页面效果为卡片中显示了zTree的树形结构,主要用于导出文件页面,敏感元新增第3步的文件选择页面。-->
<!--通过外部按钮调用此组件内的各种方法操控zTree树,具体请看"由父页面调用"-->

<style scoped>
.tree {
    height: 400px;
}

.tree .el-scrollbar__wrap {
    overflow: scroll;
    width: 110%;
    height: 106%;
}
</style>

<template>
    <div>
        <el-card>
            <div slot="header">
                <span>列表共{{ total }}个子节点</span>

                <el-popover
                    placement="bottom"
                    width="400"
                    trigger="click"
                    @hide="getMainData"
                    v-if="url !== ''">
                    <el-input type="textarea" v-model="tableRegex" maxlength="100"></el-input>
                    <span style="color: red">
                        弹出框关闭时自动筛选<br>
                        通配符为*<br>
                        输入条件$TDMP.table1,精准查询出TDMP下的table1表(table1可以加*号,适用下面两条)<br>
                        输入条件CUST*,会查出CUST_001,CUST_002等结果<br>
                        输入条件aim*CUST*,会查出aimCUST_001,aimpulsCUST等结果<br>
                        带通配符为忽略大小写模糊查询,不带通配符为匹配大小写精准查询<br>
                    </span>
                    <el-button style="float: right; padding: 3px 0" type="text" slot="reference">高级检索</el-button>
                </el-popover>

            </div>
            <el-input
                placeholder="输入关键字进行过滤"
                v-model="filtration">
            </el-input>
            <el-scrollbar class="tree">
                <!--            ztree渲染位置-->
                <ul :id="randomId" class="ztree"></ul>
            </el-scrollbar>
        </el-card>
    </div>
</template>

<script>
module.exports = {
    name: "export",
    components: {},
    data() {
        return {
            //初始化时的随机值,用于ZTree的id(因为敏感元新增页面第二步一个页面用了两次cardTree,导致id重复)
            randomId: "",
            //tree的配置
            defaultProps: {
                children: 'children',
                label: 'label',
                isLeaf: (data, node) => {
                    if (data.children === undefined) {
                        return true;
                    }
                }
            },
            //打开的路径id数组(因为tree是风琴式的,所以路径只有一条)
            unfold: [],
            //过滤树的关键字
            filtration: "",
            //页面主数据,卡片中的树
            //高级检索输入的条件
            tableRegex: "",
            //已选项列表个数
            total: 0,
            //保存tree中获取到的json对象的属性名
            treeJsonKey: [],
            //zTree的对象
            treeObj: {}
        }
    },
    watch: {
        filtration(val) {
            //显示出所有隐藏的文件
            var nodes = this.treeObj.getNodesByParam("isHidden", true);
            this.treeObj.showNodes(nodes);
            //过滤逻辑
            let hiddenNodes = this.treeObj.getNodesByFilter(this.filter); // 查找节点集合
            this.treeObj.hideNodes(hiddenNodes);
        }
    },
    methods: {
        //zTree自定义筛选,返回模糊查询 label 中不包含this.filtration的节点
        filter(node) {
            return (node.level == 2 && node.label.indexOf(this.filtration) === -1);
        },


        /**
         * 由父页面调用
         **/
        //处理提交,参数:是否提存返回值
        submit(refine) {
            let nodes = this.treeObj.getSelectedNodes();
            //这里会使用循环获取nodes中所有项的父路径,其实只用获取第一个的就行了,因为选中的时候规定只能选中同一路径下的节点
            //若要优化请兼顾其他使用位置,他们的nodes不一定在同一路径下
            let selected = this.getPathNodes(nodes);

            if (refine){
                selected = this.refineSubmit(selected);
            }

            //返回选中项
            return selected;
        },
        //由父页面调用,获取增量。只获取通过addTree()增加的节点的tree数组,参数:是否提存返回值
        getIncrement(refine){
            //原理:在addTree()中会将所有叶子节点都改为选中状态,但选中状态是不显示到页面上的。这里获取所有选中状态即为增量
            let checkedNodes = this.treeObj.getCheckedNodes();
            let checked = this.getPathNodes(checkedNodes);
            if (refine){
                checked = this.refineSubmit(checked);
            }

            return checked
        },

        //由父页面调用,删除参数项
        removeTree(nodes){
            nodes.forEach((node)=>{
                //第一个label相同的节点
                let nodesByParam = this.treeObj.getNodesByParam("label", node.label)[0];
                if(!node.isParent){
                    //如果不是一个父节点,则删除
                    this.treeObj.removeNode(nodesByParam);
                }else {
                    //如果不是叶子节点则递归子节点
                    //需要添加的不存在的节点
                    let childs = [];
                    // zTree中若只有一个子对象,将会展现为对象,而不是数组,此时会导致递归失败
                    if (Array.isArray(node.children)) {
                        childs = node.children
                    } else {
                        childs.push(node.children);
                    }
                    //删除子节点中的叶子节点
                    this.removeTree(childs);
                    // 删除子节点中的叶子节点后检查其本身是否变为了叶子节点
                    this.removeParentNode(nodesByParam);
                }
            });
        },

        //由父页面调用,删除选中项
        removeSelected() {
            let selectedNodes = this.treeObj.getSelectedNodes();
            let parentNode = {};
            if (selectedNodes.length > 0) {
                parentNode = selectedNodes[0].getParentNode();
            }
            for (let i=selectedNodes.length - 1; i >= 0; i--) {
                this.treeObj.removeNode(selectedNodes[i]);
            }
            //判断如果删除后父节点为叶子节点则同样删除。
            this.removeParentNode(parentNode);
            //更新总数
            let nodes = this.treeObj.getNodes();//获取 zTree 的全部节点数据
            if (nodes !== undefined) {
                this.total = this.getAllChildrenNodes(nodes);
            } else {
                this.total = 0;
            }
        },
        //可由父页面调用,向treeJson添加值
        addTree(tree) {
            debugger
            this.addChildren(null, tree)
            //显示出所有隐藏的文件
            let hiddenNodes = this.treeObj.getNodesByParam("isHidden", true);
            this.treeObj.showNodes(hiddenNodes);
            this.filtration = "";
            //更新总数
            let nodes = this.treeObj.getNodes();//获取 zTree 的全部节点数据
            this.total = this.getAllChildrenNodes(nodes);
        },

        //由父页面调用,获取所有树,而不是选中树,参数:是否提存返回值
        getTree(refine) {
            let nodes = this.treeObj.getNodes();
            if (refine){
                nodes = this.refineSubmit(nodes);
            }
            return nodes
        },
        //由父页面调用,获取所有的叶子节点
        getLeafNodes(){
            let nodes = this.treeObj.getNodesByParam("isParent", false, null);
            return nodes
        },

        /**
         * 数据处理
         */
        //给一个节点对象,获取所有叶子节点的个数
        getAllChildrenNodes(treeNode) {//给定一个节点对象
            let result = 0;
            for (let i = 0; i < treeNode.length; i++) {
                if (treeNode[i].isParent) {//如果是父
                    let childrenNodes = treeNode[i].children;
                    result += this.getAllChildrenNodes(childrenNodes);
                } else {
                    result++
                }
            }
            this.total = result;
            return result;
        },
        //对树节点进行筛选时执行的方法,返回 true 表示这个节点可以显示,返回 false 则表示这个节点会被隐藏
        filterNode(value, data) {
            if (!value) return true;
            return data.label.indexOf(value) !== -1;
        },
        //获取页面主数据(树)
        getMainData() {
            this.params.tableRegex = this.tableRegex;
            this.$httpUtils.get(this.url, this.params, true)
                .then(res => {
                    debugger
                    //排空处理
                    let treeJson = [];
                    if (this.$stringUtils.isEmpty(res.treeJson)){
                        treeJson = res.treeJson;
                    }else {
                        treeJson = JSON.parse(res.treeJson);
                        for (const treeJsonKey in treeJson[0]) {
                            this.treeJsonKey.push(treeJsonKey)
                        }
                    }

                    this.total = res.totalCount;
                    this.renderTree(treeJson);
                })
        },

        //若该节点不为父节点则删除它,若参数节点的父节点删除后参数节点的爷爷节点将不再是父节点,则同样删除。
        removeParentNode(node) {
            if (node === null) {
                //该节点为空,说明已经删除到了根节点,递归出口
                return
            }
            if (!node.isParent) {
                let parentNode = node.getParentNode();
                this.treeObj.removeNode(node);
                //递归删除父节点
                this.removeParentNode(parentNode);
            }
        },

        //递归添加children
        addChildren(parent, children) {
            for (const treeIndex in children) {
                let child = children[treeIndex];
                let node = this.treeObj.getNodeByParam("id", child.id, parent);
                if (node) {
                    if (!child.isParent) {
                        //如果找到了这个节点,并且这个节点是叶子节点
                        continue;
                    }
                    //需要添加的不存在的节点
                    let childs = [];
                    // zTree中若只有一个子对象,将会展现为对象,而不是数组,此时会导致递归失败
                    if (Array.isArray(child.children)) {
                        childs = child.children
                    } else {
                        childs.push(child.children);
                    }
                    // 如果有该节点,则将该节点作为父节点,其子节点作为children继续轮询添加
                    this.addChildren(node, childs)
                } else {
                    if(this.incrementFlag){
                        //设置节点的所有叶子节点为选中状态(该值即为增量)
                        this.setLeafChecked(child);
                    }
                    //若不存在该节点,则向父节点添加该节点
                    this.treeObj.addNodes(parent, child, false);
                }
            }
        },
        //设置节点的所有叶子节点为选中状态
        setLeafChecked(node){
            if(!node.isParent){
                //如果是叶子节点则设置选中
                node.checked = true;
            }else {
                //如果不是叶子节点则递归子节点
                //需要添加的不存在的节点
                let childs = [];
                // zTree中若只有一个子对象,将会展现为对象,而不是数组,此时会导致递归失败
                if (Array.isArray(node.children)) {
                    childs = node.children
                } else {
                    childs.push(node.children);
                }
                for (const childIndex in childs) {
                    this.setLeafChecked(childs[childIndex]);
                }
            }
        },

        //将nodes用树形结构分类,返回一个tree数组
        getPathNodes(nodes){
            let map = new Map();
            let tree = [];
            //向map中添加数据key:parentTId,value:[node]
            nodes.forEach((node)=>{
                let parentTId = node.parentTId;
                if (parentTId === null) {
                    tree.push(node)
                    return
                }

                //向map中添加数据key:parentTId,value:[node]
                if(map.has(parentTId)){
                    let value = map.get(parentTId);
                    value.push(node);
                }else {
                    let nodes = [];
                    nodes.push(node);
                    map.set(parentTId,nodes);
                }
            })
            //将map转化为带父路径的tree
            for (let [key,value] of map.entries()) {
                //获取路径arr
                let pathNode = this.treeObj.getNodeByTId(key);
                let oldPath = pathNode.getPath();
                let pathArr = {};
                $.extend(true,pathArr,oldPath);

                //根据路径arr,再按路径添加父节点
                let length = oldPath.length;
                pathArr[length-1].children = value;
                for (let i = length - 2; i >= 0; i--) {
                    pathArr[i].children = [pathArr[i + 1]]
                }
                tree.push(pathArr[0])
            }
            return tree
        },
        //提纯cardTree选中的数据,原数据中存在zTree添加的其他属性
        refineSubmit(nodes){
            let tree = [];
            for (const nodesIndex in nodes) {
                let node =nodes[nodesIndex];
                let refineNode = {};
                refineNode.id = node.id;
                refineNode.label = node.label;
                if(node.children !== undefined && node.children.length !== 0){
                    // zTree中若只有一个子对象,将会展现为对象,而不是数组,此时会导致递归失败
                    let childs = [];
                    if(Array.isArray(node.children)){
                        childs = node.children
                    }else {
                        childs.push(node.children);
                    }
                    refineNode.children = this.refineSubmit(childs);
                }
                tree.push(refineNode)
            }
            return tree;
        },


        /**
         * ZTree
         *
         */

        /**
         * 渲染数据
         */
        renderTree(zNodes) {
            // zTree 的参数配置,深入使用请参考 API 文档(setting 配置详解)
            let setting = {
                edit: {
                    enable: false,
                    showRemoveBtn: false,
                    showRenameBtn: false,
                    drag: {
                        isCopy: false,
                        isMove: true,
                        next: false
                    }
                },
                data: {
                    simpleData: {
                        enable: true
                    },
                    key: {
                        name: "label"
                    }
                },
                check:{
                  chkboxType: {"Y": "ps", "N": "ps"},
                },
                view: {
                    filter: true,  //是否启动过滤
                    expandLevel: 0,  //展开层级
                    showFilterChildren: true, //是否显示过滤数据孩子节点
                    showFilterParent: true, //是否显示过滤数据父节点
                    showLine: false
                },

                callback: {
                    beforeDrag: function (treeId, treeNodes) {
                        for (var i = 0, l = treeNodes.length; i < l; i++) {
                            if (treeNodes[i].drag === false) {
                                return false;
                            }
                        }
                        return true;
                    },
                    beforeDrop: function (treeId, treeNodes, targetNode, moveType) {
                        var tmpTreeNodes = treeNodes;
                        for (var i = 0, l = tmpTreeNodes.length; i < l; i++) {
                            var pnode = tmpTreeNodes[i].getParentNode();
                            if (null != pnode) {
                                var newPnode = {
                                    'id': pnode.id,
                                    'pId': pnode.pId,
                                    'name': pnode.name,
                                    'open': pnode.open
                                };
                                treeNodes.push(newPnode);
                            }
                        }
                        return true;
                    },
                    onClick: function (event, treeId, treeNode, clickFlag) { //支持shift键
                        var preClickedNode = window.preClickedNode;
                        window.preClickedNode = treeNode;
                        event = window.event || event;//兼容IE
                        if (event.ctrlKey && preClickedNode != null) { // ctrl键禁止跨层选择
                            var zTreeObject = $.fn.zTree.getZTreeObj(treeId);
                            if (treeNode.level == '1') {
                                this.cancelParentNode(zTreeObject, treeNode); //递归取消根节点
                                this.cancelChildrenNode(zTreeObject, treeNode); //递归取消子节点
                            } else if (treeNode.level == '0') {
                                this.cancelChildrenNode(zTreeObject, treeNode); //递归取消子节点
                            } else {
                                this.cancelParentNode(zTreeObject, treeNode); //递归取消父节点
                            }
                        }
                        if (!event.shiftKey || !preClickedNode) return;// shift键
                        if (preClickedNode.getParentNode() != treeNode.getParentNode()) {
                            preClickedNode = null;
                            return;
                        }
                        var obj = jQuery.fn.zTree.getZTreeObj(treeId);
                        obj.selectNode(preClickedNode, true, true);
                        var firstNode = obj.getNodeIndex(preClickedNode);
                        var lastNode = obj.getNodeIndex(treeNode);
                        var count = lastNode - firstNode;
                        var nodeNew = preClickedNode;
                        if (count > 0) {
                            for (var i = 1; i < count; i++) {
                                nodeNew = nodeNew.getNextNode();
                                if(nodeNew.isHidden)continue;//用于排除隐藏项
                                obj.selectNode(nodeNew, true, true);
                            }
                        } else {
                            for (var j = 1; j < (-count); j++) {
                                nodeNew = nodeNew.getPreNode();
                                if(nodeNew.isHidden)continue;//用于排除隐藏项
                                obj.selectNode(nodeNew, true, true);
                            }
                        }
                        window.preClickedNode = null;
                    }
                }
            };
            this.$nextTick(() => {
                // zTree 的数据属性,深入使用请参考 API 文档(zTreeNode 节点数据详解)
                $.fn.zTree.init($("#" + this.randomId), setting, zNodes);
                this.treeObj = $.fn.zTree.getZTreeObj(this.randomId);
                //更新总数
                let nodes = this.treeObj.getNodes();//获取 zTree 的全部节点数据
                this.total = this.getAllChildrenNodes(nodes);
                //调用父组件设置状态,标识该页面已经渲染完毕
                this.$emit('set-state');
            })
        },

        /**
         * Z-tree
         * @param zTreeObject
         * @param treeNode
         */
        cancelParentNode(zTreeObject, treeNode) {
            if (treeNode.getParentNode() != null) {
                var node = treeNode.getParentNode();
                zTreeObject.cancelSelectedNode(node);
                this.cancelParentNode(zTreeObject, node);
            }
        },
        /**
         * Z-Tree
         * @param zTreeObject
         * @param treeNode
         */
        cancelChildrenNode(zTreeObject, treeNode) {
            if (treeNode.children != null) {
                for (var i = 0; i < treeNode.children.length; i++) {
                    zTreeObject.cancelSelectedNode(treeNode.children[i]);
                    this.cancelChildrenNode(zTreeObject, treeNode.children[i]);
                }
            }
        }
    },
    created() {
        //解决同一个页面多次使用该cardTree时id重复
        this.randomId = "mainTree" + Math.floor(Math.random() * 100000);
        if (this.url !== "") {
            this.getMainData()
        } else {
            // 否则使用mainData
            this.renderTree(this.mainData);

        }
    },
    /**
     * <!--
     卡片树,用于导出,选择文件等功能
     使用方法:父页面点击按钮获得此页面的返回值:
     */
    props: {
        //url用于获取树,如果不传入该值,并且不会显示高级检索,因为高级检索是使用url时的筛选
        url: {
            default: "",
            type: String,
            request: false
        },
        //通常与url同时出现,他用于使用url时需传的其他参数
        params: {
            default: function () {
                return {}
            },
            type: Object,
            request: false
        },
        //mainData是树数据,他的格式同elementUI树型组件
        //这里this.mainData,如果不传输url,则会使用undefined创建树。
        mainData: {
            default: function () {
                return undefined
            },
            type: Array,
            request: false
        },
        //删除旗帜,若为true则在树返回时会删除选中节点
        deleteFlag: {
            default: false,
            type: Boolean,
            request: false
        },
        //增量旗帜,该参数决定了在调用add向树中添加数据时是否记载增量
        incrementFlag:{
            default: false,
            type: Boolean,
            request: false
        }
    }
}
</script>

<style scoped>

</style>

cardTree使用注意要点:

调用传参:

        url:用于获取zTree的数据,该数据需要准寻一定的格式,具体格式请参照zTree官网

        params:当我们发送请求时通常会携带一些参数

        mainData:若不希望通过url向zTree中传递数据,也可以使用mainData向树中填充数据,当然这也必须准寻zTree官方的格式

        incrementFlag:增量旗帜,该值能让树记录其中的增量,以便于之后获取增量

组件使用:

        因为该组件由其他页面调用处理,所以需要符合一定的规则才能正常调用。

其他页面调用cardTree组件:

两个cardTree组件和两个按钮共同实现该博客一开始的那种穿梭树的效果,当然你也可以只调用一次cardTree用于实现文件导出功能的选择文件和文件夹。

<!--调用cardTree组件,拼接成树形穿梭框-->

<template>
    <div v-loading="loading" element-loading-text="加载中...">

        <el-row :gutter="20" style="margin-bottom: 20px">
            <el-col :span="10">
                <card-tree ref="unselectedTree" v-cloak :url="url" :params="params" :delete-flag="true" @set-state="unSelectedState = true"></card-tree>
            </el-col>
            <el-col :span="4" style="padding-top: 250px;padding-left: 5%">
                <el-button size="small" type="primary" @click="cardTreeSubmit('selectedTree')"><</el-button>
                <el-button size="small" type="primary" @click="cardTreeSubmit('unselectedTree')">></el-button>
            </el-col>
            <el-col :span="10">
                <card-tree ref="selectedTree" v-cloak :main-data="selectedData" :delete-flag="true" :increment-flag="incrementFlag" @set-state="selectedState = true"></card-tree>
            </el-col>
        </el-row>
        <div style="height: 30px">
            <div style="float: right">
                <el-button @click="last" v-if="flag === 0">上一步</el-button>
                <el-button type="primary" @click="next">下一步</el-button>
            </div>
        </div>
    </div>
</template>

<script>
module.exports =  {
    name: "two",
    watch: {
        flag(newValue, oldValue) {
            //根据传入的flag决定是否记录zTree增量
            this.incrementFlag = (newValue === 0) ? false : true
        },
        //当selectedState和unSelectedState两棵树都渲染完毕时结束"加载中..."的显示
        unSelectedState(newValue){
            if(newValue && this.selectedState){
                let tree = this.$refs.selectedTree.getTree(false);
                this.$refs.unselectedTree.removeTree(tree);
                this.loading = false;
            }
        },
        selectedState(newValue){
            if(newValue && this.unSelectedState){
                let tree = this.$refs.selectedTree.getTree(false);
                this.$refs.unselectedTree.removeTree(tree);
                this.loading = false;
            }
        }
    },
    components: {
        'card-tree': 'url:components/static_menu/cardTree.vue',
    },
    data() {
        return {
            //传入cardTree的url,获取渲染ztree的json
            url: "",
            //作为获取json请求的参数
            params:{
              id:""
            },
            //增量flag,该值控制ztree是否记录增量
            incrementFlag: false,
            //树状态,当树渲染完毕后将会给他们赋值true,标识页面渲染完毕
            selectedState: false,
            unSelectedState: false,
            //当表格未加载成功时显示加载动画
            loading: true
        };
    },
    methods: {
        //由父页面调用,该方法调用 selectedTree选中树,获取选中树的增量
        getIncrement(refine){
            return this.$refs.selectedTree.getIncrement(refine);
        },
        //调用父页面调用,
        next() {
            let selectedTree = this.$refs.selectedTree.getTree(false);
            if(selectedTree === undefined || selectedTree.length === 0){
                //获取到所有数据后它不为叶子节点,说明有选中文件
                this.$message.error('请选择文件');
                return
            }
            //将选中数据提交给父页面处理
            this.$emit('update:selectedData',selectedTree)
            this.$emit('get-active', true);
        },
        //处理上一步按钮
        last() {
            this.$emit('get-active', false);
        },
        /**
         * 数据处理相关
         */

        //控制 >< 按钮,将参数中的数据传到另一个中
        cardTreeSubmit(originalTree) {
            let submit = this.$refs[originalTree].submit(false);
            let laterTree = (originalTree === 'unselectedTree') ? 'selectedTree' : 'unselectedTree';
            // 把选中项添加到另一棵树中,并删除原树中的选中项
            this.$refs[laterTree].addTree(submit);
            this.$refs[originalTree].removeSelected();
        },
    },
    created(){
        // 给url赋值,该url将会用于树中查询zTree渲染数据的json
        switch (this.dataOne.scanType){
            case "db":
                this.url = "source/db/tableTree";
                break;
            case "file":
                this.url = "source/file/fileList";
                break;
            case "hadoop":
                this.url = "source/hadoop/tableTree";
                break;
            default:
                this.$message.error("暂时只支持db、file、hadoop数据源")
        }
        //树获取数据请求中的参数
        this.params.id = this.dataOne.dataSourceId;
    },

    //接收来自父级的对象
    props:{
        //因为该页面用于修改范围和新增两处,而修改范围时不允许上一步,flag用作区分,当修改范围时传值
        flag:{
            type: Number,
            required: false
        },
        //选中的所有数据
        selectedData:{
            type: Array,
            required: false
        },
        //横幅中需要显示第一步中的数据
        dataOne:{
            type: Object,
            required: true
        }
    }
}
</script>

<style scoped>

</style>

源码解析:

cardTree ==> DOM部分:

        由elementUI的card组件组成外框和卡片头,zTree渲染进卡片体中,并使用elementUI的<el-scrollbar>滚动条组件对zTree包裹(scrollbar组件不能在官网看到,若需要可以直接搜索使用方法或查看源码)

cardTree ==> JS部分:

       js部分分为多条方法线,每个方法线对应操作zTree数据,(注:以下API统统表示zTreeAPI,其余API会著名)

        一、页面加载初始化zTree

        1.vue.created()进入页面时生成一个TreeId,该id用于如果一个页面中使用了多个cardTree组件时id重复的问题

        2.根据传递进入页面的url或者mainData进入renderTree()方法初始化zTree组件,初始化组件时定义了shift,ctrl功能选中文件时使用,可查询zTreeAPI了解方法。

        3.初始化组件结束后会调用setState表示已初始化完毕,这个值可以用于父组件用作elementUI的v-loading

        二、自定义筛选功能

        1.监听卡片中的输入内容,若值改变则调用API筛选所有 "isHidden=true" 的节点,显示出这些节点,

        2.调用API.getNodesByFilter,模糊查询node.level中不包含输入值的节点并隐藏(赛选规则由this.filter返回)

        三、返回选中项

        submit()方法可由父页面调用,参数:Boole 是否去除zTree添加的属性(提纯)

this.$refs[originalTree].submit(false);

        1.调用API.getSelectedNodes()获取选中节点

        2.进入getPathNodes(选中节点)补充其所有父节点

                循环检测父节点id

                        若没有父节点id直接装进tree数组返回

                        若有父节点id则装入map<父节点id,[选中的节点数组]>中之后再次校验父节点的父节点

                循环父节点map

                       $.extend(true,arr1[],arr2[])深度合并一次API.getPath()获取到的路径,修改时防止修改到源数据

                        循环替换API.getPath()获取到的父路径,将选中的节点修改到第[length-1]个,再循环替换最终得到顶级父节点下所有节点都是已选中节点

                注意:

                这种方式只能运用于3级节点或以下,若有3级节点及以上的情况需要map去重或者递归循环

        3.根据传值是否去除zTree赋予的属性(提纯)

                循环提取nodes中的id,label,children属性到tree数组中并返回,若循环项存在children,则轮询提取。

        四、接收节点

        addTree(tree)可由父页面调用,用于该树接收节点

                1.进入addChildren(null,tree)递归向树中添加节点,参数:parent 目标节点;children 需要添加的节点

                        循环children 调用API.getNodeByParam("id", child.id, parent);在parent下查找本次循环的节点

                        若找到了这个节点并且该节点是叶子节点则直接退出本次循环,若不是叶子节点则调用addChildren(该节点,该节点的子节点)进入递归

                        若没有找到本次循环的节点则根据传入的incrementFlag是否将本次循环的节点设置为增量,然后再调用API.addNodes()向zTree中添加该节点

                                增量其实就是递归将该节点及其子节点的checked设置为true,该属性表示勾选状态,但在配置zTree时为配置多选框显示,所以在页面上没有表示出来,但可以通过API.getCheckedNodes()获取所有勾选的节点(即增量)

                2.更新总数

                        调用API.getNodes()获取zTree的全部节点

                        循环递归求总数,并赋值给this.total显示在页面上

        五、返回增量

                增量其实就是在新增数据时利用了未配置显示的zTree多选框,此处只需要调用API.getChechedNodes()获取选中多选框的节点再和返回选中项一样添加父路径和提纯

        六、删除选中项

                1.调用API.getSelectedNodes()获取所有选中项

                2.调用API.getParentNode()获取父节点 -- 注:此处是shift/ctrl选中节点,不能跨越父节点选择,所以只需要任意取一个节点获取父节点就可以。

                3.循环调用API.removeNode()删除选中节点

                4.判断若删除完选中节点后父节点变为叶子节点则同样删除

                        此处递归调用如果删除完选中节点后 父节点任然为父节点,则删除并递归判断爷爷节点

        七、删除参数项

                1.循环参数节点,第一轮循环循环的是顶级父节点

                2.调用API.getNodesByParam("label", node.label)[0]; 根据label查找zTree中的第0个节点

                3.判断找到的节点若不是一个父节点则直接删除

                        若是一个父节点,则递归调用删除其子节点

                        递归结束判断其本身是否变为了一个叶子节点,若变为了叶子节点则删除

        八、获取整棵树,而不是选中节点

                1.调用API.getNodes(); 获取所有节点

                2.根据方法参数决定是否调用this.refineSubmit()提纯去除zTree添加的属性

                3.返回节点

        九、获取所有叶子节点

                1.调用API.getNodesByParam("isParent", false, null); 筛选isParent属性为false的节点

                2.返回节点        

cardTree ==> 使用部分:

        一、dom部分

                1.使用v-loading做页面未加载完毕时的覆盖"加载中..."字样。使用过程中若树太大会加载较慢,需要给用户反馈

                2.使用<el-row>准备两个引用card-tree的标签和两个切换的按钮

                3.<card-tree>属性说明:

                        ref:同html-ref。

                        url 和 params:cardTree获取树数据的url和请求体携带的数据

                        main-data:树数据,支持不通过url去后端查询数据,可传递该数据直接向树中添加

                        increment-flag:增量标识,若传递true则该树会在通过addTree()新增节点时记录增量

                        @set-state:cardTree初始化结束后会通过this.$emit("set-state")调用此方法,用于标识加载结束

                        注:这些值通过vue-props传递进入cardTree

        二、js部分

                1.vue-components:标记cardTree的位置,以便于页面上使用<cardTree>标签显示出cardTree

                2.vue-watch:

                        监听flag,因为其他业务过程中需要一个变化的 增量flag用于标识cardTree是否记录增量,所以监听了flag,正常情况可不监听flag。

                         unSelectedState 和 selectedState 主要用于监听两颗树是否加载完毕,加载完毕时取消页面的“加载中...”覆盖层             

                3.vue-methods:

                        getIncrement(refine): 可由其他页面调用该方法获取selectedTree(该页面第二个Tree的名称,意为选中树,另一颗为未选中树)中的增量数据

                        next(): 下一页按钮实现,获取selectedTree中的所有节点数据,跳转

                        cardTreeSubmit(originalTree): 统一了两个按钮,该方法用于将参数中的选中节点传输到另一个

                                通过this.$refs[originalTree].submit(false);调用cardTree中的选中节点

                                通过this.$refs[laterTree].addTree(submit);调用另一颗cardTree将选中节点新增进另一颗cardTree

                                通过this.$refs[originalTree].removeSelected();删除选中节点,完成数据转移

                4.vue-created 和 vue-props:

                        此处和cardTree无关,用于增加本页面复用性的区别代码,

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值