如何实现一个Tree组件
开发技术:vue
实现思路
写这个组件的初衷就是因为上级需要一个展示demo,内容就是一个竖向滚动卡片与目录展示,想着内容不多,也懒得去用什么第三方组件库了,就干脆自己实现一个树组件,也不用多么高大上,拥有基本功能就行。
其实,树组件的核心实现思想就是递归
所以就先定义了一下数据结构:
data:[
{
key: '0',
title: '0',
children: [
{
key: '0-1',
title: '0-1',
children: []
},
{
key: '0-2',
title: '0-2',
children: []
},
{
key: '0-3',
title: '0-3',
children: []
},
{
key: '0-4',
title: '0-4',
children: [
{
key: '0-4-0',
title: '0-4-0',
children: []
},
{
key: '0-4-1',
title: '0-4-1',
children: []
},
{
key: '0-4-2',
title: '0-4-2',
children: []
}
]
}
]
},
{
key: '1',
title: '1',
children: []
}
]
组件实现
这个组件主要有两个部分,一个包裹树节点的外层组件(index.vue),负责树的一些统一样式与状态管理;一个树节点组件,负责递归展示各节点(treeNode.vue)。两个放在一个目录(Tree)下,都是组件的组成部分。
树组件(index.vue)
果然还是代码最简捷明了。
<template>
<div class="tree">
<ul>
<tree-node
v-on="$listeners"
v-for="item in $attrs.treeList"
v-bind="$attrs"
:treeItem="item"
:key="item.key"
@changeTree="changeTree"
:expandedKeys="expandedList"
/>
</ul>
</div>
</template>
<script>
import TreeNode from '@components/Tree/treeNode'
export default {
name: 'Tree',
data() {
return {
expandedList: [] // 已展开节点
}
},
components: { TreeNode },
created() {
this.expandedList = this.$attrs.expandedKeys
},
methods: {
// 展开事件
changeTree(key) {
if (this.expandedList.includes(key)) {
this.expandedList.splice(this.expandedList.indexOf(key), 1)
} else {
this.expandedList.push(key)
}
}
}
}
</script>
<style lang="less" scoped>
.tree {
line-height: 20px;
ul > li {
padding-left: 0;
}
.treeNode-show-line:not(:last-child):before {
position: absolute;
left: 6px;
width: 1px;
height: calc(100% - 22px);
margin: 22px 0 0;
border-left: 1px solid #d9d9d9;
content: ' ';
}
}
</style>
树节点组件(treeNode.vue)
<template>
<li :class="['treeNode',$attrs.showLine?'treeNode-show-line':'']" v-if="$attrs.treeItem.children.length>0">
<div
@click="changeTree($attrs.treeItem.key)"
:class="['expandedIcon', $attrs.expandedKeys.includes($attrs.treeItem.key)?'close':'open']"
></div>
<span :class="['treeNode-content',$attrs.selected===$attrs.treeItem.key?'selected':'']">{{$attrs.treeItem.title}}</span>
<ul v-if="$attrs.expandedKeys.includes($attrs.treeItem.key)">
<tree-node
v-on="$listeners"
v-for="item in $attrs.treeItem.children"
:treeItem="item"
:key="item.key"
v-bind="$attrs"
/>
</ul>
</li>
<li :class="['treeNode',$attrs.showLine?'treeNode-show-line':'']" v-else>
<div class="expandedIcon leaf"></div>
<span :class="['treeNode-content',$attrs.selected===$attrs.treeItem.key?'selected':'']">{{$attrs.treeItem.title}}</span>
</li>
</template>
<script>
import TreeNode from '@components/Tree/treeNode'
export default {
name: 'TreeNode',
components: {
TreeNode
},
methods: {
changeTree(key) {
this.$emit('changeTree', key)
}
}
}
</script>
<style lang="less" scoped>
.treeNode {
padding: 4px 0 4px 18px;
position: relative;
.treeNode-show-line:not(:last-child):before {
position: absolute;
left: 20px;
width: 1px;
height: calc(100% - 22px);
margin: 22px 0 0;
border-left: 1px solid #d9d9d9;
content: ' ';
}
.expandedIcon {
width: 16px;
height: 16px;
display: inline-block;
&.open {
background: url('data:image/png;base64,iVBORw...FTkSuQmCC') // 展开图标,需要自己设置,也可以弄成动态的属性
no-repeat;
background-size: 100%;
}
&.close {
background: url('data:image/png;base64,iVBOR...I=')// 关闭图标,需要自己设置,也可以弄成动态的属性
no-repeat;
background-size: 100%;
}
&.leaf {
background: url('data:image/png;base64,iVBORw0KG...YII=')// 叶子节点图标,需要自己设置,也可以弄成动态的属性
no-repeat;
background-size: 100%;
}
}
.treeNode-content {
padding: 0 5px;
}
.selected {
font-weight: bold;
}
}
</style>
调用
最后调用Index.vue就可以啦。
<template>
<Tree
:treeList="treeList"
:expandedKeys="expandedKeys"
:selected="selected"
:showLine="showLine"
/>
</template>
<script>
import Tree from '@components/Tree'
export default {
name: 'Tsubasa',
data() {
return {
treeList: [...], // 要展示的数据
expandedKeys:[], //已展开节点
selected:'',//选中节点
showLine:true // 是否显示连接线
}
},
components: { Tree },
}
</script>
因为是自己开发的组件,所以更多样式细节和功能可以自己修改和添加。
我的实现肯定不是最好最优的,之后有时间我会继续完善的。
最后附上项目地址:https://github.com/Gshengx/demo