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来实现吧。

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

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

golang RSA 解密前端jsencrypt发送的数据时异常 crypto/rsa: decryption error 解决方法

golang中 RSA解密前端&#xff08;jsencrypt&#xff09;发来的密文后出现 "crypto/rsa: decryption error" &#xff0c; 这个问题首先需要确认你的私匙和公匙是否匹配&#xff0c; 如果匹配 那检查入参数据类型&#xff0c; 前端发送来的rsa加密后的数据一般都是…

【算法进阶2-动态规划】斐波那契数列(递归调用、动态规划)、钢条切割问题(自定而下实现、自底向上、切割方案)

1 斐波那契数 2 钢条切割问题 2.1 最优解情况 2.2 钢条切割问题之自定而下实现 2.3 钢条切割问题之自底向上实现 2.4 钢条切割问题-重构解-切割方案 1 斐波那契数 # 1 子问题的重复计算 def fibonacci(n: int) -> int:"""使用递归方式计算第 n 个斐波那契数…

初识C语言指针(4)

目录 1. 字符指针变量 2. 数组指针变量 3. ⼆维数组传参的本质 4. 函数指针变量 5. typedef 关键字 6. 函数指针数组 结语 1. 字符指针变量 字符指针变量就是存储字符或字符串首字符地址的变量&#xff0c;字符指针变量有2种使用方式。 最常用的使用方式&#xff1a…

Datawhale X 李宏毅苹果书 AI夏令营(深度学习入门)task3

实践方法论 在应用机器学习算法时&#xff0c;实践方法论能够帮助我们更好地训练模型。如果在 Kaggle 上的结果不太好&#xff0c;虽然 Kaggle 上呈现的是测试数据的结果&#xff0c;但要先检查训练数据的损失。看看模型在训练数据上面&#xff0c;有没有学起来&#xff0c;再…

智能手机摄影综评:品牌联名与自建影像品牌的战略分析

随着智能手机摄影技术的飞速发展&#xff0c;各大厂商不仅与知名摄影品牌展开合作&#xff0c;还通过自建影像品牌来提升产品的摄影能力和品牌形象。本文将重点分析小米、华为、荣耀、OPPO、Vivo和苹果在摄影品牌联名与自建影像品牌方面的战略&#xff0c;探讨这些策略如何影响…

【案例55】WebSphere非root用户启动方案

问题背景 很多项目为了安全因素考虑&#xff0c;想让在Linux服务器中启动的程序都用非root用户启动。 解决方案 创建用户和组 现在我们用 root 用户登录&#xff0c;并创建用户和组。 ##创建用户 [rootnc-test ~]# useradd wasadmin##修改密码 [rootnc-test~]# passwd was…

Python优化算法16——鲸鱼优化算法(WOA)

科研里面优化算法都用的多&#xff0c;尤其是各种动物园里面的智能仿生优化算法&#xff0c;但是目前都是MATLAB的代码多&#xff0c;python几乎没有什么包&#xff0c;这次把优化算法系列的代码都从底层手写开始。 需要看以前的优化算法文章可以参考&#xff1a;Python优化算…

【学习笔记】技术分析-华为智驾控制器MDC Pro 610分析

华为的智能驾驶控制器一直在迭代&#xff0c;和网络上广泛披露的早期MDC 610相比&#xff0c;华为 MDC Pro 610 智能驾驶控制器&#xff0c;现在的样品设计采用了海思的双系统级芯片 (SoC) 提高了处理能力&#xff0c;三星的存储模块为无缝数据处理提供了充足的内存&#xff0c…

一分钟制作电子版的招生简章

​在当今信息化社会&#xff0c;快速、高效地传播信息显得尤为重要。招生简章作为学校、机构招生的重要宣传材料&#xff0c;其电子版制作更是需要简洁明了、吸引眼球。一分钟你就能制作出一份精美的电子版招生简章。让我们一起来看看&#xff0c;如何实现这一目标。 1.要制作电…

Linux 可视化管理工具:Webmin

&#x1f600;前言 在 Linux 系统的运维管理中&#xff0c;命令行界面&#xff08;CLI&#xff09;是主要的操作方式。然而&#xff0c;对于许多系统管理员或开发者来说&#xff0c;使用 CLI 进行管理和维护任务并不总是最直观或最方便的方式。为了简化操作并提高效率&#xff…

今天你City了吗?维乐Angel Revo带你穿梭都市自由随风~

当7月的热浪在都市中翻滚&#xff0c;你是否渴望逃离钢筋水泥的束缚&#xff0c;寻找一片属于自己的绿意盎然&#xff1f;今天你City了吗&#xff1f;快带上VELO Angel Revo一起抓住夏日的尾巴&#xff0c;用一场骑行与这座城市的风景共舞&#xff01;      轻巧出行&#…

面向对象编程:深入PHP的封装、继承和多态性!

文章目录 面向对象OOP的核心概念定义类、创建对象构造函数和析构函数访问修饰符继承方法重写接口和抽象类静态方法和属性魔术方法 错误处理错误处理概述错误级别异常处理自定义异常设置错误处理忽略错误错误日志断言 总结 面向对象编程&#xff08;OOP&#xff09;是一种编程范…

海绵城市雨水监测系统简介

海绵城市雨水监测系统主要有&#xff1a;数据采集、无线数据传输、后台云服务、终端平台显示等部分组成。系统通过前端数据采集水质&#xff08;ss\cod\浊度、PH等&#xff09;、雨水雨量、流量、水位、土壤湿度、气象等数据。通过无线数据传输通讯&#xff08;4G、5G、以太网、…

洞见数据价值,激活组织活力,让决策更精准的智慧交通开源了。

智慧交通视觉监控平台是一款功能强大且简单易用的实时算法视频监控系统。它的愿景是最底层打通各大芯片厂商相互间的壁垒&#xff0c;省去繁琐重复的适配流程&#xff0c;实现芯片、算法、应用的全流程组合&#xff0c;从而大大减少企业级应用约95%的开发成本。用户只需在界面上…

人工智能在专业领域的斗争

介绍 ChatGPT 等大型语言模型 (LLM) 在用自然语言讨论一般话题的能力方面令人印象深刻。然而&#xff0c;他们在医学、金融和法律等专业领域却举步维艰。这是由于缺乏真正的理解&#xff0c;并且注重模仿而不是智力。 大语言模型正处于炒作的顶峰。由于能够用自然语言回答和讨…

python库(20):Jsonschema库描述JSON数据的规范

1 Jsonschema简介 在当今信息时代&#xff0c;数据规范与交换变得越来越重要&#xff0c;JSON&#xff08;JavaScript Object Notation&#xff09;作为一种轻量级的数据交换格式&#xff0c;被广泛应用于网络通信与前后端数据交互。 JSON Schema是一种用于描述JSON数据的规范…

力扣2025.分割数组的最多方案数

力扣2025.分割数组的最多方案数 哈希表 前缀和 用两个哈希表分别存元素(之后会遍历)左侧和右侧的前缀和 typedef long long LL;class Solution {public:int waysToPartition(vector<int>& nums, int k) {int n nums.size(),ans 0;vector<LL> sum(n);unor…

【Linux系列】SH 与 BASH 的区别:深入解析与使用案例

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

实现MySQL的主从复制基础

目录 1 MySQL实现主从复制的原理 1.1 实现主从复制的规则 1.2 如何实现主从复制 2 MySQL 实现主从复制实践 2.1 实验环境 2.2 my.cnf 配置添加 2.2.1 配置MSTER 端配置文件 2.2.2 配置SLAVE 端配置文件 2.2.3 三台MySQL服务器重启服务 2.3 创建用于复制的用户 2.4 保证三台主机…

51单片机——模块化编程

1、模块化编程介绍 传统方式编程&#xff1a;所有的函数均放在main.c里&#xff0c;若使用的模块比较多&#xff0c;则一个文件内会有很多的代码&#xff0c;不利于代码的组织和管理&#xff0c;而且很影响编程者的思路。 模块化编程&#xff1a;把各个模块的代码放在不同的.…