具体方法实现

实现树形菜单,本文将给出两种实现方式。

  1. 针对Layui前端模板EasyWeb iframe未实现的添加\修改\删除节点的功能,这里给出与后端实时同步的前端页面刷新方式实现节点的增删改。
  2. 基于Layui扩展的Dtree包,直接实现不依赖于后端的节点增删改,即只是前端样式更改,不会更改后端存放的.json数据。

方法一:针对Layui模板的前后端统一更新

PS:我这里使用MongoDB实现json文件的存储,当然也可以将json文件直接存储在文件系统中。文中所有数据均为json格式。
实现效果图:
在这里插入图片描述
数据库存储格式(data中存放表结构,name存放用户索引):
在这里插入图片描述

当进行增删改操作时,json文件的封装在前端完成,通过Ajax请求将封装好的Json数据作为字符串数组传到后端,后端将其存入数据库。
每次增删改操作完成后,均会由insTb.reload再次调用该接口实现后端数据后,前端显示与后端数据的实时同步。

//前端通过Layui-table直接请求数据库数据
layui.use(['layer', 'form', 'admin', 'treeTable', 'util', 'xmSelect'], function () {
        var $ = layui.jquery;
        var layer = layui.layer;
        var form = layui.form;
        var admin = layui.admin;
        var treeTable = layui.treeTable;
        var util = layui.util;
        var xmSelect = layui.xmSelect;
        var tbDataList = [];
        // 渲染表格
        var insTb = treeTable.render({
            elem: '#authoritiesTable',
            // url: '/iframe/test.json',
            url: '/request/getMongoJson?name=user1',
            toolbar: ['<p>',
                '<button lay-event="add" class="layui-btn layui-btn-sm icon-btn"><i class="layui-icon">&#xe654;</i>添加</button>&nbsp;',
                '<button lay-event="del" class="layui-btn layui-btn-sm layui-btn-danger icon-btn"><i class="layui-icon">&#xe640;</i>删除</button>',
                '</p>'].join(''),
            tree: {
                iconIndex: 2,
                idName: 'authorityId',
                pidName: 'parentId',
                isPidData: true,
                arrowType: 'arrow2',
                getIcon: 'ew-tree-icon-style2'
            },
            cols: [
                [
                // {type: 'checkbox'},
                {type: 'numbers'},
                {
                    title: '类型', templet: function (d) {
                        return [
                            '<span class="layui-badge layui-badge-green">菜单</span>',
                            '<span class="layui-badge layui-badge-gray">文件</span>'
                        ][d.isMenu];
                    }, align: 'center', width: 80
                },
                {field: 'authorityName', title: '文件名称<i class=\"layui-icon\">&#xe642;</i>', minWidth: 150,edit:'text'},
                {field: 'menuUrl', title: '菜单url'},
                {field: 'authority', title: '权限标识'},
                {field: 'orderNumber', title: '排序号', align: 'center', width: 80},

                {title: '创建时间', templet: '<p>{{layui.util.toDateString(d.createTime)}}</p>', align: 'center'},
                {title: '操作', toolbar: '#authoritiesTbBar', align: 'center', width: 190}
            ]
            ],
            done: function (data) {
                tbDataList = data;
            }
        });
	//后端根据用户name值查询并获取MongoDB中的数据,封装为Json传回前端
	@ResponseBody
    @RequestMapping(value = "/getMongoJson", method = RequestMethod.GET)
    public String getMongoJson(String name){
        Query query = new Query(Criteria.where("name").is(name));
//        query.fields().exclude("_id").include("data");
        List<Map> list = mongoTemplate.find(query, Map.class,  "User");
        Gson gson = new Gson();
        String res = "";
        if (list.size() != 0)
            res = gson.toJson(list.get(0));
        System.out.println(res);
//        FindIterable<Map> documents = mongoTemplate.getCollection("User").find(bson,Map.class);
//        for (Map document : documents) {
//            System.out.println(document);
//        }
        return res;
    }

1. 删除

删除一个节点,我们需要考虑两点:一是删除一个目录节点之后,对应子菜单以及子节点的删除;二是删除后,对后续节点ID值以及其parentID的更新。本文将通过队列进行整棵树的遍历操作,实现对应增删改操作。

        /* 删除 */
        function doDel(obj) {
            if(obj.data.authorityId != 1){
                layer.confirm('确定要删除选中数据吗?', {
                    skin: 'layui-layer-admin',
                    shade: .1
                }, function (i) {
                    layer.close(i);
                    var loadIndex = layer.load(2);
                    var optiondata = $.extend(true, {}, insTb.options.data)
                    var tree = insTb.options.data[0]              //insTb.options.data中存放着整个树
                    var delNode = obj.data.authorityId              //delNode记录待删除的节点号
                    var delParent
                    var delLength = 1                               //记录一共删除的节点个数
                    var mark = false
                    // console.log(tree)
                    let node = []
                    let res = []
                    node.push(tree)
                    while (node.length != 0) {                        //使用一个队列实现遍历整棵树
                        var temp = node.shift()
                        if (node.length != 0)
                            var temp2 = node[0]
                        else
                            var temp2 = temp
                        if (temp.authorityId != delNode) {
                            if (mark) {
                                temp.authorityId -= delLength
                                temp.orderNumber -= delLength
                                if (temp.parentId > delParent)
                                    temp.parentId -= delLength
                            }
                            res.push(temp)
                        }
                        if (temp.authorityId == delNode && !mark) {           //如果当前节点是待删除根节点,判断其是否为目录,删除其所有子节点并将其标记为-2
                            // console.log(delNode)
                            if (temp.isMenu == 0) {                  //并计算出删除的总结点数
                                delLength += (temp2.authorityId - temp.authorityId - 1);    //删除的节点数就是它自己加上它的子节点(下一个同级目录与它的序号差值)
                                temp.authorityId = -2
                                delete temp.children
                            } else {
                                temp.authorityId = -2
                            }
                            mark = true
                            delParent = temp.parentId
                        }
                        if (temp.children != undefined)                  //继续向后遍历
                            for (var i = temp.children.length - 1; i >= 0; i--) {
                                node.unshift(temp.children[i])
                            }
                    }

                    for (var i = 0; i < res.length; i++) {
                        delete res[i].children
                        delete res[i].LAY_INDEX
                        delete res[i].open
                        res[i]['open'] = true
                        res[i] = JSON.stringify(res[i])
                    }
                    console.log(res)
                    $.ajax({
                        url: 'request/changeMongoJson',
                        type: 'POST',
                        async: false,
                        traditional: true,
                        data: {"res": res, "name": "user1"},
                        success: function (response) {
                            //请求成功后执行的代码
                            // insTb.options.data = optiondata
                            // obj.del()
                            res = []
                            node = []
                            layer.close(loadIndex);
                            layer.msg("删除成功", {icon: 1});
                            insTb.options.data = optiondata
                            insTb.refresh();
                        },
                        error: function (status) {
                            //失败后执行的代码
                            // alert("fail")
                            // layer.close(loadIndex);
                            // layer.msg("无法删除根目录", {icon: 2});
                            // insTb.refresh();
                        }
                    })
                 });
            }
            else
                layer.msg("无法删除根目录", {icon: 2});
        }

2. 添加

添加操作相对简单于删除,只需将新增节点添加到其父节点的下一个,并将后续节点的ID以及parentID向后移动一位(+1)。

function doAdd(obj){
            mData = obj.data
            if(obj.data.isMenu == 0)
                admin.open({
                    type: 1,
                    area: '600px',
                    title: '新建文件',
                    content: $('#authoritiesEditDialog').html(),
                    success: function (layero, dIndex) {
                        // 回显表单数据
                        form.val('authoritiesEditForm', mData);
                        // 表单提交事件
                        form.on('submit(authoritiesEditSubmit)', function (data) {
                            data.field.parentId = insXmSel.getValue('valueStr');
                            var newNode = new Object();
                            newNode["authorityId"] = parseInt(data.field.authorityId) + 1
                            newNode["orderNumber"] = parseInt(data.field.authorityId) + 1
                            newNode["authorityName"] = data.field.authorityName
                            newNode["authority"] = data.field.authority
                            newNode["isMenu"] = parseInt(data.field.isMenu)
                            newNode["menuUrl"] = data.field.menuUrl
                            newNode["parentId"] = parseInt(data.field.parentId)
                            newNode["menuIcon"] = null
                            Date.prototype.Format = function (fmt) { // author: meizz
                                var o = {
                                    "M+": this.getMonth() + 1, // 月份
                                    "d+": this.getDate(), // 日
                                    "h+": this.getHours(), // 小时
                                    "m+": this.getMinutes(), // 分
                                    "s+": this.getSeconds(), // 秒
                                    "q+": Math.floor((this.getMonth() + 3) / 3), // 季度
                                    "S": this.getMilliseconds() // 毫秒
                                };
                                if (/(y+)/.test(fmt))
                                    fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
                                for (var k in o)
                                    if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
                                return fmt;
                            }
                            newNode["createTime"] = new Date().Format("yyyy-MM-dd hh:mm:ss")
                            newNode["updateTime"] = new Date().Format("yyyy-MM-dd hh:mm:ss")
                            newNode["open"] = true
                            // console.log(newNode)
                            var tree = insTb.options.data[0]            //tt.options.data中存放着整个树
                            var addNode = newNode.authorityId              //delNode记录待添加的节点号
                            // console.log(insTb)
                            let node = []
                            let res = []
                            var nodeNum = 0;
                            node.push(tree)
                            while(node.length != 0) {                        //使用一个队列实现遍历整棵树
                                // if(addNode > )
                                nodeNum = nodeNum + 1;
                                var temp = node.shift()
                                // console.log(temp)
                                var tempcopy = new Object()
                                if(temp.authorityId == addNode) {
                                    res.push(newNode)
                                }
                                if(temp.authorityId >= addNode) {       //待添加节点会增加到其父节点的第一个子节点(父节点编号10,则新增节点编号11)
                                    tempcopy["authorityId"] = temp.authorityId + 1      //所以所有编号在10之后的向后一位
                                    tempcopy["orderNumber"] = temp.authorityId + 1
                                } else {
                                    tempcopy["authorityId"] = temp.authorityId
                                    tempcopy["orderNumber"] = temp.authorityId
                                }
                                tempcopy["authorityName"] =temp.authorityName
                                tempcopy["authority"] = temp.authority
                                tempcopy["isMenu"] = temp.isMenu
                                tempcopy["menuUrl"] = temp.menuUrl
                                if(temp.parentId >= addNode) tempcopy["parentId"] = temp.parentId + 1       //同理,对应的父节点也向后一位
                                else tempcopy["parentId"] = temp.parentId
                                tempcopy["menuIcon"] = null
                                tempcopy["createTime"] = temp.createTime
                                tempcopy["updateTime"] = temp.updateTime
                                tempcopy["open"] = true
                                res.push(tempcopy)
                                // console.log(temp)
                                if(temp.children != undefined)                  //继续向后遍历
                                    for(var i = temp.children.length - 1; i >= 0; i--){
                                        node.unshift(temp.children[i])
                                    }
                            }
                            if(nodeNum < addNode)
                                res.push(newNode)
                           //  console.log(tree)
                           // console.log(res)
                            for(var i = 0; i < res.length;i ++){
                                res[i] = JSON.stringify(res[i])
                            }
                            console.log(res)
                            var loadIndex = layer.load(2);
                            $.ajax({
                                url:'request/changeMongoJson',
                                type:'POST',
                                async: false,
                                traditional: true,
                                data:{"res": res, "name": "user1"},
                                success:function(response){
                                    res = []
                                    node = []
                                    layer.close(dIndex);
                                    layer.close(loadIndex)
                                    layer.msg("添加成功", {icon: 1});
                                    insTb.refresh();
                                },
                                error:function(status){
                                    //失败后执行的代码
                                    // alert("fail")
                                }
                            })
                            return false;
                        });
                        // 渲染下拉树
                        var insXmSel = xmSelect.render({
                            el: '#authoritiesEditParentSel',
                            height: '0px',
                            data: insTb.options.data,
                            initValue: mData ? [mData.authorityId] : [],
                            model: {label: {type: 'text'}},
                            prop: {
                                name: 'authorityName',
                                value: 'authorityId'
                            },
                            radio: true,
                            clickClose: true,
                            tree: {
                                show: true,
                                indent: 15,
                                strict: false,
                                expandedKeys: true
                            }
                        });
                        // 弹窗不出现滚动条
                        $(layero).children('.layui-layer-content').css('overflow', 'visible');
                    }
                });
            else
                layer.msg("这不是一个菜单", {icon: 2});
        }

3. 后端

后端只需接收相应的字符串数组,将其封装为标准的Json格式并写入MongoDB数据库中。

    @RequestMapping(path = "/changeMongoJson", method = RequestMethod.POST)
    @ResponseBody
    public String changeMongoJson(String[] res, String name){
        for (String s:res
             ) {
            System.out.println(s);
        }
        int len = res.length;
        StringBuilder sb = new StringBuilder();
        sb.append("{\n" +
                "  \"code\": 0,\n" +
                "  \"msg\": \"\",\n" +
                "  \"count\": "+ len +",\n" +
                "  \"name\": \""+ name +"\",\n" +
                "  \"data\": [");
        for(String temp : res){
            sb.append(temp + ",");
        }
        sb.deleteCharAt(sb.length() - 1);
        sb.append(" ]\n" +
                "}");

        Query query = new Query(Criteria.where("name").is("user1"));
//        query.fields().exclude("_id").include("data");
//        Update update = new Update();
//        update.set("count", res.length);
//        update.push("data", res);
        mongoTemplate.remove(query, Map.class,  "User");
//        System.out.println(1);
//        System.out.println(sb);
        mongoTemplate.insert(sb.toString(), "User");
//        System.out.println(sb);
//        Gson gson = new Gson();
//        TreeNode temp = gson.fromJson(res, TreeNode.class);
        return "iframe/index.html";
    }

方法二:基于Dtree实现的纯前端树形增删改

这个方法对后端访问频率低,在增删改时直接进行前端树样式的变换,即不是依靠先存入后端,再从后端读数据的模式,减少了数据库访问量。
实现效果图:
在这里插入图片描述
Dtree组件已经给出了前端修改的具体方法,只需调用对应方法,即可以实现树结构变更:

  1. 改–DTree1.changeTreeNodeEdit(true);
  2. 增–DTree1.changeTreeNodeAdd(treeNode.nodeId);
  3. 删–DTree1.changeTreeNodeDel(true);
<script type="text/javascript">
    layui.extend({
        dtree: '../assets/module/dtree/dtree'
    }).use(['element','layer', 'table', 'code' ,'util', 'dtree', 'form'], function(){
        var element = layui.element, layer = layui.layer, table = layui.table, util = layui.util, dtree = layui.dtree, form = layui.form, $ = layui.$;
        var olddata;
        // $.ajax({
        //     async: false,
        //     url: 'iframe/test2.json',
        //     success: function(res){
        //         olddata = res;
        //         console.log(olddata)
        //     }
        // })
        var DTree1 = dtree.render({
            async: false,
            elem: "#demoTree",
            method: "GET",
            url: 'iframe/test2.json',
            // data: data,
            toolbar:true,
            scroll:"#toolbarDiv",
            dataFormat: "list",  //配置data的风格为list
            toolbarFun: {
                addTreeNode: function(treeNode, $div){
                    console.log(treeNode)
                    var id = treeNode.nodeId.replaceAll("_node_", "00")
                    var title = treeNode.context
                    var parentId = treeNode.parentId.replaceAll("_node_", "00")
                    $.ajax({
                        type: "post",
                        traditional: true,
                        data: {"status": "add", "filename": "test2","id": id, "title": title, "parentId": parentId},
                        url: "request/changenewJson",
                        success: function(result){
                            DTree1.changeTreeNodeAdd(treeNode.nodeId); // 添加成功,返回ID
                            // DTree1.changeTreeNodeAdd(true); // 添加成功
                            // DTree1.changeTreeNodeAdd(result); // 添加成功,返回一个JSON对象
                            // DTree1.changeTreeNodeAdd("refresh"); // 添加成功,局部刷新树
                        },
                        error: function(){
                            DTree1.changeTreeNodeAdd(false); // 添加成功,返回ID
                        }
                    });
                },
                editTreeNode: function(treeNode, $div){
                    console.log(treeNode)
                    if (treeNode.nodeId === "000") {
                        layer.msg("根目录无法修改" , {icon: 2})
                        DTree1.changeTreeNodeEdit(false);//修改失败
                        return
                    }
                    var id = treeNode.nodeId.replaceAll("_node_", "00")
                    var title = treeNode.context
                    var parentId = treeNode.parentId.replaceAll("_node_", "00")
                    $.ajax({
                        type: "post",
                        traditional: true,
                        data: {"status": "edit", "filename": "test2","id": id, "title": title, "parentId": parentId},
                        url: "request/changenewJson",
                        success: function(result){
                            DTree1.changeTreeNodeEdit(true);// 修改成功
                            //DTree1.changeTreeNodeEdit(result.param); // 修改成功,返回一个JSON对象
                        },
                        error: function(){
                            DTree1.changeTreeNodeEdit(false);//修改失败
                        }
                    });
                },
                delTreeNode: function(treeNode, $div){
                    console.log(treeNode)
                    if (treeNode.nodeId === "000") {
                        layer.msg("根目录无法删除" , {icon: 2})
                        DTree1.changeTreeNodeDel(false);// 删除失败1
                        return
                    }
                    var id = treeNode.nodeId.replaceAll("_node_", "00")
                    var title = treeNode.context
                    var parentId = treeNode.parentId.replaceAll("_node_", "00")
                    $.ajax({
                        type: "post",
                        traditional: true,
                        data: {"status": "del", "filename": "test2","id": id, "title": title, "parentId": parentId},
                        url: "request/changenewJson",
                        success: function(result){
                            DTree1.changeTreeNodeDel(true); // 删除成功
                        },
                        error: function(){
                            DTree1.changeTreeNodeDel(false);// 删除失败
                        }
                    });
                }
            }
        });

        dtree.on("node(demoTree)", function(obj){
            layer.msg(JSON.stringify(obj.param));
        })
    });
</script>

文中的组件地址

本文给出两种树形表格的实现方式,前者是基于EasyWeb-iframe框架实现;后者是基于Dtree组件实现。
EasyWeb-iframe下载地址:easyweb
Dtree组件下载地址:Dtree

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐