http://localhost:8080/login?username=admin&password=admin
返回token
在Authorization字段加上token 访问localhost:8080/menu/findNavTree?userName=admin
接口返回的json对象如下:
{
"status": 200,
"msg": "success",
"data": [
{
"id": 1,
"parentId": 0,
"name": "系统管理",
"url": null,
"type": 0,
...
"level": 0,
"children": [
{
"id": 2,
"parentId": 1,
"name": "用户管理",
"url": "/sys/user",
"type": 1,
... 省略其他字段
"level": 1,
"children": []
}
.......//省略
]
},
{
"id": 43,
"parentId": 0,
"name": "服务治理",
"url": "",
"type": 0,
"level": 0,
"children": [
{
"id": 44,
"parentId": 43,
"name": "注册中心",
"url": "http://192.168.109.11:8500",
"type": 1,
.....
"level": 1,
"children": []
}
]
}
.......//省略
]
}
获取后端菜单数据,然后以格式化形式存入routes中
getRequest('/menu/findNavTree?userName=' + userName).then(res => {
// 添加动态路由
let dynamicRoutes = addDynamicRoutes(res.data)
console.log('dynamicRoutes: ', dynamicRoutes)
router.options.routes[0].children = router.options.routes[0].children.concat(dynamicRoutes)
router.addRoutes(router.options.routes)
// 保存加载状态
store.commit('menuRouteLoaded', true)
// 保存菜单树
store.commit('setNavTree', res.data)
})
参数menuList表示需要格式化的数据,routes存放格式化数据
原理:
假设后端json数据简化后对应的层级如下:系统管理的children有用户管理和机构管理。机构管理的children有查询操作和删除操作…
addDynamicRoutes函数的解析过程如下:遍历menuList [{系统管理},{服务治理},{接口文档}],发现系统管理和服务治理的children属性均不为空数组[],将他们的children存入temp数组。而接口文档的children为[],将接口文档push到routes。遍历完menuList然后判断temp数组是否加入了结点,如果加入了结点就递归解析。
第二轮遍历的menuList为[{用户管理,机构管理},{机构中心}]发现用户管理和机构管理的children为[],将用户管理和机构管理push到routes…不断重复此过程。
我们发现存入路由中的数据children为[],类似于树的叶子节点。路由过程的意义在于用户点击菜单项,到达叶子菜单项后,路由会根据component字段渲染相应的页面。
系统管理
----用户管理
----机构管理
服务治理
----机构中心
接口文档
注意: 保存菜单树使用的是后端数据,而不是格式化的路由数据。
store.commit(‘setNavTree’, res.data)
function addDynamicRoutes(menuList = [],routes =[]) {
var temp = []
for (var i = 0; i < menuList.length; i++) {
//children不为[],将children存入temp数组,之后处理
if (menuList[i].children && menuList[i].children.length >= 1) {
temp = temp.concat(menuList[i].children)
} else if (menuList[i].url && /\S/.test(menuList[i].url)) {
menuList[i].url = menuList[i].url.replace(/^\//, '')
// 创建路由配置
var route = {
path: menuList[i].url,
name: menuList[i].name,
component: null,
meta: {
icon: menuList[i].icon,
index: menuList[i].id
}
}
try {
// 根据菜单URL动态加载vue组件,这里要求vue组件须按照url路径存储
// 如url="sys/user",则组件路径应是"@/views/sys/user.vue",否则组件加载不到
let array = menuList[i].url.split('/')
let url = ''
for (let i = 0; i < array.length; i++) {
url += array[i].substring(0, 1).toUpperCase() + array[i].substring(1) + '/'
}
url = url.substring(0, url.length - 1)
console.log('url',url)
route['component'] = resolve => require(['@/views/' + url + '.vue'], resolve)
} catch (e) {
}
routes.push(route)
}
}
if (temp.length >= 1) {
addDynamicRoutes(temp, routes)
} else {
console.log('动态路由加载...')
console.log(routes)
console.log('动态路由加载完成.')
}
return routes
}
后端的查询菜单方法
findTree方法先查询顶级菜单,然后查找子菜单。
public List<SysMenu> findTree(String userName, int menuType) {
List<SysMenu> sysMenus = new ArrayList<>();
List<SysMenu> menus = findByUser(userName);
for (SysMenu menu : menus) {
if (menu.getParentId() == null || menu.getParentId() == 0) {
menu.setLevel(0); //顶级菜单
if(!exists(sysMenus, menu)) { //防止重复添加
sysMenus.add(menu);
}
}
}
sysMenus.sort((o1, o2) -> o1.getOrderNum().compareTo(o2.getOrderNum()));
findChildren(sysMenus, menus, menuType);
return sysMenus;
}
findChidren方法是一个递归查询操作,递归出口SysMenus==NULL。
参数:SysMenus 父级别菜单;menus 当前用户查询到的所有菜单
menuType 标识变量,判断是否查询按钮操作菜单
按钮菜单比如查看、删除等属于第三级别,不属于菜单项范畴。
private void findChildren(List<SysMenu> SysMenus, List<SysMenu> menus, int menuType) {
for (SysMenu SysMenu : SysMenus) {
List<SysMenu> children = new ArrayList<>();
for (SysMenu menu : menus) {
if(menuType == 1 && menu.getType() == 2) {
// 如果是获取类型不需要按钮,且菜单类型是按钮的,直接过滤掉
continue ;
}
if (SysMenu.getId() != null && SysMenu.getId().equals(menu.getParentId())) {
//找到父菜单的子菜单
menu.setParentName(SysMenu.getName());
menu.setLevel(SysMenu.getLevel() + 1);
if(!exists(children, menu)) {
children.add(menu);
}
}
}
//设置children
SysMenu.setChildren(children);
children.sort((o1, o2) -> o1.getOrderNum().compareTo(o2.getOrderNum()));
findChildren(children, menus, menuType);
} //end of loop
}
总结:后端查询父菜单,然后分别查询子菜单,将子菜单添加到父菜单的children属性中。