使用递归算法实现动态菜单层级管理(前后端)

一、概述

  最近利用业余时间想搭一个基于设备管理和OA的sass平台,首当其冲就是前后端项目的搭建了,技术栈用的也是目前比较流行的spring全家桶+vue;菜单想做成根据用户权限展示的动态菜单,实现逻辑这里就不赘述了;获取菜单的接口后端直接遍历查询到的菜单列表进行分层统计后统一返回前端,开始我只写了三层菜单结构,但是测试的时候发现,如果要配四级甚至五级菜单呢,难道继续往下写吗,就目前的三层结构都快把头绕晕了,所以就决定抽象出一个方法进行处理,详细解决方案如下

二、后端解决方案

  1.vue组件需要的菜单格式

[{
    "icon": "el-icon-lx-emoji",
    "index": "2",
    "title": "资源管理",
    "subs": [{
        "icon": "el-icon-lx-emoji",
        "index": "6",
        "title": "人力资源管理",
        "subs": [{
            "icon": "el-icon-lx-emoji",
            "index": "e",
            "title": "人员基本信息",
            "subs": null
        }, {
            "icon": "el-icon-lx-emoji",
            "index": "g",
            "title": "人员能力监控",
            "subs": null
        }, {
            "icon": "el-icon-lx-emoji",
            "index": "d",
            "title": "人员权限管理",
            "subs": null
        }]
    }]
}, {
    "icon": "el-icon-lx-emoji",
    "index": "3",
    "title": "检测过程管理",
    "subs": null
}]

 说明:每个菜单对象都有一个subs属性,subs为子菜单数组,里面存的是菜单对象,理论上可以一直放下去以表示N级菜单;

  2.后端原始代码

public List<MenuInfoModel> getMenus(Integer userId) {
        List<MenuInfoModel> res = new ArrayList<>(16);
        // 先查出当前登录人能看到的根菜单
        List<DmSysMenu> rootMenus = menuMapper.getMenus(userId, "0");
        rootMenus.stream().forEach(root -> {
            MenuInfoModel model = new MenuInfoModel();
            model.setIcon(root.getIcon());
            model.setTitle(root.getName());
            model.setIndex(root.getUrl());
            if (Objects.equals(root.getLeaf(),CommonConstant.MENU_LEAF)) {
                // 如果跟菜单本身就是叶子节点则加入菜单列表后返回即可
                res.add(model);
                return;
            }
            // 根据根菜单获取其下的二级菜单
            List<DmSysMenu> menus = menuMapper.getMenus(userId, String.valueOf(root.getMenuId()));
            if (menus.isEmpty()) {
                return;
            }
            // 初始化二级菜单列表
            List<MenuInfoModel> subs = new ArrayList<>();
            menus.stream().forEach(menu -> {
                MenuInfoModel childrenMenu = new MenuInfoModel();
                childrenMenu.setIcon(menu.getIcon());
                childrenMenu.setTitle(menu.getName());
                childrenMenu.setIndex(menu.getUrl());
                if (Objects.equals(menu.getLeaf(),CommonConstant.MENU_LEAF)) {
                    // 表示此二级菜单为叶子节点,加入二级菜单列表后直接返回
                    subs.add(childrenMenu);
                    return;
                }
                // 根据二级菜单id获取其下的三级菜单
                List<DmSysMenu> thirdMenus = menuMapper.getMenus(userId, String.valueOf(menu.getMenuId()));
                if (thirdMenus.isEmpty()) {
                    return;
                }
                // 初始化三级菜单列表
                List<MenuInfoModel> thirdSubs = new ArrayList<>();
                thirdMenus.stream().forEach(thirdMenu -> {
                    MenuInfoModel grandsonMenu = new MenuInfoModel();
                    if (Objects.equals(thirdMenu.getLeaf(),CommonConstant.MENU_LEAF)) {
                        // 表示此三级菜单为叶子节点,加入三级菜单列表后直接返回
                        grandsonMenu.setIcon(thirdMenu.getIcon());
                        grandsonMenu.setTitle(thirdMenu.getName());
                        grandsonMenu.setIndex(thirdMenu.getUrl());
                        thirdSubs.add(grandsonMenu);
                    }
                });
                childrenMenu.setSubs(thirdSubs);
                subs.add(childrenMenu);
            });
            model.setSubs(subs);

            res.add(model);
        });
        return res;
    }

说明:此处代码仅支持三级以内的菜单,大于三级的菜单无法显示,使用递归算法抽象方法代码如下:

@Override
    public List<MenuInfoModel> getMenus(Integer userId) {
        List<MenuInfoModel> res = new ArrayList<>(16);
        // 先查出当前登录人能看到的根菜单
        List<DmSysMenu> rootMenus = menuMapper.getMenus(userId, "0");
        rootMenus.stream().forEach(root -> {
            MenuInfoModel model = getModel(root,userId);
            res.add(model);
        });
        return res;
    }

    /**
     * 获取菜单对象
     * @param menu
     * @param userId
     * @return
     */
    private MenuInfoModel getModel(DmSysMenu menu,Integer userId) {
        List<MenuInfoModel> subs = new ArrayList<>(16);
        MenuInfoModel model = new MenuInfoModel();
        model.setIcon(menu.getIcon());
        model.setTitle(menu.getName());
        model.setIndex(menu.getUrl());
        if (Objects.equals(menu.getLeaf(),CommonConstant.MENU_LEAF)) {
            // 如果跟菜单本身就是叶子节点则返回
            return model;
        }
        // 获取下级菜单
        List<DmSysMenu> menus = menuMapper.getMenus(userId, String.valueOf(menu.getMenuId()));
        if (menus.isEmpty()) {
            return model;
        }
        for (DmSysMenu sysMenu : menus) {
            MenuInfoModel childMenu = getModel(sysMenu,userId);
            subs.add(childMenu);
        }
        model.setSubs(subs);
        return model;
    }

  至此,后端的优化改造就完成了,无论几级菜单均可查询封装好

三、前端解决方案

  1.菜单组件leftSide.vue

  

<template>
    <div class="sidebar">
        <el-menu
            class="sidebar-el-menu"
            :default-active="onRoutes"
            :collapse="collapse"
            background-color="#324157"
            text-color="#bfcbd9"
            active-text-color="#20a0ff"
            unique-opened
            router
        >
            <!-- 引入组件 -->
            <menu-tree :menuData="items"></menu-tree>
        </el-menu>
    </div>
</template>

<script>
import bus from '../common/bus';
import {sys} from "../../api/api";
import MenuTree from '../../components/common/MentTree'
export default {
    components: {
        MenuTree
    },
    data() {
        return {
            collapse: false,
            items: []
        };
    },
    mounted : function () {
        sys.getMenus({userName:localStorage.getItem("ms_username")}).then(res => {
            if (res.status == 1) {
                this.$message({
                    message: res.msg,
                    type: "error",
                });
                return;
            }
            this.items = res.obj;
        });
    }
};
</script>

  2.菜单树递归组件 MentTree.vue

<template>
    <div>
        <template v-for="menu in this.menuData">
            <el-submenu :key="menu.index" :index="menu.index" v-if="menu.subs">
                <template slot="title">
                    <i :class="menu.icon"></i>
                    <span slot="title">{{menu.title}}</span>
                </template>
                <menu-tree :menuData="menu.subs"></menu-tree>
            </el-submenu>
            <el-menu-item :key="menu.index" :index="menu.index" v-else>
                <i :class="menu.icon"></i>
                <span slot="title">{{menu.title}}</span>
            </el-menu-item>
        </template>
    </div>
</template>

<script>
    export default {
        props: ['menuData'],
        name: 'MenuTree'
    }
</script>

  这样的话动态菜单的前后端改造也就完成了,我最多配了五层菜单,效果如下:

  

 

posted @ 2021-03-24 12:41  木木他爹  阅读(308)  评论(0编辑  收藏  举报