用addRoutes实现动态路由

原文转自前端路上,转载请注明出处。

之前在基于Vue实现后台系统权限控制一文中提到路由权限的实现思路,因为不喜欢在每次路由跳转的before钩子里做判断,所以在初始化Vue实例前对路由做了筛选,再用实际路由初始化Vue实例,代价是登录页需要从Vue实例中独立出来,实现上倒没什么问题,不过这种做法需要在登录和首页之间通过url跳转,感觉总是不太”优雅”,实际上只要能在登录后动态修改当前实例的路由就行了,之前确实没办法,但vue-router 2.2版本新增了一个router.addRoutes(routes)方法,让动态路由得以实现。

想当然的实现方案

用动态路由实现路由权限控制貌似是一个完美的方案,初始路由只有登录和404,登录后动态添加可用路由,同时将菜单数据保存到Vuex或本地用于实现动态菜单,关键节点大致如下:

12345678910111213141516171819202122232425262728293031323334353637383940414243444546
//初始路由:[{  path: ‘/login‘,  name: ‘login‘,  component: (resolve) => require([‘../views/common/404.vue‘], resolve)}, {  path: ‘/404‘,  name: ‘404‘,  component: (resolve) => require([‘../views/common/404.vue‘], resolve)}, {  path: ‘*‘,  redirect: ‘/404‘}]

//登录逻辑let vm = this;axios.get(‘/login‘, vm.user).then((res) => {    let extendsRoutes = filterRoutes(res.menus);     <!--    //假设得到的可用路由如下    [{      path: ‘/‘,      name: ‘首页‘,      component: (resolve) => require([‘../views/index.vue‘], resolve),      children: [{        path: ‘/menus‘,        name: ‘菜单管理‘,        component: (resolve) => require([‘../views/menus.vue‘], resolve)      }, {        path: ‘/resources‘,        name: ‘资源管理‘,        component: (resolve) => require([‘../views/resources.vue‘], resolve)      }]    }]-->    //存菜单    sessionStorage.setItem(‘menus‘,JSON.stringify(extendsRoutes[0].children));    //动态添加路由    vm.$router.addRoutes(extendsRoutes);    //跳转到应用界面    vm.$router.push({path:‘/‘});})

//首页获取菜单数据this.menus = JSON.parse(sessionStorage.getItem(‘menus‘)); //用此数据循环菜单..

目前为止看上去一切顺利,然而前方有坑。

动态路由的坑

第一个坑是,如果你将这套逻辑实现之后会发现打开应用看到的第一个页面是404,这是因为启动服务后将默认打开首页’/‘,然而初始路由中没有这个路径,因此根据路由规则跳转到了404。我们希望结果当然是跳转到’/login’,因此需要对这种情况做判断,在用户登录之前所有请求都要指向’/login’,这个判断可以在before钩子里做也可以在根组件里做,建议做在根组件的created回调里,核心代码大概这样:

1234
let isLogin = sessionStorage.getItem(‘user‘);if(!isLogin){    return this.$router.push({path:‘/login‘});}

这时候已经可以顺利登录了,登录后很快就会发现第二个坑,手动刷新页面又会跳到404,这是因为刷新会导致Vue重新实例化,路由也恢复到了初始路由,于是当前路径又被重定向到了404,这个问题的根源是可用路由没有实现持久化,那么可以通过将路由数据存sessionStorage来解决,实例化之前如果检测到本地路由就直接合并路由,像这样:

1234567891011
//检测本地路由let localRoutes = sessionStorage.getItem(‘routes‘);if(localRoutes){    router.addRoutes(JSON.parse(localRoutes));}//实例化new Vue({  el: ‘#app‘,  router,  render: h => h(App)});

理论上可以,但实际操作要远比上述代码复杂,因为存在本地的只能是权限数据而不是真实路由,路由在存、取之前都要先根据权限匹配获得,过程还是挺繁琐的,而且必须依赖sessionStorage这种持久存储,没有其他方法。问题就出在这个sessionStorage上,原则上权限只能在内存变量中流转,不能直接暴露到用户可操作的地方,试想只要用户手动修改了sessionStorage里的权限,再刷新一下页面就能突破前端路由控制了,非常的不靠谱。

改进方案

既然不能存本地,那就每次刷新都重新从服务端获取,所以改进后的方案是本地存用户token,每次刷新要凭token从服务端重新获取用户信息和权限,然后动态更新路由,获取权限操作可以跟登录检测一起放在根组件的created回调中进行,确保访问任何路径都会先执行这一步,但因为获取权限是异步操作,在此之前仍然会经过应用初始化,所以还是会遇到404的问题,为此我们只需做一个小调整,将不匹配路径(‘*’)跳404的路由从初始路由中移除,动态更新路由时再把这个配置加进去,如下:

123456
let userPath = ...//我们的动态路由//注入时拼接404处理路由this.$router.addRoutes(userPath.concat([{  path: ‘*‘,  redirect: ‘/404‘}]));

这样就解决了刷新问题,后面还有几个小问题就简单了。

首先是菜单,之前通过$router.options.routes访问路由数据实现动态菜单,但这个数据不是响应式的,无法追踪动态路由的变化,因此我们需要将得到的导航菜单数据存到sessionStorage或Vuex里实现数据共享。

资源权限控制也受到很大的影响,实现较为细致的权限控制需要一个自定义权限验证指令和一个全局验证方法,之前的方案里权限是在Vue实例化之前获取的,所以可以很方便的拿到权限后实现验证方法,然后用验证方法实现自定义指令,再将方法全局混合进Vue,然后实例化,这样实例中的 所有组件都可以使用自定义指令和验证方法;但现在的方案是先实例化再获取权限,实例化之前根本没有权限数据,所以自定义指无法实现,等拿到权限后实现了验证方法,却无法再全局混合了。

这个问题最后也解决了,但解决方案就彻底的”有辱斯文”了,首先是全局方法的实现,直接这么做:

123
Vue.prototype.has = function(){    ...}

使用方式跟全局混合的方法完全一样。

自定义指令的实现本来很头疼,因为全局指令只能在实例化之前实现,但那时候又确实没有权限,不过既然验证方法这么做的话,指令倒是也顺便解决了,像这样:

12345678
//权限指令Vue.directive(‘has‘, {  bind: function(el, binding) {    if (!Vue.prototype.has(binding.value)) {      el.parentNode.removeChild(el);    }  }});

神奇的prototype貌似自带惰性效果,可以先注册后实现,具体原因我也不太明白,如过有大牛路过,希望能留下答案。

后记

生命不息,折腾不止啊,本来已经放弃的思路,捋着捋着竟然捋顺了,然后又花了大半天把原来多入口的项目改成了单入口,虽然麻烦了一顿,但心里总算舒坦了。

时间: 2024-10-07 18:42:55

用addRoutes实现动态路由的相关文章

vue-router+vuex实现加载动态路由和菜单

前言 动态路由加载和动态菜单渲染的应用在后端权限控制中十分常见,后端只要加载权限路由进行渲染返回到浏览器就可以.在前后端分离中,权限控制动态路由和动态菜单也是一个非常常见的问题.其实我们最最理想的效果是什么呢?我们访问一个应用,在登录之前有哪些路由是一定要加载的呢?你看我总结如下,你看下是不是这些: 1.登录路由 (登录功能路由) 2.系统路由(系统消息路由,比如欢迎界面,404,error等的路由) 但是在vue中,一旦实例化,就必须初始化路由,但这个时候你还没有登录,没有获取你的权限路由呀,

vue动态添加路由addRoutes之不能将动态路由存入缓存

在我不知道vue的路由还可以通过addRoutes动态添加时,我只知道vue的路由都是写死在路由表中的,每当跳转时再去加载相应的路由.直到在一个新公司接到需要根据用户的权限显示不同的菜单的需求时才知道了原来vue-router还有一个addRoutes的API,立马研究了一下. router.addRoutes: 函数签名: router.addRoutes(routes: Array<RouteConfig>) 动态添加更多的路由规则.参数必须是一个符合routes选项要求的数组. 点这里去

spring cloud 2.x版本 Gateway动态路由教程

摘要 本文采用的Spring cloud为2.1.8RELEASE,version=Greenwich.SR3 本文基于前面的几篇Spring cloud Gateway文章的实现. 参考 Gateway路由网关教程 Gateway自定义过滤器教程 前言 写了几篇关于Spring Cloud Gateway的文章后发现,Gateway涉及的知识范围太广了,真是深刻体会了"一入Spring cloud深似海". 现实生产环境中,使用Spring Cloud Gateway都是作为所有流量

CIsco动态路由配置之EIGRP

Cisco配置动态路由,可以使用EIGRP(增强型内部网关路由选择协议)进行配置 EIGRP支持: 快速收敛:使用DUAL(弥散更新算法)来实现快速收敛. 带宽的有效利用:发送部分.增量的路由更新而不是发送整个路由表,从而实现带宽的有效利用. 支持多个网络层协议:使用独立于协议的模块(PDM)来支持IP,IPX,Appletalk等协议. 支持VLSM(变长子网掩码)和CIDR(无类域间路由) 思路:分别配置各个路由器的端口IP 使用EIGRP 在各自路由器配置所属的网段 R1#CONF T E

Cisco PT模拟实验(13) 路由器RIP动态路由的配置

Cisco PT模拟实验(13) 路由器RIP动态路由的配置 实验目的: 掌握RIP动态路由选择协议的配置方法 掌握路由选择表中的RIP路由描述 熟悉路由选择和分组转发的原理及过程 实验背景: 公司通过一台三层交换机连到企业网的出口路由器上,路由器再与互联网服务提供商 ISP的另一台路由器连接.现要企业网设备上做适当配置,实现企业网内部主机与外网主机之间的相互通信.为了简化网管的管理维护工作,公司决定采用动态路由配置 -- RIPv2协议实现互通. 技术原理: 动态路由选择:路由器使用路由选择协

第一章 大网高级 &nbsp; 动态路由RIP

  动态路由 实验要求: (一)配置路由器的接口地址,实现路由互通. (二)配置RIP v1 ,使用show IP route命令查看路由表,在R2上ping R1 或R3, 验证网络通信是否正常. (三)配置RIP v2 ,使用ping,show IP route查看命令,验证网络通信是否正常. 一.接口地址设置 二.RIPv1设置 三.查看路由表 R1 R2 R3 四.验证 五.RIPv2 设置(版本2) R1 R2 R3 六.查看路由表 R1 R2 R3 七.验证

动态路由距离矢量路由协议RIP

动态路由距离矢量路由协议RIP R1连接R2连接R3 R1 ip192.168.12.1 R2 ip192.168.12.2,192.168.23.2 R3 ip192.168.23.3,lo:3.3.3.3 R1(config)#int s0/0 R1(config-if)#ip add 192.168.12.1 255.255.255.0 R1(config-if)#no shut R1(config)#router rip R1(config-router)#network 192.168

网路配置动态路由

网络配置动态路由 介绍: 动态路由协议通过路由信息的交换生成并维护转发引擎所需的路由表.当网络拓扑结构改变时动态路由协议可以自动更新路由表,并负责决定数据传输最佳路径. 在动态路由中,管理员不再需要与静态路由一样,手工对路由器上的路由表进行维护,而是在每台路由器上运行一个路由协议.这个路由协议会根据路由器上的接口的配置(如IP地址的配置)及所连接的链路的状态,生成路由表中的路由表项. 一.实验拓扑图: 二.实验目标: 实战:使用网络配置动态路由. 三.实验环境: 路由器 接口 ip地址 R1 e

动态路由备份

动态路由备份主要应用DCC(拨号控制中心)功能动态维护拨号链路,即基于路由进行拨号备份提供了可靠的连接和规范的按需拨号服务 特点:动态路由备份主要针对动态路由协议产生的路由进行备份,也可以对静态路由和直连路由进行备份主链路断开时备份链路将自动启动,不会导致拨号延迟(该延迟不包含路由收敛时间)不依赖于具体的路由协议,可以和其它动态路由协议配合工作 通过配置要监控的网段,来实现在主链路故障时启动备份链路 工作步骤: 1.系统监控到达监控网络路由更新,检查是否存在至少一条有效路由2.如果不存在有效路由