项目技术杂记:20150605

1. JS代码的整理

表单验证

使用插件: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. 界面尽量模块化

    模块化的界面有利于扩展和维护.

2. Python代码的整理

数据库的基本操作

    数据库的默认封装函数在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')



3. 在设计中,我们往往有一个功能是查询,例如前端往后台传递十几条查询的字段,但是有些字段是空的(默认不进行查询),而其余的字段通常是通过正则表达式进行查询的.这时候我们需要编写一个字典,用来存储查询的字段.因为在传递空字符串情况下(例如下例中user字段为空),所查询的数据可能有误:



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:


    一般的m_update编写如下:


logic_util.m_update(T_COMPANY, {'_id' : ObjectId(_id)}, 
	areacode = areacode, name = name, type = _type, jianc = jianc, parentid = parentid)



    代码的设计最好不能使用:删除+插入, 来代替更新.因为_id具有唯一性,而删除+插入的操作,会产生一个新的数据,而非更新原数据.


    如果我们要用到其他的数据表,我们应该这样编写(增加dbname参数):


bridge, page = mongo_util.m_list(T_BRIDGE, dbname=self.subdomain, fields = fields, findall = '1', **kwargs)




命名规范


1. 全局变量要全部大写

2. 搜索条件












你可能感兴趣的:(项目技术杂记:20150605)