Implement permission control based on vue and dynamically render the menu bar

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, 404etc.

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:

Insert image description here

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, menuRoutesand the initial value is the corresponding static routing array

Insert image description here

  • Secondly encapsulate the menu component and display different menu bars menuRoutesthrough递归渲染

    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. addRouteFinally, 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

Guess you like

Origin blog.csdn.net/CRMEB/article/details/131942317