前言
- 【音乐博客】上线啦!
- 该动态路由已集成到【音乐博客】中,如有需要,可查看permission文件、vuex的store文件、router路由
- 如果在面试中被面试官问到你项目中有什么亮点?
- 你还在尴尬的低头说:就普通的增删改查显示页面吗?
- 下面为大家展示在项目中使用动态路由成为一个小亮点!
此图是周末和女友逛K11觉得挺好看的,特意分享给各位猿友欣赏,希望我们可以玩转路由像小女孩一样,自由飞翔
动态路由
为什么使用动态路由?
很多时候我们在项目的路由都是在前端配置好的
但是有的时候为了进行全面的权限控制,会需要后台给出路由表,前端再渲染。不用在前端配置。 比如:根据用户的角色,登录进来显示不同的菜单
思路整理
菜单中有个角色管理,在页面上添加角色对应要显示的菜单,相当于把前端配置的路由表数据给后端存在数据库
拿到数据需要我们自己再处理
- 路由中的
component
后台是给不了的,这里我们只需要后台按照我们提供的前端component
路径给数据,因为后端返回的component是字符串路径,而前端需要的是一个组件对象,写个方法循环加载,将字符串转换为组件对象
const loadViewsd = (view: any) => { return (resolve: any) => require( [`@/views/${view}.vue`], resolve )}
- 这样我们就拿到了最重要的数据,即
component
- 路由中的
利用
vuerouter的beforeEacg、addRoutes
来配合上边两步实现效果把后台提供的数据处理成我们需要的路由表
添加到路由中
Router.addRoutes(路由数据)
大体步骤:
1.后端返回一个json格式的路由表
2.因为后端传回来的是都是字符串格式的,但前端这里需要的是一个组件对象,写个方法遍历一下,将字符串转换为组件对象
3.利用vue-router的beforeEach、addRoutes、vuex来配合上边两步实现效果
4.左侧菜单拦截根据拿到转换好的路由列表进行展示
拦截路由 -> 后端取到路由 -> 保存路由到vuex(用户登录进来只会从后端取一次,其余都从本地取,所以用户,只有退出在登录路由才会更新)
代码实现
新建一个router.js
里面就是我们的路由:每个路由都使用到组件Layout
,这个组件是整体的页面布局:左侧菜单列,右侧页面,所以children下边的第一级路由就是你自己的开发的页面,meta里包含着路由的名字,以及路由对应的icon;
因为可能会有多级菜单,所以会出现children下边嵌套children的情况;
路由是数组格式
export const asyncRoutes = [
{
path: '/permission',
// // component: Layout,
componentUrl: 'Layout',
redirect: '/permission/directive',
meta: {
title: 'permission',
icon: 'lock',
alwaysShow: true // will always show the root menu
}
},
{
path: '/sys',
// // component: Layout,
componentUrl: 'Layout',
meta: {
title: 'sys',
icon: 'example',
roles: ['admin'], // you can set roles in root nav
alwaysShow: true, // will always show the root menu
noCache: true
},
children: [
{
path: 'sys-user',
// // component: () => import(/* webpackChunkName: "sys-user" */ '@/views/sys/sys-user.vue'),
componentUrl: 'sys/sys-user',
name: 'sysUser',
meta: { title: 'sysUser', noCache: true }
},
{
path: 'sys-menu',
// // component: () => import(/* webpackChunkName: "sys-menu" */ '@/views/sys/sys-menu.vue'),
componentUrl: 'sys/sys-menu',
name: 'sysMenu',
meta: { title: 'sysMenu', noCache: true }
},
{
path: 'sys-role',
// // component: () => import(/* webpackChunkName: "sys-role" */ '@/views/sys/sys-role.vue'),
componentUrl: 'sys/sys-role',
name: 'sysRole',
meta: { title: 'sysRole', noCache: true }
}
]
}
]
基本路由表已经建立好了
我们在什么时候进行获取完整的路由表数据
这个时候我们就要想到路由钩子函数,当然是Router.beforeEach
中做
router.beforeEach(async (to: Route, _: Route, next: any) => {
// Start progress bar
NProgress.start()
// Determine whether the user has logged in
if (UserModule.token) {
if (to.path === '/login') {
next({ path: '/' })
NProgress.done()
} else {
// 刚开始登录第一次进来,则进来
if (UserModule.roles.length === 0) {
try {
// Note: roles must be a object array! such as: ['admin'] or ['developer', 'editor']
await UserModule.GetUserInfo() // 获取用户角色
const roles = UserModule.roles
// Generate accessible routes map based on role
PermissionModule.GenerateRoutes(roles) // 将路由渲染到我们菜单上
// 若为空,则在去数据库读取渲染到菜单上
if (PermissionModule.dynamicRoutes.length === 0) {
const { data } = await getSysRole({
page: 1,
limit: 8,
roleKey: roles[0]
})
PermissionModule.dynamicRoutes = filterAsyncRouter(data.items[0].routes)
}
router.addRoutes(PermissionModule.dynamicRoutes) // 动态添加路由
next({ ...to, replace: true })
} catch (err) {
// Remove token and redirect to login page
UserModule.ResetToken()
Message.error(err || 'Has Error')
next(`/login?redirect=${to.path}`)
NProgress.done()
}
} else {
next()
}
}
} else {}
})
接下来我们在看看上述的PermissionModule.GenerateRoutes(roles)
方法
这个方法是写在我们的vuex.store
里面
@Action
public async GenerateRoutes(roles: string[]) {
let accessedRoutes
if (roles.includes('admin')) {
accessedRoutes = adminRoutes //就是我们上面定义的router.js
} else {
// 非管理员就后端读取数据库拿到用户的角色菜单
const {data} = await getSysRole({
page: 1,
limit: 8,
roleKey: roles[0]
})
accessedRoutes = filterAsyncRouter(data.items[0].routes)
}
this.SET_ROUTES(accessedRoutes)
}
//遍历后台传来的路由字符串,转换为组件对象
export const filterAsyncRouter = (asyncRouterMap: any) =>{
const accessedRouters = asyncRouterMap.filter((route: any) => {
if (route.componentUrl) {
if (route.componentUrl === 'Layout') {//Layout组件特殊处理
route.component = Layout
delete route.componentUrl // 这里有个坑,赋完值记得手动删除,因为打包编译失败,因为route里没有这个属性
} else {
route.component = loadViewsd(route.componentUrl)
delete route.componentUrl
}
}
if (route.children && route.children.length) {
route.children = filterAsyncRouter(route.children)
}
return route
})
//全不匹配的情况下,返回404,路由按顺序从上到下,依次匹配。最后一个*能匹配全部,
accessedRouters.push({
path: '*',
redirect: '/404',
meta: { hidden: true }
})
return accessedRouters
}
// 字符串路径转为组件对象
// 将后端传回的"componentUrl": "Layout", 转为"component": Layout组件对象
export const loadViewsd = (view: any) => {
return (resolve: any) => require([`@/views/${view}.vue`], resolve)
}
动态路由遇到的坑
打包编译失败
项目开发dev中,动态路由显示正常,而到了打包build的时候,发现打包报错-serve:'vue-cli-service serve'
思考
一眼看过去以为是node-modules出了问题,结果重装了一下,结果无效
解决方案
这个报错一定是整体配置文件出了问题,检查一下vue.config.js文件是不是加了什么,可以注释掉试试看,因为我github上有build成功的这个项目;
所以我拉下来对比了之后,发现果然是动态路由这边的问题,可以看到下面是用了ts的定义类型,我们的路由定义了RouteConfig,而在vue-router源码中是没有componentUrl这个属性,然后我在开发的时候手动添加了,开发的时候是可以跑成功,但是打包的时候是编译失败的,因为他vue-router源码中是没有componentUrl这个属性;
后面我是怎么解决这个问题呢,我重新复制一份这个router.js文件,类型定义为any类型,就解决了(这个build err真的找了很久,没想到之前不小心改了他源码,感动啊终于找到问题所在)
import Router, { RouteConfig } from 'vue-router'
export const asyncRoutes: RouteConfig[] = [
{
path: '/permission',
component: Layout,
componentUrl: 'Layout',
redirect: '/permission/directive',
meta: {
title: 'permission',
icon: 'lock',
alwaysShow: true // will always show the root menu
},
children: [
{
path: 'page',
component: () => import(/* webpackChunkName: "permission-page" */ '@/views/permission/page.vue'),
componentUrl: 'permission/page',
name: 'PagePermission',
meta: {
title: 'pagePermission'
}
}
]
}
]
动态路由加载报错
解决方案
在上面的filterAsyncRouter
方法中将字符串转组件对象,component
赋完值之后,要顺手把componentUrl
给删除掉,和route类型有关,反正后续也不需要删除多余属性比较好
原文链接
参考文档
Vue 动态路由的实现(后台传递路由,前端拿到并生成侧边栏)
本文使用 mdnice 排版