用vue递归组件写树形控件

最近移动端H5项目有树级选择需求,网上没有找到好用的插件,于是参照着网上的教程和Vue官网自己写了一个。效果如图所示:

用vue递归组件写树形控件

组件在它的模板内可以递归地调用自己,只有当它有 name 选项时才可以。 

模拟数据格式如下:

dataList =  [{
	nodeName: "电能耗模型",
	    child: [{
	        nodeName: '园区',
	            child: [{
	                nodeName: "新华科技园"
	            },
	            {
	                nodeName: "百度大厦"
	            }
	            ]
	        }]
	    },
	    {
	        nodeName: "电费统计",
	            child: [{
	                nodeName: '园区',
	                    child: [{
	                        nodeName: "新华科技园"
	             },
	             {
	                nodeName: "百度大厦"
	             }
	             ]
	        }]
	    }
	]

子组件

官网强调了子组件name的重要性,所以name必须和子组件名一样才可以递归调用。

<p-tree v-for='(cel, index) in model.children' :model='cel' :key="index"></p-tree>


export default {
name: 'pTree'
}

定义一个open和一个isFolder变量。open判断该组件是否是打开状态,isFolder根据是否存在子组件数据或者子组件的数据长度,处理是否显示箭头,是否是展开的箭头。因为子组件数据不是固定的,所以isFolder需要写到计算属性里。

	data() {
			return {
				open: false
			}
		},
		computed: {
			isFolder() {
				return this.model.children && this.model.children.length
			}
		},

显示隐藏事件


toggle() {
	if (this.isFolder) {
		this.open = !this.open;
	}
}
<template>
	<li>
		<div>
			<i @click='toggle' v-if='isFolder' class="iconfont" :class="[open?'icon-jiantou-copy':'icon-jiantou']"></i>
			<i v-else class="iconfont icon-jiantou-noFloder-copy"></i>
			<span @click="sendChildVal(model)">
				{{model.nodeName}}
			</span>
		</div>
		<ul v-show="open" v-if='isFolder'>
			<p-tree v-for='(cel, index) in model.children' :model='cel' :key="index"></p-tree>
		</ul>
	</li>
</template>

<script>
	export default {
		name: 'pTree',
		props: ['model', 'treeVal'],
		components: {},
		data() {
			return {
				open: false
			}
		},
		computed: {
			isFolder() {
				return this.model.children && this.model.children.length
			}
		},
		methods: {
			toggle() {
				if (this.isFolder) {
					this.open = !this.open;
				}
			},
			sendChildVal(model){
				this.bus.$emit('treeVal', model)
			}
		}
	}
</script>

父组件

父组件外层是ul,根据数据的内容进行循环嵌套pTree 子组件。 

<template>
    <ul v-for="(item, index) in dataList " :key="index">
	    <pTree :model='item'></pTree>
    </ul>
</template>	

<script>
    import pTree from "./pTree.vue"
    components: {
		pTree
	}
}
</script>

传值问题:

通过简单的props和$emit进行传值的话,你会发现只有第一级树形能返回值,因为除了第一级,其他的都是属于子组件的子组件,并非父子关系。

这里我们使用总线传值的方法,在根实例下为每个实例添加一个bus属性。

new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})
Vue.prototype.bus = new Vue()

子组件传值:

<span @click="sendChildVal(model)">
				{{model.nodeName}}
</span>

	

methods: {
			sendChildVal(model){
				this.bus.$emit('treeVal', model)
			}
		}

父组件接收:

data() {
    return {
	ptreeVal: {}
 }
}

mounted() {
    this.bus.$on('treeVal', val => {//通过$on监听事件treeVal
        this.ptreeVal = val 
     })
}