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

目录
  • 前言
  • 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'
    }
  }
]
/**
 * 动态路由
 * 用来放置有权限(roles 属性)的路由
 * 必须带有 name 属性
 */
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文件。

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

/**
 * 判断用户是否有权限访问单个路由
 * roles:用户角色
 * route:访问的路由
 */
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']。

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

/**
 * 筛选可访问的动态路由
 * roles:用户角色
 * route:访问的动态列表
 */
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'
/**
 * 判断用户是否有权限访问单个路由
 * roles:用户角色
 * route:访问的路由
 */
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
  }
}
/**
 * 筛选可访问的动态路由
 * roles:用户角色
 * route:访问的动态列表
 */
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纯前端实现路由权限内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • vue2/vue3路由权限管理的方法实例

    1. Vue 路由权限控制一般有2种方法 a.路由元信息(meta) b.动态加载菜单和路由(addRoutes) 2 路由元信息(meta)来进行路由权限控制 2.1 在vue2种的实现 如果一个网站有不同的角色,比如 管理员 和 普通用户 ,要求不同的角色能访问的页面是不一样的 这个时候我们就可以 把所有的页面都放在路由表里 ,只要 在访问的时候判断一下角色权限 .如果有权限就让访问,没有权限的话就拒绝访问,跳转到404页面 vue-router 在构建路由时提供了元信息 meta 配置接口

  • vue3使用vue-router及路由权限拦截方式

    目录 使用vue-router及路由权限拦截 vue3使用vue-router讲解 使用vue-router及路由权限拦截 vue3 使用 vue-router 的方式和 vue2 基本一样,只不过初始化路由时需要用到一些函数来定义而已,另外 vue-cli 工具本身在创建 vue3 项目时就可以根据提示来进行安装配置 vue-router , 所以本篇只是针对那些忘记安装的小伙伴. 第一步肯定是要先安装啦: npm install vue-router@4 接着我们在根目录 src 下创建 r

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

    目录 前言 RBAC模型 代码实现 登录 菜单信息 动态路由筛选 总结 前言 在开发管理后台时,都会存在多个角色登录,登录成功后,不同的角色会展示不同的菜单路由.这就是我们通常所说的动态路由权限,实现路由权限的方案有多种,比较常用的是由前端使用addRoutes(V3版本改成了addRoute)动态挂载路由和服务端返回可访问的路由菜单这两种.今天主要是从前端角度,实现路由权限的功能. RBAC模型 前端实现路由权限主要是基于RBAC模型. RBAC(Role-Based Access Contr

  • 前端配合后端实现Vue路由权限的方法实例

    目录 前言 实现思路 代码实现 登录 本地路由列表 生成路由 挂载路由 总结 前言 在开发管理后台时,都会存在多个角色登录,登录成功后,不同的角色会展示不同的菜单路由.这就是我们通常所说的动态路由权限,实现路由权限的方案有多种,比较常用的是由前端使用addRoutes(V3版本改成了addRoute)动态挂载路由和服务端返回可访问的路由菜单这两种.上一篇文章讲了纯前端实现路由权限,没看过的可以点击文章链接纯前端实现Vue路由权限.今天主要是基于后端返回路由菜单的基础上,实现路由权限功能. 实现思

  • 基于Vue的ajax公共方法(详解)

    为了减少代码的冗余,决定抽离出请求ajax的公共方法,供同事们使用. 我使用了ES6语法,编写了这个方法. /** * @param type 请求类型,分为POST/GET * @param url 请求url * @param contentType * @param headers * @param data * @returns {Promise<any>} */ ajaxData: function (type, url, contentType, headers, data) {

  • JS前端常见的竞态问题解决方法详解

    目录 什么是竞态问题 取消过期请求 XMLHttpRequest 取消请求 fetch API 取消请求 axios 取消请求 可取消的 promise 忽略过期请求 封装指令式 promise 使用唯一 id 标识每次请求 「取消」和「忽略」的比较 「取消」更实际 「忽略」更通用 总结 什么是竞态问题 竞态问题,又叫竞态条件(race condition),它旨在描述一个系统或者进程的输出依赖于不受控制的事件出现顺序或者出现时机. 此词源自于两个信号试着彼此竞争,来影响谁先输出. 简单来说,竞

  • Vue websocket封装实现方法详解

    目录 1.封装的ws.js文件 2.使用方法 1.封装的ws.js文件 let global_callback = null let socket = '' // 存储 WebSocket 连接. let timeoutObj = null // 心跳定时器 let serverTimoutObj = null // 服务超时定时关闭 let lockReconnect = false // 是否真正建立了连接 let timeoutnum = null // 重新连接的定时器, 没连接上会一直

  • Vue中watch使用方法详解

    目录 前言 watch immediate和handler deep 拓展 前言 说到 vue 中的 watch 方法,大家可能首先想到,它是用来监听数据的变化,一旦数据发生变化可以执行一些其他的操作.但是 watch 的操作可不止如此,本章就带大家一起深剖细析 vue 中的 watch 方法. watch 因为 vue 是双向数据绑定,所以当页面数据发生变化时,我们通过 watch 方法就可以拿到数据变化前和变化后的值,从而做一系列操作,下面我们通过一个简单的例子来解释. 先看下面这段代码 <

  • Laravel5权限管理方法详解

    本文实例讲述了Laravel5权限管理的实现方法.分享给大家供大家参考,具体如下: 关于权限管理的思考 最近用laravel设计后台,后台需要有个权限管理.权限管理实质上分为两个部分,首先是认证,然后是权限.认证部分非常好做,就是管理员登录,记录session.这个laravel中也有自带Auth来实现这个.最麻烦就是权限认证. 权限认证本质上就是谁有权限管理什么东西.这里有两个方面的维度,谁,就是用户维度,在用户维度,权限管理的粒度可以是用户一个人,也可以是将用户分组,如果将用户分组,则涉及到

  • vue自定义指令实现方法详解

    本文实例讲述了vue自定义指令实现方法.分享给大家供大家参考,具体如下: vue中的指令就是v-on v-bind v-show等等,那么自定义指令是什么呢? 自己定义的指令就是自定义指令. 语法: Vue.directive(id, definition) 这里可以参考vue中的指令 <h1 v-if="yes">Yes</h1> 其中,if就是指令ID,yes是expression Vue.directive()传入接受两个参数,id是指指令ID,defin

  • Vue.js路由vue-router使用方法详解

    vue-router是Vue.js官方的路由插件,它和vue.js是深度集成的,适合用于构建单页面应用.vue的单页面应用是基于路由和组件的,路由用于设定访问路径,并将路径和组件映射起来.传统的页面应用,是用一些超链接来实现页面切换和跳转的.在vue-router单页面应用中,则是路径之间的切换,也就是组件的切换. 本文将以示例的形式来介绍vue-router的各个特性,一共包含6个示例,每个示例都有乞丐版,前5个示例有皇帝版. 乞丐版是将所有代码混杂在一起的HTML页面,皇帝版是基于vue-w

  • 前端框架Vue.js中Directive知识详解

    Directive 看上去虽然和Angular中的定义类似,Directive 都是对DOM功能的一种拓展,但是 Vue 的 Directive 要弱的多.因为 Vue Component 其实本来就会包含对DOM的操作,所以大多数时候我们写一个通用组件都是一个Component 而不是一个 Directive,而 在 Angular 我们写一个通用的组件一般都是一个 Directive . 所以我说 Vue 的 Directive 相比于 Angular 要弱的多,也可以说纯粹的多,他就是对

随机推荐