赞
踩
以前在公司一直使用的都是若依框架,所以对一些内容掌握的并不是很好,所以趁着在家的这段时间,自己写一个管理系统,那么就涉及到了动态路由,对不同权限的用户,展示不同菜单栏和向router中追加与该用户权限匹配的路由。
如果直接将路由表写死的话,那么在用户未登录的情况下,用户可以直接通过手动输入 url 达到目标页面。
当用户登录后,我们拿着后端返回的路由列表,去匹配本地动态路由列表中的路由 -> 路由对比
。
我们应该先写一份 公共的路由
,这个公共的路由可以在未登录的情况下访问,例如:Layout布局、登录页、404页。
并且应该将这个公共路由表,添加到路由初始化时的 routes 属性中。
然后再将需要权限才能访问的路由写到一个数组中,这个数组就是本地的动态路由表,后续需要与后端返回的路由进行对比。
import { createRouter, Router, createWebHashHistory, RouteRecordRaw } from 'vue-router'; import Layout from '@/layout/index.vue'; //Layout布局 // 公共路由表 const constantRoutes: RouteRecordRaw[] = [ { path: '/', name: 'Layout', component: Layout, }, { path: '/login', name: 'Login', component: () => import('@/view/login.vue'), meta: { title: '登录页' } }, { path: '/:pathMatch(.*)*', name: 'NotFound', component: () => import('@/view/404.vue'), meta: { title: '页面丢失了~' } } ]; // 动态路由表 const asyncRoutes: RouteRecordRaw[] = [ { path: '/', name: '/', component: () => import('@/view/index.vue'), meta: { title: '主控台' } }, { path: '/goods/list', name: '/goods/list', component: () => import('@/view/commodity/commodity.vue'), meta: { title: '商品管理' } } ]; // 创建路由,并导出实例 export const router: Router = createRouter({ history: createWebHashHistory(), routes: constantRoutes });
还差一个路由对比的功能,我们可以在上面的文件中,在写一个添加路由的函数,在里面实现路由对比,并且将它暴露出去。
// 参数:接收后端给的用户可访问的路由列表。 export function addRouters(menus: any[]){ // 功能:路由对比,将匹配的添加到路由 const routeComparison = (menus: any[])=>{ // 遍历后端返回的路由列表 menus.forEach((menuItem: any)=>{ // 判断当前遍历的这个路由,在本地动态路由列表是否存在,frontpath相当于本地的path const isMatching = asyncRoutes.find((asyncItem: any)=>menuItem.frontpath === asyncItem.path); // 如果匹配到了,则逻辑与一下:它是否已经被注册过了,如果没有被注册过我们在添加进去。 if(isMatching && !router.hasRoute(isMatching.path)){ // 将匹配出来的路由,添加到Layout布局路由的childrens中。 router.addRoute('Layout', isMatching); } // 判断当前遍历的这个路由是否有子节点,如果有子节点且子节点长度大于0,则进行递归,使用子节点进行对比。 if(mentItem.child && mentItem.child.length > 0){ routeComparison(mentItem.child); } }); } // 调用路由对比 routeComparison(menus); }
这个时候,我们可以完善一下全局前置路由守卫的代码,在src下新建:premission.ts文件,并在main.ts中引入。
import store from '@/store'; import { router, addRouters } from '@/router'; // 从Cookies中(获取|删除)Token。 import { getToken, removeToken } from '@/composables/auth'; // 是否获取了用户信息 const hasGetUserInfo = false; // 全局路由前置守卫 router.beforeEach(async (to, from, next)=>{ // 获取Token const token = getToken(); // 如果没有Token,并且访问的不是登录页,直接重定向到登录页,这里就会防止用户未登录直接进入Layout布局界面。 if(!token && to.path !== '/login'){ return next('/login'); } // 如果有Token,但是访问的是登录页 if(token && to.path === '/login'){ // 从哪里来的,回哪里去 return next(from.path); } // 如果有Token,并且没有获取用户信息呢 if(token && !hasGetUserInfo){ // 拉取用户信息去,如果token过期或者被非法篡改,会在axios的拦截器中进行处理。 const getInfoRes = await store.dispatch("getInfo"); // 进行追加路由 addRouters(getInfoRes.menus); // 将hasGetUserInfo置true hasGetUserInfo = true; } // 在最后必须要放行 next(); });
在登录之后服务器会返回Token,然后将Token存储到cookies中,然后调用 router.push('/')
打算跳转到首页,但是会触发全局路由前置守卫,接着会同步获取用户信息,然后追加路由。
这样动态路由就添加完了,但是存在着一个问题,下面进行问题复现:
router.getRoutes()
方法,又可以看到已经将路由追加进去了。按照正常的代码逻辑来看,会执行以下三步:获取用户信息、追加路由、放行路由
。
首先我们将404这个路由项注释掉,并且在beforeEach()回调函数里的第一行打上断点,输入 debugger;
即可。
router.beforeEach((to, from, next)=>{
debugger;
//.......
});
在刷新时观察控制台,可以看到给我们抛出了一个警告,意思为:没有找到与路径对应的位置。
[Vue Router warn]: No match found for location with path "刷新的那个动态路由的path"
由此得知,在进入beforeEach()中的回调函数前,就已经出现问题了。
我们可以尝试将 debugger;
删除,在相同位置打印一下回调函数中的to属性。
console.log(to);
// 结果
{
"fullPath": "/goods/list",
"path": "/goods/list",
"query": {},
"hash": "",
"params": {},
"matched": [],
"meta": {},
"href": "#/goods/list"
}
可以看到matched
数组,是一个空的,代表着没有匹配到相关的路由。如果将 constRoutes 中的 404 规则注释删掉,那么这里的 matched
就会只有一个元素,就是404路由。
<router-view />
会渲染 matched
上的内容,我们的项目中共有两个 router-view
,在 App.vue 和 Layout 布局各有一个。
matched
数组中有两个元素,第一个元素是 Layout 路由,第二个是 /goods/list 路由。matched
元素越靠前,使用的 router-view 就越靠外层。其实这一切都与 to
有关,我们在刷新之后,触发了全局前置路由守卫,然后会调用它里面的回调函数。
那么在触发 beforeEach 的回调函数时,vueRouter 需要给 matched 设置匹配的路由,如果没有设置404页,那么这个数组它是空的,就会发出一个警告,既然是空的,那么没内容可以给 router-view 渲染,然后就会出现一个空白页。
如果有404页,在动态路由添加之前,输入一个不存在的地址,matched
数组的元素肯定是一个404,所以就会让router-view渲染404页呗~
总而言之,这个to,是动态路由没有追加进来时的to,所以才会这样。
首先我们要知道:在全局前置守卫中,next()、next('/')
两个的区别。
在全局前置守卫中,调用 next() 代表着放行的意思,而调用 next(‘/’) 代表着重定向的意思。
next('/')会中断此导航,并重新触发路由守卫,而next()就不会,它就单纯的放行。
所以!!!!我们可以在追加路由后,重新触发一次路由守卫,而不是直接放行,这样就能解决问题了。
if(token && !hasGetUserInfo){
// 拉取用户信息去,如果token过期或者被非法篡改,会在axios的拦截器中进行处理。
const getInfoRes = await store.dispatch("getInfo");
// 进行追加路由
addRouters(getInfoRes.menus);
// 将hasGetUserInfo置true
hasGetUserInfo = true;
// 当添加完成后,直接进行一次重定向
return next(to.path);
}
// 一定要放行
next();
Ok!!问题解决~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。