实现动态路由有两种方式,一种是后端返回什么,前端就展示什么,另一种是后端只返回角色,前端根据角色拼接数据信息展示。相比第一种方式,第二种方式在企业中更常用
第一种方式:
(一)后端需返回类似Vue-router形式的json文件,如
[
{path:'/userlist',name:'userlist',title:'用户列表',component:'Userlist.vue'},
{path:'/role',name:'role',title:'角色管理',component:'Role.vue'},
//...
]
(二)构建基础路由
const routes = [
{
path: '/',
name: 'Home',
component: ()=>import('@/xx/xx/Home.vue')
},
{
path: '/login',
name: 'login',
component: ()=>import('@/xx/xx/Login.vue')
}
//...
]
(三)配置路由守卫beforeEach
router.beforeEach(async (to, from, next) => {
// 页面刷新 发送请求,获取路由数据
//缓存路由 vuex
if (store && store.state.nav.length == 0) {
// 没有缓存路由,发送请求
let res = await xx();
// 将路由存放到vuex中
store.dispatch('SETNAV', res.data.result);
//调用addDynamicRoutes方法,动态路由生成
let dynamicRoutes = addDynamicRoutes(res.data.result);
router.addRoutes(dynamicRoutes);//动态路由数据添加
next({...to});
} else {
next();
}
})
(四)addDynamicRoutes方法
//路由动态生成(后端返回什么,就展示什么)
function addDynamicRoutes(res){
res.forEach(v=>{
routes.push({
path:v.path,
name:v.name,
meta:{title:v.title},
component:()=>import('@/xx/xx' + v.component)
})
});
return routes;
}
(五)store里面的缓存
state: {
nav: [],
},
getters: {
navData: state => state.nav
},
mutations: {
SETNAV(state,data){
state.nav = data;
}
},
actions: {
SETNAV(content,data){
content.commit('SETNAV',data);
}
},
注:前端页面的命名应与后端返回的component中的一致
(一)构建基础路由(router.config.js)
export const constantRouterMap = [{
path:'/',
component:()=>import('@/layout/Userlayout'),
redirect:'/login',
hidden:true,
children:[{
path:'login',
name:'login',
component:()=>import('login.vue')
}]
},{
path:'/404',
component:()=>import('404.vue')
},
//...
]
(二)将基础路由放到router.js文件下
import { constantRouterMap } from './router.config';
const routes = constantRouterMap;
(三)配置路由守卫,在跳转之前进行拦截(permission.js)
//免登录名单
const whiteList = ['login','register',...];
router.beforeEach((to,from,next)=>{
//动态生成网站标题
to.meta && (typeof to.meta.title !== 'undefined' && setDocumentTitle(`${to.meta.title} - ${domTitle}`))
//判断是否有access_token
if(Vue.ls.get(ACCESS_TOKEN)){
//有token,并且是登录页
if(to.path === 'login'){
next({
path:'/'
})
}else{
//判断vuex中是否有用户信息
//Object.keys把对象的keys值放进数组里
if(!store.getters.userInfo || Object.keys(store.getters.userInfo).length === 0){
// 没有用户信息
//1.获取用户信息
store.dispatch('GetInfo')
.then(()=>{
//拿到后端放回的用户信息里面包含了roles相关信息
const roles = res.data && res.data.roles;
//2.根据roles权限生成可访问的路由表
store.dispatch('GenerateRoutes',roles)
.then(()=>{
// 把最终生成vue-router的路由表放进vue router中
router.addRoutes(store.getters.addRoutes)
// 请求带有redirect重定向时,自动登录重定向到该地址
// decodeURIComponent将地址路径解析
const redirect = decodeURIComponent(from.query.redirect || to.path)
if(to.path === redirect){
// 确保addRoutes已完成
next({
...to,
replace:true
})
}else{
// 跳转到目的路由
next({
path:redirect
})
}
})
})
.catch(()=>{
// 1.适用框架中的提示框,提示用户信息没找到
// ...
//2.调用接口,退出登录
store.dispatch('logout')
.then(()=>{
next({
path:'/login',
query:{
redirect: to.fullPath //把跳转的path地址作为参数传到下一个页面
}
})
})
})
}else{
// 有用户信息
next();
}
}
}else{
if(whiteList.includes(to.name)){
//在免登录白名单里,直接放行
next();
}else{
next({
path:'/login'
})
}
}
})
(四)将permission.js文件引入到main.js中
import './permission'
(五)store里面的用户信息
import { generatorDynamicRouter } from './generator-router';
import { constantRouterMap } from './router.config';
state: {
Info: {},
routers: constantRouterMap,
addRouters:[],
roles: []
},
getters: {
userInfo: state => state.Info,
addRouters: state => state.addRouters
},
mutations: {
SET_ROLES(state, roles) {
state.roles = roles
},
SET_INFO(state, info) {
state.Info = info;
},
SET_ROUTERS(state,routers){
state.addRouters = routers;
state.routers = constantRouterMap.concat(routers)
}
},
actions: {
// 调用接口,获取用户信息
GetInfo(content) {
return new Promise(async (resolve, reject) => {
let res = await getinfo();
let user = res.data;
//确保拿到数据
if (user && res.code === 0) {
// 1.将roles权限信息存进vuex里
content.commit('SET_ROLES', user.roles.permissions)
// 2.存用户信息
content.commit('SET_INFO', user);
//存其他有需要的信息
//...
//3.返回res
resolve(res)
} else {
//返回错误信息
reject(new Error(res.message))
}
})
},
//将roles进行过滤,只要其中的permission菜单
GenerateRoutes(content, roles) {
return new Promise((resolve) => {
let permissions = [];
roles.forEach(role => {
role.permissions.forEach(p => permissions.push(p));
});
// 将permissions菜单进行从小到大排序
permissions = permissions.sort((a, b) => (a.sortNo || 0) - (b.sortNo || 0));
// 进行动态生成菜单
const routes = generatorDynamicRouter(permissions);
// 将routes存进vuex里
content.commit('SET_ROUTERS',routes)
resolve();
})
}
},
(六)generator-router.js里面的方法
// 根级菜单
const rootRouter = {
name: 'index',
path: '/',
redirect: '/xx/index',
component: Layout,
meta: { title: '首页' },
children: []
}
// 前端路由表(页面位置component)
const constantRouterComponents = {
xxLayout,
//...
'403': () => import('@/views/error/403'),
'401': () => import('@/views/error/401'),
'404': () => import('@/views/error/404'),
//...
}
// 前端未找到页面路由(固定不用改)
const notFoundRouter = {
path: '*',
redirect: '/404',
hidden: true
}
/**
* 动态生成菜单
* @returns {Promise<Router>}
* @param permissions
*/
export function generatorDynamicRouter(permissions) {
const menuNav = [];
const childrenNav = [];
// 1.将数组生成树结构
listToTree(permissions, childrenNav, null);
rootRouter.children = rootRouter.children.concat(childrenNav);
// 替换掉redirect,替换成第一个孩子的path
rootRouter.redirect = getFirstRouter(rootRouter.children);
menuNav.push(rootRouter);
//此时menuNav虽然有了path,但没有对应的component,所以需要生成让vue-router一样的路由表
// 2.将树形结构生成vue-router路由表
const routes = generator(menuNav);
routes.push(notFoundRouter);
return routes;
}
function getFirstRouter(router) {
for (let i = 0; i < router.length; i++) {
let rt = router[i];
if (rt.children && rt.children.length > 0) {
return getFirstRouter(rt.children);
} else {
return rt.path;
}
}
}
/**
* 数组转树形结构
* @param list 源数组
* @param tree 树
* @param parentId 父ID
*/
function listToTree(list, tree, parentId) {
list.forEach(item => {
//1.判断是否为父级菜单
if (item.parentId === parentId) {
const child = {
...item,
children: []
}
// 2.迭代list,找到当前菜单符合的所有子菜单
listToTree(list, child.children, item.id);
// 3.删除不存在 children值的属性
if (child.children.length <= 0) {
delete child.children
}
// 4.加入到tree中
tree.push(child)
}
});
}
/**
* 格式化树形结构数据 生成 vue-router 层级路由表
*
* @param routerMap
* @param parent
* @returns {*}
*/
function generator(routerMap, parent) {
return routerMap.map((item) => {
let currentRouter = item
if (!item.meta) {
const {
name,
//一些所需的属性
//...
} = currentRouter;
currentRouter = {
// 如果有 path,则作为默认 path,否则 路由地址 动态拼接生成如 /dashboard/workplace形式
path: item.path || `${parent && parent.path || ''}/${item.key}`,
//路由名称
name: item.key,
// 页面动态加载
component: (constantRouterComponents[item.component || item.key]) || (() => import(
`@/views/${item.component}`)),
//页面标题
meta: {
title: name,
icon: icon || undefined,
//...
}
}
// 为了防止出现后端返回结果不规范,处理有可能出现拼接出两个 反斜杠
if (!currentRouter.path.startsWith('http')) {
currentRouter.path = currentRouter.path.replace('//', '/')
}
// 重定向
item.redirect && (currentRouter.redirect = item.redirect)
}
// 是否有子菜单,并递归处理
if (item.children && item.children.length > 0) {
// Recursion
currentRouter.children = generator(item.children, currentRouter)
}
return currentRouter
})
}