简单记录一下 Vue 3 + Vue-Router 4 实现动态路由
简单记录一下碰到的问题,以及实现方式。
该项目前端写好静态路由,通过router.name
标志路由显示。
后端登陆之后回传权限列表permissionList
,前端通过判断路由的name
属性是否在permissionList
之中来控制具体该用户可以看到哪些路由。
坑
- Vue-Router 4 中
addRoute
之后,路由并不会立即生效,地址栏输入路由,会报错No match found for location with path
,然后页面白屏。查阅官方文档,发现问题,addRoute
之后需要手动调用router.push
新增的路由才会生效。API 参考 | Vue Router (vuejs.org) 提示 请注意,添加路由并不会触发新的导航。也就是说,除非触发新的导航,否则不会显示所添加的路由。 解决方案:
// 在路由守卫中,addRoute之后需要做一下判断 if (!to.matched.length) { router.push({ path: to.path, query: to.query //防止刷新页面query丢失 }) }
- 登录之后跳转,调用登录接口之后使用
router.push
,此时依然直接报错No match found for location with path
,并且不会触发路由守卫中的事件。通过router.getRoutes()
发现此时的路由还是写好的静态路由,所以需要手动触及addRoute
解决方案:import { generateRouter } from '@/utils/generateRoute'; const onFinish = async (values: FormState) => { try { const res = await userStore.login(values); notification['success']({ message: '登录成功', description: '正在跳转' }) handleJump() btnLoading.value = false } catch (err: any) { console.log(err.message) if (~err.message.indexOf('No match for')) { // 这里需要主动触发一下更新路由 await generateRouter(router) handleJump() } btnLoading.value = false } } const handleJump = () => { const { redirect, ...othersQuery } = router.currentRoute.value.query; router.push({ name: (redirect as string) || 'HOME_PAGE', query: { ...othersQuery, }, }) }
具体代码实现
utils/lib/permission.ts
import { set, get } from './storage'
const KEY = 'PERMISSION_LIST';
const setPermission = (list: string[]) => {
set(KEY, JSON.stringify(list))
}
const getPermission = () => {
let v = get(KEY)
return v ? JSON.parse(v) : []
}
export { setPermission, getPermission };
utils/lib/generateRoute.ts
import { getPermission } from '@/utils/permission'
import { asyncRouterMap } from '@/router/route'
import type { Router, RouteRecordRaw } from 'vue-router'
const hasPermission = (route: any) => {
const permissionList = getPermission()
if (route.meta && !route.meta.hideInMenu) {
// 判断route的name值是否存在于permissionList
return permissionList.includes(route.name)
}
return true
}
export const filterAsyncRouter = (routerMap: any) => {
const accessedRouters = routerMap.filter((route: any) => {
if (hasPermission(route)) {
if (route.children && route.children.length) {
route.children = filterAsyncRouter(route.children)
}
return true
}
return false
})
return accessedRouters
}
export const generateRouter = (router: Router) => {
return new Promise(resolve => {
const validRouter = filterAsyncRouter(asyncRouterMap)
validRouter.forEach((item: RouteRecordRaw) => {
router.addRoute(item)
})
resolve(true)
})
}
router/permission.ts
该文件是路由守卫
import { nextTick } from 'vue';
import router from '.'
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
import { canTurnTo, setTitle } from '@/utils/lib';
import { getToken, isLogin } from '@/utils/auth';
import type { LocationQueryRaw } from 'vue-router';
import { constantRouterMap } from './route'
import { generateRouter } from '@/utils/generateRoute';
const LOGIN_PAGE_NAME = "login"
const HOME_PAGE_NAME = 'ROLE_PROD_MANAGE'
router.beforeEach(async (to, from, next) => {
NProgress.start()
const token = getToken();
// const userStore = useUserStore();
if (token) {
await generateRouter(router)
if (to.name === LOGIN_PAGE_NAME) {
next({
name: HOME_PAGE_NAME // 跳转到homeName页
});
NProgress.done()
} else {
if (isLogin()) {
// turnTo(to, userStore.access, next);
// next({ ...to, replace: true })
// 解决vue-router4 addRoute找不到路由问题
if (!to.matched.length) {
router.push(to.path)
}
next()
NProgress.done()
} else {
next({
name: LOGIN_PAGE_NAME,
query: {
redirect: to.name,
...to.query,
} as LocationQueryRaw,
});
NProgress.done()
}
}
} else {
// 未登录
if (to.name === LOGIN_PAGE_NAME) {
// 去登录页面
next();
NProgress.done()
} else {
next({
name: LOGIN_PAGE_NAME // 跳转到登录页
});
NProgress.done()
}
}
})
router.afterEach(to => {
setTitle(to)
window.scrollTo({ top: 0, behavior: 'smooth' });
nextTick(() => {
const content = document.querySelector('.ant-layout-content');
content && content.scrollTo({ top: 0, behavior: 'smooth' });
});
});
转载自:https://juejin.cn/post/7091359801690554405