现在写后台管理都是直接CV之前的模版,最近正好在看vue-admin的后台项目,干脆模仿着重新搭一个,可能太久没写过这么多bug了吧,所以它想让我重新怀念一下,结果可直接跳到最后查看(我没有后台接口,也懒得模拟了,所以直接全写的死数据)
一开始的路由拦截如下:
if (to.path === '/login') {
next({ path: '/' })
} else {
// permission/generateRoutes里面我遍历生成了自己想要的目标树,大家根据自己的项目需求进行编写即可
const accessRoutes = await store.dispatch(
'permission/generateRoutes',
)
router.addRoutes(accessRoutes)
next({ ...to, replace: true })
}
后来爆了个死循环,怀疑是没匹配到将要跳转到路由然后一直去拦截匹配造成的然后将最后一行直接改为next()排除是否是此原因
...
next()
ok,死循环消失,那就应该是这行的问题了,不过发现出现警告️vue-router.esm.js?8c4f:16 [vue-router] router.addRoutes() is deprecated and has been removed in Vue Router 4. Use router.addRoute() instead.还有重复命名路由
再次修改为如下:
const accessRoutes = await store.dispatch(
'permission/generateRoutes',
)
console.log(accessRoutes);
router.addRoute(accessRoutes);
router.options.isAddAsyncMenuData=true;
next()
结果,哎嘛不警告了直接报错,报错内容:[vue-router] "path" is required in a route configuration.
然后去看了下文档
后将代码改为:
const accessRoutes = await store.dispatch(
'permission/generateRoutes',
)
for(let i=0,length=accessRoutes.length;i<length;i+=1){
const element=accessRoutes[i]
router.addRoute(element)
}
router.options.isAddAsyncMenuData=true;
// 如果 addRoutes 并未完成,路由守卫会一层一层的执行执行,直到 addRoutes 完成,找到对应的路由
next({ ...to, replace: true })
此时死循环依然存在,后来发现这块的逻辑判断写的有问题,会一直添加一直循环,to就会一直匹配。
此处说一下我们有时候会看到类似这种next()、next('/login') 、 next(to) 或者 next({ ...to, replace: true }),这几者有什么不同呢?
其实这些就只有next()是放行通过,其余都是中断当前导航,执行新的导航。记住,只有next()是通过!只有next()是通过!只有next()是通过!其他都是中断,然后执行新导航,可能有小伙伴会反驳那我平时写类似于next('/home')也会跳转啊,你骗人呢吧。乖乖,如果跳转了可能是你外面又加了层其他判断。举例:
如果你直接在拦截器里面写next('/login'),
beforeEach((to, from, next) => {
next('/login')
}
它并不会跳转而是会一直循环下去,而是相当于
beforeEach((to, from, next) => {
beforeEach(('/login', from, next) => {
beforeEach(('/login', from, next) => {
beforeEach(('/login', from, next) => {
beforeEac... // 一直循环下去...... , 因为我们没有使用 next() 放行
}
}
}
}
再举例,如果我们想去往home首页的话就去往login,非home首页可放行
beforeEach((to, from, next) => {
if(to.path === '/home') {
next('/login')
} else {
// 如果要去的地方不是 /home , 就放行
next()
}
}
那这个有什么说法呢?
我本来要去/home路由,因此执行了第一次 beforeEach((to, from, next),但是这个路由守卫中判断了如果要去的地方是'/home',就执行next('/login'),路由守卫再拦截,路径是什么?是home首页吗?不是,是login,ok,放行,你可以走了。
再说说为什么用next({ ...to, replace: true })。
在addRoute()之后第一次访问被添加的路由会白屏,这是因为刚刚addRoute()就立刻访问被添加的路由,然而此时addRoute()没有执行结束,我这个钱还没打过来还在路上你就问我借钱我没有你懵不懵我自己个儿都懵对吧,因而找不到刚刚被添加的路由会导致白屏。因此需要从新访问一次路由才行。
此时就要使用next({ ...to, replace: true })来确保addRoute()时动态添加的路由已经被完全加载上去。
replace: true只是一个设置信息,告诉VUE本次操作后,不能通过浏览器后退按钮,返回前一个路由。当然如果你有大无畏精神你可以不写,而是只写next({ ...to }),当然你也要做好用户在addRoutes()还没有完成的时候,点击浏览器回退按钮的准备了。
它其实就是用to去匹配找对应的路由,如果没找到就再执行一次beforeEach((to, from, next)直到其中的next({ ...to})能找到对应的路由为止。
也就是说此时addRoute()已经完成,我们去找对应路由,找到对应路由后,接下来将执行前往对应路由的路由拦截,因此需要用代码来判断这一次是否就是前往对应路由的beforeEach((to, from, next),如果是,就执行next()放行。如果没有找到对应的呢?死循环呗!所以,老天爷啊你不光要正确的把路由加上,你还得给它留个正确的出口啊乖乖
ok,下面是我最终写好的路由拦截
// 存在 token 说明已经登录(其实我没有接口去请求,但是为了模版的完整性防止之后正常运用的项目中还要再修改,我本地写了个死的token值)
if (getToken()) {
// 登录过就不用再访问登录界面,去往主页即可
if (to.path === '/login') {
next({ path: '/' })
}
// 保存在store中路由不为空则放行 (如果执行了刷新操作,则 store 里的路由为空,此时需要重新添加路由)
if (store.getters.permission_routes.length || to.name != null) {
//放行
next()
} else {
const accessRoutes = await store.dispatch(
'permission/generateRoutes',
)
for(let i=0,length=accessRoutes.length;i<length;i+=1){
const element=accessRoutes[i]
router.addRoute(element)
}
router.options.isAddAsyncMenuData=true;
// 如果 addRoutes 并未完成,路由守卫会一层一层的执行执行,直到 addRoutes 完成,找到对应的路由
next({ ...to, replace: true })
}
} else {
// 未登录时,whiteList是我设置的免登录白名单就是不登录没有token也能进入
if (whiteList.indexOf(to.path) !== -1) {
// 在免登录白名单中,直接进入
next()
} else {
// 其他没有访问权限的页面将重定向到登录页面
next(`/login?redirect=${to.path}`)
}
}