分享一个用递归的方式写 menu 组件
vue 利用递归组件写导航
- 为什么要用递归的方式写 ?
原因可以有几点: 大大简化了代码量,根据 JOSN 数据快速的渲染出 DOM 结构。不需要配置太多的参数。
缺点:因为使用 JSON 的方式传递了一整个数据,所有可扩展性不是很强。
<template>
<div class="ac-menu-container">
<template v-for="(data, index) in menuData">
<div class="menu-childrens" :key="index">
<div
class="menu-item"
@click.stop="selectClick(data, index)"
:class="{
active: selectItem == data.id,
}"
:style="{ paddingLeft: `${10*level}px` }">
<span>{{ data.name }}</span>
<div class="right-icon">
<i
class="icon el-icon-arrow-down"
:class="{ expanded: data.expand }"
v-show="hasChildren(data.children)"></i>
</div>
// toolbar 部分
<div class="operation isHover" v-show="isHover">
<button
icon="el-icon-edit"
@click.stop="handleEdit(data, index, true)">编辑</button>
<el-button
class="icon-plus"
@click.stop="handleCreate(data, index, true)">新建子分类</el-button>
</div>
</div>
// 递归组件
<template v-if="data.expand && level < 3">
<ac-munu
:menuData="data.children"
:level="setLeval"
v-model="selectItem"
@on-change="handleChange"
@on-edit="childrenEdit"
@on-delete="childrenDelete"
@on-create="childrenCreate"
:isHover="isHover"></ac-munu>
</template>
</div>
</template>
</div>
</template>
- js 部分
<script>
export default {
name: 'ac-munu',
props: {
value: {
type: [String, Number],
},
menuData: {
type: [Array, Object],
default: () => {}
},
level: {
type: Number,
default: 1
},
isHover: {
type: Boolean,
default: true
}
},
computed: {
setLeval() { // 当前的层级
return this.level + 1
},
selectItem: { // 双向绑定 v-model
get() {
return this.value
},
set(value) {
this.$emit('input', value)
},
}
},
methods: {
selectClick(item, index) {
if (item.expand) {
if (this.hasChildren(item.children)) {
item.expand = !item.expand
}
return
}
if (this.hasChildren(item.children)) {
item.expand = !item.expand
return
}
if(this.selectItem === item.id) return
this.selectItem = item.id
this.$emit('on-change', item, this.level, index)
},
hasChildren(option) {
if(!Array.isArray(option)) return false
return option.length > 0
},
handleChange(item, index) {
this.$emit('on-change', item, this.level, index)
},
handleEdit(item, index, state) {
this.$emit('on-edit', item, index, state)
},
handleDelete(item, index) {
this.$emit('on-delete', item, index)
},
handleCreate(item, index, state) {
this.$emit('on-create', item, index, state)
},
// 下面是除第一层以外的事件
childrenEdit(item, index, state) {
this.$emit('on-edit', item, index, state)
},
childrenDelete(item, index) {
this.$emit('on-delete', item, index)
},
childrenCreate(item, index, state) {
this.$emit('on-create', item, index, state)
}
}
}
</script>
- 调用 部分
<sc-menu
:menuData="menuData"
v-model="current"
@on-change="menuChange"
@on-edit="menuEdit"
@on-delete="menuDelete"
@on-create="menuCrateChildren"></sc-menu>
// JSON 数据 从后端取得数据
munuData: [
{
id:1,
name: '测试标题1',
expand: false,
children: [
{
id:2,
name: '测试第一层子标题',
expand: false,
children: [
{
id: 3,
name: '测试第二层子标题',
}
]
}
]
},
{
id:4,
name: '测试标题2',
expand: false,
children: [
{
id:5,
name: '测试第一层子标题',
expand: false,
children: [
{
id: 6,
name: '测试第二层子标题',
}
]
}
]
}
]
- 最后生成的效果

如果你有更好的写法或有什么疑问欢迎留言,如果想要源码欢迎留言或私信。
网友评论