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 = falsenext('/login')} else if (to.path.includes('/login')) {routeFlag = falsenext()} else {if (routeFlag) return next()// 权限控制routeFlag = true// 1. 根据上面定义的 routeFlag 得出: 用户没有获取权限/刷新页面权限丢失,则先调用获取权限的接口const res = await store.dispatch('menu/MenuInfo')// 2. 从后端返回的权限中筛选出权限的标识 roleId, 对应前端动态路由中绑定的权限的标识 permissionCodeconst 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.permissionRouteListconst 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来实现递归调用组件本身。

 

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

 

 

 

 

 

 

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据仓库的—数据仓库的体系架构

数据仓库通常采用分层的体系架构设计,作为支撑企业决策分析需求的数据基础设施。典型的数据仓库体系架构由以下三个核心层次组成: 源数据层(Source Layer) 这是数据仓库的数据来源,包括组织内部的各种运营系统,如ERP、CRM、SCM等,以及外部数据源如互联网、社交媒体等。这些系…

2023年全国职业技能大赛“信息安全与评估”国赛模拟题-windows应急响应+题目+WP

🍬 博主介绍👨‍🎓 博主介绍:大家好,我是 hacker-routing ,很高兴认识大家~ ✨主攻领域:【渗透领域】【应急响应】 【Java】 【VulnHub靶场复现】【面试分析】 🎉点赞➕评论➕收藏 == 养成习惯(一键三连)😋 🎉欢迎关注💗一起学习👍一起讨论⭐️一起进步�…

React + three.js 实现人脸动捕与3D模型表情同步

系列文章目录 React 使用 three.js 加载 gltf 3D模型 | three.js 入门React three.js 3D模型骨骼绑定React three.js 3D模型面部表情控制React three.js 实现人脸动捕与3D模型表情同步 示例项目(github)&#xff1a;https://github.com/couchette/simple-react-three-facia…

BI数据分析软件:行业趋势与功能特点剖析

随着数据量的爆炸性增长&#xff0c;企业对于数据的需求也日益迫切。BI数据分析软件作为帮助企业实现数据驱动决策的关键工具&#xff0c;在当前的商业环境中扮演着不可或缺的角色。本文将从行业趋势、功能特点以及适用场景等方面&#xff0c;深入剖析BI数据分析软件&#xff0…

LeetCode:1两数之和 C语言

1. 两数之和 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元素在答案里不能重复出现。 你可以…

IP证书申请流程

目录 域名与IP的关系 SSL证书绑定域名还是绑定IP&#xff1f; IP证书支持免费申请吗&#xff1f; 如何申请IP地址证书 IP类型的SSL证书&#xff0c;又称之为IP SSL&#xff0c;这种SSL证书是专门用于公网IP地址验证的一种数字证书。 主要功能就是解决IP地址明文传输的安全…

毅四捕Go设计模式笔记——代理模式

代理模式&#xff08;Proxy Pattern&#xff09; 为了解决什么问题&#xff1f; 代理模式用于在不直接访问实际对象的情况下&#xff0c;通过引入一个代理对象来控制和管理对该对象的访问。主要解决的问题是对原始对象的访问控制&#xff0c;以及在不改变原始对象接口的情况下…

IvorySQL 3.2原理解析|与Oracle 12c XML函数兼容性的实现机制

[发行日期&#xff1a;2024年4月11日] IvorySQL 3.2基于PostgreSQL 16.2&#xff0c;引入了多种Oracle XML函数的全面兼容性功能&#xff0c;同时修复了多个问题&#xff0c;更多信息请参考文档网站。 >>>新版本体验链接&#xff1a; https://docs.ivorysql.org/cn…

bilibili PC客户端架构设计——基于Electron

众所周知&#xff0c;bilibili是个学习的网站&#xff0c;网页端和粉版移动端都非常的好用&#xff0c;不过&#xff0c;相对其它平台来说bilibili的PC客户端也算是大器晚成了。在有些场景PC客户端的优势也是显而易见的&#xff0c;比如&#xff0c;跓留电脑桌面的快捷、独立的…

加载 docker 镜像文件 centos7 系统 lnmp 环境 php8.2 php5.2 php7.4

# 加载镜像从tar文件 链接&#xff1a;https://pan.baidu.com/s/1s2yf7iroI-tBTK5b9zxxnA 提取码&#xff1a;6666 docker load < my_migration_image.tar # 运行新容器&#xff0c;可以使用相同的参数和命令 8233 电脑访问时对应的端口 80 docker 上的nginx 端口号 …

塔面板php7.37.4版本不支持ZipArchive手工安装扩展方法

centos 7 宝塔面板安装的PHP7.3和7.4默认已经不带zip扩展&#xff0c;要手工安装zip扩展首先需要安装libzip&#xff0c; yum -y install libzip 方法如下&#xff1a; 宝塔面板php7.3版本在SSH命令行界面执行以下语句&#xff1a; cd /www/server/php/73/src/ext/zip/ /ww…

C#在后台自动化截图指定网站并保存图片

先安装PuppeteerSharp的库 然后调用如下方法 private async Task ScreenShotAsync(string url){//using var browserFetcher new BrowserFetcher();//await browserFetcher.DownloadAsync();await using var browser await Puppeteer.LaunchAsync(new LaunchOptions { Headle…

gitlab 搭建

cat etc/initial_root_password Password: ipGg5y7GJPp/YmVHf3c3ViMKzCWYJSjU4JzUktrw8cY ###### 可修改&#xff0c;可不修改&#xff0c;并###并#并 #初始密码 #本次未修改 vim /data/gitlab/etc/gitlab.rb external_url http://ip/gitlab #访问网址及端口 #ssh远程地址 gi…

自动化运维(二十二)Ansible实战 之Jenkins模块

Ansible提供了一些模块,可以用来与Jenkins进行交互,执行各种操作,如创建任务、触发构建、获取构建结果等。通过使用这些模块,我们可以将Jenkins的配置和管理集成到Ansible的自动化流程中。 以下是一些常用的Ansible Jenkins模块: 1、jenkins_job模块 jenkins_job模块用于创建…

python django mvc

MVC框架: 1、URL映射到方法 [rootzz mysite]# cat urls.py """mysite URL Configuration The urlpatterns list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/1.11/topics/http/urls/ Examples: Function …

时钟周期检测标志信号

在某些情况下需要对系统时钟分频后的时钟进行周期检测&#xff0c;引出周期标志信号以便在后续其他情况的使用。虽然在大多数情况下我们能够知道分频后的时钟是系统时钟的几倍分频&#xff0c;但为增强在分频时钟改变情况下周期标志信号的复用性或对未知时钟的周期检测&#xf…

FFmpeg: 简易ijkplayer播放器实现--06封装打开和关闭stream

文章目录 流程图stream openstream close 流程图 stream open 初始化SDL以允许⾳频输出&#xff1b;初始化帧Frame队列初始化包Packet队列初始化时钟Clock初始化音量创建解复用读取线程read_thread创建视频刷新线程video_refresh_thread int FFPlayer::stream_open(const cha…

如何解决Uniapp更新数据不重新渲染组件

办法就是在修改数据的函数里面&#xff0c;用let thatthis&#xff0c;再给that用赋值。 原因是给数据赋值的函数没用箭头函数&#xff0c;this是函数自己的this。比如success&#xff08;res&#xff09;{} 或者用箭头函数&#xff0c;比如success&#xff08;res&#xff0…

async/await的执行

1.在function中使用了await&#xff0c;则function必须被async修饰 2.在async方法中&#xff0c;第一个await之前的代码会同步执行&#xff0c;await 之后的代码会异步执行 const fs require(fs); console.log(A); async function getFn() {console.log(B);const res await…

在Ubuntu服务器上快速安装一个redis并提供远程服务

一、快速安装一个Redis 第一步&#xff1a;更新apt源 sudo apt update第二步&#xff1a;下载Redis sudo apt install redis第三步&#xff1a;查看Redis是否已自启动 systemctl status redis二、配置Redis提供远程服务 第一步&#xff1a;先确保6379端口正常开放 如果是…