vueRouter动态路由(实现菜单权限控制)

 一、权限控制管理:

对于企业级的项目, 我们可能需要对项目做权限控制管理, 实现不同角色的用户登录项目根据所拥有的权限访问不同的页面内容,此时就需要使用到动态路由来对权限页面做限制。

【使用vue-router实现动态路由,达到实现菜单权限控制的功能】

二、实现思路:

1.动态路由: 将拥有权限的路由单独从路由表系统中提出,视为动态路由,待后续根据用户权限来导入拥有权限的动态路由
2.权限与动态路由绑定: 用户登录之后,需要获取该用户所拥有的权限,该权限前后端需要协调一致,需要对应前端拥有权限的动态路由,后端可以返回动态路由的name来做以区分,也可以在前端单独定义每个动态路由(权限)的标识进行权限的区分(我使用的方法,因为后台的权限分配有自己的逻辑不需要再次改动,只需在前端的动态路由中定义该路由的权限标识即可)
3.获取权限筛选动态路由: 获取到用户所拥有的权限之后,将后端返回的权限标识与动态路由中所定义的权限标识比对,筛选出该用户所拥有的权限(动态路由)
4.添加权限(动态路由): 使用router.addRoute()方法将拥有权限的动态路由添加到路由表系统中实现权限的控制(vue-router4.x版本已废弃router.addRoutes()来对动态路由批量导入)
5.渲染菜单: 获取到拥有权限的动态路由之后,在vuex/pinia中去组合需要渲染菜单的动态路由(+/静态路由),菜单需要在路由中按需定义菜单的名称、图片、icon等等,最后将路由中组合好的菜单循环渲染展示(不能使用router.options来获取所有的路由渲染菜单,因为该方法不能获取到动态路由)
6.退出登录删除动态路由(权限): 用户退出登录时需要删除动态路由(权限),因为如果退出登录时不删除动态路由(权限),紧接着登录其他权限不同的用户,该用户没有上一个用户所拥有的权限,所以无法查看上一个用户所看到的菜单以及页面,但是该用户可以使用浏览器的回退功能或者修改url地址栏的路径去访问上一个用户能访问的页面,这样该用户就可以访问到不属于自己权限的页面,所以在用户退出登录时需要删除动态路由(权限),来保证权限的准确性。删除动态路由使用到router.removeRoute()

三、实现代码:

第一步: 提取动态路由,每个动态路由中的meta对象中定义了要渲染菜单的名称的title和图片imgpermissionCode定义的标识为与后端确定的该用户是否具有该路由菜单的权限。
/**
 *  router/system/index.js
 *	权限的动态路由
 */
export const changeChildRouter = [
    {
        path: 'user',
        name: 'SysUser',
        component: () => import(/* webpackChunkName:"system" */'../../views/System/UserManage'),
        meta: {
            title: '用户管理',
            permissionCode: 10004,
            img: require('@/assets/images/Tree/setting-user.png')
        }
    },
    {
        path: 'department',
        name: 'SysDepartment',
        component: () => import(/* webpackChunkName:"system" */'../../views/System/DepartmentManage'),
        meta: {
            title: '部门管理',
            permissionCode: 10404,
            img: require('@/assets/images/Tree/setting-gis.png')
        }
    },
    {
        path: 'position',
        name: 'SysPosition',
        component: () => import(/* webpackChunkName:"system" */'../../views/System/PositionManage'),
        meta: {
            title: '岗位管理',
            permissionCode: 10204,
            img: require('@/assets/images/Tree/setting-user.png')
        }
    },
    {
        path: 'role',
        name: 'SysRole',
        component: () => import(/* webpackChunkName:"system" */'../../views/System/RoleManage'),
        meta: {
            title: '角色管理',
            permissionCode: 10104,
            img: require('@/assets/images/Tree/setting-user.png')
        }
    },
]
第二步: 登录之后,获取该登录用户的权限,筛选动态路由。开始有两种想法: 
  • 第一种想法: 当用户登录之后,紧接着在逻辑当中获取该用户的权限,将用户的权限存储到session当中,目的是为了防止用户刷新浏览器权限依然不会丢失。但是安全性不高,用户手动修改session中存储的权限就会导致权限丢失,或者非法获取其他用户的session权限放置未拥有该权限的用户的session中,导致该用户拥有了其他用户不属于自己的权限等等问题,所以我应用了第二种想法
  • 第二种想法: 当用户登录之后,在路由前置守卫router.beforeEach()中去获取权限,获取到权限之后,引入并筛选出该用户权限的动态路由,使用router.addRoute()将该用户的动态路由添加到路由表系统当中。将筛选出到的动态路由(权限)存储到vuex当中。由于vuex做临时存储,所以原理上刷新页面之后vuex中的数据就会丢失,但是在router.beforeEach()路由前置守卫中获取权限就解决了该问题,每次刷新页面都会先执行router.beforeEach() ,所以每次刷新页面都会先去获取该用户的权限,将根据后端返回的权限标识筛选出的动态路由存储到vuex当中。但是想要实现刷新页面动态路由(权限页面)不丢失,不能将404页面定义为静态路由放置在路由表系统当中,需要随权限获取后,动态添加404页面,目的是为了解决刷新权限页面之后权限丢失,在没有重新获取到权限的时候,检测到路由表系统当中不存在该动态路由,所以直接进入404页面的问题。【代码示例如下】
/**
 *  permission.js
 *	权限控制管理
 */
import router from "@/router"
import store from '@/store'
import { changeChildRouter } from '@/router/system'

// 防止路由无限循环
let routeFlag = false;
// 导航守卫
router.beforeEach(async (to, from, next) => {
  let token = localStorage.getItem('token');
  // 如果没有token,并且当前不是登录页,就跳转到登录界面
  if (!token && !to.path.includes('/login')) {
    routeFlag = false
    next('/login')
  } else if (to.path.includes('/login')) {
    routeFlag = false
    next()
  } else {
    if (routeFlag) return next()
    // 权限控制
    routeFlag = true
    // 1. 根据上面定义的 routeFlag 得出: 用户没有获取权限/刷新页面权限丢失,则先调用获取权限的接口
    const res = await store.dispatch('menu/MenuInfo')
    // 2. 从后端返回的权限中筛选出权限的标识 roleId, 对应前端动态路由中绑定的权限的标识 permissionCode
    const Liststr = res.map(item => item.roleId)
    // 3. 系统管理--权限: changeChildRouter 所有的动态路由
    const systemRoutes = systemAsyncRouter(changeChildRouter, Liststr)
    // 4. 保存动态路由(权限),为了退出登录之后删除
    asyncRoutes(systemRoutes)
    // 5. 添加 404 路由
    router.addRoute(
      {
        path: '/:pathMatch(.*)*',
        name: '404',
        component: () => import(/* common */'@/views/NotFound'),
        meta: { title: '404' }
      },
    )
    next({
      ...to,// 重新进入
      replace: true // 不保存本次进入页面的路由历史记录
    })
  }
})

// 系统管理——权限
function systemAsyncRouter(routes, codes) {
  // 1. 根据接口返回的权限找到可以访问的路由
  const filterRoutes = routes.filter(item => codes.includes(item.meta?.permissionCode))
  // 2. 添加动态路由
  addAsyncRouter('系统功能', filterRoutes)
  // 3. 将动态路由添加到vuex中和静态路由合并渲染侧边栏菜单
  store.commit('menu/setMenuList', filterRoutes)
  return filterRoutes
}

// 将动态路由添加到路由表系统中
function addAsyncRouter(parentName, asyncRoutesList) {
  asyncRoutesList.map(item => {
    // 可能会存在没有定义的组件的父级路由, 目的是渲染菜单时, 该路由只做子级路由的归类(仅用于展示) 
    if (!item.component) return addAsyncRouter(parentName, item.children)
    router.addRoute(parentName, item) // 添加动态路由
  })
}

// 保存所有的动态路由, 使用展开运算符优化写法,后续有其他的动态路由直接依次传入实参即可
function asyncRoutes(...args) {
  store.commit('menu/setPermissionRouteList', [].concat(...args))
}

上述代码使用到了router.addRoute()将路由添加至路由表当中,但是在此传递了两个参数:

  1. 第一个参数: 为需要添加的动态路由的一级路由的name
  2. 第二个参数: 动态路由本身

如果只传递了一个动态路由本身的参数,则表示该动态路由为一级路由

第三步: 添加动态路由之后,需要在用户退出的时候调用以下方法删除动态路由以及保存的权限
/**
 *	permission.js
 *	权限控制管理
 */
 
// 退出登录时删除动态路由以及vuex中保存的动态路由,防止下一个用户登录时,回退查看到上一个用户的动态路由(权限)
export function removeAsyncRouter() {
  // 所有的动态路由
  const permissionRouteList = store.state.menu.permissionRouteList

  const removeRouteFn = (permissionRouteList) => {
    if (permissionRouteList.length > 0) {
      permissionRouteList.map(item => {
        if (!item.component && item.children) return removeRouteFn(item.children)
        // 删除动态路由,保证用户退出后,下一个用户登录时,不会回退到上一个用户的路由
        router.removeRoute(item.name)
      })
    }
  }
  removeRouteFn(permissionRouteList)
  // 清空动态路由的左侧菜单
  store.commit('menu/setMenuList', [])
  // 清空动态路由的权限标识
  store.commit('menu/setPermissionCodeList', [])
}

 上述代码中使用router.removeRoute()来删除动态路由, 该方法接收一个参数: 路由的name, 即可将该name的路由从路由表当中删除。

第四步: 在vuex当中将动态路由(+/静态路由)组合来渲染权限菜单展示 
/**
 *	store/menu.js
 *  权限菜单
 */

import { loadAuthority } from '@/api/loaddata'
import { systemChildRouter } from '@/router/system'
const state = () => ({
  permissionCodeList: [], // 该用户有权限对应的动态路由的标识
  permissionRouteList: [], // 动态路由,单独保存一份为了退出登录的时候删除动态路由
  menuList: [], // 系统管理: 静态路由+动态路由,渲染左侧菜单
})

const mutations = {
  // 添加权限路由对应的权限标识
  setPermissionCodeList(state, permissionCodeList) {
    state.permissionCodeList = permissionCodeList
  },
  // 动态路由集合
  setPermissionRouteList(state, permissionRouteList) {
    state.permissionRouteList = permissionRouteList
  },
  // ********** 系统管理:路由权限 ***********
  setMenuList(state, permissionRouteList) {
    const menuList = [...systemChildRouter, ...permissionRouteList]
  },
}

const actions = {
  // 获取该用户的权限项
  async MenuInfo(ctx) {
    const res = await loadAuthority.getUserAuthList({ userName: localStorage.getItem('userName') })
    ctx.commit('setPermissionCodeList', res)
    return res
  }
}

export default {
  namespaced: true,
  state,
  actions,
  mutations
}

vuex store中组合了渲染菜单的: 动态路由+静态路由, 由于提前已经将渲染菜单的名称和图片定义在了每个路由当中,所有组合要渲染菜单的路由之后, 即可在vue组件中去循环渲染权限菜单 

第五步: 渲染菜单, 在vue组件计算属性当中获取vuex中组合好的渲染菜单的路由, 在模板中循环渲染展示菜单 
/**
 *	LeftMenu.vue
 *	系统管理: 权限侧边栏菜单
 */

<template>
  <a-layout-sider :width="220">
    <a-menu mode="inline" v-model:selectedKeys="current">
      <!-- 权限菜单 -->
      <left-menu-item v-for="item in menuList" :key="item.path" :item="item">
      </left-menu-item>
    </a-menu>
  </a-layout-sider>
</template>

<script>
import LeftMenuItem from './LeftMenuItem.vue'
export default {
  name: "SystemMenu",
  components: {
    LeftMenuItem
  },
  data() {
    return 
      current: ["help"],
    };
  },
  computed: {
    menuList() {
      // 获取渲染菜单的路由数组
      return this.$store.state.menu.menuList
    }
  },
};
</script>

在渲染权限侧边栏菜单的LeftMenu.vue组件中, 引入了一个封装的LeftMenuItem.vue组件, 在该组件中实现了具体的渲染菜单的模板代码, 并且使用LeftMenuItem.vue可以使用递归自身来实现可展开菜单的功能。

/**
 *	LeftMenuItem.vue
 *	封装渲染菜单组件
 */
<template>
  <div>
    <!-- 如果没有children, 则渲染在侧边栏的一级菜单 -->
    <template v-if="!item.children">
      <a-menu-item :key="item.path">
        <template #icon>
          <img :src="item.meta.img">
        </template>
        <router-link :to="item.path">{{ item.meta.title }}</router-link>
      </a-menu-item>
    </template>
    <!-- 如果有children,表明是二级菜单,该一级菜单不提供路由,只是对二级菜单的归类 -->
    <template v-else>
      <a-sub-menu :key="item.path">
        <template #icon>
          <img :src="item.meta.img">
        </template>
        <template #title>{{ item.meta.title }}</template>
        <!-- 递归组件,如果子路由里面还有路由,则继续渲染它的子路由对应的菜单 -->
        <left-menu-item v-for="item in item.children" :key="item.path" :item="item">
        </left-menu-item>
      </a-sub-menu>
    </template>
  </div>
</template>

<script>
export default {
  name: "LeftMenuItem",
  props: {
    item: {
      type: Object,
      required: true
    },
  }
}
</script>
<style lang="scss" scoped>
</style>

 递归自身组件: 想要递归自身组件, 需要在组件脚本中导出定义自身的name: 'LeftMenuItem' , 在组件内容即可调用自身的name来实现递归调用组件本身。

 

这样就实现了菜单的权限控制, 拥有不同权限的用户登录之后, 就只会看到属于自己权限的页面~

 

 

 

 

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/538664.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

阿里云2核2G服务器这么便宜,能用来做什么?

阿里云2核2G服务器这么便宜&#xff0c;能用来做什么&#xff1f;阿里云2核2G云服务器可以用来搭建网站、爬虫、邮件服务器、接口服务器、个人博客、企业官网、数据库应用、大数据计算、AI人工智能、论坛、电子商务、AI、LLM大语言模型、测试环境等&#xff0c;阿里云2核2G服务…

四、SpringBoot3 整合 Druid 数据源

本章概要 创建程序引入依赖启动类配置文件编写编写 Controller启动测试问题解决 4.1 创建程序 4.2 引入依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://ww…

解锁电气数据新价值:SolidWorks Electrical助力企业转型

在信息化、数字化的时代&#xff0c;电气数据库已成为企业不可或缺的核心资产。它以其独特的功能和优势&#xff0c;助力企业在激烈的市场竞争中脱颖而出&#xff0c;实现数字化转型的跨越式发展。 SolidWorks Electrical电气数据库具备强大的数据整合能力。它能够将企业内部各…

阿里云服务器带宽价格全解析,附报价单

阿里云服务器公网带宽怎么收费&#xff1f;北京地域服务器按固定带宽计费一个月23元/M&#xff0c;按使用流量计费0.8元/GB&#xff0c;云服务器地域不同实际带宽价格也不同&#xff0c;阿里云服务器网aliyunfuwuqi.com分享不同带宽计费模式下带宽收费价格表&#xff1a; 公网…

【论文研读】Geometric Deep Learning on Molecular Representations

Geometric Deep Learning on Molecular Representationshttps://arxiv.org/pdf/2107.12375.pdf 一、Background 随着网络时代的发展&#xff0c;生活中产生的数据量越来越多&#xff0c;但数据大体分为两类&#xff1a;欧氏数据、非欧氏数据。如图为两类常见的数据&#xff0c…

SpringBoot与MyBatisPlus的依赖版本冲突问题

记录使用SpringBoot和MyBatisPlus时遇到的版本冲突问题解决。 java版本&#xff1a;jdk17 废话&#xff1a;&#xff09;目前在IDEA中使用Spring官方的脚手架最低jdk版本竟然是jdk17了。 当使用SpringBoot3.0版本(3.2.4)&#xff0c;配合使用MP3.5.2版本时报错&#xff1a; Er…

鸿蒙应用开发之富文本(RichText)组件

前面学习了评分组件,现在来学习富文本组件,这个组件用来表示复杂的文本,把文本显示得更加有特色,比如网页一样显示。这种显示会比较复杂,所以应用的场合就会少一点。不过富文本显示最多的,就是即时通讯软件了,比如显示图片与文本,以及一些特殊的字符。 比如显示如下面的…

# Contrastive Learning(对比学习)--CLIP笔记(一)

Contrastive Learning&#xff08;对比学习&#xff09;–CLIP笔记&#xff08;一&#xff09; 参考&#xff1a;CLIP 论文逐段精读【论文精读】_哔哩哔哩_bilibili CLIP简介 CLIP是一种多模态预训练模型&#xff0c;由OpenAI在2021年提出&#xff0c;论文标题&#xff1a;L…

通俗易懂HTTP和HTTPS区别

HTTP&#xff1a;超文本传输协议&#xff0c;它是使用一种明文的方式发送我们的内容&#xff0c;没有任何的加密&#xff0c;例如我们要在网页上输入账号密码&#xff0c;如果使用HTTP协议&#xff0c;账号密码就可能会被暴露&#xff0c;默认端口是80. HTTPS&#xff1a;是HT…

【hive】远程remote debug hive的方法,用于hive监听器/钩子编写

背景 写hive监听器时候需要拿到hive对象但hive是在集群linux主机上运行的。通过jdbc提交的sql具体执行过程不会再idea中运行。所以如果需要拿到hive对象有可能存在两个思路&#xff1a; &#xff08;1&#xff09;想办法写个钩子或者监听器&#xff0c;将需要的内容写成json字…

AIX7.2下安装xlc编译器

AIX镜像中对于xlc的支持仅为提供了runtime&#xff0c;而没有对应的xlc编译器。 IBM的xlc编译器需要单独购买&#xff0c;如果不想购买可以注册IBM的账号下载一个60天的试用版。 下载地址&#xff1a; IBM 中国官方网站 &#xff08;AIX各个版本和xlc编译器版本的兼容性可以…

php未能在vscode识别?

在设置里搜php&#xff0c;找到settings.json&#xff0c;设置你的安装路径即可。 成功

Qt Creator 12.0.2 debug 无法查看变量的值 Expression too Complex

鼠标放在局部变量上提示“expression too complex”。 在调试窗口也看不到局部变量的值。 这应该是qt的一个bug&#xff0c;https://bugreports.qt.io/browse/QTCREATORBUG-24180 暂时解决方法&#xff1a; 如下图&#xff0c;需要右键项目然后执行"Clean"和&quo…

2022年全国青少年信息素养大赛Python国赛第1-10题,含解析答案

01-分苹果 把一堆苹果分给n个小朋友,每个人拿到的苹果数量不同,并且每个人至少有一个。任意输入小朋友的数量n,问这堆苹果至少应该有多少个。输入描述:任意输入小朋友的数量n输出描述:输出这堆苹果至少应该有多少个 样例输入: 3 样例输出: 6 注意: input()内不添…

李彦宏放话:百度AI大模型绝不抢开发者饭碗

关注卢松松&#xff0c;会经常给你分享一些我的经验和观点。 昨晚&#xff0c;李彦宏内部讲话称&#xff1a;AI大模型开源意义不大&#xff0c;百度绝不抢开发者饭碗。 但你一定要说话算话哦&#xff0c;可千万别说&#xff1a;“我永远不做手机&#xff0c;谁再敢提做手机就给…

#esp8266模块通过AT指令获取网络时间(苏宁时间)

一、IDE&#xff1a;keil、cubemx、Arduino......... 二、记录&#xff1a; 1.依次发送以下指令&#xff08;发送新行&#xff09; AT ATCWMODE1 ATCWDHCP1,1 ATCWJAP"Redmi K40 Gaming","87654321" ATCIPSTART"TCP","quan.suning.com&quo…

1分钟以上情感短视频素材去哪里找?推荐五个素材网站

在这个充斥着各种短视频的世界里&#xff0c;我就像是一名寻宝的冒险家&#xff0c;不断在素材的海洋中潜水&#xff0c;寻找那些能让我的视频金光闪闪的珍珠。让我告诉你&#xff0c;这不仅仅是一场寻宝&#xff0c;这简直是一场奇妙的冒险。九才素材网&#xff1a; 我的创作…

万字长文|Sam Altman创业手册(中)

Sam Altman&#xff0c;现任Open AI CEO&#xff0c;曾任美国著名创业孵化器Y Combinator最年轻的总裁&#xff08;不到30岁&#xff09;&#xff0c;被《福布斯》等媒体誉为“ChatGPT之父”&#xff0c;被《商业周刊》评为"技术领域最优秀的年轻企业家"&#xff0c;…

车载网关设计方案,开发无线车载网关需要考虑哪些因素?

随着智能车企之间的竞争日益加剧&#xff0c;各车企竞相推出新款智能汽车&#xff0c;并不断拓展车联网生态&#xff0c;以更加优质的用车体验来增强产品竞争力&#xff0c;进而深化用户忠诚度。这一举措不仅提升了车辆的功能性和智能化水平&#xff0c;更在激烈的市场竞争中为…

C/C++进阶/架构师(后端/音视频/游戏/嵌入式/高性能网络/存储/基础架构/安全)开发学习路线、系统性学习教程

C学习可以划分为几个主要阶段&#xff0c;每个阶段的学习目标和推荐资源都有所不同。下面是一个详细的分阶段学习指南&#xff1a; 入门阶段 学习目标 理解C的基本语法和结构。学习基本数据类型&#xff0c;条件判断&#xff0c;循环等控制结构。掌握函数的使用方法。初步了…