一、需求情况
有时候用Vue开发的时候,不想使用组件库,但是遇到需要目录显示的情况的时候,想要一个树形递归的组件,可以自己封装定义想要的样式效果与事件,本文讲解如何实现一个自己的树形组件。
二、开发组件
Tree.vue父组件
html
<!--
* @Author: 羊驼
* @Date: 2024-03-04 11:43:47
* @LastEditors: 羊驼
* @LastEditTime: 2024-03-12 17:16:58
* @Description: 树形组件的树
-->
<template>
<ul>
<tree-node
v-for="node in nodes"
:key="node.id"
:node="node"
:select_id="select_id"
@node-clicked="handleNodeClicked"
></tree-node>
</ul>
</template>
<script>
import TreeNode from "./TreeNode.vue";
export default {
components: {
TreeNode,
},
props: {
// 节点数据
nodes: Array,
// 当前选中的节点
select_id: String | Number,
},
methods: {
// 定义一个节点点击事件
handleNodeClicked(clickedNode) {
this.$emit("node-clicked", clickedNode);
},
},
};
</script>
<style scoped>
ul {
list-style: none;
}
</style>
TreeNode 节点组件
html
<!--
* @Author: 羊驼
* @Date: 2024-03-04 11:42:24
* @LastEditors: 羊驼
* @LastEditTime: 2024-03-12 17:19:30
* @Description: 树形节点
-->
<template>
<div>
<li
:class="{'a-selected':select_id==node.id}"
class="flex"
ref="text"
@click="childClicked(node)"
>
<div
class="icon-box"
v-if="haveChild"
>
<svg
@click="setExpanded(!isExpanded)"
t="1709605135944"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="2647"
width="25"
height="25"
:class="{rotate:isExpanded}"
:fill="select_id==node.id?'#ccc':'#000'"
>
<path
d="M500.8 604.779L267.307 371.392l-45.227 45.27 278.741 278.613L779.307 416.66l-45.248-45.248z"
p-id="2648"
></path>
</svg>
</div>
<span>{{node.name}}</span>
</li>
<ul v-if="haveChild&&isExpanded">
<tree-node
v-for="child in node.children"
:key="child.id"
:node="child"
:select_id="select_id"
@node-clicked="childClicked"
></tree-node>
</ul>
</div>
</template>
<script>
export default {
// 因为是递归组件 导入的时候一定要以懒加载的形式 不然会报错的
components: { "tree-node": () => import("./TreeNode.vue") },
data() {
return {
// 展开情况
isExpanded: false,
};
},
props: {
node: Object,
select_id: String | Number,
},
computed: {
// 判断是否可以展开
haveChild() {
return this.node.children && this.node.children.length;
},
},
methods: {
// 设置展开情况
setExpanded(value) {
this.isExpanded = value;
},
// 点击情况
childClicked(clickedNode) {
this.$emit("node-clicked", clickedNode);
},
},
};
</script>
<style scoped>
.icon-box {
width: 40px !important;
display: flex;
}
.icon {
text-align: center;
margin-right: 10px;
transition: all 0.2s ease;
transform: rotate(-90deg);
}
ul {
list-style: none;
padding-left: 20px !important;
}
.flex {
display: flex;
align-items: center;
padding: 10px 20px;
user-select: none;
}
.flex a {
padding: 0 !important;
}
.flex:hover {
background: #f2f9f5 !important;
color: #008737 !important;
}
.flex:hover a {
background: #f2f9f5 !important;
color: #008737 !important;
}
.a-selected,
.a-selected a {
background: #008737 !important;
color: #fff !important;
}
.rotate {
transform: rotate(0);
}
</style>
三、使用组件
html
<!--
* @Author: 羊驼
* @Date: 2024-03-12 16:49:46
* @LastEditors: 羊驼
* @LastEditTime: 2024-03-12 17:21:17
* @Description: 示例
-->
<template>
<div>
<tree
:nodes="nodes"
:select_id="select_id"
@node-clicked="handleNodeClicked"
>
</tree>
</div>
</template>
<script>
import Tree from "./components/Tree.vue";
export default {
// 组件引入
components: { Tree },
data() {
return {
// 测试数据
nodes: [
{
id: 1,
name: "测试1",
children: [
{
id: 2,
name: "测试1-1",
children: [
{
id: 3,
name: "测试2-1",
},
],
},
{
id: 4,
name: "测试1-2",
},
],
},
{
id: 6,
name: "测试2",
},
],
select_id: "",
};
},
methods: {
// 节点选中后
handleNodeClicked(node) {
this.select_id = node.id;
},
},
};
</script>
<style>
</style>
四、效果展示
编辑
最简版本 后续有什么需求 可以自己定义插槽和事件实现