项目总结 关于layui使用上的坑

2,459 阅读3分钟
  • 感悟源头

最近kk在做一个小型的功能业务平台,但由于客户对浏览器兼容性的要求比较强(兼容IE低版本),从技术选型上也需要配合后台研发进行。所以最后采用了一套偏后端人员开发的方案:

  • 功能组件:layui
    • 模板引擎:laytpl
    • UI组件:layui.element、layui.laydate(日历)
  • 响应式布局:Boostrap
    • 表单验证:Boostrap Validator
  • 后端开发:GUNS(一个利于快速搭建业务平台的框架)

与此同时,还包括有脱离项目页面的:

  • EDM邮件信
  • 下载报告:html形式存储本地浏览

虽说这些技术栈确实是kk初学前端时就接触过的,但layui着实在开发过程中坑了kk一把,所以在这里想做个总结。

不过这个项目也不是那么没突破,借此机会,kk确实还是接触了一把邮件信的编写,属于HTML基础上的一次突破,也算是一次很好的经验借鉴。

事不宜迟,咱们开始。

  • 开发工具类方法

  • 1. Boostrap Validator的方法封装

    var Feng = {
        initValidator: function (formId, fields) {
        $('#' + formId).bootstrapValidator({
            feedbackIcons: {
                valid: 'glyphicon glyphicon-ok',
                invalid: 'glyphicon glyphicon-remove',
                validating: 'glyphicon glyphicon-refresh'
            },
            fields: fields,
            live: 'enabled',
            message: '该字段不能为空'
            });
        }
    };

该方法封装在了工具类Feng里,可供业务页面全局调用。

//设置validator校验规则
var validateFields = {
    time: {
        trigger:"change input click", //监听change动作
        validators: {
            notEmpty: {
                message: '执行时间不能为空'
            }
        }
    },
    caption: {
        validators: {
            notEmpty: {
                message: '任务名称不能为空'
            }
        }
    }
};
//业务页面内初始化就调用Feng工具类
$(function () {
    Feng.initValidator("taskInfoForm", validateFields);
});

//点击提交触发表单校验,验证数据是否为空
UserInfoDlg.validate = function () {
    $('#userInfoForm').data('bootstrapValidator').validate();//手动对表单进行校检
    $('#taskInfoForm').data('bootstrapValidator').validate();//手动对表单进行校检
    return $('#userInfoForm').data('bootstrapValidator').isValid() && $('#taskInfoForm').data('bootstrapValidator').isValid();
};
  • 2. 基于JQ手动封装ajax方法

(function () {
    var $ax = function (url, success, error) {
        this.url = url;
        this.type = "post";
        this.data = {};
        this.dataType = "json";
        this.async = false;
        this.success = success;
        this.error = error;
    };

    $ax.prototype = {
        start: function () {
            var _this = this;

            if (this.url.indexOf("?") == -1) {
                this.url = this.url + "?jstime=" + new Date().getTime();
            } else {
                this.url = this.url + "&jstime=" + new Date().getTime();
            }
            jQuery.ajax({
                url: this.url,
                type: this.type,
                dataType: this.dataType,
                async: this.async,
                data: this.data,
                success: function (e) {
                    _this.success(e);
                },
                error: function (data) {
                    _this.error(data);
                }
            });
        },

        set: function (key, value) {
            if (typeof key == "object") {
                for (var i in key) {
                    if (typeof i == "function")
                        continue;
                    this.data[i] = key[i];
                }
            } else {
                this.data[key] = (typeof value == "undefined") ? $("#" + key).val() : value;
            }
            return this;
        },

        setData: function (data) {
            this.data = data;
            return this;
        },

        clear: function () {
            this.data = {};
            return this;
        }
    };

    window.$ax = $ax;

}());

业务页面调用$ax:

    //提交信息
    var ajax = new $ax(Feng.ctxPath + "/task/add", function (data) {//传入ajax成功(success)后的callback方法
        Feng.success("添加成功!");
        if (window.parent.MgrUser != undefined) {
            window.parent.MgrUser.table.refresh();
            UserInfoDlg.close();
        }
        window.location.reload();
    }, function (data) {//传入ajax失败(error)后的callback方法
        Feng.error("添加失败!" + data.responseJSON.message + "!");
    });
    ajax.set(this.userInfoData);//设置提交的表单信息,$ax对象根据输入data的类型放进this.data中
    ajax.start();//发出ajax请求
  • 3. 表单取值

该方法根据输入的表单名name,或是class,判断该表单输入哪种类型(radio、checkbox、日历、普通输入框),进行不同方式的取值和轻量校验。

/**
 * 设置对话框中的数据
 *
 * @param key 数据的名称
 * @param val 数据的具体值
 */
UserInfoDlg.set = function (key, value) {
    if (typeof value == "undefined") {
        if (key == 'time') {
            var value = $("#" + key).val();
            var startTime = value.substring(0, value.indexOf("-")).replace(/(\w*)年(.*)月(.*)日(.*)/g, "$1-$2-$3").replace(/\s*/g, "");
            var endTime = value.substring(value.indexOf("-") + 1).replace(/(\w*)年(.*)月(.*)日(.*)/g, "$1-$2-$3").replace(/\s*/g, "");
            console.log(startTime);
            console.log(endTime);
            this.userInfoData['startTime'] = startTime;
            this.userInfoData['endTime'] = endTime;
            return this;
        }
        if (typeof $("#" + key).val() == "undefined") {
            var str = "";
            var ids = "";
            $("input[name='" + key + "']:checkbox").each(function () {
                if (true == $(this).is(':checked')) {
                    str += $(this).val() + ",";
                }
            });
            if (str && key != 'time') {
                if (str.substr(str.length - 1) == ',') {
                    ids = str.substr(0, str.length - 1);
                }
            } else {
                $("input[name='" + key + "']:radio").each(function () {
                    if (true == $(this).is(':checked')) {
                        ids = $(this).val()
                    }
                });
            }
            this.userInfoData[key] = ids;
        } else {
            this.userInfoData[key] = $("#" + key).val();
        }
    }

    return this;
};

业务页面调用: 使用JQ链式调用,将表单所有项目的name推入即可

/**
 * 收集数据
 */
UserInfoDlg.collectData = function () {
    this.set('orgName').set('orgCode').set('orgContactPerson').set('orgContactMobile').set('caption')
        .set('url').set('time').set('period').set('importance').set('smsList').set('emailList').set('depth');
};
  • 坑1:日历组件和表单验证

  • 1. 业务场景

如上图为一个添加了Boostrap Validator表单验证的双日历(layui.laydate)输入框,表单提交前需要对其输入的内容进行非空校验。

  • 2. 技术实现

其结构组成代码如下:

    <!-- 传入jQuery文件-->
    
    <!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
    
    <!-- 最新的 Bootstrap 核心 JavaScript 文件 -->

    <!--引入BoostrapValidator-->
    
    <!--引入laydate -->
  
    <div class="form-horizontal">
        <div class="row form-row">
            <div class="col-sm-12 form-group">
                <div class="form-group" style="">
                    <label class="col-sm-2 control-label">
                        <span style="color:red;">*</span>执行时间:
                    </label>
                    <div class="col-sm-5">
                        <input class="form-control" placeholder="请选择开始日期 ~ 结束日期" id="time" name="time" value="" type="text">
                    </div>
                </div>
            </div>
        </div>
    </div>

laydate组件配置代码:

    laydate.render({
        elem: '#time'
        , range: true //或 range: '~' 来自定义分割字符
        , format: 'yyyy年MM月dd日' //可任意组合
        , done: function (value, date) {
            $("#time").change();
            setTimeout(function () {
              $('#taskInfoForm')
              .data('bootstrapValidator')
                    .updateStatus('time','NOT_VALIDATED', null)
                    .validateField('time');
            }, 200)
        }
    });
    //直接监听日历输入框的blur事件
    $("#time").blur(function () {
        setTimeout(function () {
            $('#taskInfoForm').data('bootstrapValidator')
                .updateStatus('time', 'NOT_VALIDATED', null)
                .validateField('time');
        }, 200)
    });
  • 3. 掉进的坑

Boostrap Validator对第三方js输入表单的信息,无法自动触发校验。

实现在业务场景中,就是当laydate双日历选定日期过后,点击提交触发校验,仍然视为输入框为空。

  • 4.解决方案

第一步,在日历输入框所对应的校验规则validatorField里添加:

trigger:"change input click", 

即input的这些事件皆可自动触发validate()。

done: function (value, date) {
            $("#time").change();
              $('#taskInfoForm')
              .data('bootstrapValidator')
                    .updateStatus('time','NOT_VALIDATED', null)
                    .validateField('time');
        }

第二步:在laydate完成done事件后触发input的change事件并手动触发校验一次。

结果:可以实现日历值变化时候的表单重新校验。

这下新问题出现了:

在输入框从空值到有值的过程中,仍然无法触发校验

  • 5.根本原因

kk探究了下各路大神的文章,发现其问题根本在于:

laydate 加载日期赋值给 input 在 bootstrapValidator 验证之后,所以在点击时间插件之后进行二次特定字段验证即可 取自文章:bootstrapValidator 验证框架与 layui 时间插件兼容

  • 6.优化解决方案

1.完善laydate的done回调事件,对validate二次校验设置时间延迟(200ms基本不影响体验)

        done: function (value, date) {
            $("#time").change();
            setTimeout(function () {
              $('#taskInfoForm')
              .data('bootstrapValidator')
                    .updateStatus('time','NOT_VALIDATED', null)
                    .validateField('time');
            }, 200)
        }

2.附加的,也可以完善日历输入框本身的blur事件,将validate绑定在blur事件上

    $("#time").blur(function () {
        setTimeout(function () {
            $('#taskInfoForm').data('bootstrapValidator')
                .updateStatus('time', 'NOT_VALIDATED', null)
                .validateField('time');
        }, 200)
    });

结果:可完整解决当前问题,输入日历可正常触发二次校验。

  • 坑2:模板引擎与UI组件

  • 1.掉进的坑

    laytpl与layui.element不可共用

  • 2.业务场景

    kk想尝试在页面中引用laytpl的面包屑功能,需要导入element的功能组件。

但kk发现其与写在laytpl里的DOM结构代码无法兼容,即laytpl渲染出的结构无法正常使用layui自带的UI组件,或者说已经加载了其DOM结构及样式,但未从layui里初始化其功能逻辑。

  • 3.解决方案

    最终采用了手动重构功能逻辑的方法。

如上内容属于kk个人意见,如有更好的解决方案,或内容有错误遗漏,欢迎指正!