广告
返回顶部
首页 > 资讯 > 前端开发 > JavaScript >Vue3纯前端实现Vue路由权限的方法详解
  • 502
分享到

Vue3纯前端实现Vue路由权限的方法详解

2024-04-02 19:04:59 502人浏览 薄情痞子
摘要

目录前言RBAC模型代码实现登录菜单信息动态路由筛选总结前言 在开发管理后台时,都会存在多个角色登录,登录成功后,不同的角色会展示不同的菜单路由。这就是我们通常所说的动态路由权限,实

前言

开发管理后台时,都会存在多个角色登录,登录成功后,不同的角色会展示不同的菜单路由。这就是我们通常所说的动态路由权限,实现路由权限的方案有多种,比较常用的是由前端使用addRoutes(V3版本改成了addRoute)动态挂载路由和服务端返回可访问的路由菜单这两种。今天主要是从前端角度,实现路由权限的功能。

RBAC模型

前端实现路由权限主要是基于RBAC模型。

RBAC(Role-Based Access Control)即:基于角色的权限控制。通过角色关联用户,角色关联权限的方式间接赋予用户权限。

代码实现

登录

首先是登录,登录成功后,服务端会返回用户登录的角色、token以及用户信息等。用户角色如:role: ['admin']。我们一般会将这些信息保存到Vuex里。

const login = () => {
  ruleFORMRef.value?.validate((valid: boolean) => {
    if (valid) {
      store.dispatch('userModule/login', { ...accountForm })
    } else {
      console.log('error submit!')
    }
  })
}

信息存储在Vuex:

async login({ commit }, payload: IRequest) {
  // 登录获取token
  const { data } = await accountLogin(payload)
  commit('SET_TOKEN', data.token)
  localCache.setCache('token', data.token)
  // 获取用户信息
  const userInfo = await getUserInfo(data.id)
  commit('SET_USERINFO', userInfo.data)
  localCache.setCache('userInfo', userInfo.data)
  router.replace('/')
},

服务端返回token:

服务端返回用户信息:

菜单信息

路由菜单信息分为两种,一种是默认路由constantRoutes,即所有人都能够访问的页面,不需去通过用户角色去判断,如login、404、首页等等。还有一种就是动态路由asyncRoutes,用来放置有权限(roles 属性)的路由,这部分的路由是需要访问权限的。我们最终将在动态路由里面根据用户角色筛选出能访问的动态路由列表。

我们将默认路由和动态路由都写在router/index.ts里。

import { createRouter, createWEBHashHistory, RouteRecordRaw } from 'vue-router'
const Layout = () => import('@/Layout')


export const constantRoutes: RouteRecordRaw[] = [
  {
    path: '/login',
    name: 'login',
    component: () => import('@/views/login/index.vue'),
    meta: {
      title: '登录',
      hidden: true
    }
  },
  {
    path: '/',
    component: Layout,
    redirect: '/analysis/dashboard',
    name: 'Analysis',
    meta: {
      hidden: false,
      icon: 'icon-home',
      title: '系统总览'
    },
    children: [
      {
        path: '/analysis/dashboard',
        name: 'Dashboard',
        component: () => import('@/views/analysis/dashboard/dashboard.vue'),
        meta: { title: '商品统计', hidden: false }
      },
      {
        path: '/analysis/overview',
        name: 'Overview',
        component: () => import('@/views/analysis/overview/overview.vue'),
        meta: { title: '核心技术', hidden: false }
      }
    ]
  },
  {
    path: '/product',
    component: Layout,
    redirect: '/product/cateGory',
    name: 'Product',
    meta: {
      hidden: false,
      icon: 'icon-tuijian',
      title: '商品中心'
    },
    children: [
      {
        path: '/product/category',
        name: 'Category',
        component: () => import('@/views/product/category/category.vue'),
        meta: { title: '商品类别', hidden: false }
      },
      {
        path: '/product/goods',
        name: 'Goods',
        component: () => import('@/views/product/goods/goods.vue'),
        meta: { title: '商品信息', hidden: false }
      }
    ]
  },
  {
    path: '/story',
    component: Layout,
    redirect: '/story/chat',
    name: 'Story',
    meta: {
      hidden: false,
      icon: 'icon-xiaoxi',
      title: '随便聊聊'
    },
    children: [
      {
        path: '/story/chat',
        name: 'Story',
        component: () => import('@/views/story/chat/chat.vue'),
        meta: { title: '你的故事', hidden: false }
      },
      {
        path: '/story/list',
        name: 'List',
        component: () => import('@/views/story/list/list.vue'),
        meta: { title: '故事列表', hidden: false }
      }
    ]
  },
  {
    path: '/404',
    component: () => import('@/views/404.vue'),
    meta: {
      title: 'Not Found',
      hidden: true
    }
  },
  {
    path: '/:pathMatch(.*)*',
    redirect: '/404',
    meta: {
      hidden: true,
      title: 'Not Found'
    }
  }
]

export const asyncRoutes: RouteRecordRaw[] = [
  {
    path: '/system',
    component: Layout,
    redirect: '/system/department',
    name: 'System',
    meta: {
      hidden: false,
      icon: 'icon-shezhi',
      title: '系统管理'
    },
    children: [
      {
        path: '/system/department',
        name: 'Department',
        component: () => import('@/views/system/department/department.vue'),
        meta: { title: '部门管理', hidden: false, role: ['admin'] }
      },
      {
        path: '/system/menu',
        name: 'Menu',
        component: () => import('@/views/system/menu/menu.vue'),
        meta: { title: '菜单管理', hidden: false, role: ['admin'] }
      },
      {
        path: '/system/role',
        name: 'Role',
        component: () => import('@/views/system/role/role.vue'),
        meta: { title: '角色管理', hidden: false, role: ['editor'] }
      },
      {
        path: '/system/user',
        name: 'User',
        component: () => import('@/views/system/user/user.vue'),
        meta: { title: '用户管理', hidden: false, role: ['editor'] }
      }
    ]
  }
]
const router = createRouter({
  history: createWebHashHistory(),
  routes: constantRoutes
})
export default router

我们将系统管理这个菜单作为动态路由部分,里面的子菜单meta属性下都分配有一个访问权限的role属性,我们需要将role属性和用户角色去匹配是否用户具有访问权限。

动态路由筛选

思路:

我们登录得到了用户角色role和写好路由信息(分为默认路由列表和动态路由列表),之后我们需要做的就是通过用户角色role去匹配动态路由列表里面每个子路由的role属性,得到能够访问的动态路由部分,将默认路由和我们得到的动态路由进行拼接这样我们就得到了用户能够访问的完整前端路由,最后使用addRoute将完整路由挂载到router上。

有了这样一个比较清晰的思路,接下来我们就来尝试着实现它。

我们可以将这块的逻辑也放在Vuex里面,在store/modules下新建一个permission.ts文件。

首先我们需要写一个方法去判断用户是否具有访问单个路由的权限:


const hasPermission = (roles: string[], route: any) => {
  if (route.meta && route.meta.roles) {
    return roles.some((role) => {
      if (route.meta?.roles !== undefined) {
        return route.meta.roles.includes(role)
      } else {
        return false
      }
    })
  } else {
    return true
  }
}

实现的核心是route.meta.roles.includes(role),即路由的roles是否包含了用户的角色,包含了就可以访问,否则不能。

对用户角色进行some遍历主要是用户的角色可能存在多个,如:['admin', 'editor']。

这样我们就实现了单个路由访问权限的筛选,但是动态路由列表是一个数组,每个一级路由下可能有二级路由、三级路由甚至更多,这样我们就需要用到递归函数进行筛选:


const filterAsyncRoutes = (routes: RouteRecordRaw[], roles: string[]) => {
  const res: RouteRecordRaw[] = []
  routes.forEach((route) => {
    const r = { ...route }
    if (hasPermission(roles, r)) {
      if (r.children) {
        r.children = filterAsyncRoutes(r.children, roles)
      }
      res.push(r)
    }
  })
  return res
}

这样,通过调用filterAsyncRoutes这个函数,然后传入utes:动态路由列表,roles:用户角色两个参数就能得到我们能访问的动态路由了。

然后我们将筛选得到的动态路由和默认路由通过concat拼接得到完整可访问路由,最后通过addRoute挂载。

我们将以上代码逻辑整理到sion.ts里:

import { Module } from 'vuex'
import { RouteRecordRaw } from 'vue-router'
import { constantRoutes, asyncRoutes } from '@/router'
import { IRootState } from '../types'
import router from '@/router'

const hasPermission = (roles: string[], route: any) => {
  if (route.meta && route.meta.roles) {
    return roles.some((role) => {
      if (route.meta?.roles !== undefined) {
        return route.meta.roles.includes(role)
      } else {
        return false
      }
    })
  } else {
    return true
  }
}

const filterAsyncRoutes = (routes: RouteRecordRaw[], roles: string[]) => {
  const res: RouteRecordRaw[] = []
  routes.forEach((route) => {
    const r = { ...route }
    if (hasPermission(roles, r)) {
      if (r.children) {
        r.children = filterAsyncRoutes(r.children, roles)
      }
      res.push(r)
    }
  })
  return res
}
interface IPermissionState {
  routes: RouteRecordRaw[]
  dynamicRoutes: RouteRecordRaw[]
}
export const routesModule: Module<IPermissionState, IRootState> = {
  namespaced: true,
  state: {
    routes: [],
    dynamicRoutes: []
  },
  getters: {},
  mutations: {
    SET_ROUTES(state, routes) {
      state.routes = routes
    },
    SET_DYNAMICROUTES(state, routes) {
      state.dynamicRoutes = routes
    }
  },
  actions: {
    generateRoutes({ commit }, { roles }) {
      // accessedRoutes: 筛选出的动态路由
      const accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
      // 将accessedRoutes和默认路由constantRoutes拼接得到完整可访问路由
      commit('SET_ROUTES', constantRoutes.concat(accessedRoutes))
      commit('SET_DYNAMICROUTES', accessedRoutes)
      // 通过addRoute将路由挂载到router上
      accessedRoutes.forEach((route) => {
        router.addRoute(route)
      })
    }
  }
}

这样就实现了所有代码逻辑。有个问题,addRoute应该何时调用,在哪里调用?

登录后,获取用户的权限信息,然后筛选有权限访问的路由,再调用addRoute添加路由。这个方法是可行的。但是不可能每次进入应用都需要登录,用户刷新浏览器又要登录一次。所以addRoute还是要在全局路由守卫里进行调用。

我们在router文件夹下创建一个permission.ts,用于写全局路由守卫相关逻辑:

import router from '@/router'
import { RouteLocationNormalized } from 'vue-router'
import localCache from '@/utils/cache'
import NProgress from 'nprogress'
import 'nprogress/nprogress.CSS'
import store from '@/store'

NProgress.configure({ showSpinner: false })
const whiteList = ['/login']
router.beforeEach(
  async (
    to: RouteLocationNormalized,
    from: RouteLocationNormalized,
    next: any
  ) => {
    document.title = to.meta.title as string
    const token: string = localCache.getCache('token')
    NProgress.start()
    // 判断该用户是否登录
    if (token) {
      if (to.path === '/login') {
        // 如果登录,并准备进入 login 页面,则重定向到主页
        next({ path: '/' })
        NProgress.done()
      } else {
        const roles = store.state.userModule.roles
        store.dispatch('routesModule/generateRoutes', { roles })
        // 确保添加路由已完成
        // 设置 replace: true, 因此导航将不会留下历史记录
        next({ ...to, replace: true })
        // next()
      }
    } else {
      // 如果没有 token
      if (whiteList.includes(to.path)) {
        // 如果在免登录的白名单中,则直接进入
        next()
      } else {
        // 其他没有访问权限的页面将被重定向到登录页面
        next('/login')
        NProgress.done()
      }
    }
  }
)
router.afterEach(() => {
  NProgress.done()
})

这样,完整的路由权限功能就完成了。我们可以做一下验证:

动态路由

我们登录的用户角色为roles: ['editor'],动态路由为系统管理菜单,里面有四个子路由对应有roles,正常情况下我们可以访问系统管理菜单下的角色管理和用户管理。

渲染菜单界面

筛选出的动态路由

没有任何问题!

总结

前端实现动态路由是基于RBAC思想,通过用户角色去筛选出可以访问的路由挂载在router上。这样实现有一点不好的地方在于菜单信息是写死在前端,以后要改个显示文字或权限信息,需要重新修改然后编译。

到此这篇关于vue3纯前端实现Vue路由权限的文章就介绍到这了,更多相关Vue3纯前端实现路由权限内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: Vue3纯前端实现Vue路由权限的方法详解

本文链接: https://www.lsjlt.com/news/148603.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Vue3纯前端实现Vue路由权限的方法详解
    目录前言RBAC模型代码实现登录菜单信息动态路由筛选总结前言 在开发管理后台时,都会存在多个角色登录,登录成功后,不同的角色会展示不同的菜单路由。这就是我们通常所说的动态路由权限,实...
    99+
    2022-11-13
  • 前端配合后端实现Vue路由权限的方法实例
    目录前言实现思路代码实现登录本地路由列表生成路由挂载路由总结前言 在开发管理后台时,都会存在多个角色登录,登录成功后,不同的角色会展示不同的菜单路由。这就是我们通常所说的动态路由权限...
    99+
    2022-11-13
  • vue2/vue3路由权限管理的方法实例
    1. Vue 路由权限控制一般有2种方法 a、路由元信息(meta) b、动态加载菜单和路由(addRoutes) 2 路由元信息(meta)来进行路由权限控制 2.1 在vue2种...
    99+
    2022-11-12
  • SpringSecurity动态权限的实现方法详解
    目录1. 动态管理权限规则1.1 数据库设计1.2 实战2. 测试最近在做 TienChin 项目,用的是 RuoYi-Vue 脚手架,在这个脚手架中,访问某个接口需要什么权限,这个...
    99+
    2022-11-13
  • Vuerouter的addRoute方法实现控制权限方法详解
    目录路由分为静态路由和动态路由静态路由和动态路由的优缺点动态路由实现思路动态路由遇到的问题与解决方式路由分为静态路由和动态路由 静态路由和动态路由的优缺点 1、中大型项目,采用的都是...
    99+
    2022-11-13
  • 详解vue各种权限控制与管理的实现思路
    本篇文章给大家带来了关于vue的相关知识,其中主要详细介绍了vue各种权限控制与管理的实现思路,感兴趣的朋友下面一起来看一下吧,希望对大家有帮助。一、 菜单权限菜单权限:控制用户在系统中能够看到哪些菜单项菜单权限指的就是后台系统中的左侧的菜...
    99+
    2023-05-14
    前端 Vue.js Vuex
  • vue前端RSA加密java后端解密的方法实现
    目录一、前言二、前端代码与用法三、后端代码与用法一、前言 最近安全测试的总是测出安全漏洞来,让开发改。 想了想干脆把请求参数都加密下,前端加密后端解密,这样总差不多了。 看了下AES...
    99+
    2023-02-24
    vue RSA加密 java后端解密
  • 前端实现打印功能的两种方法详解
    目录前言方法一:window.print()   方法二:利用iframe,iframe.contentWindow.print()补充:导出步骤总结:前言 前端...
    99+
    2023-01-06
    前端实现打印功能 前端打印语句 前端打印功能实现
  • 利用node.js实现自动生成前端项目组件的方法详解
    本文主要给大家介绍了关于利用node.js实现自动生成前端项目组件的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍: 脚本编写背景 写这个小脚本的初衷是,项目本身添加一个组件太繁...
    99+
    2022-06-04
    自动生成 详解 组件
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作