Vue+menu permissions+dynamic routing
Implementation principle: The user logs in, the server returns relevant permissions, performs persistent storage, and filters dynamic routes. At the same time, the menu bar also needs to be rendered dynamically.
Static routing
Static routing, also called constant routing, is a routing interface that can be accessed by all roles. Such as: login
, 404
etc.
ts
复制代码
const constantRoute = [
{
//登录
path: '/login',
component: () => import('@/views/login/index.vue'),
name: 'login',
meta: {
title: '登录',
hidden: true,
icon: 'Promotion',
},
},
{
//登录成功以后的布局路由
path: '/',
component: () => import('@/layout/layout.vue'),
name: 'layout',
meta: {
title: '',
hidden: false,
icon: '',
},
redirect: '/home',
children: [
{
path: '/home',
name: 'home',
component: () => import('@/views/home/index.vue'),
meta: {
title: '首页',
hidden: false,
icon: 'House',
},
},
],
},
{
//404
path: '/404',
component: () => import('@/views/404/index.vue'),
name: '404',
meta: {
title: '404',
hidden: true,
icon: 'DocumentDelete',
},
},
]
The corresponding menu permissions are as shown in the figure:
dynamic routing
That is, they are owned by different roles 权限路由
. Generally, after successful login, a request is sent to the backend, and the server returns the corresponding permissions, and then filters them.
ts
复制代码
//返回的用户信息
[
{
"userId": 1,
"avatar": "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif",
"username": "admin",
"password": "111111",
"desc": "平台管理员",
"roles": ["平台管理员"],
"buttons": ["cuser.detail"],
"routes": [
"Home",
"User",
"Role",
"Permission",
"Trademark",
"Product",
"Acl"
],
"token": "Admin Token"
},
]
//所有的权限路由
const asyncRoute = [
{
path: '/acl',
component: () => import('@/layout/index.vue'),
name: 'Acl',
meta: {
title: '权限管理',
icon: 'Lock',
},
redirect: '/acl/user',
children: [
{
path: '/acl/user',
component: () => import('@/views/acl/user/index.vue'),
name: 'User',
meta: {
title: '用户管理',
icon: 'User',
},
},
{
path: '/acl/role',
component: () => import('@/views/acl/role/index.vue'),
name: 'Role',
meta: {
title: '角色管理',
icon: 'UserFilled',
},
},
{
path: '/acl/permission',
component: () => import('@/views/acl/permission/index.vue'),
name: 'Permission',
meta: {
title: '菜单管理',
icon: 'Monitor',
},
},
],
},
{
path: '/product',
component: () => import('@/layout/index.vue'),
name: 'Product',
meta: {
title: '商品管理',
icon: 'Goods',
},
redirect: '/product/trademark',
children: [
{
path: '/product/trademark',
component: () => import('@/views/product/trademark/index.vue'),
name: 'Trademark',
meta: {
title: '品牌管理',
icon: 'ShoppingCartFull',
},
},
{
path: '/product/attr',
component: () => import('@/views/product/attr/index.vue'),
name: 'Attr',
meta: {
title: '属性管理',
icon: 'ChromeFilled',
},
},
{
path: '/product/spu',
component: () => import('@/views/product/spu/index.vue'),
name: 'Spu',
meta: {
title: 'SPU管理',
icon: 'Calendar',
},
},
{
path: '/product/sku',
component: () => import('@/views/product/sku/index.vue'),
name: 'Sku',
meta: {
title: 'SKU管理',
icon: 'Orange',
},
},
],
},
]
Menu permissions
This demo uses the el-menu component of element-plus.
In simpler development, we often hard-code menus, which results in the menu lists seen by different characters being consistent.
Therefore, it is generally necessary to implement dynamic routing 二次封装一个对应的菜单权限组件
.
Implementation steps
- Define a global state through pinia or vuex global state management tool,
menuRoutes
and the initial value is the corresponding static routing array
-
Secondly encapsulate the menu component and display different menu bars
menuRoutes
through递归渲染
Important: You need to use the recursive component of vue3, so you need to define the component name. At the same time
menuRoutes
, it needs to be passed from father to son.
vue
复制代码
<template>
<div>
<template v-for="(item, index) in props.menuList" :key="item.path">
<!-- 没有子路由 -->
<template v-if="!item.children">
<el-menu-item
:index="item.path"
v-if="!item.meta.hidden"
@click="goRoute"
>
<template #title>
<el-icon>
<component :is="item.meta.icon" />
</el-icon>
<span>{
{ item.meta.title }}</span>
</template>
</el-menu-item>
</template>
<!-- 只有一个子路由 (例如home页,它是layout的子路由,但是只有一个,直接渲染home) -->
<el-menu-item
v-if="item.children && item.children.length == 1"
:index="item.children[0].path"
@click="goRoute"
>
<template #title>
<el-icon>
<component :is="item.children[0].meta.icon" />
</el-icon>
<span>{
{ item.children[0].meta.title }}</span>
</template>
</el-menu-item>
<!-- 有多个子路由 -->
<el-sub-menu
:index="item.path"
v-if="item.children && item.children.length > 1"
>
<template #title>
<el-icon>
<component :is="item.meta.icon"></component>
</el-icon>
<span>{
{ item.meta.title }}</span>
</template>
<!-- 子路由递归动态渲染 -->
<Menu :menuList="item.children"></Menu>
</el-sub-menu>
</template>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted, watch } from 'vue'
import { useRouter } from 'vue-router'
const $router = useRouter()
//获取父组件传递的路由数组
interface Iprops {
menuList: any[]
}
const props = withDefaults(defineProps<Iprops>(), {
menuList: () => [],
})
const goRoute = (vc: any) => {
$router.push(vc.index)
}
</script>
<script lang="ts">
export default {
name: 'Menu',
}
</script>
-
After successful login, the user information is obtained , and the corresponding permission list data is obtained , passed in
所有之前定义好的权限路由
, and filtered.addRoute
Finally, add dynamic routing through methods.ts 复制代码 import { constantRoute, asyncRoute, anyRoute } from '@/router/routes' //getUserInfo const res = await getUserInfo() let routes = this.filterAsyncRoute( _.cloneDeep(asyncRoute), res.data.checkUser.routes, ) //修改菜单栏显示 this.menuRoutes = [...constantRoute, ...routes, anyRoute] //通过addRoute追加动态路由 let activeRoutes = [...routes, anyRoute] activeRoutes.forEach((route) => { router.addRoute(route) }) //过滤权限路由 filterAsyncRoute(asyncRoute: RouteRecordRaw[], routes: RouteRecordName[]) { let result: RouteRecordRaw[] = [] asyncRoute.forEach((item) => { if (routes.includes(item.name!)) { result.push(item) if (item.children) { item.children = this.filterAsyncRoute(item.children, routes) } } }) return result }, },
Notes: 1. Every time you filter permission routes, you must make a deep copy of asyncRoute, and you will understand it (the reference type data is the address)
2. The data in pinia is non-persistently cached, so the data will be lost as soon as it is refreshed. Solution: While using pinia's persistence plug-in or route authentication, put a navigation guard in front of the route. Every time it jumps, determine whether user information is stored in pinia. If not, call the getUserInfo method again to obtain the user information.
3. Based on the second point, it is not possible to obtain the warehouse through synchronization statements outside the component. It must be obtained through the following methods
javascript
复制代码
import pinia from '@/store/index'
let userStore = useUserStore(pinia)
4. So far, we have successfully implemented menu permissions + dynamic routing, but there is still a bug.
BUG: If we are in 动态路由页面进行刷新
, it will cause a white screen
Reason: When refreshing the page, the routing pre-navigation guard is triggered to obtain the user information. If obtained, it is released. But when released, the dynamic routing has not been loaded yet! Make sure that user information is obtained and all routing components are rendered.
Solution: next({…to})
意义:死循环加载,直至路由组件加载完毕
To learn more about Vue, please follow CRMEB