目录,三个文件
tree-node.vue
<template>
<div class="tree-node-container">
<node-content></node-content>
<div
class="tree-node-children"
:style="{
paddingLeft: indent
}"
v-if="nextShow">
<tree-node
v-for="(child, idx) of nodeData.children"
:nodeData="child"
:indent="indent"
:key="idx">
</tree-node>
</div>
</div>
</template>
<script>
export default {
name: 'tree-node',
props: {
nodeData: {
type: Object,
required: true
}
},
components: {
'node-content': {
render (h) {
let slot = this.$parent.tree.$slots.default
let { nodeData, parentData, level, nextShow } = this.$parent
return (slot ? slot({ parentData, data: nodeData, level, nextShow }) : '<div>未定义插槽内容</div>')
}
}
},
data () {
return {
tree: false,
level: 0,
parentData: null,
childrenShow: true,
indent: undefined
}
},
computed: {
nextShow () {
return this.nodeData.children && this.nodeData.children.length && this.childrenShow
}
},
created () {
let parent = this.$parent
if (parent.isTree) {
this.level = 1
} else {
this.level = parent.level + 1
this.parentData = parent.nodeData
}
while (parent && !parent.isTree) {
parent = parent.$parent
}
this.tree = parent
this.indent = this.tree.indent
console.log('this.nodeData',this.nodeData)
this.tree.registerNodeComponent(this.nodeData.id, this)
},
beforeDestroy () {
console.log('pppppppppppppppppppppp')
this.tree.removeNodeComponent(this.nodeData.id)
},
mounted(){
// this.tree.removeNodeComponent(this.nodeData.id)
},
methods: {
showChildren (show) {
this.childrenShow = show
}
}
}
</script>
tree.vue
<template>
<div class="tree-container">
<tree-node
v-for="(nodeData, idx) of treeData"
:nodeData="nodeData"
:key="idx">
</tree-node>
</div>
</template>
<script>
import treeNode from './tree-node'
export default {
components: {
treeNode
},
props: {
treeData: {
type: Array,
requied: true
},
indent: {
type: String,
default: '20px'
}
},
data () {
return {
isTree: true,
level: 0,
componentMap: {},
width:'200px',
height:'500px'
}
},
methods: {
registerNodeComponent (id, component) {
this.componentMap[id] = component
},
removeNodeComponent (id) {
console.log('this.componentMap',this.componentMap,id)
this.componentMap[id] = undefined
},
showChildren (id, show) {
this.componentMap[id] && this.componentMap[id].showChildren(show)
}
},
mounted(){
console.log('this',this.componentMap)
}
}
</script>
<style scoped>
.tree-container{
text-align:left
}
</style>
使用
Tree.vue
<template>
<div class="tree-page">
<div class="tree">
<tree :treeData="treeData" ref="tree">
<template v-slot="{ parentData, data, level, nextShow }">
<div class="node-slot">
<div class="slot-content"
:class="{active_content:activeId === data.id}"
@click="clickTitle(data)">
<img :src="data.icon" />
<span class="node-title">{{ data.title }}</span>
</div>
<div class="expand">
<div
class="expand-btn"
@click.stop="clickExpand(data, nextShow)"
v-if="data.children && data.children.length"
>
<span v-if="nextShow">+</span>
<span v-else>-</span>
</div>
</div>
</div>
</template>
</tree>
</div>
</div>
</template>
<script>
import tree from "../components/tree/tree.vue";
export default {
name: "app",
components: {
tree,
},
data() {
return {
treeData:[
{
title: "第一个",
icon: "icons/上升.svg",
id: "1",
children: [
{ title: "第一/1个", icon: "icons/上升.svg", id: "1-1" },
{
title: "第一/2个",
icon: "icons/上升.svg",
id: "1-2",
children: [
{ title: "第一ce个", icon: "icons/上升.svg", id: "1-2-1" },
],
},
],
},
{ title: "第二个", icon: "icons/上升.svg", id: "2" },
{ title: "第三个", icon: "icons/上升.svg", id: "3" },
],
nodeId: 100,
activeId:''
};
},
methods: {
clickExpand(data, nextShow) {
this.$refs.tree.showChildren(data.id, !nextShow);
},
clickTitle(data){
this.activeId = data.id
}
},
};
</script>
<style scoped>
.tree-page {
display: flex;
width: 200px;
}
.tree {
flex: auto;
overflow: auto;
padding: 20px;
}
.node-slot {
display: flex;
align-items: center;
line-height: 2;
font-size: 20px;
}
.expand {
flex-shrink: 0;
width: 20px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 2px;
}
.expand-btn {
font-size: 24px;
color: #888;
line-height: 1;
border-radius: 50%;
cursor: pointer;
}
.dot {
border-radius: 50%;
border: 3px solid #aaa;
width: 0;
}
.slot-content {
flex: auto;
padding: 0 4px;
}
.slot-content:hover {
background: rgba(64, 158, 255, 0.2);
}
.node-title {
vertical-align: middle;
}
.node-menu-list {
font-size: 14px;
vertical-align: middle;
margin-left: 10px;
}
.menu {
padding: 2px 6px;
margin: 0 2px;
}
.active_content{
color: red;
background: rgba(64, 158, 255, 0.2);
}
img{
width: 16px;
height:16px;
background-color: blue;
}
</style>
以上参照了这位的博客Vue树组件实现 - 沐码小站
下面是用element 抽屉封装的,感觉没那么灵活
两个文件
menuChild.vue
<template>
<el-collapse class="coll-wrap">
<div
v-for="(item, i) in list"
:key="item.id"
:style="{
'padding-left': deep + '0px',
}"
@click="menuClick(item)"
>
<el-collapse-item v-if="item.children" :title="item.title" :name="i">
<template #title>
<img :src="item.icon" />
<span> {{ item.title }}</span>
</template>
<SubMenu
:labelData="labelData"
@updateLabel="updateLabel"
:deep="deep + 1"
v-if="item.children"
:list="item.children"
></SubMenu>
</el-collapse-item>
<div class="item" :class="{ active: labelData == item.id }" v-else>
<img :src="item.icon" /><span> {{ item.title }}</span>
</div>
</div>
</el-collapse>
</template>
<script>
// import SubMenu from "./menu.vue"
export default {
name: "SubMenu",
data() {
return {
activeName: "",
// list:[
// {title:'第一个',icon:"icons/上升.svg",id:'1',children:[
// {title:'第一/1个',icon:"icons/上升.svg",id:'1-1'},
// {title:'第一/2个',icon:"icons/上升.svg",id:'1-2'}
// ]},
// {title:'第二个',icon:"icons/上升.svg",id:'2'},
// {title:'第三个',icon:"icons/上升.svg",id:'3'}
// ]
};
},
props: {
list: {
type: Array,
default() {
return [
{
title: "第一个",
icon: "icons/上升.svg",
id: "1",
children: [
{ title: "第一/1个", icon: "icons/上升.svg", id: "1-1" },
{
title: "第一/2个",
icon: "icons/上升.svg",
id: "1-2",
children: [
{ title: "第一ce个", icon: "icons/上升.svg", id: "1-2-1" },
],
},
],
},
{ title: "第二个", icon: "icons/上升.svg", id: "2" },
{ title: "第三个", icon: "icons/上升.svg", id: "3" },
];
},
},
labelData: {
type: String,
default() {
return "";
},
},
deep: {
type: Number,
default() {
return 0;
},
},
},
// components:{
// SubMenu
// },
mounted() {
console.log(this.list);
},
methods: {
updateLabel(item) {
this.$emit("updateLabel", item);
},
menuClick(item) {
if (!item.children) {
this.activeName = item.id;
console.log("item", item);
this.$emit("updateLabel", item);
}
},
},
};
</script>
<style scoped>
.coll-wrap /deep/ .el-collapse-item__content {
padding-bottom: 0 !important;
}
.coll-wrap /deep/ .el-collapse-item__wrap {
border: 0 !important;
}
.item {
padding: 15px 0;
background-color: aqua;
}
.active {
background-color: aliceblue;
}
img {
width: 16px;
height: 16px;
background-color: blue;
}
.el-collapse {
border: 0 !important;
background-color: aqua;
}
.el-collapse-item /deep/ .el-collapse-item__header {
border: 0 !important;
background-color: aqua;
}
</style>
menu.vue
<template>
<MenuChild
class="my_menu"
:style="{
width:width,
height:height,
}"
:labelData="labelData"
@updateLabel = "updateLabel"
></MenuChild>
</template>
<script>
import MenuChild from "./menuChild.vue"
export default {
data(){
return{
labelData:'',
width:'200px',
height:'500px'
}
},
components:{
MenuChild
},
methods:{
updateLabel(item){
console.log(item)
this.labelData = item.id
}
}
}
</script>
<style scoped>
.my_menu{
background-color: aqua;
}
</style>