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

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

想当然的实现方案

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
//初始路由:
[{
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回调里,核心代码大概这样:

1
2
3
4
let isLogin = sessionStorage.getItem('user');
if(!isLogin){
return this.$router.push({path:'/login'});
}

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

1
2
3
4
5
6
7
8
9
10
11
//检测本地路由
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的路由从初始路由中移除,动态更新路由时再把这个配置加进去,如下:

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

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

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

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

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

1
2
3
Vue.prototype.has = function(){
...
}

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

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

1
2
3
4
5
6
7
8
//权限指令
Vue.directive('has', {
bind: function(el, binding) {
if (!Vue.prototype.has(binding.value)) {
el.parentNode.removeChild(el);
}
}
});

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

后记

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

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

  1. vue-router的两种动态路由

    第一种实质上不能算动态路由,充其量算是参数的传递 https://www.jianshu.com/p/f499d9f64958 第二种是真正的动态路由,利用 router.addRoutes()设置动 ...

  2. Vue 实现动态路由及登录&404页面跳转控制&页面刷新空白解决方案

    Vue实现动态路由及登录&404页面跳转控制&页面刷新空白解决方案   by:授客 QQ:1033553122   开发环境   Win 10   Vue 2.9.6   node-v ...

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

    在我不知道vue的路由还可以通过addRoutes动态添加时,我只知道vue的路由都是写死在路由表中的,每当跳转时再去加载相应的路由.直到在一个新公司接到需要根据用户的权限显示不同的菜单的需求时才知道 ...

  4. vue+iview实现动态路由和权限验证

    github上关于vue动态添加路由的例子很多,本项目参考了部分项目后,在iview框架基础上完成了动态路由的动态添加和菜单刷新.为了帮助其他需要的朋友,现分享出实现逻辑,欢迎一起交流学习. Gith ...

  5. 从壹开始 [vueAdmin后台] 之三 || 动态路由配置 & 项目快速开发

    回顾 今天VS 2019正式发布,实验一波,你安装了么?Blog.Core 预计今天会升级到 Core 3.0 版本. 哈喽大家周三好!本来今天呢要写 Id4 了,但是写到了一半,突然有人问到了关于 ...

  6. vue的动态路由(登录之后拿到动态路由通过addRouters()动态添加路由)

    登录后我们拿到路由动态路由,后端传的数据可能为这个 { path: '/index', meta: { title: '首页', icon: 'icon-shouye', tab_index: , / ...

  7. 使用 vue-element-admin 动态路由渲染

    附上:vue-element-admin 官方文档 vue-element-admin https://panjiachen.github.io/vue-element-admin-site/zh/g ...

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

    摘要 本文采用的Spring cloud为2.1.8RELEASE,version=Greenwich.SR3 本文基于前面的几篇Spring cloud Gateway文章的实现. 参考 Gatew ...

  9. Vue 动态路由的实现以及 Springsecurity 按钮级别的权限控制

    思路: 动态路由实现:在导航守卫中判断用户是否有用户信息,通过调用接口,拿到后台根据用户角色生成的菜单树,格式化菜单树结构信息并递归生成层级路由表并使用Vuex保存,通过 router.addRout ...

随机推荐

  1. 同顶级域名下 通过Cookie 跨域实现单点登陆

    Cookie对于web开发者而言真是一个小甜饼,因为它保留了用户的登录状态.但是当登陆站点在不同域名时就会出现问题了. 在Cookie规范上说,一个cookie只能用于一个域名,不能够发给其它的域名. ...

  2. MySQL 多实例的安装

    Step 1 . 准备工作 关闭防火墙; 安装 MySQL依赖于libaio 库:下载,解压,重命名 MySQL的执行文件:新建用户组和用户 等准备工作在此不再赘述. 可执行文件目录为 /data/m ...

  3. hbase coprocessor 二级索引

    Coprocessor方式二级索引 1. Coprocessor提供了一种机制可以让开发者直接在RegionServer上运行自定义代码来管理数据.通常我们使用get或者scan来从Hbase中获取数 ...

  4. Markdown 语法文档

    Markdown 语法文档 前言 Markdown 是一种轻量级标记语言,创始人为約翰・格魯伯(英语:John Gruber); 它允许人们 "使用易读易写的纯文本格式编写文档,然后转换成有 ...

  5. Kubernetes 笔记 012 Pod 的自动扩容与缩容

    本文首发于我的公众号 Linux云计算网络(id: cloud_dev),专注于干货分享,号内有 10T 书籍和视频资源,后台回复「1024」即可领取,欢迎大家关注,二维码文末可以扫. Hi,大家好, ...

  6. Python3+Flask+uwsgi部署

    python3 按照常规的方式安装即可: wget https://www.python.org/ftp/python/3.5.4/Python-3.5.4.tgz tar zxvf Python-3 ...

  7. 【深度学习】--GAN从入门到初始

    一.前述 GAN,生成对抗网络,在2016年基本火爆深度学习,所有有必要学习一下.生成对抗网络直观的应用可以帮我们生成数据,图片. 二.具体 1.生活案例 比如假设真钱 r 坏人定义为G  我们通过 ...

  8. redis的string类型!!!!

    value的最大存储量为512m,可储存图像数据或json数据等 1.redis的增加操作 127.0.0.1:6379> set name China     --返回ok,说明增加成功,若n ...

  9. qml demo分析(objectlistmodel-自定义qml数据)

    一.效果展示 如图1所示,是一个ListView窗口,自定义了文本内容和项背景色. 图1 ListView 二.源码分析 代码比较简单,主要使用了QQmlContext类的setContextProp ...

  10. Centos 7.6搭建Tomcat 环境,发布Java项目

    安装 JDK Tomcat 的安装依赖 JDK,在安装 Tomcat 之前需要先安装 Java JDK.输入命令 java -version,如果显示 JDK 版本,证明已经安装了 JDK java ...