组件由两个文件构成,都放在了Sidebar目录下,分别是index.vue和SideItem.vue,使用时直接引入index.vue即可。
菜单栏数据从路由表获取,示例中通过mapGetters从store中获取permission_routes。
在不考虑权限时,这些数据可以直接从router/index.js文件引入,permission_routes是根据登录用户权限筛选后的路由。示例中路由表的数据格式如下:
/**
* hidden: true 如果设置为true,选项将不会显示在侧边栏中(默认值为false)
* alwaysShow: true 如果设置为true,将始终显示根菜单
* meta : {
roles: ['admin','editor'] 控制页面路由时使用
title: 'title' 侧边栏和面包屑中显示的名称(推荐设置)
icon: 'el-icon-x' 图标显示在侧边栏中
}
*/
[
{
path: '/admin/a',
component: Admin,
meta: { title: '1', icon: 'el-icon-menu' , roles: ['admin']},
// alwaysShow: true,
children: [
{ path: 'aa',
component: () => import('@/views/admin/Menu1'),
meta: { title: '11', icon: 'el-icon-menu', roles: ['admin']},
// alwaysShow: true,
children: [
{ path: 'aaa',
component: () => import('@/views/admin/Menu1/Menu2'),
meta: { title: '111', icon: 'el-icon-menu' , roles: ['admin']},
// alwaysShow: true,
children: [
{ path: 'aaaa',
component: () => import('@/views/admin/Menu1/Menu2/Menu3'),
meta: { title: '1111', icon: 'el-icon-menu' , roles: ['admin']}
}
]
}
]
}
]
},
{
path: '/admin/b',
component: Admin,
meta: { title: '2', icon: 'el-icon-menu' , roles: ['admin']},
children: [
{ path: 'ba',
component: () => import('@/views/admin/menu2/Menu21'),
meta: { title: '21', icon: 'el-icon-menu', roles: ['admin']}
},
{ path: 'bb',
component: () => import('@/views/admin/menu2/Menu22'),
meta: { title: '22', icon: 'el-icon-menu', roles: ['admin']}
}
]
}
]
菜单栏组件逻辑:菜单栏由el-menu整体包裹,可展开的菜单项由el-submenu包裹,最终不可再展开的菜单项是el-menu-item。无论是el-submenu还是el-menu-item,都通过SideItem组件生成。如果有子菜单(children),SideItem组件会通过调用组件自身的方式递归生成(类似函数递归),由于是自调用,组件名一定要写。
fragment是一个vue编码但不渲染的标签(vue-fragment),可用div替代它。
resolvePath()方法结合组件传参(basePath),将路由表中当前路由的path与其上级路由path拼接,组成当前菜单的完整路径。
最终代码:
...Sidebar/index.vue
<template>
<el-menu background-color="#49586D" text-color="#EFF3F6" active-text-color="#2CA9E1" :unique-opened="false"
:collapse-transition="false" :default-active="activePath" router mode="vertical">
<!-- SideItem组件 -->
<side-item v-for="route in permission_routes" :route="route" :basePath="route.path" />
</el-menu>
</template>
<script>
import {mapGetters} from 'vuex'
import SideItem from './SideItem.vue'
export default {
name: 'SideBar',
components: { SideItem },
data() {
return {
activePath: ''
}
},
created() {
this.activePath = this.$route.path
},
computed: {
// 获取所有路由
...mapGetters([
'permission_routes'
]),
}
};
</script>
<style lang="less" scoped>
.el-menu {
border-right: none;
}
</style>
...Sidebar/SideItem.vue
<template>
<fragment v-if="!route.hidden">
<!-- 没有子菜单 -->
<el-menu-item v-if="!route.children" :index="basePath">
<i :class="route.meta.icon"></i>
<span slot="title">{{route.meta.title}}</span>
</el-menu-item>
<!-- 有一个子菜单, 可不显示根菜单 -->
<fragment v-else-if="(route.children.length == 1) && !route.alwaysShow">
<!-- SideItem组件递归 -->
<side-item
v-for="child in route.children"
:route="child"
:basePath="resolvePath(child.path)"
/>
</fragment>
<!-- 有子菜单, 且显示根菜单 -->
<el-submenu v-else :index="basePath">
<template slot="title">
<i :class="route.meta.icon"></i>
<span slot="title">{{route.meta.title}}</span>
</template>
<!-- SideItem组件递归 -->
<side-item
v-for="child in route.children"
:route="child"
:basePath="resolvePath(child.path)"
/>
</el-submenu>
</fragment>
</template>
<script>
import path from 'path'
export default {
name: 'SideItem',
props: {
route: {
type: Object,
required: true
},
basePath: {
type: String,
default: ''
}
},
methods: {
// 路径拼接
resolvePath(routePath) {
return path.resolve(this.basePath, routePath)
}
},
};
</script>
<style lang="less" scoped>
</style>
菜单栏效果如下:
把 // alwaysShow: true 注释放开后的效果: