这篇文章主要讲的是动态路由添加,下篇我们再仔细说明一下按钮权限的做法,
- 本文基础框架在element-ui及element-admin-template基础上进行的二次开发
动态路由权限:
首先这里我们应该想清楚我们要怎么去做这个动态路由,按照我对动态路由的理解,一般都是服务端储存路由数据,返回到客户端进行路由列表渲染,element-admin官方的根据角色权限渲染可能是另外一种方式,不过在我自己处理的前端框架中一般情况都是第一种方案,这里只是给大家提供一个思路,特殊情况的需要自己处理。
1 全局路由守卫添加如下代码
// 获取动态路由然后使用VUEX进行组织
const accessRoutes = await store.dispatch('permission/generateRoutes', menuList);
// 高版本vue-router使用addRoute 方法,低版本则使用 addRoutes
accessRoutes.forEach(item => {
router.addRoute(item)
})
这里我们先看下这个menuList的返回格式 下图中这个是单条数据格式,我解释下关键的几个字段,首先是id(主键),然后是pid(父级ID),fullAuth(权限授权),path(命名路由),因为返回的并不是一棵树,所有我们接下来要进行实际的数据组织
{
"description": "Dubbo管理",
"createTime": "2021-10-13 18:46:15",
"createBy": "c107e1f74d484b218bdf11341f5c048d",
"deleteTime": null,
"deleteBy": null,
"updateBy": null,
"updateTime": null,
"isDel": "0",
"id": "1a1f1e0f528d49399c0ead0fb92e6b1d",
"pid": "b2e13293b744402081e023337a58b23e",
"name": "Dubbo管理",
"fkDicMenuType": "01", // 菜单类型 01 - 菜单, 02 - 按钮
"fkDicMenuShow": "01", // 是否显示 01 - 是, 02 - 否
"auth": "dubbo",
"fullAuth": "system:dubbo",
"path": "dubbo",
"icon": null,
"sort": 7,
"fkModularId": "cafc9923d4404babada7ebcb90a2f377",
"parent": null,
"flag": true
}
2 组织原始数据
下面的代码不难看懂,递归一个树,为什么设置pid初始值为1呢,因为跟pid为1,这个要看后端的具体返回
function dataToTree(data, pid = '1') => {
if (!data) return [];
let res = [];
let loopList = data.filter(ele => {
return ele.pid == pid;
})
if (loopList && loopList.length) {
let children = [];
loopList.forEach(im => {
children = utils.dataToTree(data, im.id);
if (children && children.length) {
im.children = children;
} else {
im.children = undefined;
}
})
res.push(...loopList);
return res;
} else {
return []
}
}
3 修改原始数据
这里我直接上代码,然后看注释
return new Promise(res => {
let accessedRoutes, originMenuList;
if (true || rootState.app.isPro) {
// 过滤之前保存一份原始menu 用途是按钮权限获取
originMenuList = lodash.cloneDeep(menuList);
// 利用刚才的工具方法递归数据
originMenuList = utils.dataToTree(originMenuList);
// 组织树结构
accessedRoutes = getMenuItems(asyncRoutes, menuList);
// 过滤不显示的路由
accessedRoutes = filterMenu(accessedRoutes);
// 排序
accessedRoutes = lodash.sortBy(accessedRoutes, it => {
return it.sort;
});
console.log(accessedRoutes);
} else {
accessedRoutes = asyncRoutes;
}
// 保存原始数据
commit('SET_ORIGINMENULIST', originMenuList)
// 添加目前的路由数据
commit('SET_ROUTES', accessedRoutes)
// 返回数据
res (accessedRoutes)
})
我们逐个分析这几个核心方法
- getMenuItems
function getMenuItems(list, accessMenu) {
if (!accessMenu || !accessMenu.length) return [];
return list.map(item => {
// 查找是否有当前路由
let menu = accessMenu.find(it => {
return it.path == item.name;
});
if (menu) {
let {
path,
alwaysShow,
meta,
redirect,
component,
icon,
} = item,
{
name,
fkDicMenuType,
fkDicMenuShow,
auth,
fullAuth,
sort
} = menu,
children = undefined;
let target = menu.path;
// 如果数据有children 就进入递归方法传入item.children
if (item.children && item.children.length > 0) {
children = getMenuItems(item.children, accessMenu);
};
return {
alwaysShow, // 遵循路由规则 不熟悉的童鞋可以去看下element-admin
children, // 子集
redirect,
component, // 当前路由的component
path, // path路径
meta: {
title: name, // 路由名称
icon:meta.icon, // 路由图标 -- 根据自己的需求去改,后来我舍弃了,没必要
affix: meta.affix // 遵循路由规则 不熟悉的童鞋可以去看下element-admin
},
fkDicMenuShow, // 路由是否显示
fkDicMenuType, // 路由类型
auth,
fullAuth, // 路由权限,用来处理按钮权限的标识
sort,
name: target
};
} else {
// 这里就是为了我下一步过滤做准备
return {
fkDicMenuShow: '02'
}
}
});
};
到此呢我们就完成了基本的数据组装,可能有些童鞋会理解不了,精心思考就明白了,无非就是多了一个递归写法,如果有children就是找,接下来要做的事情就是过滤那些不展示的路由
- filterMenu
function filterMenu(list){
return list.filter((item) => {
let children = undefined;
if(item.children && item.children.length > 0){
children = filterMenu(item.children);
item.children = children;
if (!children || children.length == 0) {
return false;
} else {
return item.fkDicMenuShow == '01';
}
}else{
return item.fkDicMenuShow == '01';
}
});
};
这段代码的作用就是将后端返回中不显示的菜单过滤掉,同样也是使用了递归方法
4 添加路由
我是在全局路由守卫中添加的
accessRoutes.forEach(item => {
router.addRoute(item)
})
// 所有不存在的路由都是404
router.addRoute({ path: '*', redirect: '/404', hidden: true });
ok,大功告成!