美文网首页前端杂货铺
VUE 树状选择 递归组件

VUE 树状选择 递归组件

作者: waanhappy | 来源:发表于2017-07-24 17:15 被阅读0次

    使用vue递归组件实现上一个多级的树状结构

    先上图

    image

    demo链接 欢迎访问

    获取到的数据

    {
        "list": [
            {"id": 1000,"name": 1000},
            {"id": 1100,"name": 1100,"pid": 1000},
            {"id": 1110,"name": 1110,"pid": 1100},
            {"id": 1120,"name": 1120,"pid": 1100},
            {"id": 1121,"name": 1121,"pid": 1120},
            {"id": 1122,"name": 1122,"pid": 1120},
            {"id": 1200,"name": 1200,"pid": 1000},
            {"id": 1210,"name": 1210,"pid": 1200},
            {"id": 1300,"name": 1300,"pid": 1000},
            {"id": 2000,"name": 2000},
            {"id": 2100,"name": 2100,"pid": 2000},
            {"id": 2200,"name": 2200,"pid": 2000}
        ]
    }
    

    pid 表d示父节点的id

    拿到数据后需要将其整理为如下结构

    {
        1000: {
            id: 1000,
            name: 1000,
            branch: [1100,1200,1300],
            state: 0, // 选中状态 0:branch未选中 1:branch部分选中 2:branch全选
            open: false // 展开状态
        },
        1100: {
            id: 1100,
            name: 1100,
            branch: [],
            state: 0, // 选中状态 0:branch未选中 1:branch部分选中 2:branch全选
            open: false // 展开状态
        },
        1110: {
            id: 1110,
            name: 1110,
            checked: false // 选中状态
        }
        ...
    }
    
    

    数据方法示例如下:

    /**
     * list: [] 获取到的list数据
     * format: Function:(list[n])=> obj , 可选:格式化数据
     *      例如: function (item) { return '分支'+item.name;}
    **/
    function treeInit(list, format) {
        var allBranch = {}; 
        var rootIds = []; // 根id的集合 即:没有pId的对象的id的集合
        list.forEach(function (data) {
    
            var id = data.id;
            var pid = data.pid;
    
            var leaf;
            var pLeaf;
    
            if (allBranch[id]) {
                leaf = allBranch[id];
                leaf.checked = false;
                leaf.id = id;
            } else {
                leaf = {
    
                    checked: false,
                    id: id
                };
                allBranch[id] = leaf;
            }
            
            Object.assign(leaf, format ? format(data) : {});
    
            if (pid) {
                leaf.pid = pid;
                pLeaf = allBranch[pid];
                if (pLeaf) {
                    if (pLeaf.branch) {
                        pLeaf.branch.push(id);
                    } else {
                        pLeaf.branch = [id];
                        pLeaf.open = true;
                        pLeaf.state = 0;
                    }
                } else {
                    allBranch[pid] = {
                        branch: [id],
                        open: true,
                        state: 0
                    };
                }
            } else {
                rootIds.push(id);
            }
        });
        return {
            root: rootIds,
            tree: allBranch,
        };
    }
    

    模板

    模板包含一个tree组件和用于递归的branch组件

    // tree 模版
    <ul class="list-unstyled tree">
        <li is="treebranch" v-for="b in root" :key="b" :branch="tree[b]" :tree="tree" @check="check" @toggle="toggle"></li>
    </ul>
    
    // tree 接收的数据
    ['tree', 'root'] // tree:上面格式化过的 treeData   root: 根节点的id数组 ( [1000,2000] )
    
    // tree 的方法
    check: function (branchId) {
        var state = {}; // 需要更新的状态
        var tree = this.tree;
        checkSub(branchId, tree, state, !tree[branchId].checked); // 从当前的节点往下递归计算选中状态并将需要更新的状态存入state中
        checkParent(tree[branchId].pid, tree, state); // 从当前的节点往上计算状态并将需要更新的状态存入state中
        this.$emit('update', state);
    },
    
    toggle: function (branchId) {
        var state = {}; // 需要更新的状态
        addState(state, branchId, { open: !this.tree[branchId].open }); //将状态添加到state中
        this.$emit('update', state);
    }
    
    // branch模板
    
        <li>
            <a class="tree-switcher rotate-animate mr5" :class="{open:branch.open,hold:!branch.branch}" @click="toggle(branch.id)"></a>
            <a class="tree-state"  @click="check(branch.id)">
                <i class="box mr5" :class="{some:branch.state===1,all:branch.checked||branch.state===2}"></i>
                <span>{{branch.name}}</span>
            </a>
            <ul class="tree-group list-unstyled pl20" v-if="branch.branch" v-show="branch.open">
                <li is="treebranch" v-for="a in branch.branch" :key="a" :branch="tree[a]" :tree="tree" @check="check" @toggle="toggle"></li>
            </ul>
        </li>
        
    // branch 接收的数据
        ['branch', 'tree'] // branch:当前branch的id  tree:整个treeData
        
    // branch 方法
        
        // 选中/取消选中的操作处理
        check: function (bId) {
            this.$emit('check', bId);
        },
        // 折叠/展开的事件处理
        toggle: function (bId) {
            this.$emit('toggle', bId);
        }
    

    到这里已经实现了 tree组件了

    调用

    <tree :tree="treeData" :root="rootIds" @update="update"><tree>
    // data
    {
        treeData: treeData,
        rootIds: rootIds 
    }
    // 方法 
    update: function (newState) {
        var temp, i, j, k;
        Object.keys(newState).forEach(function(item){
            var newChange = newState[i];
            var branch = this.treeData[i];
            
        }, this);
        for (i in newState) {
            temp = this.treeData[i];
            k = newState[i].state;
            for (j in k) {
                temp[j] = k[j];
            }
        }
    }
    

    以上为基本实现方案、其中调用了三个方法checkSub、checkParent和addState,实现了一个小型的通用数据更新对象state

    // 从下至上的状态检查修改上一级的state值
    function checkParent(pid, treeData, resObj) {
    
        var parent = treeData[pid];
        if (!parent) return;
        var all = true;
        var some = false;
        var state;
    
        parent.branch.forEach(function (item) {
            var it = treeData[item];
            var checked = getAttr(resObj, [item, 'state', 'checked'], it.checked);
    
            var state = getAttr(resObj, [item, 'state', 'state'], it.state);
    
            if (checked || state === 1) {
                some = true;
            }
            if (!checked) {
                all = false;
            }
        });
        if (all) {
            state = 2;
        } else {
            if (some) {
                state = 1;
            } else {
                state = 0;
            }
        }
        if (parent.state !== state) {
            addState(resObj, pid, { state: state });
        }
        if (parent.checked !== all) {
            addState(resObj, pid, { checked: all });
        }
        checkParent(parent.pid, treeData, resObj);
    
    }
    
    // 创建一个更新的对象
    function addState(obj, key, state) {
        var temp = obj[key];
        if (!temp) {
            temp = { state: {} };
            obj[key] = temp;
        }
        for (var i in state) {
            temp.state[i] = state[i];
        }
    }
    
    // 递归上到下递归修改状态
    function checkSub(id, tree, resObj, checked) {
        var obj = tree[id];
        var targetState = checked ? 2 : 0;
    
        addState(resObj, id, { checked: checked });
    
        if (obj.branch) {
            addState(resObj, id, { state: targetState });
            obj.branch.forEach(function (item) {
                checkSub(item, tree, resObj, checked);
            });
        }
    
    }
    
    // 本人最常用的一个方法实现 用于获取不确定的对象的数据
    function getAttr(obj, arr, defaultVal) {
    
        if (typeof arr === 'string') {
    
            arr = arr.split('.').filter(function (item) {
    
                return item;
    
            });
    
        }
    
    
        arr.every(function (item) {
    
            return obj = obj[item];
    
        });
    
    
        if (obj === undefined) {
            if (defaultVal === undefined) {
                defaultVal = '';
            }
            return defaultVal;
    
        }
    
        return obj;
    
    }
    

    至此本tree-demo完毕。
    才疏学浅某些方法的实现不甚完美,望见谅。

    相关文章

      网友评论

        本文标题:VUE 树状选择 递归组件

        本文链接:https://www.haomeiwen.com/subject/powbkxtx.html