表单验证
使用插件:validform.min.js
封装函数: FuncValidform(id)
//验证角色表单 function FuncValidform(modalName) { $("#" + modalName + " form").Validform({ ajaxPost: true, tiptype: function(msg, o, cssctl) { if (!o.obj.is("form")) { var objtip = o.obj.siblings(".Validform_checktip"); cssctl(objtip, o.type); objtip.text(msg); } else { var objtip = o.obj.find("#msgdemo"); cssctl(objtip, o.type); objtip.text(msg); } }, callback: function(data) { if (data.status == true) { alert('添加成功'); if (data.location) { location.href = data.location; //如果存在跳转页面,则进行跳转 } else { location.reload(); } } else { if (data.data) { alert('添加失败:' + data.data); } } } }); }新增/编辑
新增和编辑通常共用一个form表单,但是新增往往要求要清空form界面上的输入值,并且要求form界面的所有选项都有效;但是编辑清空下往往会要求某些按钮处于无效状态.
情况1: 编辑状况下输入框的名称不可修改,所以新增要主动让名称属性设置为可修改(出现点击编辑后,再次点击新增的情况)
编辑的代码:
//编辑情况下,计划名称不可编辑 $('#id_edit_plan_name').attr('readonly', true);新增的代码:
//新增情况下,计划名称可编辑 $('#id_edit_plan_name').attr('readonly', false);情况2: 编辑状况下类似下拉框/按钮无效,所以新增要主动让下拉框/按钮处于有效状态(业务上的需求,有些数据在编辑状态下不允许进行修改)
编辑的代码:
//编辑情况下,如果状态不为"危桥认定",则选择桥梁不可编辑 $('#id_selectbridge').attr('disabled', true);新增的代码:
//编辑情况下,选择桥梁可编辑 $('#id_selectbridge').attr('disabled', false);情况3: 编辑情况下会动态生成下拉框,而新增情况下要清空下拉框并重新赋值(额外补充:使下拉框无效不应该设置readonly属性为false, 这样依旧可以进行选择.应该让disabled属性为true)
编辑的代码:
$('#id_edit_status option').remove(); var option1 = "<option value=1>危桥认定</option>"; var option2 = "<option value=2>整治方案</option>"; $('#id_edit_status').append(option1); $('#id_edit_status').append(option2);新增的代码:
//新增情况下,默认为危桥认定,且不可修改 var option1 = "<option value=1>危桥认定</option>"; //删除之前所选择的 $('#id_edit_status').find("option").remove(); $('#id_edit_status').append(option1);情况4: 新增情况下清空所输入的数据,应该使用id进行一一清空.输入框使用val('')清空,下拉框样式用attr("value", "")清空
//input输入框 $('#id_edit_plan_name').val(''); //input输入框(时间控件) $('#id_startdate').attr("value", '');
情况5:新增和编辑都是通过action进行跳转,但是编辑额外增加了读取原始数据的功能
新增和编辑跳转的代码:
<form class="form-horizontal" method="post" action = "/dangerBridge/add">编辑额外增加的代码:
$.post("/dangerBridge/one", {'_id' : _id}, function(data){});
情况6: 新增的页面通常通过data-target来进行跳转,而编辑的则通过modal的show来显示界面:
新增的代码:
<a href="" class="btn btn-primary btn-sm" data-toggle="modal" data-target="#dangerbridge_add" id = "id_dangerbridge_add">新增</a>
编辑的代码:
$("#dangerbridge_add").modal("show");change事件
1. 在存在级联情况下(例如两个下拉框分别显示市和区,只有选择市后,才能显示市下面正确的区),这里我们可以通过change事件来动态生成区
$('#id_edit_status').change(function(){ //得到市 var city = $(this).val(); //根据市动态生成区 ...... })删除
1. 删除全部时候的全选操作
//删除所选时候全选 $("#id_sel_bridge").click(function(){ $("input[name='chk']").prop("checked",$("#id_sel_bridge").prop("checked")); });这里有一点要注意的是:使用JQ方法获取全部name为'chk'的复选框,所以页面上要保证chk是唯一的.(因为插件的存在,所以存在内嵌页面的编写方法,如果内嵌页面中也使用了复选框,要保证复选框的name不是chk)
2. 存在不能删除自身的操作
例如用户登陆中,用户不能删除自身的账号,所以我们一般要进行判断
<!-- 用户不可删除自身 --> {% if item.get('_id', '') != login_user.get('_id', '') %} <button class="btn btn-xs btn-danger class-del-user" title="删除" > 删除 </button> {% end %}3. 删除的具体操作
删除时,通常向后台传递具有唯一性的_id.但是在删除所选情况下,我们首先得判断用户是否选择了删除项.
得到所有的选择项:
var arrChk =$("input[name='chk']:checked");得到所有未选择的选项:
var arrNoChk = $("input[name='chk']").not("input:checked");我们可以通过判断长度来确定是否有删除项:
var arrChk =$("input[name='chk']:checked"); if (arrChk.length == 0) { alert("未选择任何要删除的单位!"); return; }如果删除多个,可遍历数组进行删除:
$(arrChk).each(function() { var _tr = $(this).parents('tr'); $.post('/dangerBridge/del', {'_id' : _tr.attr('id')},function(data) { if (!data.status) { alert(data.data); } }); });如果不进行删除,则我们需要撤销之前所选择的:
//清空界面上的所有勾选标志 $(arrChk).each(function() { $(this).attr('checked', false); }); //情况全选按钮 $('#id_sel_bridge').attr('checked', false);点击行事件
存在以下情况:页面中有上下两张table表,上面的table表是汇总,下面的table表是详细情况.这时候得触发一个事件,即点击上面的table表时向后台传递数据,然后页面动态更新,并且点击的行要高亮:
// 点击危桥计划行 $("#plan_table td").click(function() { //存在编辑选项,点击编辑时候不触发事件 if ($(this).attr('type') != 'opts'){ //给选中的td进行着色 var row = $(this).parents("tr"); row.children('td').each(function() { $(this).addClass("highlighted"); }); //向后台传递数据 var planid = row.attr('id'); var name = $('#id_repair_name').val(); var status = $('#id_repair_status').val(); location.href = "/dangerBridge?name=" + name + "&status=" + status + "&planid=" + planid + "&page_index={{danger_bridges_page.get('page_index', 1)}}"; } });但是,刷新页面情况下,我们仍然要保证点击的行保持高亮:
首先,我们从后台得到的数据确定哪个tr是之前选中的:
{% if _planid == planid %} <tr id="{{ _planid }}" class="highlighted"> {% else %} <tr id="{{ _planid }}"> {% end %}然后我们高亮这一行即可:
$(".highlighted").children('td').each(function() { $(this).addClass("highlighted"); });分页
一般的分页效果如下:
代码如下:
<!-- 分页功能 --> <div class="text-center"> <ul class="pagination" id = "id_danger_bridge_page"> {% for page_index in danger_bridges_page.get('page_num_arr', [1]) %} {% if page_index == danger_bridges_page.get('page_index', 1) %} <!-- 高亮当前所选择的页索引 --> <li value = '{{ page_index }}' class = "active"><a href="javascript:void(0)">{{ page_index }}</a></li> {% else %} <li value = '{{ page_index }}'><a href="javascript:void(0)">{{ page_index }}</a></li> {% end %} {% end %} </ul> </div>但是这样的编写手法有一个很严重的问题:如果页数存在几十页,那么界面的页码分布就会如:1,2,3,4,5,6,7,8,9,10,11,.....,56,57,58
我们应该要达到如下的效果:
整个页面显示当前索引页的前四页和后五页.
前端的JS代码和上面完全一样,但是后台代码需要进行特殊处理.以下为处理的Python代码:
#根据所负责的区域编码范围,筛选出此用户所负责的桥梁 bridges, bridges_page = logic_util.m_list(T_BRIDGE, **search_condition) #对page进行特殊处理,只显示当前页的前4页+ 当前页 + 后4页(从第五页开始,为这个规律) page_index = bridges_page.get('page_index', 0) page_index_start = 1 page_index_end = bridges_page.get('page_num', 0) if (5 >= page_index): if page_index_end < 10: bridges_page['page_num_arr'] = range(page_index_start, page_index_end + 1) else: bridges_page['page_num_arr'] = range(page_index_start, 11) elif (5 < page_index <= page_index_end - 5): bridges_page['page_num_arr'] = range(page_index - 4, page_index + 6) else: if page_index_end - 9 < 1: bridges_page['page_num_arr'] = range(page_index_start, page_index_end + 1) else: bridges_page['page_num_arr'] = range(page_index_end - 9, page_index_end + 1)
table中的编辑
我们要在一个table中实现修改的操作:
我们首先要创建两个td(td1,td2).td1用来显示结果,td2用来显示一个input的修改项.首先显示的是td1,隐藏td2:
<td class="xunj_acount text-center" style="min-width:120px; max-width:150px; overflow:hidden;">{{ item.get('count', '') }} <i class="fa fa-pencil-square-o"></i></td> <td class="xunj_acount_info text-center hidden" style="min-width:120px; max-width:150px; overflow:hidden;"> <input type="text" id = "{{ item.get('_id', '') }}_check_count" class="form-control inline-block check-acount" value="{{ item.get('count', '') }}" style="width:80px; height:27px;" /> <button class="btn btn-xs btn-default check_count_btn" style="margin-left:-7px;"> <i class="fa fa-check-square-o"></i> </button> </td>当我们点击td1的时候,隐藏td1,显示td2,这样我们就可以手动输入:
//編輯 $('.fa-pencil-square-o').click(function(){ $(this).parent().addClass('hidden'); $(this).parent().next().removeClass('hidden'); });当我们修改好后点击按钮,则触发以下事件:
$('.check_count_btn').click(function(){ var _id = $(this).parent().parent('tr').attr('id'); var check_acount = $('#' + _id + '_check_count').val(); $.post("/dayCheckBridge/add", {'_id' : _id, 'count' : check_acount}, function(data) { if (data.status) { location.reload(); } else { alert(data.data); } }); });这样传递到后台去,写入数据库,刷新界面后,则界面就显示修改过后的内容.
这里有一点需要注意:
id要保持唯一性,所以我这里的id设定为:
id = "{{ item.get('_id', '') }}_check_count"
日期控件的编写
1. 需要引入的css文件
<link rel="stylesheet" href="/static/css/datetimepicker.css">
2.需要引入的JS文件
<!-- 时间控件需要导入的js文件--> <script src="/static/js/date-time/bootstrap-datetimepicker.min.js"></script> <script src="/static/js/date-time/bootstrap-datetimepicker.zh-CN.js"></script>
3. 所编写的代码
//日期操作:日期修改则进行相应的显示.在日期选择后,修复还提示错误信息的BUG $(".form_datetime").datetimepicker({ weekStart: 1, language: "zh-CN", format: "yyyy-mm-dd", autoclose: true, todayBtn: true, todayHighlight: true, minView: 2, pickerPosition: "bottom-right" }) .on('changeDate', function(ev){ //日期标准长度为10:2015-06-02 if($(this).val().length == 10) { $(this).siblings('.Validform_wrong').remove(); $(this).removeClass('Validform_error'); } });
table表按照列名进行自动排序
这里通过插件来完成:
var len = SHOW_COL_NAME_LIST.length; $('#table_sort').dataTable({ "bPaginate": false, //翻页功能 "bLengthChange": false, //改变每页显示数据数量 "bFilter": false, //过滤功能 "bSort": true, //排序功能 "bInfo": false,//页脚信息 "bAutoWidth": false,//自动宽度 "sScrollY": "700px", "bScrollCollapse": true, "aoColumnDefs": [ { "bSortable": false, "aTargets": [0, len + col_len] } ] });
这里有一点需要注意的是:如果显示的列数是动态变化的,那么aTargets也要动态变化.
动态删除选项
我们要达到以下的效果:
当我们点击关闭(x符号)时,自动刷新界面.比如我们点击了"主线桥"的关闭:
当全部都点击关闭时候,这个div就全部隐藏.
首先,html代码如下:
<div class="alert well" {% if not map_search_code_name_show %} style="display:none;" {% end %}> <input type="hidden" class="form-control" id='id_map_search_code_name' code = "{{ map_search_code }}" name = "{{ map_search_name }}"> <!-- 存储其他页面所传递的地区编码和单位ID --> <input type="hidden" class="form-control" id='id_search_areacode' name = "{{ search_areacode }}" value = "{{ search_areacode }}"> <input type="hidden" class="form-control" id='id_search_companyid' name = "{{ search_companyid }}" value = "{{ search_companyid }}"> <button type="button" class="close" data-dismiss="alert" aria-hidden="true"> x </button> 高级查询筛选项: {% for name, value in map_search_code_name_show.items() %} <span class="btn btn-xs btn-default-alt" style="margin-bottom:3px">{{ mapColName.get(name, '') }}:{{ value }}<i class="fa fa-times-circle ml10 del_label" value = "{{ name }}"></i></span> {% end %} </div>
而我们点击x时候,触发事件即可.
$('.del_label').click(function(){ //要删除的查询条件 var delSearch = $(this).attr("value"); $(this).parent().remove(); });
使用easyUI的树型结构
树型代码较为复杂,需要结合后台的Python代码进行总结.
编写JQ的插件
如果一个页面可以被分割为多部分,那么插件的编写有利于模块化.插件可解决两个重要的问题:
1. 假设界面可以被分割为A,B,C三个部分,那么A部分的事件可以只触发A,B部分页面的刷新,界面C可以保持不变.
2. 假设一个form表单是多个页面共用的,那么我们将form表单编写成插件后,多个界面可以通过调用接口来完成form表单的操作,而无需在每个页面中显式的编写form表单的代码.
具体的实现参考项目:省级系统
命名规则的约定:1. 全局变量要全部大写
2. HTML的代码中写ID时候一律以id_开头,写class的时候,一律以class_开头,并且变量名全部以下划线作为分隔符(个别情况可以字母大小写来区分)
id = "id_name" class = "class_name"3. 共用的form表单(新增和编辑共用)中,变量名同一以id_add_开头.且跳转的action编写为add模式(编辑也同样跳转此URL)
<form class="form-horizontal" method="post" action = "/dangerBridge/add">4. 查询和重置功能以search进行标识
<a class="btn btn-info btn-sm" id="id_search">查询</a> <a class="btn btn-info btn-sm" id="id_search_reset">重置</a>5. 一些隐藏的数据以下划线开头(_)
<input type="hidden" class="form-control" name='_id'>
6. 界面尽量模块化
模块化的界面有利于扩展和维护.
数据库的基本操作
数据库的默认封装函数在logic/util.py文件中,基础的操作如:m_insert, m_find_one,m_list, m_update.
m_insert:
logic_util.m_insert(T_WEIQIAO_PLAN, name = name, areacode = areacode, bridgeids = bridgeids, startdate = startdate, enddate = enddate, addon = addon, status = _status, usable = usable, companyid = companyid)备注: 插入的数据要明确,所以推荐直接写明,而非使用一个字典,然后传递进去,如:
search_condition = {} search_condition['name'] = name search_condition['areacode'] = areacode ... logic_util.m_insert(T_WEIQIAO_PLAN, **search_condition)m_find_one:
这一般用于查找单条数据,比如通过'_id'查找唯一的用户名.
user = logic_util.m_find_one(T_PASSPORT, _id = ObjectId(_id), fields = {'pwd': 0})备注:
1. mongo中存储的_id一般是ObjectId格式,所以查找时候要用ObjectId进行类型转换.但是除了_id以外,其他(例如单位表中存储用户的id)的一律保存未字符串,而非ObjectId格式.
2. fields用于判断哪些字段不被读取,设置为0则不被读取,设置为1则被读取.例如用户表中密码不能被读取.
m_list:
m_list的一般格式如下:
children_companys, children_companys_page = logic_util.m_list(T_COMPANY, parentid = guangy_companyid)这里有以下几点需要特别注意:
1. m_list中默认读取十条数据.
2. m_list中并没有进行严格的排序.
如果我们要读取全部的数据,我们要提供参数findall = "1";如果要排序,我们要增加字段sorts,例如:
bridges, bridges_page = logic_util.m_list(TABLE, sorts = [('year', 1), ('month', 1)], fields = {'year' : 1, 'month' : 1}, findall = '1')
users, users_page = logic_util.m_list(T_USER, user = {'$regex' : user})
users, users_page = logic_util.m_list(T_USER)
search_condition = {} user = self.get_argument('user', '') name = self.get_argument('name', '') if user: search_condition['user'] = {'$regex' : user} if name: search_condition['name'] = {'$regex' : name} users, users_page = logic_util.m_list(T_USER, **search_condition)
1. 通常将这类简单的代码封装成一个函数:
def make_search_condition(keys): search_condition = {} for key in keys: value = self.get_argument(key) if value: search_condition[key] = value return search_condition search_keys = ['user', 'name'] search_condition = make_search_condition(search_keys)
一般的m_update编写如下:
logic_util.m_update(T_COMPANY, {'_id' : ObjectId(_id)}, areacode = areacode, name = name, type = _type, jianc = jianc, parentid = parentid)
如果我们要用到其他的数据表,我们应该这样编写(增加dbname参数):
bridge, page = mongo_util.m_list(T_BRIDGE, dbname=self.subdomain, fields = fields, findall = '1', **kwargs)
1. 全局变量要全部大写
2. 搜索条件