VUE 树状选择 递归组件

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

先上图

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,395评论 25 707
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,560评论 18 399
  • 好几天没写日记了,我去,今天下雨了,买了毛笔在练字哦,买了新琴,弹起来哦,好好生活,早睡早起,多运动爱生活,么么哒...
    十一旧事阅读 140评论 0 0
  • 自己第一次当老师,难免有做的不好的地方,别太责怪自己,慢慢改进,别一口吃一个胖子。完美主义得慢慢改。
    1Remold7阅读 224评论 0 0