之前在⼀⽂中提到路由权限的实现思路,因为不喜欢在每次路由跳转的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); //存菜单
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貌似⾃带惰性效果,可以先注册后实现,具体原因我也不太明⽩,如过有⼤⽜路过,希望能留下答案。以上就是本⽂的全部内容,希望对⼤家的学习有所帮助,也希望⼤家多多⽀持。
因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- igbc.cn 版权所有 湘ICP备2023023988号-5
违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务