Vue3-动态路由

举报
林太白 发表于 2025/12/24 10:48:56 2025/12/24
【摘要】 Vue3-动态路由

outline: deep

Vue3-动态路由

动态菜单路由

这部分我也将其归类为RBAC权限之中的一部分

1、认识动态菜单路由

在做RBAC权限管理系统的时候,都会遇到这么一个需求

每个用户的权限是不一样的,他可以访问的页面,可以操作的菜单,包括可能操作的按钮都是不一样的

这个时候就需要由后端控制用户返回的页面以及菜单按钮权限,前端去实现动态路由,动态渲染侧边菜单栏

我们的后端部分已经写好了,接下来我们前端实现一下

2、功能实现

配置动态路由在获取后端给我们的路由表以后,我们逐个去对比我们本地的文件,返回路由对应的文件

这部分我们主要是对于权限的使用和拦截

👉主文件使用拦截器

🍎主文件之中引入路由权限模块

import '@/utils/authUserPermission' // 导入用户权限配置

🍎先看看我们最简单的路由守卫authUserPermission

接下来我们简单写一个最简单的路由守卫模块,看一下正常我们项目中的路由守卫模块应该如何做,然后再继续进行不断的迭代以及优化。这里就以前置路由守卫和后置路由守卫为主,之前没接触这里的可以看看vue2我们路由守卫的模块

// 授权用户权限
import router from '@/router'

// 引入token
import { getToken, removeToken } from '@/utils/auth'

//定义页面白名单 
const whitePageList = ['/register', '/login'];
// 路由守卫
router.beforeEach((to, from, next) => {
  console.log('进入路由守卫');
  if (getToken()) {
    if (to.path === '/login') {
      console.log('已登录进入!');
      next({ path: '/admin' });
    } else {
      next()
    }
  } else if (whitePageList.indexOf(to.path) !== -1) {
    console.log('白名单进入!');
    next();
  } else {
    console.log('去登陆');
    next('/login');
  }
});
router.afterEach(() => {
  console.log('路由加载完成!');
});

🍎在路由中间拉取我们用户信息

// 引入用户信息
import useUserStore from '@/store/modules/user'

useUserStore().getInfo().then(() => {
    console.log('获取用户信息成功');
}).catch(err => {})

🍎 拉取路由信息

这个时候我们就需要去拉取一下用户的信息,这里我们先完善一下我们路由部分的信息,添加一个静态路由以及一个动态路由部分,这里我先以一个文件为案例

📦router
 ┣ 📜dynamicRouter.ts
 ┣ 📜index.ts
 ┗ 📜staticRouter.ts

接下来我们改写我们的路由模块,在改写之前,确保你已经正确添加了属于自己的以下这些模块

@/layout/index.vue //后台布局主页

//重定向页面
src\views\redirect\index.vue

🍎这部分包含的一些模块

重定向页面src\views\redirect\index.vue

// src\views\redirect\index.vue


<template>
  <div></div>
</template>

<script setup>
import { useRoute, useRouter } from 'vue-router'

const route = useRoute()
const router = useRouter()
const { params, query } = route
const { path } = params

router.replace({ path: '/' + path, query })
</script>

3、完善路由拦截功能

最后完善一下我们的功能,暂时完整如下

import router from '@/router'
import { ElMessage } from 'element-plus'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { getToken } from '@/utils/auth'
import { isHttp } from '@/utils/validate'
import useUserStore from '@/store/modules/user'
import usePermissionStore from '@/store/modules/usePermissionStore'
const isRelogin={ show: true }; //是否需要重新拉取用户信息
NProgress.configure({ showSpinner: false });

const whiteList = ['/login', '/register','/flowerscreen'];

router.beforeEach((to, from, next) => {
  NProgress.start()
  if (getToken()) {
    /* has token*/
    if (to.path === '/login') {
      next({ path: '/' })
      NProgress.done()
    } else if (whiteList.indexOf(to.path) !== -1) {
      next()
    } else {
      if (useUserStore().roles.length === 0) {
        isRelogin.show = true
        // 判断当前用户是否已拉取完user_info信息
        useUserStore().getInfo().then(() => {
          isRelogin.show = false
          usePermissionStore().generateRoutes().then(accessRoutes => {
            // 根据roles权限生成可访问的路由表
            accessRoutes.forEach(route => {
              if (!isHttp(route.path)) {
                router.addRoute(route) // 动态添加可访问路由表
              }
            })
            next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
          })
        }).catch(err => {
          useUserStore().logOut().then(() => {
            ElMessage.error(err)
            next({ path: '/' })
          })
        })
      } else {
        next()
      }
    }
  } else {
    // 没有token
    if (whiteList.indexOf(to.path) !== -1) {
      // 在免登录白名单,直接进入
      next()
    } else {
      next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
      NProgress.done()
    }
  }
})

router.afterEach(() => {
  NProgress.done()
})

4、路由权限校验

🍎添加位置

如何进行动态路由菜单的控制呢,也就是根据我们角色的权限然后生成对应的菜单,根据roles权限生成可访问的路由表,这个时候就需要在我们路由进入之前进行我们动态路由的判断,当前用户拉取完user_info信息以后,拿到他授权的菜单信息

也就是在我们路由拦截之中进行判断和操作

router.beforeEach((to, from, next) => {})

userRouteInterception路由权限拦截,拉取完用户信息以后我们就可以访问用户对应的信息,然后在静态路由之中加上动态路由

useUserStore().getInfo().then(() => {
  isRelogin.show = false
  usePermissionStore().generateRoutes().then(accessRoutes => {
    // 根据roles权限生成可访问的路由表
    accessRoutes.forEach(route => {
      if (!isHttp(route.path)) {
        router.addRoute(route) // 动态添加可访问路由表
      }
    })
    next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
  })
})

🍎添加权限配置仓库

借助于权限配置仓库我们动态获取路由

// 引入权限配置仓库
import usePermissionStore from '@/store/modules/usePermissionStore'

 usePermissionStore().generateRoutes().then(accessRoutes => {
  // 根据roles权限生成可访问的路由表
  accessRoutes.forEach(route => {
    if (!isHttp(route.path)) {
      router.addRoute(route) // 动态添加可访问路由表
    }
  })
  next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
})

src\store\modules\usePermissionStore.ts权限仓库的主要目的就是为了将路由和我们已经有的路由结合,同时将我们的路由和我们本地的文件匹配起来

import auth from '@/store/modules/auth'
import { webSetting }  from "@/config/webSetting"; 
import ParentView from '@/layout/ParentView/index.vue'
import InnerLink from '@/layout/InnerLink/index.vue'
import { defineStore } from 'pinia';  //引入pinia
import { getRouters } from '@/api/common/common' //导入路由接口
// 引入动态静态路由
import router, { constantRoutes, dynamicRoutes } from '@/router'

// 匹配views里面所有的.vue文件
const modules = import.meta.glob('./../../views/**/*.vue')
const usePermissionStore = defineStore(
  'permission',
  {
    state: () => ({
      routes: [],
      addRoutes: [],
      defaultRoutes: [],
      topbarRouters: [],
      sidebarRouters: []
    }),
    actions: {
      setRoutes(routes) {
        this.addRoutes = routes
        this.routes = constantRoutes.concat(routes);
        // console.log('this.routes', this.routes)
      },
      setDefaultRoutes(routes) {
        this.defaultRoutes = constantRoutes.concat(routes)
      },
      setTopbarRoutes(routes) {
        this.topbarRouters = routes
      },
      setSidebarRouters(routes) {
        this.sidebarRouters = routes
      },
      // roles为当前用户角色
      generateRoutes() {
        return new Promise(resolve => {
          // 向后端请求路由数据
          
        })
      }
    }
  },
)

🍎处理后端接口返回路由

generateRoutes方法我们主要是处理来自后端给我们的数据

getRouters().then(res => {
    const sdata = JSON.parse(JSON.stringify(res.data))
    const rdata = JSON.parse(JSON.stringify(res.data))
    // console.log('sdata', sdata)
    // console.log('rdata', rdata)
    const defaultData = JSON.parse(JSON.stringify(res.data))

    // 处理侧边栏菜单路由
    const sidebarRoutes = filterAsyncRouter(sdata)
    // console.log('sidebarRoutes', sidebarRoutes)

    // 处理实际路由配置(带重写)
    const rewriteRoutes = filterAsyncRouter(rdata, false, true)
    // console.log('rewriteRoutes', rewriteRoutes)

    rewriteRoutes.forEach(item => {
      item.component = webSetting.Layout
    })
    // 处理默认路由
    const defaultRoutes = filterAsyncRouter(defaultData)
    // console.log('defaultRoutes', defaultRoutes)

    // 处理动态路由(根据权限)
    const asyncRoutes = filterDynamicRoutes(dynamicRoutes)
    // console.log('asyncRoutes', asyncRoutes)


    asyncRoutes.forEach(route => { router.addRoute(route) })
    this.setRoutes(rewriteRoutes)
    this.setSidebarRouters(constantRoutes.concat(sidebarRoutes))
    this.setDefaultRoutes(sidebarRoutes)
    this.setTopbarRoutes(defaultRoutes)
    resolve(rewriteRoutes)
  })

逐层来看,首先处理我们的侧边栏菜单,处理后端返回的动态路由配置,将其转换为 Vue Router 可用的格式

// filterAsyncRouter

// 处理后端返回的动态路由配置,将其转换为 Vue Router 可用的格式
function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
  return asyncRouterMap.filter(route => {
    if (type && route.children) {
      route.children = filterChildren(route.children)
    }
    if (route.component) {
      // Layout ParentView 组件特殊处理
      if (route.component === 'Layout') {
        route.component = webSetting.Layout
      } else if (route.component === 'ParentView') {
        route.component = ParentView
      } else if (route.component === 'InnerLink') {
        route.component = InnerLink
      } else {
        route.component = loadView(route.component)
      }
    }
    if (route.children != null && route.children && route.children.length) {
      route.children = filterAsyncRouter(route.children, route, type)
    } else {
      delete route['children']
      delete route['redirect']
    }
    return true
  })
}

接下来遍历动态路由,查看用户是否确实拥有这个权限

// 动态路由遍历,验证是否具备权限
export function filterDynamicRoutes(routes) {
  const res = []
  routes.forEach(route => {
    if (route.permissions) {
      if (auth.hasPermiOr(route.permissions)) {
        res.push(route)
      }
    } else if (route.roles) {
      if (auth.hasRoleOr(route.roles)) {
        res.push(route)
      }
    }
  })
  return res
}

 // 验证用户是否含有指定权限,只需包含其中一个
  hasPermiOr(permissions) {
    return permissions.some(item => {
      return authPermission(item)
    })
  },

然后我们依次添加拼接路由,最后返回就可以了

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。