动态加载侧栏菜单
数据案例:后台返回的 json数据,请点击跳到文章最后查看
有三种方法可实现,但都有各自的缺点
- 第一种(不推荐)
注册所有的路由,根据动态菜单动态加载
缺点:不安全,用户可以通过更改浏览的的路径进去他没有权限的页面 - 第二种(不推荐)
通过枚举定义每个角色的路由,根据角色为其注册
缺点:不够灵活,如果某个角色需要添加一个新的路由权限,就需要手动改前端代码重新发布 - 第三种(推荐)
通过后台返回的菜单去动态注册路由,需要添加路由时就让后端去修改好了,后台返回什么就注册什么(不关我事哈;后台的锅是😄)
这里使用第三种方法
主要分两步:
- 动态注册路由
- 将渲染动态菜单
目的:往路由/main
里动态添加子路由
router/index.ts
const routes: Array<RouteRecordRaw> = [
{
path: '/',
redirect: '/main'
},
{
path: '/login',
name: 'login',
component: () => import('@/views/login/index.vue')
},
{
path: '/main',
name: 'main',
component: () => import('@/views/main/main.vue')
// children: [] 往这里面添加子路由
},
{
path: '/:pathMatch(.*)*',
name: 'not-found',
component: () => import('@/views/not-found/404.vue')
}
]
第一步:动态加载路由
注意:实际开发中,各级目录的获取实际看后台返回的数据,不管返回的数据是什么,目的都是为了拿到一二…级目录为其注册路由
通过观察返回数据可知,数据中 type=1
为一级目录,type=2
为二级目录,type=3
为权限
根据返回的数据先创建,对应的vue文件
和路由
路由:以overview
为例 overview.ts
const overview = () => import('@/views/main/analysis/overview/overview.vue')
export default {
path: '/main/analysis/overview',
name: 'overview',
component: overview,
children: []
}
vue文件:overview.vue
<template>
<div>overview</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
setup() {
return {}
}
})
</script>
<style scoped></style>
根据案例的数据,这里写一个递归函数对一二级目录的抽取
新建 `utils/map-menu.ts’
思路:
- 先加载默认所有的routers
- 根据菜单获取需要的routers动态加载(递归查找符合条件的路由并添加到routes里等待添加到
router.main.children
) - 将路由注册,即将routes 添加到 router.main.children
import { RouteRecordRaw } from 'vue-router'
import router from '@/router'
export function mapMenuToRoutes(userMenu: any[]): RouteRecordRaw[] {
const routes: RouteRecordRaw[] = []
//1.先加载默认所有的routers
const allRouters: RouteRecordRaw[] = []
// webpack提供的require.context,可获取文件夹下的所有文件
// require.context(需获取文件所在的文件夹,是否递归查找,获取规则【正则表达式】)
const routerFiles = require.context('../router/main', true, /\.ts/)
// console.log('routerFiles', routerFiles.keys())
routerFiles.keys().forEach((key) => {
const route = require('../router/main' + key.split('.')[1])
const routeArr = route.default
// console.log(route.default)
allRouters.push(routeArr)
})
// 2.根据菜单获取需要的routers动态加载
//递归查找符合条件的路由并添加到routes里等待加载
const _recurseGetRoute = (menus: any) => {
for (const menu of menus) {
if (menu.type === 2) {
const needRoute = allRouters.find((item) => {
return item.path == menu.url
})
if (needRoute) {
routes.push(needRoute)
}
if (!firstMenu) {
firstMenu = needRoute
}
} else {
_recurseGetRoute(menu.children)
}
}
}
_recurseGetRoute(userMenu)
// console.log('routes', routes)
//将路由注册,即将routes 添加到 router.main.children
routes.forEach((element) => {
router.addRoute('main', element)
})
return routes
}
第二步:渲染动态菜单
- 通过上面获取到的全部
json数据(即后台返回的原数据)
通过el-menu
组件渲染 - 为每个菜单添加路由
在·
utils/map-menu.ts
添加用于获取当期路由的递归函数
let currentMenu: any = null
export function pathMapToMenu(userMenu: any[], currentPath: string): any {
// console.log(userMenu)
for (const item of userMenu) {
if (item.type === 2 && item.url === currentPath) {
//找到了当前路径对应的路由
// console.log(item)
if (!currentMenu) {
currentMenu = item
}
return item
} else {
pathMapToMenu(item.children ?? [], currentPath)
}
}
}
export {currentMenu}
在侧边栏组件添加动态菜单,并调用上面的函数,为每个菜单添加路由
<template>
<div>
<div class="logo">
<img class="img" src="@/assets/logo透明.png" />
<div v-if="!collapse" class="title">后台管理端</div>
</div>
<div>
<el-menu
active-text-color="#ffd04b"
background-color="#0a2137"
class="el-menu-vertical-demo"
:collapse-transition="false"
:default-active="defaultValue"
:unique-opened="true"
:collapse="collapse"
text-color="#fff"
>
<template v-for="item in userMenus" :key="item.id + ''">
<!-- 二级菜单 -->
<template v-if="item.type === 1">
<el-sub-menu :index="item.id + ''">
<template #title>
<el-icon v-if="item.icon"
><component :is="getIcon(item.icon)"
/></el-icon>
<span>{{ item.name }}</span>
</template>
<el-menu-item-group>
<template
v-for="subItem in item.children"
:key="subItem.id + ''"
>
<el-menu-item
:index="subItem.id + ''"
@click="clickMenuItem(subItem)"
>
<el-icon v-if="subItem.icon"
><component :is="getIcon(subItem.icon)"
/></el-icon>
<span>{{ subItem.name }}</span>
</el-menu-item>
</template>
</el-menu-item-group>
</el-sub-menu>
</template>
<!-- 一级菜单 -->
<template v-else-if="item.type === 2">
<el-menu-item :index="item.id + ''" @click="clickMenuItem(item)">
<el-icon v-if="item.icon"
><component :is="getIcon(item.icon)"
/></el-icon>
<span>{{ item.name }}</span>
</el-menu-item>
</template>
</template>
</el-menu>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, computed, ref } from 'vue'
import { userStore } from '@/store'
import { IMenuItemState } from './type'
import { useRouter, useRoute } from 'vue-router'
import { pathMapToMenu, currentMenu } from '@/utils/map-menu'
export default defineComponent({
props: {
collapse: {
type: Boolean,
default: false
}
},
setup() {
const store = userStore()
const router = useRouter()
const route = useRoute()
const currentPath = route.path
console.log('currentPath', currentPath)
const userMenus = store.state.login.userMenus
// console.log(userMenus)
pathMapToMenu(userMenus, currentPath)
const defaultValue = ref(currentMenu.id + '')
// console.log('currentPathMenu', currentMenu)
const getIcon = (icon: string): string => {
const iconArr = icon.split('-')
if (iconArr.length > 3) {
iconArr.splice(0, 2)
return iconArr.join('-')
} else {
return iconArr[2]
}
}
const clickMenuItem = (menuItem: any) => {
router.push({
path: menuItem.url ?? 'not-found'
})
}
return {
userMenus,
getIcon,
clickMenuItem,
defaultValue
}
}
})
</script>
<style scoped lang="less">
.logo {
display: flex;
flex-wrap: nowrap;
height: 48px;
overflow: hidden;
.img {
// height: 40px;
width: 40px;
height: auto;
padding: 4px 10px;
}
.title {
line-height: 44px;
color: #fff;
font-size: 18px;
}
}
.el-menu-item.is-active {
background-color: #434a50 !important;
}
.el-menu-item {
// background-color: #545c64;
}
/deep/ .el-menu-item-group__title {
padding: 0;
}
.el-menu-vertical-demo:not(.el-menu--collapse) {
width: 210px;
// transition: 1s linear all !important;
// min-height: 400px;
}
.el-menu {
border-right: none;
}
</style>
到这里动态侧边栏菜单就完成了
json数据
[
{
"id": 38,
"name": "系统总览",
"type": 1,
"url": "/main/analysis",
"icon": "el-icon-monitor",
"sort": 1,
"children": [
{
"id": 39,
"url": "/main/analysis/overview",
"name": "核心技术",
"sort": 106,
"type": 2,
"children": null,
"parentId": 38
},
{
"id": 40,
"url": "/main/analysis/dashboard",
"name": "商品统计",
"sort": 107,
"type": 2,
"children": null,
"parentId": 38
}
]
},
{
"id": 1,
"name": "系统管理",
"type": 1,
"url": "/main/system",
"icon": "el-icon-setting",
"sort": 2,
"children": [
{
"id": 2,
"url": "/main/system/user",
"name": "用户管理",
"sort": 100,
"type": 2,
"children": [
{
"id": 5,
"url": null,
"name": "创建用户",
"sort": null,
"type": 3,
"parentId": 2,
"permission": "system:users:create"
},
{
"id": 6,
"url": null,
"name": "删除用户",
"sort": null,
"type": 3,
"parentId": 2,
"permission": "system:users:delete"
},
{
"id": 7,
"url": null,
"name": "修改用户",
"sort": null,
"type": 3,
"parentId": 2,
"permission": "system:users:update"
},
{
"id": 8,
"url": null,
"name": "查询用户",
"sort": null,
"type": 3,
"parentId": 2,
"permission": "system:users:query"
}
],
"parentId": 1
},
{
"id": 3,
"url": "/main/system/department",
"name": "部门管理",
"sort": 101,
"type": 2,
"children": [
{
"id": 17,
"url": null,
"name": "创建部门",
"sort": null,
"type": 3,
"parentId": 3,
"permission": "system:department:create"
},
{
"id": 18,
"url": null,
"name": "删除部门",
"sort": null,
"type": 3,
"parentId": 3,
"permission": "system:department:delete"
},
{
"id": 19,
"url": null,
"name": "修改部门",
"sort": null,
"type": 3,
"parentId": 3,
"permission": "system:department:update"
},
{
"id": 20,
"url": null,
"name": "查询部门",
"sort": null,
"type": 3,
"parentId": 3,
"permission": "system:department:query"
}
],
"parentId": 1
},
{
"id": 4,
"url": "/main/system/menu",
"name": "菜单管理",
"sort": 103,
"type": 2,
"children": [
{
"id": 21,
"url": null,
"name": "创建菜单",
"sort": null,
"type": 3,
"parentId": 4,
"permission": "system:menu:create"
},
{
"id": 22,
"url": null,
"name": "删除菜单",
"sort": null,
"type": 3,
"parentId": 4,
"permission": "system:menu:delete"
},
{
"id": 23,
"url": null,
"name": "修改菜单",
"sort": null,
"type": 3,
"parentId": 4,
"permission": "system:menu:update"
},
{
"id": 24,
"url": null,
"name": "查询菜单",
"sort": null,
"type": 3,
"parentId": 4,
"permission": "system:menu:query"
}
],
"parentId": 1
},
{
"id": 25,
"url": "/main/system/role",
"name": "角色管理",
"sort": 102,
"type": 2,
"children": [
{
"id": 26,
"url": null,
"name": "创建角色",
"sort": null,
"type": 3,
"parentId": 25,
"permission": "system:role:create"
},
{
"id": 27,
"url": null,
"name": "删除角色",
"sort": null,
"type": 3,
"parentId": 25,
"permission": "system:role:delete"
},
{
"id": 28,
"url": null,
"name": "修改角色",
"sort": null,
"type": 3,
"parentId": 25,
"permission": "system:role:update"
},
{
"id": 29,
"url": null,
"name": "查询角色",
"sort": null,
"type": 3,
"parentId": 25,
"permission": "system:role:query"
}
],
"parentId": 1
}
]
},
{
"id": 9,
"name": "商品中心",
"type": 1,
"url": "/main/product",
"icon": "el-icon-goods",
"sort": 3,
"children": [
{
"id": 15,
"url": "/main/product/category",
"name": "商品类别",
"sort": 104,
"type": 2,
"children": [
{
"id": 30,
"url": null,
"name": "创建类别",
"sort": null,
"type": 3,
"parentId": 15,
"permission": "system:category:create"
},
{
"id": 31,
"url": null,
"name": "删除类别",
"sort": null,
"type": 3,
"parentId": 15,
"permission": "system:category:delete"
},
{
"id": 32,
"url": null,
"name": "修改类别",
"sort": null,
"type": 3,
"parentId": 15,
"permission": "system:category:update"
},
{
"id": 33,
"url": null,
"name": "查询类别",
"sort": null,
"type": 3,
"parentId": 15,
"permission": "system:category:query"
}
],
"parentId": 9
},
{
"id": 16,
"url": "/main/product/goods",
"name": "商品信息",
"sort": 105,
"type": 2,
"children": [
{
"id": 34,
"url": null,
"name": "创建商品",
"sort": null,
"type": 3,
"parentId": 16,
"permission": "system:goods:create"
},
{
"id": 35,
"url": null,
"name": "删除商品",
"sort": null,
"type": 3,
"parentId": 16,
"permission": "system:goods:delete"
},
{
"id": 36,
"url": null,
"name": "修改商品",
"sort": null,
"type": 3,
"parentId": 16,
"permission": "system:goods:update"
},
{
"id": 37,
"url": null,
"name": "查询商品",
"sort": null,
"type": 3,
"parentId": 16,
"permission": "system:goods:query"
}
],
"parentId": 9
}
]
},
{
"id": 41,
"name": "随便聊聊",
"type": 1,
"url": "/main/story",
"icon": "el-icon-chat-line-round",
"sort": 4,
"children": [
{
"id": 42,
"url": "/main/story/chat",
"name": "你的故事",
"sort": 108,
"type": 2,
"children": null,
"parentId": 41
},
{
"id": 43,
"url": "/main/story/list",
"name": "故事列表",
"sort": 109,
"type": 2,
"children": [],
"parentId": 41
}
]
}
]