vue递归实现树形组件

本文实例为大家分享了vue递归实现树形组件的具体代码,供大家参考,具体内容如下

1. 先来看一下效果:

2. 代码部分 (myTree.vue)

图片可以自己引一下自己的图片,或者使用iconfont的css引入。

<template>
    <div class="tree">
        <ul class="ul">
            <li v-for="(item,index) of treeMenu" :key="index">
                <div class="jiantou" @click="changeStatus(index)">
                    <img src="../../assets/right.png" v-if="!scopesDefault[index]===true && item.children">
                    <img src="../../assets/down.png" v-if="scopesDefault[index]===true && item.children ">
                </div>
                <input type="checkbox" @click="checkBox(item)" v-model="item.check">
                <span @click="changeStatus(index)">{{item.label}}</span>
                <div class="subtree">
                    <tree-menu :treeMenu='item.children' v-if="scopesDefault[index]" @selectnode = "selectnode"></tree-menu>
                </div>
            </li>  
        </ul> 
    </div> 
</template>
<script>
    export default{
    name:'treeMenu',
     props:{
        treeMenu:{
            type:Array,
            default:[]
        },
    },
    data(){
        return{
            scopesDefault: [],
            scopes: [], 
            node:[],
            flatTreeMenu:[],
            check:'',
        }
    },
    methods:{
        //展开
        scope() {
            this.treeMenu.forEach((item, index) => {
                this.scopesDefault[index] = false
                if ('children' in item) {
                    this.scopes[index] = true
                    //console.log(item, index)
                } else {
                    this.scopes[index] = false
                }
            })
        },
        changeStatus(index) {
            if (this.scopesDefault[index] == true) {
                this.$set(this.scopesDefault, index, false)
            } else {
                this.$set(this.scopesDefault, index, this.scopes[index])
            }
        },
        //nodelist 深度优先递归
        checkBox(node,nodelist=[]){
            //console.log("start:",node,nodelist)
            if(node!==null){
                nodelist.push(node);
                if(node.children){
                    let children=node.children;
                    for(let i=0;i<children.length;i++){
                        this.checkBox(children[i],nodelist)//递归调用
                        children[i].check = nodelist[0].check==false?true:false;//选中父节点,子节点全选,取消,子节点取消
                    }
                }
            }
            this.node=node;
            this.check=node.check
        },
        selectnode(node){
            this.$emit("selectnode",node);
        }
    },
    watch:{ 
        node:{
            handler(val){
                this.selectnode(val);
            },
            immediate: true
        },
        check:{
            handler(val){
                this.selectnode(this.node);
            },
            immediate: true
        }
    },
    mounted(){
        this.scope(); 
    }
}
</script>
<style lang = "scss" scoped>
.tree{
    .ul{
        margin: 5px 0 5px 0;
        >li{
            .jiantou{
                display: inline-block;
                width: 15px;
                >img{
                position: relative;
                top: 2.0px;
                left: 4px;
                }
            }
            .subtree{
                margin-left: 20px;
                margin-top: 8px;
                margin-bottom: 8px;
            }
        }
    }
}
input[type=checkbox]{
    visibility: hidden;
    cursor: pointer;
    position: relative;
    width: 15px;
    height: 15px;
    font-size: 14px;
    border: 1px solid #dcdfe6;
    background-color: #fff !important;
    &::after{
        position: absolute;
        top: 0;
        background-color: #fff;
        border: 1px solid #ddd;
        color: #000;
        width: 15px;
        height: 15px;
        display: inline-block;
        visibility: visible;
        padding-left: 0px;
        text-align: center;
        content: ' ';
        border-radius: 3px;
        transition: all linear .1s;
    }
    &:checked::after{
        content: "\2713";
        font-size: 12px;
        background-color: #409eff;
        border: 1px solid #409eff;
        transition: all linear .1s;
        color: #fff;
        font-weight: bold;
    }
}
.check{
    &:checked::after{
        content: "--" !important;
    }
}
</style>

讲解:

1、调用组件:

我这用来一个global.js来控制组件的使用(这个js附在文章末尾了),在component文件夹中建立一个myTree文件夹,里面放同名vue文件(myTree.vue),这样无论在哪里调用这个组件,都可以直接使用<my-tree></my-tree>的方式去调用。

2、组件的方法:

scope():会生成一个数组,里面有根节点是否有子节点,如本代码里设定的数据,会有scopes=[true,true,true]这样的结果。
changeStatus():每点击标题或者箭头,如果当前下标的节点有没有子节点,再将结果动态赋值给scopesDefault[index],将这个值放于dom上控制开关,递归组件。
checkBox():在组件内部实现了点击全选、点击取消全选的功能,递归调用当前方法,将子元素的状态随父元素一起变化。
selectnode():将当前点击的node的节点内容上传到父组件。

3、监听:

同时监听:不同节点的切换、同一个节点的是否选中的切换,监听得到的结果都传到父组件中。

4、组件递归:调用时与父组件相同

3. 使用组件(useMyTree.vue)

<template>
    <div class = "loginModuel">
        <my-tree :treeMenu='tree' @selectnode="selectnode"></my-tree>
    </div>
</template>
<script>
export default{
    data(){
        return{
            msg:"这是登录页面",
            tree:[
                  {
                    id:1,
                    label:"1级目录1",
                    check:false,
                    children:[
                        {
                            id:"1-1",
                            pid:1,
                            label:"1.1目录",
                            check:false
                        },
                        {
                            id:"1-2",
                            pid:1,
                            label:"1.2目录",
                            check:false
                        },
                        {
                            id:"1-3",
                            pid:1,
                            label:"1.3目录",
                            check:false
                        },
                    ]
                },
                {
                  id:2,
                  label:"1级目录2",
                  check:false,
                  children:[
                      {
                          id:"2-1",
                          label:"2.1目录",
                          check:false,
                          pid:2,
                          children:[
                            {
                                id:"2-1-1",
                                pid:'2-1',
                                label:"2.1.1目录",
                                check:false,
                                children:[
                                    {
                                        id:"2-1-1-1",
                                        pid:'2-1-1',
                                        label:"2.1.1.1目录",
                                        check:false,
                                        children:[
                                            {
                                                id:"2-1-1-1-1",
                                                pid:'2-1-1-1',
                                                label:"2.1.1.1.1目录",
                                                check:false,
                                            },
                                            {
                                                id:"2-1-1-1-2",
                                                pid:'2-1-1-1',
                                                label:"2.1.1.1.2目录",
                                                check:false,
                                            },
                                        ]
                                    },
                                ]
                            },
                            {
                                id:"2-1-2",
                                pid:'2-1',
                                label:"2.1.2目录",
                                check:false,
                            },
                            {
                                id:"2-1-3",
                                pid:'2-1',
                                label:"2.1.3目录",
                                check:false,
                            },
                        ]
                      },
                      {
                          id:"2-2",
                          pid:2,
                          label:"2.2目录",
                          check:false
                      }
                  ]
              },//在此继续添加目录
              {
                id:3,
                label:"1级目录3",
                check:false,
                children:[
                    {
                        id:"3-1",
                        pid:3,
                        label:"3.1目录",
                        check:false,
                        children:[
                            {
                                id:"3-1-1",
                                pid:"3-1",
                                label:"3.1.1目录",
                                check:false,
                                children:[
                                    {
                                        id:"3-1-1-1",
                                        pid:"3-1-1",
                                        label:"3.1.1.1目录",
                                        check:false,
                                        children:[
                                            {
                                                id:"3-1-1-1-1",
                                                pid:"3-1-1-1",
                                                label:"3.1.1.1.1目录",
                                                check:false
                                            },
                                        ]
                                    },
                                ]
                            },
                        ]
                    }
                ]
            },
            ],
            plist:[],//此级以上所有父节点列表
            flatTree:[],//tree的平行数据
            node:'',//当前点击的node,
        }
    },
    methods:{
        //将tree树形数据转换为平行数据
        transformData(tree){
            tree.forEach(item=>{
                this.flatTree.push(item);
                item.children && item.children.length>0 ? this.transformData(item.children) : ""
            })
        },
        //子组件传递过来的点击的node的值
        selectnode(node){
            this.node=node;
            this.flatTree=[];
            this.transformData(this.tree);
            if(node.check==false){//这个节点已经被选中,正在点击取消选中
                this.plist=[];//每次点击一个新的节点都将原来plist的内容清空
                this.getParentnode(this.flatTree,node.pid)
            }else{//正在选中
                this.childAllToParent(node,this.flatTree,1);
            }
        },
        //子节点取消选中,拿到此子节点所有的父节点plist
        getParentnode(tree,pid){
            //this.plist=[]
            if(pid!==null){
                tree.forEach(item=>{
                    if(item.id==pid){
                        this.plist.push(item)
                        this.getParentnode(this.flatTree,item.pid)
                    }
                })
            } 
            if(!pid){
                this.plist.forEach(item=>{
                    this.updateParentCheck(this.tree,item)
                })
            }
        },
        //将原数据tree对应id的项的check值改为false
        updateParentCheck(tree,plistItem){
            //console.log("方法updateParentCheck接收的plistItem参数:",plistItem)
            tree.forEach(item=>{
                if(item.id==plistItem.id){
                    item.check=false;
                }
                if(item.id!==plistItem.id && item.children){
                    this.updateParentCheck(item.children,plistItem)
                }
            })
        },
        //子节点全部选中后父节点选中
        childAllToParent(node,flatTree,j){
            let fatherNode='';
            let brotherNode=[];
            this.flatTree.forEach(item=>{
                if(node.pid && node.pid==item.id){
                    fatherNode=item;//找到了父节点--用于改变check的值
                }
            })
            //判断该结点所有的兄弟节点是否全部选中
            flatTree.forEach(item=>{
                if(item.pid && node.pid && item.pid==node.pid){
                    brotherNode.push(item)//找到所有的兄弟节点
                }
            })
            //i为被选中的兄弟节点的个数
            let i=0;
            this.flatTree.forEach(item=>{
                if(node.pid==item.pid && item.check==true){
                    i=i+1;
                }
            })
            //修改父节点的选中值
            if(i==brotherNode.length && fatherNode){
                fatherNode.check=true
            }
            // console.log(`第j次递归 j=${j}`)
            // console.log(`选中的bro=${i},brother的个数:${brotherNode.length}`)
            // console.log("父节点:",fatherNode,"兄弟节点",brotherNode)
            if(fatherNode.pid!==undefined){
                j=j+1;
                this.childAllToParent(fatherNode,this.flatTree,j)
            }
        }
    },
    mounted(){
        this.transformData(this.tree);//数据初始化:将tree树形数据转换为平行数据
        //console.log(this.flatTree)
    }
}
</script>
<style lang = "scss" scoped>
.loginModuel{
    margin-left: 400px;
    margin-top: 100px;
    .tree{
        .ul{
            >li{
                margin: 5px 0 5px 0;
                >img{
                    position: relative;
                    top: 2.4px;
                    left: 4px;
                }
            }
            .ul2{
                >li{
                    position: relative;
                    left: 20px;
                    margin: 5px 0 5px 0;
                    >img{
                        //transition: all ease-in-out 1s;
                        position: relative;
                        top: 2.4px;
                        left: 4px;
                    }
                }
            }
        }
    }
}

input[type=checkbox]{
    cursor: pointer;
    position: relative;
    width: 15px;
    height: 15px;
    font-size: 14px;
    border: 1px solid #dcdfe6;
    background-color: #fff !important;
    &::after{
        position: absolute;
        top: 0;
        background-color: #fff;
        border: 1px solid #ddd;
        color: #000;
        width: 15px;
        height: 15px;
        display: inline-block;
        visibility: visible;
        padding-left: 0px;
        text-align: center;
        content: ' ';
        border-radius: 3px;
        transition: all linear .1s;
    }
    &:checked::after{
        content: "✓";
        font-size: 12px;
        background-color: #409eff;
        border: 1px solid #409eff;
        transition: all linear .1s;
        color: #fff;
        font-weight: bold;
    }
}
</style>

子组件主要是实现全选和取消全选。由于递归组件的原因,子组件拿不到完整的数据,所以接下来的两个功能:全选后某一个子节点取消选中则父节点取消选中、子节点全选后父节点自觉选中的功能就要在父组件中完成了。

讲解:

1、设值:

树形数据必须有pid属性,用于向上遍历。

2、方法:

transformData():将层级数据转为平行数据,避免后期不停的递归调用消耗时间,平级数据使用一般的循环即可完成。
selectnode():由子组件传递过来的方法,大致分为两个方向:选中、取消选中。选中时实现功能一:子节点全选后父节点自觉选中;取消选中实现功能二:全选后某一个子节点取消选中则父节点取消选中。
getParentnode():用于实现功能二。子节点取消选中后,根据pid,将在它上面级别的所有父节点列表拿到,再由方法updateParentCheck()将父节点的check值全部改为false
childAllToParent():用于实现功能一。递归调用该方法,将操作节点的父节点拿到,根据兄弟节点有相同的pid,拿到兄弟节点的个数,如果兄弟节点中被选中的个数等于兄弟节点的个数,则修改父节点的check值为true,直到到了根节点结束递归。

  • 这个组件实现起来不是很难,只要是心细就能很好的完成。
  • 如果后期需要使用某些数据的话,直接挂到data里就可以。
  • 如果有更好的方法或者存在某些疑问,欢迎小伙伴留言!

附: (global.js => 放于component文件夹下)

import Vue from 'vue';

function capitalizeFirstLetter(string){
    return string.charAt(0).toUpperCase() + string.slice(1);
}
const requireComponent = require.context(
    '.',true,/\.vue$/
    //找到components文件夹下以.vue命名的文件
)
requireComponent.keys().forEach(fileName => {
    const componetConfig = requireComponent(fileName);
    let a = fileName.lastIndexOf('/');
    fileName = '.' + fileName.slice(a);
    const componetName = capitalizeFirstLetter(
        fileName.replace(/^\.\//,'').replace(/\.\w+$/,'')
    )
    Vue.component(componetName,componetConfig.default || componetConfig)
})
  • 由此其实可以实现很多递归组件,如侧边栏。
  • 下面我放一个自己写的侧边栏的动图,方法比这个树形组件要简单些,毕竟不用考虑复选框的值。感兴趣的小伙伴们可以试着实践一下

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 用 Vue.js 递归组件实现可折叠的树形菜单(demo)

    在Vue.js中一个递归组件调用的是其本身,如: Vue.component('recursive-component', { template: `<!--Invoking myself!--> <recursive-component></recursive-component>` }); 递归组件常用于在blog上显示注释.嵌套的菜单,或者基本上是父和子相同的类型,尽管具体内容不同.例如: 现在给您演示一下如何有效地使用递归组件,我将通过建立一个可扩展/收缩的树形

  • vue左侧菜单,树形图递归实现代码

    学习vue有一段时间了,最近使用vue做了一套后台管理系统,左侧菜单需求是这样的,可以多层,数据由后台传递.也因为自己对官方文档的不熟悉使得自己踩了不少坑,今天写出来和大家一起分享. 效果图如下所示: 先说说遇到的坑,由于是子父组件,当时传递使用的是子父组件的传递,但是这时候只能获取到第一层的数据,第二层往后获取不到数据,踩了一下午坑以后才知道,子组件使用了递归组件,这时候他已经不能往父组件传递了,子传父,只能逐层传递这时候已经隔层了,所以我使用了非子父组件传递,两个页面都引入bus.js,这里

  • Vue递归实现树形菜单方法实例

    什么是树形菜单还是要简单的啰嗦一下,比如: 上图是截图自elementui的实例,实现方式是用文档结构(类似像原生Dom文档结构的写法)的方式,好处就是很灵活,可以方便的自定义,作为一个通用视图组件库这是正确的做法. 在实际的企业应用中,菜单要比这复杂很多,层次也要多很多,如果我们采取手动编写文档结构的方式,会导致代码亢长,阅读和维护都很低效.毫无疑问所有Vuer都会想到用一个数据结构来驱动文档结构.vue-router的数据结构恰恰就是完美的嵌套层次结构(树结构),同时vue文档中也提到了递归

  • vue递归组件实战之简单树形控件实例代码

    1.递归组件-简单树形控件预览及问题 在编写树形组件时遇到的问题: 组件如何才能递归调用? 递归组件点击事件如何传递? 2.树形控件基本结构及样式 <template> <ul class="vue-tree"> <li class="tree-item"> <div class="tree-content"><!--节点内容--> <div class="expand-

  • Vue.js 递归组件实现树形菜单(实例分享)

    最近看了 Vue.js 的递归组件,实现了一个最基本的树形菜单. 项目结构: main.js 作为入口,很简单: import Vue from 'vue' Vue.config.debug = true import main from './components/main.vue' new Vue({ el: '#app', render: h => h(main) }) 它引入了一个组件 main.vue: <template> <div class="tree-m

  • vue用递归组件写树形控件的实例代码

    最近在vue项目中遇到需要用树形控件的部分,比如导航目录是不确定的,所以必须要用树形结构,不管导航目录有几级,都可以自动显示出来,我一开始觉得element-ui有树形控件,不需要自己写,调用就可以了,后来才发现,调用完事之后,样式不可控,而且要加东西特别困难,无法满足项目需求,于是,一首<凉凉>送给自己,后来去翻vue官网,发现居然有递归组件,一开始我写了两个组件,互相调用,可以写出来,后来返现,如果项目要用到5棵树,我要写10个组件,而且样式控制起来超级恶心,于是我就各种查资料,原生的也试

  • vuejs使用递归组件实现树形目录的方法

    上篇文章我提到了通讯录的开发,里面的目录使用了vue的递归组件实现的树形目录,这篇文章就来讲讲如何实现树形目录吧! 首先实现效果如下,觉得菜单还是比较nice的是吧: 这边数据调用的是数据库的数据的,需要数据库进行数据的构造,这里涉及到java的构造多叉树的知识,后续我会另外写一篇文章详细讲解,这里讲下前端. 数据可以先构造json使用,这里用到的格式大概如下,以childList来嵌套子菜单: { id:YH, name:银行, pid:0, childList:[{ id:YH******,

  • Vue递归组件+Vuex开发树形组件Tree--递归组件的简单实现

    写在前面 首先,本篇文章所开发的组件并非一个已经开源的上线组件,所以如果你急于需要一个插件来只做你的项目,那么并不能带给你及时的帮助.这个组件的开发预计写两篇文章,一遍写组件,一篇写组件逻辑.这篇文章也是我自己开发的从无到有的过程,所以它可以为你提供一些Tree组件开发的思路,代码写到一定程度,不能完全依赖插件了,有时间可以看看插件源码或者动手去开发,这样真的能加深对技术的掌握程度. 开发过程 1.数据仓库-Vuex 2.组件的循环创建-递归组件 需求决定了我的技术选型,项目需求是一个中国各级政

  • Vue.js递归组件构建树形菜单

    在Vue.js中一个递归组件调用的是其本身,如: Vue.component('recursive-component', { template: `<!--Invoking myself!--> <recursive-component></recursive-component> }); 递归组件常用于在blog上显示注释.嵌套的菜单,或者基本上是父和子相同的类型,尽管具体内容不同.例如: 现在给您演示一下如何有效地使用递归组件,我将通过建立一个可扩展/收缩的树形菜

  • Vue2递归组件实现树形菜单

    今天看了老长时间递归组件,官方给的教程太简便了,根本看不出到底怎么用.于是自己查网摸索了一下,这儿只把核心思想写出来. 效果如下图,点击后打开二级菜单,再点击后打开三级. //js //引子 //思想:当v-if='false'时,循环时进行的.所以一开始就设置为false. ggg:{ name:'gs', template:` <div> <p @click.stop='show=!show'>我是p标签</p> //这儿show必须要初始值为false,不然就是堆

随机推荐