背景:根据后端返回数据生成多级菜单,菜单项可能会有很深的层级,如果直接使用elementUI 去编写会写很深的层级,代码繁杂,一旦后面菜单项有改动又不利于维护
如何做到多级菜单?使用递归组件
elmentUI原本写法:
html
<el-menu
default-active="2"
class="el-menu-vertical-demo"
@open="handleOpen"
@close="handleClose"
background-color="#545c64"
text-color="#fff"
active-text-color="#ffd04b">
<el-submenu index="1">
<template slot="title">
<i class="el-icon-location"></i>
<span>导航一</span>
</template>
<el-submenu index="1-4">
<template slot="title">选项4</template>
<el-menu-item index="1-4-1">选项1</el-menu-item>
</el-submenu>
</el-submenu>
<el-menu-item index="4">
<i class="el-icon-setting"></i>
<span slot="title">导航四</span>
</el-menu-item>
</el-menu>
使用递归组件写法:
主要思路:
- 通过数据查找hasOneChild()判断是否有children,有证明有子菜单,有子菜单使用el-submenu封装的组件
- SidebarItem.vue组件内部调用自己的组件;
- 渲染元素组件Item.vue使用函数式组件写法;
html
<template v-if="hasOneChild(item.children, item) && (!oneChild.children || oneChild.noShowChild)">
<app-link v-if="item.redirect != 'noRedirect' && item.meta" :to="resolvePath(item.path)">
<el-menu-item :index="resolvePath(item.path)" class="submenu-title-noDropdown">
<Item v-if="item.meta" :icon="item.meta.icon" :title="item.meta.title"></Item>
</el-menu-item>
</app-link>
</template>
<!-- 有子菜单:有多个children -->
<el-submenu v-else :index="resolvePath(item.path)">
<template slot="title" v-if="item.meta">
<!-- 没有组件数据要处理可以使用函数式组件进行渲染 -->
<Item :icon="item.meta.icon" :title="item.meta.title"></Item>
</template>
<!-- el-submenu里面还有多级菜单 -->
<sidebar-item
v-for="child in item.children"
:key="child.path"
:item="child"
:base-path="resolvePath(child.path)"
class="nest-menu"
></sidebar-item>
</el-submenu>
javascript
// 判断当前菜单是否有子菜单
hasOneChild(children = [], item) {
// 判断如果菜单是隐藏直接不显示
// if(item.hidden) return false;
if (children.length === 0) return false;
const showChildArr = children.filter(child => {
console.log(child);
if (child.hidden) return false;
else return true;
});
// 没有找到child说明没有子菜单
if (showChildArr.length === 0) {
this.oneChild = { ...item, path: item.path, noShowChild: true };
return true;
}
console.log(this.oneChild, "this.oneChild");
return false;
},
函数式组件Item.vue写法:
- functional:true定义组件为函数式组件;
- props接收父级传入属性;
- render函数生成虚拟节点;
javascript
<script>
export default {
name: "Item",
functional: true,
// 组件传入的数据
props: {
title: {
type: String,
default: ""
},
icon: {
type: String,
default: ""
}
},
// render函数生成虚拟节点
render(h, context) {
// <i class="iconfont el-icon-location"></i>
// <span>{{item.meta.title}}</span>
const { title, icon } = context.props; //获取传入的属性
const vNode = [];
if (icon) {
const iconClass = `iconfont ` + icon;
vNode.push(<i class={iconClass} style="font-size:18px;"></i>);
}
if (title) {
// JSX语法
vNode.push(<span style="margin-left:8px;">{title}</span>);
}
return vNode;
}
};
</script>