前言
最近做后台管理系统的时候需要根据用户权限显示菜单栏,即需要动态添加路由控制,实现过程中发现Vue-router的坑和填坑的过程;
以下是手动添加路由,也可通过后端数据获取动态路由配置,具体实现流程如下:
Router
// 默认路由数组
export const constantRouterMap = [
{
path: '/',
component: () => import('@/views/home'),
meta: { name: '首页', hideBackBtn: true },
children: []
},
{
path: '/login',
name: '/login',
component: () => import('@/views/login')
}
]
// Admin权限路由
export const AdminAuthRoute = {
path: '/home',
redirect: '/userList', // 默认打开用户管理tab
component: () => import('@/views/home'),
children: [
{
path: '/userList',
name: '/userList',
component: () => import('@/views/userList/index'),
},
{
path: '/policyManager',
name: '/policyManager',
component: () => import('@/views/policyManager/index'),
}
]
}
// 其他权限路由
export const OtherAuthRoute = {
path: '/home',
redirect: '/userList', // 默认打开用户管理tab
component: () => import('@/views/home'),
children: [
{
path: '/userList',
name: '/userList',
component: () => import('@/views/userList/index'),
},
]
}
export default new Router({
routes: constantRouterMap
})
Home(一般也叫Layout)页面
//内部也包含<router-view />,其他样式代码不粘贴了,不是重点
<el-main>
<router-view />
</el-main>
以上代码表示,默认导出path有‘/login’和‘/’,Admin权限是home为页面菜单栏有两个页面选项,Other权限则是home为页面菜单栏有一个页面选项。
router.beforeEach
// 路由拦截器
router.beforeEach(async(to, from, next) => {
// 未登录
if (to.path === Paths.loginPath) {
return next()
} else {
if (!store.getters.token) {
return next({ path: Paths.loginPath })
}
}
// 不需要重复刷新userInfo
if (store.getters.userInfo && Object.keys(store.getters.addRouterObj).length) {
if (to.path === '/') {
return next({ path: Paths.homePath, replace: true })
} else {
return next()
}
}
try {
let data = await store.dispatch('requestUserInfo')
await store.dispatch('handleRoutes', data.type)
// 此处如果直接用next()的话,在添加的路由中刷新页面会出现页面空白,此处后面会分析
return next({ path: to.path })
} catch (err) {
// 获取个人信息失败,清空数据,重新登录
store.commit('resetUser')
return next({ path: to.path })
}
})
store->permission
// store/permission.js
import router from '@/router/index'
import {
AdminAuthRoute,
OtherAuthRoute
} from '@/router'
const permission = {
state: {
addRouterObj: {}
},
mutations: {
SET_ROUTERS: (state, obj) => {
state.addRouterObj = obj
}
},
actions: {
// 账户类型 type :1:admin 2:other
handleRoutes({ commit }, type) {
return new Promise(resolve => {
let addRoutes = {}
if (type === 1) {
addRoutes = AdminAuthRoute
} else {
addRoutes = OtherAuthRoute
}
commit('SET_ROUTERS', addRoutes)
if (Object.keys(addRoutes).length) {
router.addRoute(addRoutes)
}
console.log(router.getRoutes())
resolve()
})
}
}
}
export default permission
以上代码有几种判断
-
首先判断登录状态,比较好理解
-
store中有值的时候,不需要再次请求UserInfo,正常可以直接next(),以防用户在地址栏直接删除path去到“/”,转接一下回到"/home"
-
若store中没有权限参数或者已添加路由对象,则需要请求UserInfo,然后permission根据权限添加路由,最后
next()即可
遇到的坑
1.addRoute()无法覆盖相同name的路由
router.vuejs.org/zh/api/#rou…,(文档:如果该路由规则有 name,并且已经存在一个与之相同的名字,则会覆盖它)Vue-router3.x也没有removeRoute的api提供;
以上的例子,用户登录Admin权限的账号,返回登录页,重新登录Other权限的账号,此时菜单栏虽然只显示了一个菜单,但是仍可以通过浏览器输入Admin权限的路由地址进入页面。以下是我的解决方法:
//登出或者401的时候可以通过刷新页面的方式回到登录页,且将router已添加的路由删除掉
location.reload()
tips:可以通过router.getRoutes()查看当前路由记录,查看log就知道是否覆盖的情况
2.登录成功后,$router.push('/'),会报如下错误
Uncaught (in promise) Error: Redirected when going from "/login" to "/" via a navigation guard.
github.com/vuejs/vue-r…,这个issue中也有提及,router.push 后beforeEach 里next修改了路由就会报错,但是不影响跳转,不过也做了处理去catch报错
this.$router.push({ path: '/' }).catch(() => {})
3.addRoute刷新页面出现空白
// 路由拦截器
router.beforeEach(async(to, from, next) => {
//前置的代码,跟上面一样
...
try {
let data = await store.dispatch('requestUserInfo')
await store.dispatch('handleRoutes', data.type)
return next({ path: to.path })
// return next()
} catch (err) {
// 获取个人信息失败,清空数据,重新登录
store.commit('resetUser')
return next({ path: to.path })
}
})
addRoutes()后,路由还没有被加进去; next({ ...to, replace: true }),进入下一轮beforeEach,此时,路由已经加进去,只要逻辑正确,就可以next() 重点在于要理解next:next() 表示放行,让路由跳转继续;next({path:xxx}) ,表示被拦截,因此会再次进入beforeEach,此时to.path===xx
4.AddRoutes替换为AddRoute
addRoutes(routes: RouteConfig[]): void
addRoute(parent: string, route: RouteConfig): void
addRoute(route: RouteConfig): void
源码解释得比较直接,一个是添加RouteConfig数组,一个是添加RouteConfig对象,还多了个父路由的参数的api选择