我的前端工具集(十)按钮点击操作锁
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;width:+" + 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