在上一篇文章,我们根据用户角色获取了路由,这一篇文章,我们就根据路由来生成菜单栏,这也是后台管理系统的一个必备功能。
这篇文章将会从路由设计以及递归组件两个方面进行讲解,希望能够对大家有所帮助。
项目地址:gitee
系列文章地址:
1. 项目基础架构
2. 登录与权限控制
路由设置
目前网上大部分的实战课程在设置路由时,都会采用如下方式
// 写法1
const routes = [
{
path: '/',
component: () => import('XXX')
children: [
{
path: 'a',
component: () => import('A'),
children [
{
path: 'aa',
component: () => import('AA')
}
]
},
{
path: 'b',
component: () => import('B')
}
]
}
]
写法1符合我们的很直观想法,但是个人感觉这种写法不太清晰,因此笔者采用了写法2
// 写法2
const router = [
// 路由1
{
path: '/a',
redirect: '/a/aa',
// 布局组件
component: Layout,
children: [
{
path: 'aa',
component: () => import('AA'),
},
{
path: 'bb',
component: () => import('BB'),
}
]
},
// 路由2
{
// ...
}
// ...
]
个人感觉这种写法的优点在于,每个路由对象对应一个菜单项,结构比较清晰。
其实这两种写法对于菜单的生成是没有影响的,大家可以自行选择。
几个路由参数
alwaysShow // 默认为false,当菜单栏只有一个子项时,会直接显示为一个el-menu-item
// 如果设置为true,则该路由对应的菜单栏为el-submenu
hidden: // 是否在菜单栏中显示该路由,比如/login.hidden = true
meta: {
icon // 菜单栏图标
title // 菜单栏标题
}
递归生成菜单栏
之前我们已经获取到了权限路由,可以定义一个getter
来获取它
// src/store/getters.js
const getters = {
// ...
permission_routes: state => state.permission.routes
// ...
}
这样,我们就能通过路由生成菜单栏。
我们的菜单栏组件定义在src/Layout/SideBar
文件夹内
首先看SideBar/index.vue
,过滤出可以显示的项
created() {
this.routes = this.permissionRoutes.filter(r => !r.hidden)
}
之后循环this.routes
生成菜单
<el-menu
router
>
<sidebar-item
v-for="route in routes"
:key="route.path"
:sidebarItemRoute="route"
:base-path="route.path"
/>
</el-menu>
sidebar-item
是自定义的组件,表示每个菜单项,该组件接收两个props
props: {
sidebarItemRoute: {
type: Object,
required: true
},
basePath: {
type: String,
default: ''
}
}
菜单项组件
sidebar-item
是一个递归组件
<template v-if="showAsMenuItem && !sidebarItemRoute.alwaysShow">
<el-menu-item :index="resolvePath(menuItemRoute.path)">
<i :class="menuItemRoute.meta.icon"></i>
<span>{{ menuItemRoute.meta.title }}</span>
</el-menu-item>
</template>
<el-submenu v-else :index="resolvePath(sidebarItemRoute.path)">
<template slot="title">
<i :class="sidebarItemRoute.meta.icon"></i>
<span>{{ sidebarItemRoute.meta.title }}</span>
</template>
<sidebar-item
v-for="child in sidebarItemRoute.children"
:key="child.path"
:sidebarItemRoute="child"
:base-path="resolvePath(child.path)"
/>
</el-submenu>
当showAsMenuItem === true
并且alwaysShow === false
时,当前路由展示为一个el-menu-item
。showAsMenuItem
的判断在handleRoute
方法中,基本逻辑为,如果当前路由没有子路由,则showAsMenuItem
为true
,或者当前路由只有一个子路由,并且这个子路由没有子路由时,则showAsMenuItem
为true
handleRoute() {
// 如果没有子路由,递归结束
if (!this.sidebarItemRoute.children) {
this.showAsMenuItem = true
return
}
// 过滤掉children中hidden的
const showingChildren = this.sidebarItemRoute.children.filter(
item => !item.hidden
)
// 当只有一个子路由,并且这个子路由没有children属性时
if (showingChildren.length === 1 && !showingChildren[0].children) {
this.showAsMenuItem = true
this.menuItemRoute = showingChildren[0]
}
}
另外一个需要处理的就是path
,处理思路如下,父组件传递一个base-path
,子组件将base-path
与自己的path
拼接得到完整的路径。因此我们能在模板中看到这样的代码:<el-menu-item :index="resolvePath(menuItemRoute.path)">
和<el-submenu v-else :index="resolvePath(sidebarItemRoute.path)">
// js
resolvePath(routePath) {
return path.resolve(this.basePath, routePath)
}
但是有一个特殊情况需要处理,就是当路由没有子路由时,它的base-path
就已经是完整的path
了,但是在模板中需要将base-path
与path
进行拼接,因此需要将它自己的path
重置为空字符串
handleRoute() {
if (!this.sidebarItemRoute.children) {
this.showAsMenuItem = true
// 如果没有子路由,basePath就是这一层的路由,因此拼接一个空字符串
this.menuItemRoute = {
...this.sidebarItemRoute,
path: ''
}
return
}
// ...
}
这样就OK了。
一个小BUG
一个Vue
组件必须有且仅有一个根元素,一般我们会使用div
,但是在sidebar-item
组件中使用div
作为根元素会有一个bug
,就是当我们点击菜单收缩时,菜单并没有收缩,这是因为我们把sidebar
组件和sidebar-item
组件分开写,导致el-menu
和el-sub-menu,el-menu-item
之间多了一层div
,导致element-ui
内部的样式出现了问题,我们可以采用一个插件来解决:vue-fragment
// main.js
import Fragment from 'vue-fragment'
Vue.use(Fragment.Plugin)
在sidebar-item
组件内以<fragment>
标签作为根元素就可以了。如果你有其他的解决方案,也可以评论告诉我!!
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!