我的前端工具集(十)按钮点击操作锁

我的前端工具集(十)按钮点击操作锁

 

liuyuhang原创,未经允许禁止转载

目录

我的前端工具集

1、需求

  很多时候,在用户操作的时候是不按照正常的思路来的,做一个程序的goodcase比较容易,做badcase就比较困难。

  在前端的诸多内容中,封装一个统一的操作锁就比较重要,防止重复提交,防止用户在同一个按钮上点个没完。

2、思路

  在点击这个问题上,不管点击的是什么,只要提供一个遮罩,比如进度条,比如弹出框,都可以阻止用户继续点击。

  但是这个方式比较恶,相当于将整个app都彻底拒绝,本质上将整个app都做成了一个单例的应用,在很多程度上,

  这当然是不允许的。

  所以,提供一个极小的,起眼的或不起眼的小遮罩,只遮住这个刚刚被点击过的操作按钮或元素,该遮罩提供一定

  时间的效果,同时可以以某个条件触发去除该遮罩。

  很多时候,这种操作锁都是发生在提交表单的等待时间,可能等待前端校验(通常瞬时),也可能等待后端回执,来

  决定下一步要做什么。常见的做法是在发ajax之前指定一个flag,在该flag的值为A的时候,该操作无法重复,在操作

  结束以后,该flag值为B,以此来防止重复提交。

  我不确定操作与操作之间的关系,也许点击某一个操作的时候,允许操作A执行,但是不允许操作B执行。

  所以,各种点击操作之间,应该有一定的关联关系,而且,一个页面通常会有多个点击来进行,因此需要对每一个

  点击进行注册,分别管理上锁,并且可以分别关闭之。

  获取当前点击元素相对于父元素的位置,偏移等信息,创建一个新的div,该div允许浮动,并且插入到该点击元素的父元素内。

  使其形成一个遮罩,同时给一个简单的表示,表示该操作正在执行,你可以替换成文字,动画,图片,anything...

  

  自己改吧,当然也可以做成一个进度条,一个燃烧特效,一个计时器,都可以......

3、代码

  代码如下,都在注释里,手累不想多写了

/**
 * 按钮添加遮罩操作锁,该锁为浏览器操作锁,可使用js绕过,ajax中不建议再加额外操作锁,若用户使用js绕过,应在后端再加操作锁
 * 本API包括以下内容:
 * 1.定义全局变量缓冲区window.clickTimerMap = {};
 * 2.注册按钮或click操作的内容到缓冲区,点击后使用矩形半透明遮罩遮挡该按钮防止重复点击
 * 3.手动关闭计时器与遮罩的函数removeBtnClick(btnId),该操作锁完毕后应关闭遮罩,调用该函数
 * 注:
 * 注册按钮的函数为registClickCtrl(ids);
 * 该功能主要针对网络延迟可能较高的操作,以及不想让用户进行频繁使用的操作
 * 
 * @author Liuyuhang at 2018 in tit-group
 */

/**
 * 按钮遮罩计时器的全局变量缓冲区
 */
window.clickTimerMap = {};
/**
 * 注册按钮遮罩的函数
 * @param:ids,按钮的id的数组,保证该页面加载成功后使用该函数,不要跨域使用
 * @see:window.clickTimerMap
 * @see:removeBtnClick(btnId)
 * @ex:registClickCtrl([ "test1", "test2" ])
 */
function registClickCtrl(ids) {
    if (ids.length > 0) {
        for (var i = 0; i < ids.length; i++) {
            if ($("#" + ids[i]).length > 0) {
                $("#" + ids[i]).unbind("click." + ids[i]); //unbind
                addClick(ids[i]); //bind
                console.log("regist bind :#" + ids[i])
            } else {
                console.error("# " + ids[i] + "   的元素并不存在")
            }
        }
    }
    //===
    /**
     * 内部函数
     * 按钮遮罩函数,默认30秒关闭遮罩,不管成功与否
     * 手动关闭遮罩,@see:removeBtnClick(btnId)
     */
    function addClick(id) {
        $("#" + id).bind("click." + id, function() {
            //获取位置和大小
            var targetTop = $(this).position().top;
            var targetLeft = $(this).position().left;

            var marginLeft = $(this).css("margin-left");
            var marginRight = $(this).css("margin-right");
            var marginTop = $(this).css("margin-top");
            var marginBottom = $(this).css("margin-bottom");

            var targetWidth = $(this).css("width");
            var targetHeight = $(this).css("height");
            var targetId = $(this).attr("id");
            //创建遮罩
            var div = "<div id='" + targetId + "-div' style='opacity:0.5;background-color:white;z-index:300;position:absolute;top:"
                + targetTop + "px;left:" + targetLeft + "px;+" + targetWidth + ";height:" + targetHeight + ";font-size:20px;"
                + "margin:" + marginTop + " " + marginRight + " " + marginBottom + " " + marginLeft + ";' "
                + "class='text-center'></div>";
            //加载遮罩
            $(this).parent().append(div);
            var count = targetId + "-count";
            clickTimerMap[count] = 0
            //加载遮罩进程内容
            clickTimerMap[targetId] = setInterval(function() {
                if (clickTimerMap[count] % 6 == 0) {
                    $("#" + targetId + "-div").html(".")
                } else if (clickTimerMap[count] % 6 == 1) {
                    $("#" + targetId + "-div").html("..")
                } else if (clickTimerMap[count] % 6 == 2) {
                    $("#" + targetId + "-div").html("...")
                } else if (clickTimerMap[count] % 6 == 3) {
                    $("#" + targetId + "-div").html("....")
                } else if (clickTimerMap[count] % 6 == 4) {
                    $("#" + targetId + "-div").html(".....")
                } else if (clickTimerMap[count] % 6 == 5) {
                    $("#" + targetId + "-div").html("......")
                }
                if (clickTimerMap[count] > 100) { //30秒就应该停止了,没响应也不应该继续等
                    clearInterval(clickTimerMap[targetId])
                    $("#" + targetId + "-div").remove();
                }
                clickTimerMap[count]++;
            }, 300);
        })
    }
}
/**
 * 手动关闭计时器与遮罩的函数
 * @param:btnId,要关闭的启用遮罩的按钮的id
 * @see:window.clickTimerMap
 * @see:registClickCtrl(ids)
 */
function removeBtnClick(btnId) {
    setTimeout(function() {
        clearInterval(clickTimerMap[btnId])
        $("#" + btnId + "-div").remove();
    }, 100)
}

4、使用

这里贴一个实际工作中的部分代码实例

希望你只关注操作锁的注册和移除......

    //init
    $(function() {
        ......
        registClickCtrl([ "createAssetSubmitBtn", "modifyAssetSubmitBtn", "removeAssetSubmitBtn" ]); //注册按钮操作锁
    })

    ......
        /**
     * 修改已有资产,提交表单的函数
     */
    function modifyAssetSubmit(id) {
        var id = window.assetId;
        if (null != id && '' != id && 'undefinded' != id) {
            var data = {
                id : id,
                assetName : $("#assetName").val(),
                assetDesc : $("#assetDesc").val(),
                assetCode : $("#assetCode").val(),
                assetModalIds : $("#assetModalIds").val().toString(),
                assetTypeId : $("#assetTypeId").val(),
                inspecteStatus : $("#inspecteStatus").val(),
                fixStatus : $("#fixStatus").val(),
                otherPropertyGroupId : $("#propertyGroupIdOther").val(),
            }
            if (checkData(data) == false) {
                removeBtnClick("modifyAssetSubmitBtn"); //解除操作锁
                topTipModal("操作提示:", "<span class='text-danger'>资产名称,资产描述,资产编码不能为空,或长度不符合要求,请更改!</span>", 3000);
                return null;
            } else {
                $.ajax({
                    type : 'POST',
                    url : local + "modifyAssetById.do",
                    data : data,
                    async : true,
                    success : function(resultMap) {
                        removeBtnClick("modifyAssetSubmitBtn"); //解除操作锁
                        if (resultMap.message == "success") {
                            topTipModal("操作提示:", "<span class='text-success'>修改成功,正在刷新列表!</span>", 3000);
                            getAssetAll();
                            //点击确定后隐藏表单
                            $("#addAsset").collapse("hide");
                        } else {
                            topTipModal("操作提示:", "<span class='text-warning'>修改失败,请刷新后尝试重新操作!<br>" + resultMap.message + "!</span>", 3000);
                            return null;
                        }
                    },
                    error : function(resultMap) {
                        removeBtnClick("modifyAssetSubmitBtn"); //解除操作锁
                        topTipModal("操作提示:", "<span class='text-danger'>修改失败,错误码:" + resultMap + "</span>", 3000);
                        console.error(resultMap);
                    }
                });
            }
        } else {
            removeBtnClick("modifyAssetSubmitBtn"); //解除操作锁
            topTipModal("操作提示:", "<span class='text-warning'>请选择一个资产,再尝试修改操作!</span>", 3000);
        }
        //==========
        /**
         * 内部函数,检查data
         */
        function checkData(data) {
            var name = data.assetName;
            var desc = data.assetDesc;
            var code = data.assetCode;
            var ids = data.assetModalIds;
            if (!isEmpty(name) || !isEmpty(desc) || !isEmpty(code) || ids.length > 511 || code.length < 2 || code.length > 8 || name.length > 32 || desc.length > 128) {
                return false;
            } else {
                return true;
            }
        }
    }        

以上!

原文地址:https://www.cnblogs.com/liuyuhangCastle/p/9926193.html