zoukankan      html  css  js  c++  java
  • 多级菜单 多级树形结构 多级树排序 多级树节点移动

    此文将介绍一种简单可行的多级树结构算法,并支持节点的上下移动。

    首先,本文的算法是启蒙于一个.net项目中的多级树结构算法。该项目中,所有节点的排序值,通通按照显示顺序排列(如图)。

    这种方式的缺点是:当“插入”,“移动”,“修改(修改所属父节点)”和“删除”节点,需要对子节点和父节点的排序值都要重新设置(需要有序下移当前节点之后的所有节点。如果只是一个简单的二叉树,那还好,但如果是多级树,则使得逻辑异常复杂)。

    那么,如何在此基础之上,进行修改。得到一个简单的算法,在插入或移动节点时,不用使用那么复杂的算法呢?答案如下:

    1.(表)结构

    表结构不变(Id,ParentId,Position,Name ... ),排序值的设置逻辑变成:局部排序,即,只做(same level)同级别的节点的排序。(如下图)

    2.节移动,节点移动规则,只做同级节点之间上下移动(采用新结构之后,也就屏蔽掉了移动一个节点时,还得同时移动当前节点之后所有节点的排序位的烦恼,现在就只要关系自己的排序位就行了。),非同级移动可以通过修改挂靠父节点来做到。

    3.新增,只用查询同级节点中Max 排序位,然后+1作为自己的排序位即可。

    4.修改,保持当前排序位不变。如果是修改挂靠父节点,也只是单纯修改ParentId即可,当前节点排序修改为将来父节点中子节点中Max排序值,当前节点的子节点的排序值不用变。

    5.删除,将当前节点之后的同级节点的排序值+1,其他不变。


    最后,JAVA CODE直接上代码!

    由于管理系统中,有很多地方都用到树形结构,所以抽象继承是必须的。

    第一大类就是TreeRsVo,所有树形结构的model类都继承它,TreeRsVo使用泛型来指定“子节点列表”属性(因为每个树形类可能有自己特有的属性,而“子节点列表”的元素类型就是自身,所以通过泛型进行传递,复用TreeRsVo基类里面的排序方法)。 


    /**
    * 树-基类
    * Created by TonyZeng on 2017/4/21.
    */
    public class TreeRsVo<T extends TreeRsVo> {
    private Long id;//ID
    private Long parentId;//父节点ID
    private String name;//
    private Boolean available;//是否可用
    private Integer position;//位置(用于排序)
    private List<T> children;//子列表

    //此处省略部分get&set方法

    /**
    * 孩子节点排序
    */
    public void sortChildren() {
    this.children.sort((m1, m2) -> {
    int orderBy1 = m1.getPosition();
    int orderBy2 = m2.getPosition();
    return orderBy1 < orderBy2 ? -1 : (orderBy1 == orderBy2 ? 0 : 1);
    });
    // 对每个节点的下一层节点进行排序
    for (T n : children) {
    if (n != null && n.getChildren() != null) {
    n.sortChildren();
    }
    }
    }
    }

    /**
    * 系统菜单-响应Vo(树)(此类Mapping from DB Table Model Class,即相当于数据库model类)
    * Created by TonyZeng on 2017/3/13.
    */
    public class MenuResponseVo extends TreeRsVo<MenuResponseVo> {
    private String link; //链接
    private String icon;//图标
    private List<ButtonResponseVo> buttons;//按钮列表
    //此处省略部分get&set方法
    }

    /**
    * 创建有序树
    * Created by TonyZeng on 2016/5/13.
    */
    public class TreeMapper {
    /**
    * 创建有序菜单树
    * @param list
    * @return
    */
    public static MenuResponseVo MenuTree(List<MenuResponseVo> list) {
    //组装Map数据,将所有菜单全部放到Map中,并用菜单ID作为Key来标记,方便后面的各节点寻找父节点。
    Map<Long, MenuResponseVo> dataMap = new HashMap<>();//<{菜单ID},{菜单对象}>
    for (MenuResponseVo menu : list) {
    dataMap.put(menu.getId(), menu);
    }
    //创建根节点
    MenuResponseVo root = new MenuResponseVo();
    //组装树形结构,将子节点全部挂靠到自己的父节点
    for (Map.Entry<Long, MenuResponseVo> entry : dataMap.entrySet()) {
    MenuResponseVo menu = entry.getValue();
    if (menu.getParentId().equals(0L)) {
    root.getChildren().add(menu);
    } else {
    dataMap.get(menu.getParentId()).getChildren().add(menu);
    }
    }
    //对多级树形结构进行“二叉树排序”
    root.sortChildren();
    return root;
    }
    }

    /**
    * 系统设置-系统菜单设置服务
    * Created by TonyZeng on 2017/2/6.
    */
    @Service("sysMenuBIService")
    public class SysMenuBIServiceImpl implements SysMenuBIService {
    @Autowired
    SysMenuService sysMenuService;

    /**
    * 获取菜单树形结构接口
    *
    * @return 完整的菜单多级树形结构
    */
    @Override
    public BaseDto getMenuList() {
    List<MenuResponseVo> list = new ArrayList<>();
    for (SysMenu x : sysMenuService.findAll(new Sort(Sort.Direction.ASC, "Position"))) {
    list.add(VoMapper.mapping(x));
    }
    return new BaseDto(TreeMapper.MenuTree(list).getChildren());
    }

    /**
    * 添加菜单
    *
    * @param requestVo
    * @return
    */
    @Override
    public BaseDto addMenu(AddMenuRqVo requestVo) {
    //验证参数
    String validateResult = new ValidateUtil<AddMenuRqVo>().validate(requestVo);
    if (validateResult != null) {
    return new BaseDto(-1, validateResult);
    }
    try {
    //新增时,检查是否存在相同的菜单编码
    List sameCodeMenus = sysMenuService.findByMenuCode(requestVo.getCode());
    if (sameCodeMenus != null && sameCodeMenus.size() > 0) {
    return new BaseDto(-1, "菜单编码已存在,请重新填写");
    }
    //vo to po
    SysMenu model = PoMapper.mapping(requestVo);
    //查询兄弟节点中,最大的排序值,并加一作为自己的排序值
    model.setPosition(sysMenuService.findMaxPositionOfBrother(requestVo.getParentId()) + 1);
    model = sysMenuService.save(model);
    if (model.getId() != null) {
    return new BaseDto(model.getId());
    } else {
    return new BaseDto(-1, "系统设置-添加菜单 失败");
    }
    } catch (Exception ex) {
    return new BaseDto(-1, "系统设置-添加菜单 失败");
    }
    }

    /**
    * 更新菜单
    *
    * @param requestVo
    * @return
    */
    @Override
    public BaseDto updateMenu(UpdateMenuRqVo requestVo) {
    //验证参数
    String validateResult = new ValidateUtil<AddMenuRqVo>().validate(requestVo);
    if (validateResult != null) {
    return new BaseDto(-1, validateResult);
    }
    try {
    //新增时,检查是否存在相同的菜单编码
    SysMenu po = sysMenuService.find(requestVo.getId());
    if (po == null) {
    return new BaseDto(-1, "菜单不存在");
    }

    SysMenu model = PoMapper.mapping(requestVo);
    //排序值不变
    model.setPosition(po.getPosition());
    model = sysMenuService.save(model);
    if (model.getId() != null) {
    return new BaseDto(model.getId());
    } else {
    return new BaseDto(-1, "系统设置-更新菜单 失败");
    }
    } catch (Exception ex) {
    return new BaseDto(-1, "系统设置-更新菜单 失败");
    }
    }

    /**
    * 删除菜单
    *
    * @param id 菜单id (修改菜单必填)
    * @return
    */
    @Override
    public BaseDto deleteMenu(Long id) {
    //验证参数
    if (id == null) {
    return new BaseDto(-1, "请提供id");
    }
    try {
    SysMenu po = sysMenuService.find(id);
    if (po == null) {
    return new BaseDto(-1, "菜单不存在");
    }
    //删除
    sysMenuService.delete(id);
    //重置菜单的排序值
    resetMenu(po.getParentId());
    return new BaseDto(id);
    } catch (Exception ex) {
    return new BaseDto(-1, "系统设置-删除菜单 失败");
    }
    }

    /**
    * 移动菜单排序
    * 算法:
    * 1.找到(previous or next)菜单
    * 2.与其交换位置
    *
    * @param requestVo
    * @return
    */
    @Override
    public BaseDto moveMenu(MoveRqVo requestVo) {
    //验证参数
    String validateResult = new ValidateUtil<MoveRqVo>().validate(requestVo);
    if (validateResult != null) {
    return new BaseDto(-1, validateResult);
    }

    try {
    //新增时,检查是否存在相同的菜单编码
    SysMenu self = sysMenuService.find(requestVo.getId());
    if (self == null) {
    return new BaseDto(-1, "菜单不存在");
    }

    List<SysMenu> brotherList = sysMenuService.findByParentId(self.getParentId());
    //如果有兄弟节点
    if (brotherList.size() > 0) {
    //Get index of this menus in brother menus.
    int indexOfThisMenus = 0;
    for (int i = 0; i < brotherList.size(); i++) {
    if (brotherList.get(i).getId().equals(self.getId())) {
    indexOfThisMenus = i;
    }
    }
    SysMenu brother;
    if (requestVo.getDown().equals(true)) {
    //判断是否已经为最底部的菜单
    if (indexOfThisMenus == (brotherList.size() - 1)) {
    return new BaseDto(-1, "已经是(同级中)最底部的菜单了");
    } else {
    //获取后临的兄弟菜单
    brother = brotherList.get(indexOfThisMenus + 1);
    }
    //交换位置
    brother.setPosition(brother.getPosition() - 1);
    self.setPosition(self.getPosition() + 1);
    } else {
    //判断是否已经为最底部的菜单
    if (indexOfThisMenus == 0) {
    return new BaseDto(-1, "已经是(同级中)最顶部的菜单了");
    } else {
    //获取前临的兄弟菜单
    brother = brotherList.get(indexOfThisMenus - 1);
    }
    //交换位置
    brother.setPosition(brother.getPosition() + 1);
    self.setPosition(self.getPosition() - 1);
    }
    sysMenuService.save(brother);
    sysMenuService.save(self);
    if (self.getId() != null && brother.getId() != null) {
    return new BaseDto(self.getId());
    } else {
    return new BaseDto(-1, "系统设置-移菜单位置 失败");
    }
    } else {
    //如果没有兄弟节点
    return new BaseDto(self.getId());
    }
    } catch (Exception ex) {
    return new BaseDto(-1, "系统设置-移菜单位置 失败");
    }
    }

    /**
    * 重置菜单的排序值
    *
    * @param parentId 父节点ID
    * @return
    */
    @Override
    public BaseDto resetMenu(Long parentId) {
    return resetMenu(sysMenuService.findByParentId(parentId));
    }

    /**
    * 重置菜单的排序值
    *
    * @param brothers 兄弟节点列表
    * @return
    */
    private BaseDto resetMenu(List<SysMenu> brothers) { //如果有兄弟节点
    try {
    if (brothers.size() > 0) {
    for (int i = 0; i < brothers.size(); i++) {
    brothers.get(i).setPosition(i + 1);
    }
    sysMenuService.save(brothers);
    return new BaseDto(0, "系统设置-重置菜单的排序值 成功");
    } else {
    return new BaseDto(0, "系统设置-重置菜单的排序值 失败");
    }
    } catch (Exception ex) {
    return new BaseDto(-1, "系统设置-重置菜单的排序值 失败");
    }
    }
    }

    3.JSON 结构,在TreeMapper生成了树之后,to json 将得到如下json结构。
    {
    "data": [
    {
    "id": 1,
    "parentId": 0,
    "name": "会员档案",
    "available": true,
    "position": 1,
    "children": [
    {
    "id": 33,
    "parentId": 1,
    "name": "档案管理",
    "available": true,
    "position": 1,
    "children": [],
    "link": "~/user/CustomerMgr.aspx",
    "icon": "user"
    },
    {
    "id": 29,
    "parentId": 1,
    "name": "密码修改",
    "available": true,
    "position": 2,
    "children": [
    {
    "id": 30,
    "parentId": 29,
    "name": "密码修改Child44",
    "available": true,
    "position": 1,
    "children": [],
    "link": "",
    "icon": ""
    },
    {
    "id": 26,
    "parentId": 29,
    "name": "密码修改Child1",
    "available": true,
    "position": 2,
    "children": [],
    "link": "",
    "icon": ""
    }
    ],
    "link": "~/user/PersonalInfoMgr.aspx",
    "icon": "userhome"
    }
    ],
    "link": "",
    "icon": "groupgear"
    }
    ]
    }






  • 相关阅读:
    2019/10/9 CSP-S 模拟测
    简单的面向对象
    魔术变量
    函数
    全局变量
    超级全局变量
    for循环
    while循环
    php数组排序
    python打造XslGenerator
  • 原文地址:https://www.cnblogs.com/tonyzeng/p/6744595.html
Copyright © 2011-2022 走看看