python + layui.tree 生成树菜单,实现节点的增、删、改

1、渲染tree

表结构

class Class(db.Model):
    __tablename__ = 'class'  # 表名
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)  # 主键
    class_name = db.Column(db.String(200), nullable=False)      # 分类名称、不能为空
    parent_id = db.Column(db.Integer, nullable=False)     # 父级id、不能为空

先写好获取tree数据的方法

function get_class() {  //获取节点数据
   var treeData = [];
   layui.$.ajax({
       url:'/get_class/',  //接口
       type:'get',     //请求方式
       async:false,  //同步请求
       success: function(resut){
           treeData = resut.data;
       }
   });
   console.log('节点数据:',treeData);
   return treeData;
}

这里需要用同步请求async:false,不然会获取不到数据。

渲染tree

layui.use(['tree','layer'],function () {
    var tree=layui.tree;
    tree.render({ //加载tree
        elem:'#class_tree'
        ,data: get_class()
        ,id:'treeId'
        ,showCheckbox: true //开启复选框
    })
})

tree容器:

<div id="class_tree"></div>

tree.render参数选项:

参数 参数说明
elem 指向容器选择器,我这里是指向容器的id
data 要渲染的数据
id 设定实例唯一索引,用于基础方法传参使用
showCheckbox 是开启复选框,开启时为true
edit 开启节点的操作图标,默认 false,目前支持['add', 'update', 'del']
accordion 是否开启手风琴模式,false
onlyIconControl 是否仅允许节点左侧图标控制展开收缩。默认 false(即点击节点本身也可控制)。若为 true,则只能通过节点左侧图标来展开收缩
isJump 是否允许点击节点时弹出新窗口跳转。默认 false,若开启,需在节点数据中设定 link 参数(值为 url 格式)
showLine 是否开启连接线。默认 true,若设为 false,则节点左侧出现三角图标。
text 要自定义各类默认文本 ,text: {undefined defaultNodeName: '未命名',none: '无数据' //数据为空时的提示文本}

后端接口

因为layui.tree接收的数据格式为

[
    {'id': 1,'parent_id': 0, 'title': '一级分类1', 'children': [
        {'id': 2, 'parent_id': 1, 'title': '二级分类1', 'children': [
            {'id': 3, 'parent_id': 2, 'title': '三级分类1', 'children': [
                {'id': 5, 'parent_id': 3, 'title': '四级分类1', 'children': []}, 
                {'id': 6, 'parent_id': 3, 'title': '四级分类2', 'children': []}
            ]}
        ]}, 
        {'id': 9, 'parent_id': 1, 'title': '二级分类2', 'children': []}
    ]}, 
    {'id': 21, 'parent_id': 0, 'title': '一级分类2', 'children': []}, 
]

所以后端代码:

@app.route('/tree/')
def tree():
    return render_template('tree.html')

def build_tree(data,parent_id):
    '''生成树菜单的数据
    :param data: 数据
    :param parent_id: 父级id
    :return:
    '''
    tree = []
    for row in data:
        if row['parent_id'] == parent_id:
            child = build_tree(data, row['id'])
            row['children'] = []
            if child:
                row['children'] += child
            tree.append(row)
    return tree
# 获取节点数据
@app.route('/get_class/',methods=['get'])
def get_class():
    categorys = Class.query.all()
    list = []
    for cla in categorys:
        data = dict(id=cla.id, parent_id=cla.parent_id, title=cla.class_name)
        list.append(data)
    print('list:',list)
    data = build_tree(list, 0)
    print('data:', data)
    return jsonify({'code': 0, 'msg': '获取节点数据成功', 'data': data})

查看页面效果
image.png

2、开启操作图标(添加、编辑、删除)

添加顶级节点

因为tree.render操作图标的添加,只针对一级节点以下的节点添加,所以要先单独写一个添加一级节点
在容器上面增加按钮:

<button class="layui-btn layui-btn-sm layui-btn-radius" lay-demo="add_project" style="margin-left:10px"><i class="layui-icon">&#xe654;</i>添加项目</button>
image.png

增加顶级节点表单代码:

<!--添加顶级节点-->
<div id="add_project" style="display: none;margin-top: 20px;margin-left: 20px">
    <ul id="add_project_form" class="layui-form layui-form-pane" style="margin-right: 15px">
        <li class="layui-form-item">
            <label class="layui-form-label">项目名称</label>
            <div class="layui-input-block">
                <input name="project_name" placeholder="请输入项目名称" autocomplete="off" class="layui-input" lay-verify="required">
            </div>
        </li>
        <li class="layui-form-item" style="text-align: center">
            <div class="layui-input-block">
                <button type="submit" class="layui-btn" lay-submit lay-filter="add_project">立即提交</button>
                <button type="reset" class="layui-btn layui-btn-primary">重置</button>
            </div>
        </li>
    </ul>
</div>

修改js:

layui.use(['tree','layer','util'],function () {
    var $ = layui.$,form=layui.form,tree=layui.tree,util=layui.util;
    tree.render({ //加载tree
        elem:'#class_tree'
        ,data: get_class()
        ,id:'treeId'
        ,showCheckbox: true //开启复选框
        ,edit:['add', 'update', 'del']
    })
    util.event('lay-demo', {  //添加顶级节点
        add_project:function () {
            layer.open({
                type: 1
                , title: '添加项目'
                , area: '400px'
                , shade: 0.4
                , content: $('#add_project')
                , success: function (layero, index) {
                    layero.find('.layui-layer-content').css('overflow', 'visible');
                    form.render().on('submit(add_project)', function(data) {
                        var form_data = {form_data: JSON.stringify(data.field)};
                        console.log('添加项目form_data:', form_data);
                        $.ajax({
                            data: form_data,
                            type: "post",
                            dataType: "JSON",
                            url: '/add_project/',
                            success:function (result) {
                                if (result.code===0){
                                    layer.close(index);
                                    layer.msg(result.msg,{icon: 6});
                                    tree.reload('treeId', {data: get_class()});  //添加成功时,需要重载tree
                                } else {
                                    layer.alert(result.msg,{icon: 5});
                                }
                            }
                        })
                    })
                }
            })
        }
    })
})

后端代码:

# 添加顶级节点
@app.route('/add_project/',methods=['post'])
def add_project():
    data = json.loads(request.form.get('form_data'))
    project_name = data['project_name']
    add_obj = Class(class_name=project_name, parent_id=0)  # 增加的内容
    try:
        db.session.add(add_obj)
        db.session.commit()
        return jsonify({'code': 0, 'msg': '添加顶级节点成功'})
    except Exception as e:
        return jsonify({'code': e, 'msg': '添加失败,请重试'})

开启操作图标(添加、编辑、删除)

tree.render增加代码:

,edit: ['add', 'update', 'del'] //操作节点的图标
,operate: function(obj){
    var type = obj.type;
    var data = obj.data; //得到当前节点的数据
    var elem = obj.elem; //得到当前节点元素
    console.log('elem:',elem,'data:',data)

    if(type === 'add'){  //添加子节点
        $.post('/add_class/',{parent_id: data.id, title: "未命名"},function (result) {
            if (result.code===0){
                layer.msg(result.msg,{icon: 6});
                tree.reload('treeId', {data: get_class()});
            }else if(result.code===1){
               layer.alert(result.msg,{icon: 5});
                tree.reload('treeId', {data: get_class()});
            }else {
                layer.alert(result.msg,{icon: 5})
            }

        })
    }else if(type === 'update'){  //修改节点
        $.post('/edit_class/',{class_id: data.id, title: data.title},function (result) {
            if (result.code===0){
                layer.msg(result.msg,{icon: 6});
                tree.reload('treeId', {data: get_class()});
            }else {
                layer.alert(result.msg,{icon: 5})
            }
        })
    }else if(type === 'del'){  //删除节点
        if(data.children.length!==0){  //该节点有子节点时不允许删除
            layer.alert('请先删除该节点的子节点!!', {icon: 5});
            tree.reload('treeId', {data: get_class()});
        }else {
            $.post('/del_class/',{class_id: data.id},function (result) {
                if (result.code===0){
                    layer.msg(result.msg,{icon: 6});
                    tree.reload('treeId', {data: get_class()});
                }else if(result.code===1) {
                    layer.alert(result.msg,{icon: 5});
                    tree.reload('treeId', {data: get_class()});
                }else {
                    layer.alert(result.msg,{icon: 5})
                }
            })
        }
    }
}

我这里添加节点默认命名为“未命名”,删除节点的时候做了判断,只能删除没有子节点的数据。

后端代码:

# 添加节点
@app.route('/add_class/',methods=['post'])
def add_class():
    parent_id = request.values.get('parent_id')
    title = request.values.get('title')
    class_name = Class.query.with_entities(Class.class_name)

    title_d = []
    for class_n in class_name:
        class_name = class_n.class_name
        title_d.append(class_name)
    if title in title_d:  # 判断“未命名”是否已存在
        class_obj = Class.query.filter_by(class_name=title).all()
        class_id = class_obj[0].id
        return jsonify({'code': 1, 'msg': '你添加的节点 "未命名" 还未修改名称','id':class_id})
    add_obj = Class(class_name=title, parent_id=parent_id)  # 增加的内容
    try:
        db.session.add(add_obj)
        db.session.commit()
        return jsonify({'code': 0, 'msg': '添加节点 “未命名” 成功,请修改节点名称'})
    except Exception as e:
        return jsonify({'code': e,'msg':'添加失败,请重试'})

# 修改节点
@app.route('/edit_class/',methods=['post'])
def edit_class():
    class_id = request.values.get('class_id')
    title = request.values.get('title')
    edit_obj = Class.query.get(class_id)  # 要修改的数据
    try:
        edit_obj.class_name = title
        db.session.commit()
        return jsonify({'code': 0, 'msg': '修改节点成功'})
    except Exception as e:
        return jsonify({'code': e, 'msg': '修改失败,请重试'})

# 删除节点
@app.route('/del_class/',methods=['post'])
def del_class():
    class_id = request.values.get('class_id')
    del_obj = Class.query.get(class_id)  # 要删除的数据
    try:
        db.session.delete(del_obj)
        db.session.commit()
        return jsonify({'code': 0, 'msg': '删除节点成功'})
    except Exception as e:
        return jsonify({'code': e, 'msg': '删除失败,请重试'})

添加节点的时候如果已经存在“未命名”,则会给出提示'你添加的节点 "未命名" 还未修改名称',需要将“未命名”修改名称后才能成功添加节点。

前端js和css都是基于layui-v2.6.8

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,188评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,464评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,562评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,893评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,917评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,708评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,430评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,342评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,801评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,976评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,115评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,804评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,458评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,008评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,135评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,365评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,055评论 2 355

推荐阅读更多精彩内容