前端杂货铺

VUE 树状选择 递归组件

2017-07-24  本文已影响0人  waanhappy

使用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完毕。
才疏学浅某些方法的实现不甚完美,望见谅。

上一篇下一篇

猜你喜欢

热点阅读