vue用addRoutes实现动态路由的示例


Posted in Javascript onSeptember 15, 2017

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

想当然的实现方案

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

//初始路由:
[{
 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回调里,核心代码大概这样:

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

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

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

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

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

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

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

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

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

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

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

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

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
基于jQuery制作迷你背词汇工具
Jul 27 Javascript
javascript:FF/Chrome与IE动态加载元素的区别说明
Jan 26 Javascript
node.js中的fs.fchownSync方法使用说明
Dec 16 Javascript
jQuery学习笔记之2个小技巧
Jan 19 Javascript
jfreechart插件将数据展示成饼状图、柱状图和折线图
Apr 13 Javascript
JS+CSS实现自适应选项卡宽度的圆角滑动门效果
Sep 15 Javascript
JS实现表单验证功能(验证手机号是否存在,验证码倒计时)
Oct 11 Javascript
getElementById().innerHTML与getElementById().value的区别
Oct 27 Javascript
vuejs项目打包之后的首屏加载优化及打包之后出现的问题
Apr 01 Javascript
vue组件name的作用小结
May 23 Javascript
详解一次Vue低版本安卓白屏问题的解决过程
May 30 Javascript
JS如何在数组指定位置插入元素
Mar 10 Javascript
Vue渲染函数详解
Sep 15 #Javascript
JavaScript中如何判断一个值的类型
Sep 15 #Javascript
详解利用 Vue.js 实现前后端分离的RBAC角色权限管理
Sep 15 #Javascript
动态创建Angular组件实现popup弹窗功能
Sep 15 #Javascript
Vue2.0基于vue-cli+webpack Vuex的用法(实例讲解)
Sep 15 #Javascript
angular4模块中给标签添加背景图的实现方法
Sep 15 #Javascript
基于Vue生产环境部署详解
Sep 15 #Javascript
You might like
PHP-Java-Bridge使用笔记
2014/09/22 PHP
PHP explode()函数的几个应用和implode()函数有什么区别
2015/11/05 PHP
PHP利用缓存处理用户注册时的邮箱验证,成功后用户数据存入数据库操作示例
2019/12/31 PHP
jquery $.each()使用探讨
2013/09/23 Javascript
jQuery实现HTML5 placeholder效果实例
2014/12/09 Javascript
jquery判断对象是否为空并遍历对象的简单实例
2016/07/26 Javascript
ionic实现带字的toggle滑动组件
2016/08/27 Javascript
js 将input框中的输入自动转化成半角大写(税号输入框)
2017/02/16 Javascript
Vuex和前端缓存的整合策略详解
2017/05/09 Javascript
基于javaScript的this指向总结
2017/07/22 Javascript
form表单序列化详解(推荐)
2017/08/15 Javascript
Vue.js实现按钮的动态绑定效果及实现代码
2017/08/21 Javascript
解决vue select当前value没有更新到vue对象属性的问题
2018/08/30 Javascript
vuejs+element UI点击编辑表格某一行时获取内容填入表单的示例
2018/10/31 Javascript
微信小程序实现底部导航
2018/11/05 Javascript
jQuery实现的模仿雨滴下落动画效果
2018/12/11 jQuery
JS实现json数组排序操作实例分析
2019/10/28 Javascript
Python中使用Inotify监控文件实例
2015/02/14 Python
python实现通过pil模块对图片格式进行转换的方法
2015/03/24 Python
各个系统下的Python解释器相关安装方法
2015/10/12 Python
一篇文章快速了解Python的GIL
2018/01/12 Python
python实现单向链表详解
2018/02/08 Python
pytorch 把MNIST数据集转换成图片和txt的方法
2018/05/20 Python
对python中的float除法和整除法的实例详解
2019/07/20 Python
Django文件上传与下载(FileFlid)
2019/10/06 Python
python实现飞船大战
2020/04/24 Python
移动HTML5前端框架—MUI的使用
2017/12/18 HTML / CSS
意大利和国际最佳时尚品牌:Drestige
2019/12/28 全球购物
函授毕业生的自我鉴定
2013/11/26 职场文书
节水口号标语
2014/06/19 职场文书
调研汇报材料范文
2014/08/17 职场文书
党的群众路线教育实践活动剖析材料
2014/09/30 职场文书
感谢信怎么写
2015/01/21 职场文书
初中教师个人总结
2015/02/10 职场文书
漫改真人电影「萌系男友是燃燃的橘色」公开先导视觉图
2022/03/21 日漫
SQL Server数据库查询出现阻塞之性能调优
2022/04/10 SQL Server