菜单|路由权限

文章讲述了在Vue.js项目中,如何根据不同用户的角色实现动态的菜单权限管理。通过拆分静态路由、异步路由和任意路由,结合用户信息过滤出用户可访问的异步路由,并将其添加到路由器中,以确保菜单列表的变化与实际可访问路由同步。同时,文中提到了路由守卫在处理异步路由加载时的角色,防止因路由未加载完毕导致的白屏问题。
摘要由CSDN通过智能技术生成

同一个项目:不同人(职位是不一样的,他能访问到的菜单、按钮的权限是不一样的),这里先总结一下菜单权限。


前提

1.要先知道项目中总共有多少路由组件

2.拆分路由:静态路由、异步路由 and 任意路由;

  • 静态路由:任意用户都是可以看得见的,如Home。
  • 异步路由:根据不同的角色,拥有不同的权限,是做路由权限的主要依据。
  • 任意路由:顾名思义,访问没有注册过的路由,自动跳转到404。

举个栗子

 login(登录页面)、404(404一级路由)、任意路由、首页(/home)、数据大屏、权限管理(三个子路由)、商品管理模块(四个子路由)

路由拆分:

// 配置常量路由
export const constantRoutes: Array<RouteRecordRaw> = [
  {
    // 登陆
    path: '/login',
    name: 'login',
    component: () => import('@/views/login/index.vue'),
    meta: {
      title: '登录', //菜单标题,
      isHidden: true, // 是否隐藏
      icon: 'Promotion', // 前缀图标
    },
  },
  {
    // 登陆成功以后展示数据的路由
    path: '/',
    name: 'layout',
    redirect: '/home',
    component: () => import('@/layout/index.vue'),
    meta: {
      title: '',
      isHidden: false, // 是否隐藏
      icon: '', // 前缀图标
    },
    children: [
      {
        path: '/home',
        name: 'Home',
        component: () => import('@/views/home/index.vue'),
        meta: {
          title: '首页',
          isHidden: false, // 是否隐藏
          icon: 'HomeFilled', // 前缀图标
        },
      },
    ],
  },
  {
    path: '/screen',
    name: 'Screen',
    component: () => import('@/views/screen/index.vue'),
    meta: {
      title: '数据可视化',
      isHidden: false, // 是否隐藏
      icon: 'Monitor', // 前缀图标
    },
  },
  {
    // 404
    path: '/404',
    name: '404',
    component: () => import('@/views/404/index.vue'),
    meta: {
      title: '404',
      isHidden: true, // 是否隐藏
      icon: 'Check', // 前缀图标
    },
  },
]
// 异步路由
export const asyncRoute = [
  {
    path: '/acl',
    name: 'Acl',
    component: () => import('@/layout/index.vue'),
    meta: {
      title: '权限管理',
      isHidden: false, // 是否隐藏
      icon: 'Rank', // 前缀图标
    },
    redirect: '/acl/user',
    children: [
      {
        path: '/acl/user',
        name: 'User',
        component: () => import('@/views/acl/user/index.vue'),
        meta: {
          title: '用户管理',
          isHidden: false,
          icon: 'User',
        },
      },
      {
        path: '/acl/role',
        name: 'Role',
        component: () => import('@/views/acl/role/index.vue'),
        meta: {
          title: '角色管理',
          isHidden: false,
          icon: 'ForkSpoon',
        },
      },
      {
        path: '/acl/permission',
        name: 'Permission',
        component: () => import('@/views/acl/permission/index.vue'),
        meta: {
          title: '菜单管理',
          isHidden: false,
          icon: 'HelpFilled',
        },
      },
    ],
  },
  {
    path: '/product',
    name: 'Product',
    component: () => import('@/layout/index.vue'),
    meta: {
      title: '商品管理',
      isHidden: false, // 是否隐藏
      icon: 'CreditCard', // 前缀图标
    },
    redirect: '/product/trademark',
    children: [
      {
        path: '/product/trademark',
        name: 'Trademark',
        component: () => import('@/views/product/trademark/index.vue'),
        meta: {
          title: '品牌管理',
          isHidden: false, // 是否隐藏
          icon: 'Present', // 前缀图标
        },
      },
      {
        path: '/product/attr',
        name: 'Attr',
        component: () => import('@/views/product/attr/index.vue'),
        meta: {
          title: '属性管理',
          isHidden: false, // 是否隐藏
          icon: 'Soccer', // 前缀图标
        },
      },
      {
        path: '/product/spu',
        name: 'Spu',
        component: () => import('@/views/product/spu/index.vue'),
        meta: {
          title: 'SPU管理',
          isHidden: false, // 是否隐藏
          icon: 'Calendar', // 前缀图标
        },
      },
      {
        path: '/product/sku',
        name: 'Sku',
        component: () => import('@/views/product/sku/index.vue'),
        meta: {
          title: 'SKU管理',
          isHidden: false, // 是否隐藏
          icon: 'Suitcase', // 前缀图标
        },
      },
    ],
  },
]
// 任意路由
export const anyRoute = [
  {
    // 任意路由,没找到以上页面,重定向到404
    path: '/:pathMatch(.*)*',
    redirect: '/404',
    name: 'Any',
    meta: {
      title: '任意路由',
      isHidden: true, // 是否隐藏
      icon: 'Switch', // 前缀图标
    },
  },
]

默认展示的是常量路由,在收集用户信息时,可以把用户拥有的routes与现已注册过的异步路由做对比,将该用户拥有的异步路由过滤出来,再与常量路由、任意路由合并即可构成菜单列表展示

注意:这里所用到异步路由是深拷贝过的,防止不同用户登陆产生影响。

...
import { constantRoutes, asyncRoute, anyRoute } from '@/router/routes'
// 路由器
import router from '@/router'
// 引入深拷贝的方法
// @ts-ignore // 忽略一下
import cloneDeep from 'lodash/cloneDeep'

// 用于过滤当前用户展示的异步路由
const filterAsyncRoute = (asyncRoute: any, routes: any) => {
  return asyncRoute.filter((item: any) => {
    if (routes.includes(item.name)) {
      // 子路由也是需要过滤的,秒啊
      if (item.children && item.children.length > 0) {
        item.children = filterAsyncRoute(item.children, routes)
      }
      return true
    }
  })
}

export const useUserStore = defineStore(Names.USER, {
  state: (): UserState => {
    return {
      token: GET_TOKEN(), // 用户唯一标识
      menuRoutes: constantRoutes, // 仓库存储菜单的路由
      username: '',
      avatar: '',
      buttons: [], // 存储当前用户是否包含某个按钮的
    }
  },
  getters: {},
  actions: {
...

    // 获取用户信息
    async userInfo() {
      let result: UserInfoResponseData = await reqUserInfo()
      // console.log(result)
      if (result.code === 200) {
        this.username = result.data.name
        this.avatar = result.data.avatar
        this.buttons = result.data.buttons
        // 用户信息过滤成功,获取当前用户展示的异步路由
        let userAsyncRoute = filterAsyncRoute(
          cloneDeep(asyncRoute),
          result.data.routes,
        )
        // 菜单的数据
        this.menuRoutes = [...constantRoutes, ...userAsyncRoute, ...anyRoute]
        // 目前路由器管理的只有常量路由:用户的异步路由以及任意路由要动态追加
        ;[...userAsyncRoute, ...anyRoute].forEach((route: any) => {
          router.addRoute(route)
        })
        console.log(router.getRoutes())

        return 'ok'
      } else {
        return Promise.reject(new Error(result.message))
      }
    },
...
  },
})

但是,此时只是菜单列表发生了变化,点击路由组件的时候无响应????

menuList是菜单组件需要递归显示的,而对应的路由组件,却不在路由器中.....所以还需要将过滤的用户异步路由和任意路由追加到路由器中,这样就好啦。

// 目前路由器管理的只有常量路由:用户的异步路由以及任意路由要动态追加
[...userAsyncRoute, ...anyRoute].forEach((route: any) => {
     router.addRoute(route)
 })

 这时候又出现了问题?????

点击路由组件跳转的时候,发现路由组件白屏|刷新一下就没了,此时是因为路由守卫的问题了,拿到用户信息就立即放行,异步路由还没有加载完毕,出现空白的效果。需要等到加载好了再放行就可以解决。

 

// 访问某个路由之前触发
router.beforeEach(async (to: any, from: any, next: any) => {
...
      //登录成功访问其余六个路由(登录排除)
      //有用户信息
      if (username) {
        //放行
        next()
      } else {
        //如果没有用户信息,在守卫这里发请求获取到了用户信息再放行
        try {
          //获取用户信息
          await userStore.userInfo()
          //放行
          //万一:刷新的时候是异步路由,有可能获取到用户信息、异步路由还没有加载完毕,出现空白的效果
          next({ ...to })
        } catch (error) {
          //token过期:获取不到用户信息了
          //用户手动修改本地存储token
          //退出登录->用户相关的数据清空
          await userStore.userLogout()
          next({ path: '/login', query: { redirect: to.path } })
        }
      }
    }
...
})

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值