首先, 就是在设计后台数据库里的菜单表, 无论字段是怎么在变化, 但是不变的大致有三个:
主键id,
对应的URL,
parentId(表明该数据是某个菜单下的子菜单).
单纯的从java后台来说. 我们需要做的就是遍历出这些数据,然后按一定顺序组成一串json数据返回给前台.
返回的数据格式也有常规:
id, URL, parentId,Children(这里的类型是List, 这个就算是一个重点了).
一般来说实体类最基础包含就是要有: id, URL, parentId,Children.
大致模样如下, 其他字段是为了满足我所使用的前台框架需要的条件, 这就要根据自己用的框架进行配合了:
[
{
"id": 1,
"parentId": 0,
"url": "",
"text": "系统设置",
"checked": "false",
"content": "测试",
"state": "open",
"children": [
{
"id": 2,
"parentId": 1,
"url": "",
"text": "菜单设置",
"checked": "false",
"content": "测试",
"state": "null",
"children": [
{
"id": 5,
"parentId": 2,
"url": "sys/menu/index",
"text": "分配权限",
"checked": "false",
"content": "测试",
"state": "null",
"children": []
}
]
},
{
"id": 3,
"parentId": 1,
"url": "sys/menu/index",
"text": "角色管理",
"checked": "false",
"content": "测试",
"state": "null",
"children": []
},
{
"id": 4,
"parentId": 1,
"url": "sys/menu/index",
"text": "用户管理",
"checked": "false",
"content": "测试",
"state": "null",
"children": []
}
]
},
{
"id": 8,
"parentId": 0,
"url": "",
"text": "基础设置",
"checked": "false",
"content": "测试",
"state": "open"
}
]
不用觉得很复杂. 把这几条数据拆开看. 无非就是 一个菜单(有或没有子菜单).
如果没有子菜单,则parentId就要是0, 这里的parentId就是一个辨识符号的作用. 如果不为0,则表示它是某一个菜单的子菜单.
如果是子菜单, 则将其放入对应的父菜单里面的Children这个List里面去.
大致意思就这样子.
接下来就是具体的代码. 按常规来讲, 提到无限菜单的形成大家立马想起的就是用 "递归".
这里简介下递归, 最为简单的来讲就是:
写一个方法A, 在这个方法A里面 通过判断某些条件,继续调用方法A, 直到满足了我们最初设定的跳出这个方法的条件才结束.
第一步:
先通过一个最简单的查询方法, 查询出所有的菜单.以List形式返回.
List<Object> menu = menuService.getMenu();
第二步:
准备一个 容纳 顶级菜单(也就是parentId==0的所有菜单就是最顶级的菜单) 的 List容器
//准备容器
List<Object> menuList = new ArrayList<>();
第三步:
遍历 由第一步 所得到的所有菜单, 根据条件判断, 得到所有顶级菜单, 并将其放入 第二步 准备的容器中.
for (Object o : menu) {
TbMenu menuOne = (TbMenu)o;//先转换成你已经写好的菜单实体类
//接着遍历出父最顶级一层的菜单. 条件就是 parentId ==0
if (0==menuOne.getParentId()){
menuList.add(menuOne);
}
}
第四步:
这里就要开始准备写递归方法了. getChild(父菜单id, 所有菜单)
// id: 父菜单id; rootMenu: 所有菜单
private List<TbMenu> getChild(long id, List<Object> rootMenu) {
// 准备接收子菜单的 容器
List<TbMenu> ChildrenList= new ArrayList<>();
//遍历带过来的 所有菜单
for (Object menus : rootMenu) {
TbMenu menu = (TbMenu)menus;//先转换成项目中具体实体类
// 如果有菜单的 父id 不为0 ,并且,该菜单的父id 和传递进来的一级菜单id相同.则证明它是传递进来的菜单下的子菜单
if (menu.getParentId()!=0&&menu.getParentId()==id) {
childList.add(menu);
}
}
// 这里算是重点: 开始条用递归方法. 也就是 将 childList 这个子菜单再循环一遍.
for (TbMenu menu : ChildrenList) {
// 明确: 如果一行数据中的url没有值,则表明它是一个父级菜单(其下一定有子菜单)
if (StringUtils.isBlank(menu.getUrl())) {
//递归(再次调用方法, 以该菜单id为辨认.再次和所有菜单进行遍历比较.找出它的子菜单)并将它的子菜单赋值进Children这个实体类shuxin
menu.setChildren(getChild(menu.getId(), rootMenu));
}
}
// 重点二: 这里就是递归退出条件: 如果childList.size() == 0,则表明传递进来的菜单下没有子菜单.
if (childList.size() == 0) {
return null;
}
return childList;
}
以上的递归只要看明白了就好.
第五步:
这里的代码准确来说是在 第三步 得到所有 顶级菜单后执行的. 第四步就是个独立的用来递归的方法而已.
// 遍历顶级菜单.调用递归方法.
for (Object b : menuList) {
TbMenu menuTwo= (TbMenu)b;//先转换
//调用递归方法: getChild(顶级菜单id, 所有菜单内容).得到该菜单下的子菜单.
menuTwo.setChildren(getChild(menuTwo.getId(), menu));
}
用文字说明(啰嗦下就是):
遍历所有 顶级菜单
循环, 拿到一个 (A)父菜单Id, 和全部菜单进行比较, 找出 A 的所有子菜单, 然后把这些子菜单放进 一个List.
再遍历得到的这个 List, 循环, 拿到 一个(Aa)子菜单的id, 再次和全部菜单进行比较.
每一次遍历就是 一个对象. 每一次递归方法结束, 都要用 该对象.setChildren(子菜单).
最后: 全部代码记录如下:
package manage.controller;
import com.alibaba.fastjson.JSON;
import manage.pojo.TbMenu;
import manage.service.MenuService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.ArrayList;
import java.util.List;
@Controller
@RequestMapping("/admin/menu")
public class MenuController {
@Autowired
private MenuService menuService;
@RequestMapping(value="/tree",produces = "text/json;charset=UTF-8")
@ResponseBody
public String getTree() {
List<Object> menu = menuService.getMenu();
//准备容器
List<Object> menuList = new ArrayList<>();
for (Object o : menu) {
TbMenu menuOne = (TbMenu)o;//先转换
//接着遍历出父最顶级一层的菜单
if (0==menuOne.getParentId()){
menuList.add(menuOne);
}
}
// 遍历顶级菜单.调用递归方法.
for (Object b : menuList) {
TbMenu menuTwo= (TbMenu)b;//先转换
//调用递归方法: getChild(顶级菜单id, 所有菜单内容).得到该菜单下的子菜单.
menuTwo.setChildren(getChild(menuTwo.getId(), menu));
}
// 将按顺序装好的菜单List转换成前台需要的json格式.
final String result = JSON.toJSONString(menuList);
System.out.println(result);
return result;
}
// id: 一级菜单id; rootMenu: 所有菜单
private List<TbMenu> getChild(long id, List<Object> rootMenu) {
// 准备接收子菜单
List<TbMenu> childList = new ArrayList<>();
//遍历所有菜单
for (Object menus : rootMenu) {
TbMenu menu = (TbMenu)menus;//先转换成项目中具体实体类
// 如果有菜单的 父id 不为0 ,并且,该菜单的父id 和传递进来的一级菜单id相同.则证明它是传递进来的一级菜单下的子菜单
if (menu.getParentId()!=0&&menu.getParentId()==id) {
childList.add(menu);
}
}
// 将 childList 这个子菜单再循环一遍.
for (TbMenu menu : childList) {
// 明确: 如果一行数据中的url没有值,则表明它是一个父级菜单(其下一定有子菜单)
if (StringUtils.isBlank(menu.getUrl())) {
// 递归(再次调用方法, 以该菜单id为辨认.再次和所有菜单进行遍历比较.找出它的子菜单)
menu.setChildren(getChild(menu.getId(), rootMenu));
}
}
// 递归退出条件: 如果childList.size() == 0,则表明传递进来的菜单下没有子菜单.
if (childList.size() == 0) {
return null;
}
return childList;
}
}