Flask+LayUI开发手记(四):弹出层实现增删改查功能

news2024/11/16 11:56:20

       在上一节用dataTable实现数据列表时,已经加了表头工具栏和表内工具栏,栏内的按钮功能都是用来完成数据的增删改查了,这又分成两类功能,一类是删除或设置,这类功能简单,只需要选定记录,然后提交到后端服务进行特定字段的修改即可,另一类是查明细、增加、修改记录,这三类功能,都需要生成一个新的数据编辑界面完成记录全内容的展示,然后输入相应的字段值,提交到后端服务进行相应的数据更新操作,同时,在录入和提交时,还需要对录入字段值进行合规检查。

       编辑功能的前端录入界面是采用layUI-form来实现的,由数据列表界面进入编辑录入界面的方式可以有多种,标准做法是在列表中选定记录后直接跳转到编辑页面,但我还是喜欢用layUI-layer弹出层功能来展示编辑界面,这样列表和编辑界面同时存在,控制上可以做出多种变化,比如编辑界面选择full模式,从外观看就可跳转实现是完全一样的。

       增删改查编辑功能的实现包括三部分,即列表页面下的JS控制实现、编辑页面的实现和后端编辑服务程序的实现,下面一个个列出来。

       第一个 member_list.html.j2,列表页面下的JavaScript实现,实际就是在上一节数据列表的基础上,加入各个按钮的处理实现,其代码如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <title>会员管理</title>
    <link rel="stylesheet" href="/static/layui/css/layui.css"  media="all">
</head>
<body>

<table id="table_list" lay-filter="table_list" style="margin-top:-15px;"></table>

<script type="text/html" id="toolBar">
    <div class="layui-btn-container">
        <div class="layui-inline">
            <label class="layui-btn-sm">会员名称:</label>
            <div class="layui-input-inline">
                <input type="text" id="searchtext" placeholder="请输入名称" autocomplete="off" class="layui-input layui-btn-sm">
            </div>
        </div>
        <div class="layui-inline">
            <div class="layui-input-inline" style="padding-left:10px;padding-top:8px">
                <button id="btn_search" type="button" class="layui-btn layui-btn-normal layui-btn-sm" lay-event="search">
                    <i class="layui-icon layui-icon-search"></i>查询
                </button>
                <button id="btn_add" type="button" class="layui-btn layui-btn-sm" lay-event="add">
                    <i class="layui-icon layui-icon-add-1"></i>增加会员
                </button>
                <button id="btn_mban" type="button" class="layui-btn layui-btn-sm" lay-event="mban">
                    <i class="layui-icon layui-icon-lock"></i>批量封禁
                </button>
           </div>
        </div>
    </div>
</script>

<script type="text/html" id="linetoolBar">
    {% raw %}
    {{# if (d.status == 0 ) { }}
        <a lay-event="ban" title="封禁"><i class="layui-icon layui-icon-lock" style="color:red;"></i></a>
    {{# } if (d.status == 9) { }}
        <a lay-event="unban" title="解禁"><i class="layui-icon layui-icon-ok-circle" style="color:green;"></i></a>
    {{# } }}
    <a lay-event="edit" title="编辑" ><i class="layui-icon layui-icon-edit"></i></a>
    <a lay-envent="rsetpwd" title="重置密码"><i class="layui-icon layui-icon-password"></i></a>
    <a lay-event="del" title="删除"><i class="layui-icon layui-icon-delete" style="color:red;"></i></a>
    {% endraw %}

</script>
<script src="/static/layui/layui.js"></script>
<script>
layui.use(['jquery','layer','table'], function(){
	var layer=layui.layer
        ,$=layui.jquery
        ,table=layui.table;
    var cur_row;  	//初始化表格当前行
    var url_list = '{{url_for("sysadm.member_list")}}';
    var url_edit = '{{url_for("sysadm.member_edit")}}';

    table.render({
            elem: '#table_list'
            ,height: 'full'
            ,url: url_list
            ,toolbar: '#toolBar'
            ,method: 'POST'
            ,page: true //开启分页
            ,limits: [16, 20, 30, 40, 50]           
            ,limit : 16
            ,even : true
            ,size : 'sm'
            ,cols: [[ 
            { type: 'checkbox', fixed: 'left' }
            ,{field: 'id', title: 'ID', width:30, sort: true, fixed: 'left'}
            ,{field: 'username', title: '会员名', width:90, sort: true, fixed: 'left'}
            ,{field: 'nickname', title: '昵称', width:90, sort: true}
            ,{field: 'email', title: '邮箱', width:170, sort: true}
            ,{field: 'sex_name', title: '性别', width:60, sort: true}
            ,{field: 'telephone', title: '电话', width:100, sort: true}
            ,{field: 'role_note', title: '会员角色', width: 100}
            ,{field: 'status_name', title: '状态', width: 30}
            ,{field: 'agent', title: '推荐人', width: 70}
            ,{field: 'regtime', title: '注册时间', width:160} 
            ,{fixed: 'right', width:120, align:'center', toolbar: '#linetoolBar'}
            ]]
    });

    //表头工具栏事件
    table.on('toolbar(table_list)', function (obj) {
        let cpage = obj.config.page.curr;
        console.log(JSON.stringify(obj.config.page))

        switch (obj.event) {
            case 'search':
                table_refresh(1);
                break;
            case 'add':
                cur_row=null;
		        table_edit('add','新增',-1,cpage);
                break;
            case 'mban':
                table_mban(cpage);
                break;
        };
    });

    //table行内工具栏事件
    table.on('tool(table_list)', function (obj) {    //obj是指这张表中的数据
        cur_row = obj.data;
        rid = cur_row.id;
        let cpage = obj.config.page.curr;
        //obj.event:获取触发事件的元素的 event 值,用于区分不同的操作
        switch(obj.event) {
            case 'edit':
                table_edit('upd',"编辑",rid,cpage);
                break;
            case 'del':
                layer.confirm('确认删除会员吗?id:' + rid, {icon: 3, title:'提示'}, function(index){
                    $.post(url_edit + '?opr=del',{id:rid},function(rs){
                        if(rs.success){
                            //调用查询方法刷新数据
                            table_refresh(cpage);
                            layer.msg(rs.msg,function(){});
                        }else{
                            layer.msg(rs.msg,function(){});
                        }
                    },'json');
                    layer.close(index);
                });                
                break;
            case 'ban':
                layer.confirm('确认封禁会员吗?id:' + rid, {icon: 3, title:'提示'}, function(index){
                    $.post(url_edit + '?opr=ban',{id:rid},function(rs){
                        if(rs.success){
                            //调用查询方法刷新数据
                            table_refresh(cpage);
                            layer.msg(rs.msg,function(){});
                        }else{
                            layer.msg(rs.msg,function(){});
                        }
                    },'json');
                    layer.close(index);
                });                
                break;
            case 'unban':
                layer.confirm('确认解禁会员吗?id:' + rid, {icon: 3, title:'提示'}, function(index){
                    $.post(url_edit + '?opr=unban',{id:rid},function(rs){
                        if(rs.success){
                            //调用查询方法刷新数据
                            table_refresh(cpage);
                            layer.msg(rs.msg,function(){});
                        }else{
                            layer.msg(rs.msg,function(){});
                        }
                    },'json');
                    layer.close(index);
                });                
                break;
             case 'resetpwd':
                layer.confirm('确认重置口令吗?id:' + rid, {icon: 3, title:'提示'}, function(index){
                    $.post(url_edit + '?opr=resetpwd',{id:rid},function(rs){
                        if(rs.success){
                            layer.msg(rs.msg,function(){});
                        }else{
                            layer.msg(rs.msg,function(){});
                        }
                    },'json');
                    layer.close(index);
                });                
                break;

        }
    });

    function table_refresh(cpage) {
        table.reload('table_list', {
            where: {                           
                'searchtext':$('#searchtext').val()
            },  
            page: { curr: cpage },
        },true);
    }

    function table_mban(cpage) {
        var checkData = table.checkStatus('table_list').data; //得到选中的数据
        if (checkData.length === 0) {
            layer.msg('请选择数据');
            return false;
        }

        var idArr = [];
        for (var i = 0; i < checkData.length; i++) {
            idArr.push(checkData[i].id);
        }
        layer.confirm('确认批量封禁会员吗?', {icon: 3, title:'提示'}, function(index){
            $.post(url_edit + '?opr=mban',
                {id:idArr.join(',')},function(rs){
                if(rs.success){
                    //调用查询方法刷新数据
                    table_refresh(cpage);
                    layer.msg(rs.msg);
                }else{
                    layer.msg(rs.msg);
                }
            },'json');
            layer.close(index);
        });                
    }

    function table_edit(opr,title,rid,cpage){
        if (opr =='add') url = url_edit;
        else url = url_edit + '?id=' + rid;
        
        layer.open({
            type: 2, 
            title:title,
            area: ['660px', '460px'],
            skin: 'layui-layer-rim',    //样式类名
            content:  url, //编辑页面
            btn:['保存','关闭'],
            yes: function(index, layero){
                table_save(layero,url,opr,cpage);
            },
            btn3: function(index, layero){
                layer.closeAll();
            },
        });
    }

    function table_save(layero,url,opr,cpage) {
        var iframeWin = window[layero.find('iframe')[0]['name']];
        var vform = iframeWin.layui.form;
        //console.log('vform:' + JSON.stringify(vform));
        vform.submit('edit-form',function(data){
            console.log('data:' + JSON.stringify(data));
            $.post(url_edit + '?opr=' + opr,
                data.field,function(rs){
                if(rs.success){
                    layer.closeAll();
                    layer.msg(rs.msg,function(){});
                    table_refresh(cpage);
                }else{
                    layer.msg(rs.msg,function(){});
                }
            },'json');
        });

/*
        var iframeWin = window[layero.find('iframe')[0]['name']];
        var formData = iframeWin.layui.form.val("edit-form");
        //console.log('formData:' + JSON.stringify(formData));

        $.post(url_edit + '?opr=' + opr,formData,function(rs){
            if(rs.success){
                layer.closeAll();
                layer.msg(rs.msg,function(){});
                table_refresh(cpage);
            }else{
                layer.msg(rs.msg,function(){});
            }
        },'json');
*/
    }

});

</script>
</body>
</html>

       这个HTML+JS列表页面,除了数据列表的渲染外,还加入了表头工具栏和行内工具栏的处理程序,表头工具栏主要包括查询、增加和批量删除,行内工具栏包括编辑、删除和重置口令、封禁/解封两个状态设置功能,展示界面如下图:

        JavaScript部分,在table.render()主函数下的两个table.on是编辑功能的总入口,table.on(toolbar(table_list))用以完成表头工具栏的功能实现,table.on(tool(table_list))实现行内工具栏的功能。

        function(obj)的入口参数obj内包含当前选中的记录信息以及table的各种参数信息,可以用console.log()打印出来进行分析,分页控制参数page和limit也在里面有。对前端界面来说,最重要的参数是当前记录的id值,以及当前页数。ID值是编辑功能的记录索引,在编辑程序的每一部分都会用到,当前页数,则用于前端更新完成后的页面刷新,如果这个参数取不到,那每次刷新都会重置到列表第一页,可以说,十分不友好。

       删除以及状态设置功能都不需要录入数据,直接确认后提交到后台服务端即可,编辑功能则统一由table_edit()函数实现,本功能里包括新增和更改功能,实际还有查询明细功能,都是用layer.open打开编辑页面进行记录数据录入。编辑页面程序member_edit.html.j2内容如下:

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>会员编辑</title>
<link rel="stylesheet" href="/static/layui/css/layui.css"  media="all">
</head>
<style>
.layui-form-select dl{
	max-height:150px;
}
</style>
<body>
<div style="padding:10px;">
    <form class="layui-form" lay-filter="edit-form" action="">
        <input type="hidden" id="id" name="id"/>
        <div class="layui-form-item">
            <div class="layui-inline" style="width:47%">
                <label class="layui-form-label required">用户名</label>
                <div class="layui-input-block">
                    <input class="layui-input" id="username" name="username" value="" placeholder="6-15位字母或数字" autocomplete="off"
                        lay-verType="tips" lay-verify="required|username" required/>
                </div>
            </div>
            <div class="layui-inline" style="width:47%">
                <label class="layui-form-label">昵称</label>
                <div class="layui-input-block">
                    <input class="layui-input" id="nickname" name="nickname" value="" placeholder="" autocomplete="off"
                        lay-verType="tips" lay-verify="required" required/>
                </div>
            </div>
        </div>
        <div class="layui-form-item">
            <div class="layui-inline" style="width:47%">
                <label class="layui-form-label required">邮箱</label>
                <div class="layui-input-block">
                    <input class="layui-input" id="email" name="email" value="" placeholder="例如:123@123.com" type="email"
                        lay-verType="tips" lay-verify="required|email" required/>
                </div>
            </div>
            <div class="layui-inline" style="width:47%">
                <label class="layui-form-label">电话</label>
                <div class="layui-input-block">
                    <input class="layui-input" id="telephone" name="telephone" value="" placeholder="例如:13999999999" type="tel"
                        lay-verType="tips" lay-verify="required|phone" required/>
                </div>
            </div>
        </div>
        <div class="layui-form-item">
            <label class="layui-form-label">性别</label>
            <div class="layui-input-block">
                <input type="radio" name="sex" value="1" title="男" checked>
                <input type="radio" name="sex" value="2" title="女"> 
                <input type="radio" name="sex" value="0" title="无" >
            </div>
        </div>
        <div class="layui-form-item">
            <div class="layui-inline" style="width:47%">
                <label class="layui-form-label">类别</label>
                <div class="layui-input-block">
                    <select name="role_cd">
                        <option value="">---请选择---</option>
                    </select>
                </div>
            </div>
            <div class="layui-inline" style="width:47%">
                <label class="layui-form-label">状态</label>
                <div class="layui-input-block">
                    <select name="status">
                        <option value="">---请选择---</option>
                    </select>
                </div>
            </div>

        </div>
    </form>
</div>
<script src="/static/layui/layui.js"></script>
<script>
layui.use(['layer','form','jquery'],function(){
	var $=layui.jquery,
    layer=layui.layer,
	form=layui.form;

		form.verify({
			username: function(value, elem){
				if (!new RegExp("^[a-zA-Z0-9_\u4e00-\u9fa5\\s·]+$").test(value)) {
					return '用户名不能有特殊字符';
				}
				if (/(^_)|(__)|(_+$)/.test(value)) {
					return '用户名首尾不能出现下划线';
				}
				if (/^\d+$/.test(value)) {
					return '用户名不能全为数字';
				}
			},
		});

    initDimension();
    initFormData();

    //由UID从服务器数据库中取出数据作为原始数据
    function initFormData(){
        rscode = null;
        {% if rsdata %}
            rscode = {{ rsdata.success }};
            var rsmsg = '{{ rsdata.msg | safe }}';
            var rsdata = {{ rsdata.data | tojson}};
            if (rscode != null) {
                console.log('rsdata:' + JSON.stringify(rsdata));
                //form.val('edit-form',$.extend({}, rsdata||{}));//将父页面传递的行数据赋值到表单中
                form.val('edit-form',rsdata);
            }
        {% endif %}
    }

    //由后台取出选项条目数据对选项进行动态刷新
    function initDimension() {
        {% if rsdim %}
            var status_dim = {{ rsdim.status_dim | safe }};
            var role_dim = {{ rsdim.role_dim | safe }};

            if (status_dim != null) set_select_option(status_dim,'status');
            if (role_dim != null) set_select_option(role_dim,'role_cd');
            form.render('select'); 
            //set_select_disable('parent_id');
        {% endif %}
    }

    //设置select中的选项条目
    function set_select_option(select_dim,sname) {

        var $select = $('[name="'+ sname + '"]');
  
        $select.empty();
        for (var i = 0; i<select_dim.length; i++ ) {
            option_item = select_dim[i];
            $select.append($('<option>').text(option_item[0] + '_' + option_item[1]).attr('value', option_item[0]));
        }
    }

    //设置select中的树型选项条目
    function set_select_tree(select_dim,sname) {

        var $select = $('[name="'+ sname + '"]');
  
        $select.empty();

        $select.append($('<option>').text('根结点_0').attr('value', 0));
        for (var i = 0; i<select_dim.length; i++ ) {
            option_item = select_dim[i];
            let level = option_item[3];
            let lstr ='├' + '─'.repeat(level)

            $select.append($('<option>').text(lstr + option_item[1] + '_' + option_item[0]).attr('value', option_item[0]));
        }
    }

    function set_select_disable(sname) {
        var $select = $('[name="'+ sname + '"]');
        $select.attr('disabled','disabled');
    }

});

</script>
</body>
</html>

       编辑页面程序的HTML页面部分,主要是一个layui-form表单的配置,在表单定义项里必须定义layui-filter作为表单的唯一标识字,在提交后的处理中都用此标识作为索引。表单输入项的具体配置规则不再多说,这里主要强调一下校验规则。

       实际上表单的校验包括了两种,一种是HTML5自带的校验,比如通过type的设置也可以对数字、密码和电话号码进行检验,必输项可以通过设置required属性进行校验。第二种校验是layui提供的校验功能,通过设置layui-verify和layui-verType来实现校验和校验信息展示。不过这些校验功能都必须由表内submit提交时才有效,采用layer.open()的btn提交时都是无效的。

        如何既用弹出页打开编辑界面并用btn提交,同时又让校验规则生效,在开发手记第二节中已经讲过了,不过用form.submit() 提交只能激活layui自带的校验功能,html5的校验仍然无效,当然,layUI的校验已经对html5的校验形成了全覆盖,所以有这一个生效也够了。

PS:在member_list.html.j2最后有一段注释掉的js程序,是不用form.submit()提交的原始程序,这种模式下校验是无效的,好在,新改的程序,不需要后台服务端做任何修改。       

        var iframeWin = window[layero.find('iframe')[0]['name']];
        var formData = iframeWin.layui.form.val("edit-form");
        //console.log('formData:' + JSON.stringify(formData));
        $.post(url_edit + '?opr=' + opr,formData,function(rs){
            if(rs.success){
                layer.closeAll();
                layer.msg(rs.msg,function(){});
                table_refresh(cpage);
            }else{
                layer.msg(rs.msg,function(){});
            }
        },'json');

        编辑页面程序的JS部分主要是完成页面的初始化工作,包括三部分,一、form.verify()用于设置输入自定义校验规则,二、initDimension()完成对选择项的初始化,三、initFormData完成对输入域初值的初始化工作。

      关于校验,layui本身提供了required必输项校验和六种格式校验(包括phone手机号、email邮箱、url网址、number数字、date日期、identity身份证),这些校验项定义在lay-verify属性里。要注意的是,required和格式校验是并存关系,也就是说如果不设必填约束,那么输入为空时是不进行格式校验的。

       当然,如果是复杂的校验,就需要进行规则自定义了,form.verify()函数中可以自定义各种校验函数,通过正则表达式可以完成所有想要的校验。layui的校验功能足够完备,基本上可以取消后端的普遍校验了,这也解决了俺的一个大问题,就是弹出层模式下,不单前端检验无效,后端基于form类的校验也失效了,现在看,真也不需要恢复了。当然,涉及到数据的验证还是要在后端服务里实现,但那已经属于处理流程的一部分了。

       校验设置上,还有一个lay-verType设置是显示信息方式,不设置这个属性缺省是msg模式,不过我更喜欢tips模式。但似乎这个项是要求每一个输入域都设置,不知道有没有在form项上的统一设置功能。

        第二个,选择项的选项初始化,是编辑界面实现的第二个功能,以前见别人写过的程序,对于选择项,是后台程序里定义一个,前端表单里定义一个,甚至后台数据库里还有一个,只要有改变,就得三个地方改。我是十分不喜欢这种编程风格的,数据就应该只有一个出处,一处修改其它地方自动更新。这块的实现就是由后端生成选项数据,然后传到前端来动态刷新选项。

        选择项的初始化,包括普通select的初始化外,还包括自定义的layui-Tree树型组件的初始化。这块有一个坑,就是按html页面约定只修改option项是无效的,layui的选择域实际上是根据select/option又生成了一个显示层,这个渲染是在页面装入时就做的。所以,初始完option后,必须要用form.render('select')刷新一下才能更新layui新生成的元素。

        第三个是输入域初值的初始化,新增功能是没有初值的,所以这块用不到,编辑和显示明细时都需要对输入域的value值进行初始化。记录的初值提取也有两种模式来实现,一是用列表主页面的数据带过来,也就是table.on里的obj.data的数据作为源,二是到服务端用id重新在数据库里读取记录数据。我觉得第二种更合适一些,毕竟列表界面的数据和实际数据库数据可能出现不一致的情况。并且对于flask,似乎也不支持直接页面跳转,横竖都要到后端走一遭,不如顺带读取一下数据,反正按主键id读取记录也不怎么耗资源。

       将数据初始化到表单里用的是form.val()来实现的。这块也有一个坑,就是特定元素,实际上也有一个同样的函数,比如$('#email‘).val()也可以实现value值的初始化,但千万不要这么用,这个函数是jquery提供的,虽然在layui中javascript原生函数、jquery函数和layUI-API可以混用,但在某些地方还真有些区别,比如这个初始化赋值,无论是整体表单赋值还是单个输入域赋值,都要用form.val()来做。

       特别指出一下,别的类型的输入项用elem.val()也没啥问题,就是radio单选项的初值设定,用元素操作设置checked属性是不生效的,必须用form.val()才行。 

       前端的实现基本完成了,下面就是后端服务的实现,这块无论是增删改查,我都集成一个函数入口中完成。这种模式可能在权限管理上有些问题,不过集中控制分支实现一直是我喜欢的编程模式,这样可以在总入口程序中统一处理一些校验功能。后端服务程序如下:

@bp.route('/member_edit/',methods=['GET','POST'])
@login_required
def member_edit():
    if request.method == 'GET':
        udim = {
            'status_dim': json.dumps(Member_Status().get_list()),
            'role_dim' : json.dumps(Member_Role().get_list())
        }
        uid= request.values.get('id')
        if uid == None:
            return render_template('admin/member_edit.html.j2',rsdim=udim)
        else:
            irow= db.session.query(Members).filter_by(uid=uid).first()
            udata = dict(id=irow.uid,username=irow.username,email=irow.email,avatar=irow.avatar,
                        nickname=irow.nickname,sex=irow.sex,telephone=irow.telephone,agent=irow.agent,
                        regtime=irow.regtime.strftime('%Y-%m-%d %H:%M:%S'),
                        status=irow.status,role_cd=irow.role_cd)
            rsdata = {
                "success": 1,
                "msg": "取会员数据成功",
                "data":udata
            }
            return render_template('admin/member_edit.html.j2',rsdata=rsdata,rsdim=udim)
    else :
        opr = request.values.get('opr')
        logging.debug('oprmode: ' + opr)
        uid = request.values.get('id')
        try : 
            if opr == 'add' :
                rs_data = member_add()
            elif opr == 'upd' :
                rs_data = member_update(uid)
            elif opr == 'del' :
                rs_data = member_delete(uid)
            elif opr == 'ban' :
                rs_data = member_ban(uid)
            elif opr == 'unban' :
                rs_data = member_unban(uid)
            elif opr == 'mban' :
                rs_data = member_mban(uid)
            else :
                rs_data = member_reset_passwd(uid)
        except SQLAlchemyError as e:
            db.session.rollback()
            rs_data = {
                'success':0,
                'msg':'更新会员记录失败:' + str(e.orig),
                'status':200
            }
        return json.dumps(rs_data)

#新增操作员    
def member_add():
    logging.debug('Add Member ....')
    username = request.values.get('username')
    nickname = request.values.get('nickname')
    email = request.values.get('email')
    telephone = request.values.get('telephone')
    avatar = request.values.get('avatar')
    role_cd = request.values.get('role_cd')
    sex = request.values.get('sex')
    status = request.values.get('status')
    rawpass = config.PASSWORD_INITIAL
    useradd = Members(username=username,password=rawpass,email=email,status=status,sex=sex,
                      role_cd=role_cd,nickname=nickname,telephone=telephone,avatar=avatar)
    useradd.password=rawpass
    db.session.add(useradd)
    db.session.commit()
    rs_data = {
        'success':1,
        'msg':'增加会员成功',
        'status':200
    }
    return rs_data

#修改操作员
def member_update(uid):
    logging.debug('Update Member %s....' % uid)

    irow= db.session.query(Members).filter_by(uid=uid).first()
    irow.username = request.values.get('username')
    irow.nickname = request.values.get('nickname')
    irow.email = request.values.get('email')
    irow.telephone = request.values.get('telephone')
    irow.avatar = request.values.get('avatar')
    irow.sex = request.values.get('sex')
    irow.role_cd = request.values.get('role_cd')
    irow.status = request.values.get('status')
    db.session.commit()
    rs_data = {
        'success':1,
        'msg':'修改会员信息成功' + uid,
        'status':200
    }
    return rs_data

#删除会员
def member_delete(uid):
    logging.debug('Member del ' + uid)
    irow= db.session.query(Members).filter_by(uid=uid).first()
    db.session.delete(irow)
    db.session.commit()
    rs_data = {
        'success':1,
        'msg':'删除会员成功' + uid,
        'status':200
    }
    return rs_data

#批量删除会员==保留
def member_mdelete(ridstr):
    logging.debug('Member muli delete ' + ridstr)
    ridlist = list(map(int,ridstr.split(',')))
    logging.debug('Ban %s...' % str(ridlist))
    rows = db.session.query(Members).filter(Members.uid.in_(ridlist)).all()
    for irow in rows:
        db.session.delete(irow)
    db.session.commit()
    rs_data = {
        'success':1,
        'msg':'删除会员成功' + ridstr,
        'status':200
    }
    return rs_data

def member_reset_passwd(uid):
    logging.debug('Member reset password ' + uid)
    irow= db.session.query(Members).filter_by(uid=uid).first()
    irow.password = config.PASSWORD_INITIAL
    db.session.commit()
    rs_data = {
        'success':1,
        'msg':'重置会员密码成功' + uid,
        'status':200
    }
    return rs_data

#封禁会员
def member_ban(rid):
    logging.debug('Member ban ' + rid)
    db.session.query(Members).filter_by(uid=rid).update({Members.status:9})
    db.session.commit()
    rs_data = {
        'success':1,
        'msg':'封禁会员成功' + rid,
        'status':200
    }
    return rs_data

#封禁会员
def member_unban(rid):
    logging.debug('Member ban ' + rid)
    db.session.query(Members).filter_by(uid=rid).update({Members.status:0})
    db.session.commit()
    rs_data = {
        'success':1,
        'msg':'解禁会员成功' + rid,
        'status':200
    }
    return rs_data

#批量封禁会员
def member_mban(ridstr):
    logging.debug('Member muli ban ' + ridstr)
    ridlist = list(map(int,ridstr.split(',')))
    logging.debug('Ban %s...' % str(ridlist))
    rows = db.session.query(Members).filter(Members.uid.in_(ridlist)).all()
    for irow in rows:
        irow.status = 9
    db.session.commit()
    rs_data = {
        'success':1,
        'msg':'批量封禁会员成功' + ridstr,
        'status':200
    }
    return rs_data

       后端服务入口的路由命名为"member_edit",分为GET和POST两部分,GET部分对应layer.open()打开页面部分的页面和数据准备,包括代码维信息的准备以及编辑记录数据的准备,作为返回数据的两部分随页面渲染功能下传。

       POST部分对应列表页面js处理中的各类post()请求,根据post请求的opr来区分功能进入各自的数据处理函数,包括增加、更改、删除这些基础功能,也包括各种业务处理。这部分程序简单,就不再深入展开阐述了。

       应该说,写到这块,有一个最深切的对python的期盼,就是希望python能够实现switch功能,这许多的功能分支,用if-elif-else简直太LOW了。查了一下,python 3.10版本之后已经提供了类switch分支的语法,叫match,不过,既然这个功能提供不久,为了稳定性,还是先用if-else来实现吧。

       增删改查的功能实现到这里就基本完成了,下面加几个功能界面,作为本节的结束吧。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2073451.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Flutter 自动化测试 - 集成测试篇

Flutter集成测试 Flutter官方对Flutter应用测试类型做了三个阶段划分&#xff0c;分别为Unit&#xff08;单元&#xff09;测试、Widget&#xff08;组件&#xff09;测试、Integration&#xff08;集成&#xff09;测试。按照维护成本来看的话从左到右依次增高&#xff0c;按照…

预测癌症免疫治疗反应-TIDE数据库学习及知识整理

TIDE&#xff08;Tumor Immune Dysfunction and Exclusion&#xff09; 是一个用于预测癌症患者对免疫检查点抑制剂&#xff08;如PD-1/PD-L1抑制剂&#xff09;反应的算法。研究者通过检测肿瘤建模队列中每个基因的表达与效应性毒性T淋巴细胞(CTL)浸润水平的相互关系及对生存情…

Open3D 近似点体素滤波(36)

Open3D 近似点体素滤波(36) 一、算法介绍二、算法实现1.代码2.效果一、算法介绍 这个算法也是体素滤波, 它保留的点是近似点,也就是新的点,原始点云中对应位置是不存在这些点的。其他的看着类似,下面是代码,滤波抽稀结果 二、算法实现 1.代码 代码如下(示例): …

学习文件IO,让你从操作系统内核的角度去理解输入和输出(Java实践篇)

本篇会加入个人的所谓鱼式疯言 ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人…

【在Linux世界中追寻伟大的One Piece】应用层协议HTTP

目录 1 -> HTTP协议 2 -> 认识URL 2.1 -> urlencode和urldecode 3 -> HTTP协议请求与响应格式 3.1 -> HTTP请求 3.2 -> HTTP响应 4 -> HTTP的方法 4.1 -> HTTP常见方法 5 -> HTTP的状态码 6 -> HTTP常见Header 7 -> 最简单的HTTP服…

Linux系统报错“version ‘GLIBC_2.34‘ not found”解决方法

注意&#xff0c;此文章慎用&#xff0c;glibc不可随意升级&#xff0c;可能导致系统崩溃 一、查看版本 ldd --version 二、添加高版本源 sudo vi /etc/apt/sources.list.d/my.list 进入编辑页面 "i"键进入插入模式 输入源 deb http://th.archive.ubuntu.com/…

【信创】推荐一款超级好用的文件同步备份工具 _ 统信 _ 麒麟 _ 方德

往期好文&#xff1a;【信创】统信UOS打包工具介绍与使用教程 Hello&#xff0c;大家好啊&#xff01;今天给大家推荐一款在Linux系统上超级好用的文件同步和备份工具——FreeFileSync。无论是在日常工作还是数据管理中&#xff0c;文件同步和备份都是至关重要的任务。FreeFile…

【自动驾驶】控制算法(五)连续方程离散化与离散LQR原理

写在前面&#xff1a; &#x1f31f; 欢迎光临 清流君 的博客小天地&#xff0c;这里是我分享技术与心得的温馨角落。&#x1f4dd; 个人主页&#xff1a;清流君_CSDN博客&#xff0c;期待与您一同探索 移动机器人 领域的无限可能。 &#x1f50d; 本文系 清流君 原创之作&…

QT6 setCentralWidget 和 takeCentralWidget

qt6 中&#xff0c;初始化界面完成之后&#xff0c;可以使用setCentralWidget 设置当前的widget为中心页面 如果你存在多个widget想要多个切换 如果存在widget1 和 widget2 在初始化的时候 setCentralWidget(widget1)触发操作切换到 widget2 如果没有先takeCentralWidget 直…

13.深入解析ThreadPoolExecutor线程池

ThreadPoolExecutor线程池 线程池简介线程池的使用创建线程池ThreadPoolExecutor——推荐使用线程池的核心参数 Executors——不推荐使用 提交任务如何执行批量任务如何执行定时、延时任务如何执行周期、重复性任务 关闭线程池线程池的参数设计分析核心线程数(corePoolSize)最大…

EEMD-MPE-KPCA-BiLSTM、EEMD-MPE-BiLSTM、EEMD-PE-BiLSTM故障识别、诊断(Matlab)

EEMD-MPE-KPCA-BiLSTM(集合经验分解-多尺度排列熵-核主元分析-双向长短期网络)故障识别、诊断&#xff08;Matlab) 目录 EEMD-MPE-KPCA-BiLSTM(集合经验分解-多尺度排列熵-核主元分析-双向长短期网络)故障识别、诊断&#xff08;Matlab)效果一览基本介绍程序设计参考资料 效果一…

RK3588人工智能开发----【1】初识NPU

NPU 的诞生&#xff01; 随着人工智能和大数据时代的到来&#xff0c;传统嵌入式处理器中的CPU和GPU逐渐无法满足日益增长的深度学习需求。为了应对这一挑战&#xff0c;在一些高端处理器中&#xff0c;NPU&#xff08;神经网络处理单元&#xff09;也被集成到了处理器里。NPU的…

【GNSS射频前端】MA2769初识

MAX2769 芯片概述&#xff1a; MAX2769是一款单芯片多系统GNSS接收器&#xff0c;采用Maxim的低功耗SiGe BiCMOS工艺技术。集成了包括双输入低噪声放大器&#xff08;LNA&#xff09;、混频器、图像拒绝滤波器、可编程增益放大器&#xff08;PGA&#xff09;、压控振荡器&#…

note38:tdsql数据库迁移

数据迁移过程中遇到的具体问题&#xff1a; ①提供给系统团队的表结构与生产不一致&#xff0c;导致脚本报错。因为历史遗留问题&#xff0c;存在部分直接在生产环境更改字段长度或添加索引的情况&#xff0c;导致测试环境和生产环境的表结构不同步。 今后所有生产的变动&…

Vulkan 学习(5)---- Vulkan 内存分配

目录 Overview枚举内存信息分配内存内存映射 Overview Vulkan 将内存管理的工作交给了开发者自己负责&#xff0c;如何分配内存&#xff0c;如何指定内存策略都是由开发者自己决定的&#xff0c;当然处理问题也是由开发者自己负责的 Vulkan 将内存划分为两大类&#xff1a;主…

Android自定义简单仿QQ运动步数进展圆环

实现效果主要效果分为三个部分&#xff1a; 1.固定蓝色的大圆弧 color borderWidth 2.可以变化的小圆弧(红色) color borderWidth 3.中间的步数文字 color textSize drawArc方法 startAngle 确定角度的起始位置 sweepAngle 确定扫过的角度 useCenter 是否使用中心&#xff1a…

MyBatis XML配置文件(下)

MyBatis的开发有两种方式&#xff1a;1、注解 2、XML。使用MyBatis的注解方式&#xff0c;主要是来完成一些简单的增删改查功能。如果需要实现复杂的SQL功能&#xff0c;建议使用XML来配置映射语句&#xff0c;也就是将SQL语句写在XML配置文件中。 MyBatis XML开发的方式需要以…

UE5学习笔记17-让人物的视线和鼠标移动时的方向一致,并且不让人物模型旋转,只改变视线方向

一、创建标准动画帧 1.我想让人物在装备武器后根据鼠标的移动方向改变人物的视线方向&#xff0c;并且人物模型不会改变朝向 2.我的动画中存在一个四个方向瞄准的动画&#xff0c;将左下&#xff0c;坐上&#xff0c;左转&#xff0c;右上&#xff0c;右下&#xff0c;右转&…

C++ 设计模式——组合模式

C 设计模式——组合模式 C 设计模式——组合模式1. 主要组成成分2. 逐步构建透明组合模式1. 定义抽象组件&#xff08;Component&#xff09;2. 实现叶子组件&#xff08;Leaf&#xff09;3. 实现组合组件&#xff08;Composite&#xff09;4. 主函数&#xff08;Main&#xff…

Nacos踩坑

最近遇到项目部署&#xff0c;遇到Nacos中的配置读取不到&#xff0c;导致服务起不来。服务器银河麒麟x86&#xff0c;Nacos版本2.3.2, openJdk8u43 报错如下&#xff1a; java.lang.UnsatisfiedLinkError: no com_alibaba_nacos_shaded_io_grpc_netty_shaded_netty_transport…