предисловие
В разработке мы часто встречаемся: меню навигации, меню отдела, дерево разрешений, комментарии и другие функции.
Эти функции имеют общие характеристики:
- иметь отношения отца и сына
- бесконечная рекурсия
Возьмем для примера навигационное меню, мы задаем навигационное меню как динамическое, то есть загружаем данные меню из динамического.
Дизайн базы данных
Схема, подходящая для хранения базы данных, выглядит следующим образом:
create table `menus`
(
`id` int primary key auto_increment,
`name` varchar(20) comment '菜单名称',
`pid` int default 0 comment '父级 ID, 最顶级为 0',
`order` int comment '排序, 序号越大, 越靠前'
)
Внешний рендеринг
Для внешнего интерфейса нам обычно нужен этот эффект:
Страница конфигурации меню:
Соответствующее меню навигации:
Обычно используемые плагины отображения дерева:JsTree, zTree, Layui Tree, Bootstrap Tree ViewЖдать.
Эти плагины обычно требуют этих двух форматов:
Базовый формат:
[
{
"id": 1,
"name": "权限管理",
"pid": 0,
"order": 1
},
{
"id": 2,
"name": "用户管理",
"pid": 1,
"order": 2
},
{
"id": 3,
"name": "角色管理",
"pid": 1,
"order": 3
},
{
"id": 4,
"name": "权限管理",
"pid": 1,
"order": 4
}
]
Формат дерева:
[
{
"id": 1,
"name": "权限管理",
"pid": 0,
"order": 1,
"children": [
{
"id": 2,
"name": "用户管理",
"pid": 1,
"order": 2,
"children": []
},
{
"id": 3,
"name": "角色管理",
"pid": 1,
"order": 3,
"children": []
},
{
"id": 4,
"name": "权限管理",
"pid": 1,
"order": 4,
"children": []
}
]
}
]
Некоторые подключаемые модули поддерживают оба формата, в то время как некоторые поддерживают только древовидную структуру, но результатом нашего запроса к базе данных часто является общая структура.На данный момент нам нужно преобразовать общий формат в древовидный формат.
Это преобразование обычно выполняется на стороне сервера (поскольку большинство подключаемых модулей внешнего интерфейса запрашивают URL-адрес в фоновом режиме для получения данных JSON, событие предварительной обработки данных после загрузки не выполняется, поэтому преобразование не может быть завершено. на переднем конце.)
конверсия данных
Во-первых, это классы сущностей Java:
public class Menu {
private int id,
private String name,
private int pid
// getter setter 略
}
После того, как запрос к базе данных обычно находится в списке:
List<Menu> menus = xxxMapper.selectXXX();
Тогда нам нужно поставить этоListЧтобы преобразовать в древовидную структуру, сначала определите класс VO с древовидной структурой:
public class MenuTreeVO {
private int id,
private String name,
private int pid,
private List<MenuVo> children,
// getter setter 略
}
Инструменты преобразования:
package im.zhaojun.util;
import im.zhaojun.model.vo.MenuTreeVO;
import java.util.ArrayList;
import java.util.List;
public class TreeUtil {
/**
* 所有待用"菜单"
*/
private static List<MenuTreeVO> all = null;
/**
* 转换为树形
* @param list 所有节点
* @return 转换后的树结构菜单
*/
public static List<MenuTreeVO> toTree(List<MenuTreeVO> list) {
// 最初, 所有的 "菜单" 都是待用的
all = new ArrayList<>(list);
// 拿到所有的顶级 "菜单"
List<MenuTreeVO> roots = new ArrayList<>();
for (MenuTreeVO menuTreeVO : list) {
if (menuTreeVO.getParentId() == 0) {
roots.add(menuTreeVO);
}
}
// 将所有顶级菜单从 "待用菜单列表" 中删除
all.removeAll(roots);
for (MenuTreeVO menuTreeVO : roots) {
menuTreeVO.setChildren(getCurrentNodeChildren(menuTreeVO));;
}
return roots;
}
/**
* 递归函数
* 递归目的: 拿到子节点
* 递归终止条件: 没有子节点
* @param parent 父节点
* @return 子节点
*/
private static List<MenuTreeVO> getCurrentNodeChildren(MenuTreeVO parent) {
// 判断当前节点有没有子节点, 没有则创建一个空长度的 List, 有就使用之前已有的所有子节点.
List<MenuTreeVO> childList = parent.getChildren() == null ? new ArrayList<>() : parent.getChildren();
// 从 "待用菜单列表" 中找到当前节点的所有子节点
for (MenuTreeVO child : all) {
if (parent.getMenuId().equals(child.getParentId())) {
childList.add(child);
}
}
// 将当前节点的所有子节点从 "待用菜单列表" 中删除
all.removeAll(childList);
// 所有的子节点再寻找它们自己的子节点
for (MenuTreeVO menuTreeVO : childList) {
menuTreeVO.setChildren(getCurrentNodeChildren(menuTreeVO));
}
return childList;
}
}
Режим вызова:
// 从数据库获取
List<Menu> menus = xxxMapper.selectXXX();
// Menu 转为 MenuTreeVO
List<MenuTreeVO> menuTreeVOS = new ArrayList<>();
for (Menu menu : menus) {
MenuTreeVO menuTreeVO = new MenuTreeVO();
BeanUtils.copyProperties(menu, menuTreeVO);
menuTreeVOS.add(menuTreeVO);
}
// 调用转换方法
xxxUtil.toTree(menuTreeVOS);
// 通过 Json 或 ModelAndView 返回给前台.
Приложение: рендеринг шаблонизатора
Иногда мы используем механизм шаблонов для рендеринга меню, но, поскольку меню имеет древовидную структуру, простое использование for в механизме шаблонов не может завершить рендеринг бесконечного меню.
Вот очень новый метод, я используюthymeleafПример двигателя:
INDEX.html часть навигации:
<div class="left-nav">
<div id="side-nav">
<ul id="nav">
<th:block th:include="public::menu(${menus})"/>
</ul>
</div>
</div>
Раздел общедоступного шаблона public.html:
<th:block th:fragment="menu(menus)">
<li th:each="menu:${menus}">
<a href="javascript:;">
<i class="iconfont"></i>
<cite th:text="${menu.menuName}">系统管理</cite>
<i class="iconfont nav_right"></i>
</a>
<ul class="sub-menu">
<li th:each="child:${menu.children}">
<a th:if="${#lists.isEmpty(child.children)}" data-th-_href="${child.url}" _href="users">
<i class="iconfont"></i>
<cite th:text="${child.menuName}">用户管理</cite>
</a>
<th:block th:unless="${#lists.isEmpty(child.children)}" th:include="this::menu(${child})" />
</li>
</ul>
</li>
</th:block>
Базовая логика заключается в том, чтобы использовать include для ссылки на шаблон.Различные механизмы шаблонов имеют эту функцию, а затем определяют, есть ли у текущего узла дочерние узлы, и если да, файл шаблона ссылается на себя для завершения рекурсии.
Эпилог
Приведенный выше код представляет собой некоторые идеи и коды при разработке фона управления разрешениями Shiro. Полный код см. по адресу:GitHub.com/Чжао, июнь 1998 г.…