当前位置: 首页 > news >正文

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

       在上一节用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_INITIALuseradd = Members(username=username,password=rawpass,email=email,status=status,sex=sex,role_cd=role_cd,nickname=nickname,telephone=telephone,avatar=avatar)useradd.password=rawpassdb.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_datadef member_reset_passwd(uid):logging.debug('Member reset password ' + uid)irow= db.session.query(Members).filter_by(uid=uid).first()irow.password = config.PASSWORD_INITIALdb.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 = 9db.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来实现吧。

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

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • (十八)Flink CEP 详解
  • Spring Boot 项目打包及在宝塔面板上部署的简易指南
  • 从C向C++28——设计模式
  • 为什么身边好多公司都开始用加密软件了?
  • C++面向对象高级开发A
  • 先进制造aps专题二十五 openai的ai大模型设计也使用了aps用的并行遗传算法
  • 互联网全景消息(1)之RabbitMq基础入门
  • 一文看懂智能终端密码模块
  • ElasticSearch索引和搜索词匹配的一些细节
  • 企业级环境部署:在 Linux 服务器上如何搭建和部署 Python 环境?
  • Vue+ElementUI+Electron环境搭建及程序打包
  • 广电数安 未来已展 | 天空卫士亮相BIRTV2024
  • 探索音视频SDK在软件集成与私有化部署中的技术难题与解决策略
  • Gartner首次发布AI代码助手魔力象限,阿里云进入挑战者象限,通义灵码产品能力全面领先
  • 解锁 QLExpress:高效数据处理的神器
  • [微信小程序] 使用ES6特性Class后出现编译异常
  • 【从零开始安装kubernetes-1.7.3】2.flannel、docker以及Harbor的配置以及作用
  • 【翻译】Mashape是如何管理15000个API和微服务的(三)
  • C++入门教程(10):for 语句
  • java第三方包学习之lombok
  • mockjs让前端开发独立于后端
  • oschina
  • Python_OOP
  • Python代码面试必读 - Data Structures and Algorithms in Python
  • Python爬虫--- 1.3 BS4库的解析器
  • Python实现BT种子转化为磁力链接【实战】
  • socket.io+express实现聊天室的思考(三)
  • Terraform入门 - 1. 安装Terraform
  • Vue.js 移动端适配之 vw 解决方案
  • vue2.0一起在懵逼的海洋里越陷越深(四)
  • 基于Android乐音识别(2)
  • 将 Measurements 和 Units 应用到物理学
  • 面试遇到的一些题
  • 前端技术周刊 2018-12-10:前端自动化测试
  • 如何借助 NoSQL 提高 JPA 应用性能
  • 通过来模仿稀土掘金个人页面的布局来学习使用CoordinatorLayout
  • 看到一个关于网页设计的文章分享过来!大家看看!
  • No resource identifier found for attribute,RxJava之zip操作符
  • 进程与线程(三)——进程/线程间通信
  • ​创新驱动,边缘计算领袖:亚马逊云科技海外服务器服务再进化
  • # 详解 JS 中的事件循环、宏/微任务、Primise对象、定时器函数,以及其在工作中的应用和注意事项
  • #define、const、typedef的差别
  • #if和#ifdef区别
  • ${ }的特别功能
  • (day 2)JavaScript学习笔记(基础之变量、常量和注释)
  • (leetcode学习)236. 二叉树的最近公共祖先
  • (ros//EnvironmentVariables)ros环境变量
  • (附源码)小程序 交通违法举报系统 毕业设计 242045
  • (论文阅读笔记)Network planning with deep reinforcement learning
  • (七)Flink Watermark
  • (三)Honghu Cloud云架构一定时调度平台
  • (三)终结任务
  • (实战篇)如何缓存数据
  • (算法)区间调度问题
  • (转)http协议