实现树形其实最基本的思想就是递归。
效果图:
首先是数据结构,也就是渲染这个树形组件需要什么样子的数据。下面是数据的例子:
treeList: [
{
label: "一级1",
show: false, // 是否展开
children: [
{
label: "二级1",
show: false,
children: [
{
label: "三级1",
show: false,
},
],
},
{
label: "二级2",
show: false,
},
],
},
{
label: "一级2",
show: true,
children: [
{
label: "二级2",
show: true,
},
{
label: "二级2",
show: true,
},
],
},
],
然后的组件的布局
<template>
<div class="tree">
<div class="tree-item" v-for="(item, i) in treeList" :key="i">
<div class="label">{{ item.label }}</div>
<div class="tree-children">
<div class="tc-item" v-for="(v, j) in item.children" :key="j">
<div class="label">{{ v.label }}</div>
<div class="tree-children"></div>
</div>
</div>
</div>
</div>
</template>
1
<script>
export default {
name: "Tree",
data() {
return {
treeList: [
{
label: "一级1",
show: false, // 是否展开
children: [
{
label: "二级1",
show: false,
children: [
{
label: "三级1",
show: false,
},
],
},
{
label: "二级2",
show: false,
},
],
},
{
label: "一级2",
show: true,
children: [
{
label: "二级2",
show: true,
},
{
label: "二级2",
show: true,
},
],
},
],
};
},
methods: {},
};
</script>
<style scoped>
.label {
height: 40px;
line-height: 40px;
}
.tree-children {
padding: 0 20px;
}
</style>
其实看布局,已经有递归的意思了,所以就可以把第二层v-for循环抽离成组件,我这里就叫TreeItem了,
抽离之后,
Tree.vue组件布局
<template>
<div class="tree">
<div class="tree-item" v-for="(item, i) in treeList" :key="i">
<div class="label">{{ item.label }}</div>
<TreeItem :item="v"></TreeItem>
</div>
</div>
</template>
TreeItem.vue组件
<template>
<div class="tree-children">
<div class="tc-item" v-for="(v, j) in item.children" :key="j">
<div class="label">
{{ v.label }}
</div>
<TreeItem
v-if="v.show"
:item="v"
></TreeItem>
</div>
</template>
<script>
export default {
name: "TreeItem",
props: {
item: {
type: Object,
default: () => {},
},
},
data() {
return {};
},
methods: {},
};
</script>
<style scoped>
.tree-children {
padding: 0 20px;
}
.label {
height: 40px;
cursor: pointer;
line-height: 40px;
}
</style>
TreeItem组件通过props接收外层循环传递的item,然后在组件自身递归调用自身。这样基本的树形结构就出渲染出来了,效果大概是这样。
然后我们需要控制节点的是否展开,在数据结构中我已经用show这个字段来控制是否展开。展开收缩其实只要控制TreeItem组件是否显示就可以做到了。
Tree.vue组件
<template>
<div class="tree">
<div class="tree-item" v-for="(item, i) in treeList" :key="i">
<div class="label" @click="toggleCollage(item)">{{ item.label }}</div>
<TreeItem v-if="item.show" :item.sync="item"></TreeItem>
</div>
</div>
</template>
TreeItem.vue组件
<template>
<div class="tree-children">
<div class="tc-item" v-for="(v, j) in item.children" :key="j">
<div class="label" @click="toggleCollage(v)">
{{ v.label }}
</div>
<TreeItem
v-if="v.show"
:item.sync="v"
></TreeItem>
</div>
</div>
</template>
一级1节点未展开,一级2展开了,效果:
然后我们思路肯定是通过点击节点来展开收缩子节点。
在这之前我们先把数据踢出去,通过外部传入,然后将TreeItem.vue组件通过ElementUI的
el-collapse-transition
组件包裹(没有安装ElementUI记得通过npm i element-ui安装下),以便后面展开收缩有过渡的效果,这样看起来更好,没有那么僵硬。
接着在给节点绑定点击事件,给组件绑定自定义事件,节点点击让show字段取反即可。
父组件需要在节点展开收缩做一些逻辑,这里绑定的自定义事件
node-click,将值传递给父组件。
然后简单再做一些样式上的调整,hover效果和label前面的小箭头(有子节点的就显示小箭头)。
最后完整代码:
Tree.vue
<template>
<div class="tree">
<div class="tree-item" v-for="(item, i) in treeList" :key="i">
<div class="label" @click="toggleCollage(item)">
<i
:style="{ transform: item.show ? 'rotate(90deg)' : '' }"
class="el-icon-caret-right rotate"
v-if="item.children && item.children.length"
></i>
{{ item.label }}
</div>
<template>
<el-collapse-transition>
<TreeItem
@node-click="nodeClick"
v-if="item.show"
:item.sync="item"
></TreeItem>
</el-collapse-transition>
</template>
</div>
</div>
</template>
<script>
import TreeItem from "./TreeItem.vue";
export default {
name: "Tree",
props: {
treeList: {
type: Array,
},
},
components: {
TreeItem,
},
data() {
return {};
},
methods: {
toggleCollage(item) {
console.log(item);
const findRes = this.treeList.find((v) => v.label === item.label);
findRes.show = !findRes.show;
this.$emit("node-click", item, this.treeList);
},
nodeClick(node, treeList) {},
},
};
</script>
<style scoped>
.label {
height: 40px;
cursor: pointer;
line-height: 40px;
}
.rotate {
transform-origin: 50% 40%;
transition: all 0.5s ease;
}
.label:hover {
background-color: #f5f7fa;
}
</style>
TreeItem.vue
<template>
<div class="tree-children">
<div class="tc-item" v-for="(v, j) in item.children" :key="j">
<div class="label" @click="toggleCollage(v)">
<i
class="el-icon-caret-right rotate"
:style="{ transform: v.show ? 'rotate(90deg)' : '' }"
v-if="v.children && v.children.length"
></i>
{{ v.label }}
</div>
<template>
<el-collapse-transition>
<TreeItem
@node-click="nodeClick"
v-if="v.show"
:item.sync="v"
></TreeItem>
</el-collapse-transition>
</template>
</div>
</div>
</template>
<script>
export default {
name: "TreeItem",
props: {
item: {
type: Object,
default: () => {},
},
},
data() {
return {};
},
methods: {
toggleCollage(item) {
item.show = !item.show;
this.$emit("node-click", item, item.children);
},
nodeClick(node, treeList) {},
},
};
</script>
<style scoped>
.tree-children {
padding: 0 20px;
}
.label {
height: 40px;
cursor: pointer;
line-height: 40px;
}
.rotate {
transform-origin: 50% 40%;
transition: all 0.5s ease;
}
.label:hover {
background-color: #f5f7fa;
}
</style>
使用组件App.vue
<template>
<div class="box">
<Tree @node-click="nodeClick" :treeList.sync="treeList"></Tree>
</div>
</template>
<script>
import Tree from "./components/Tree/index.vue";
export default {
name: "App",
components: {
Tree,
},
data() {
return {
treeList: [
{
label: "一级1",
show: false, // 是否展开
children: [
{
label: "二级1",
show: false,
children: [
{
label: "三级1",
show: false,
},
],
},
{
label: "二级2",
show: false,
},
],
},
{
label: "一级2",
show: true,
children: [
{
label: "二级2",
show: true,
},
{
label: "二级2",
show: true,
},
],
},
],
};
},
methods: {
nodeClick(node, treeList) {
console.log(node, treeList);
},
},
};
</script>
<style scoped>
.box {
width: 100%;
height: 100%;
}
</style>
有问题可以提出来,希望对您有所帮助,感谢您的观看。