全局控制按钮点击次数的3种方法,监听事件,button指令,vue封装按钮组件
最近在项目中遇到一个问题,按钮在点击时由于网络等原因,后端的数据还没有返回到前端时,多次点击按钮会发多次请求,从而出现重复数据,后来经过查询各种博客也实践不少方法,这里总结出3种控制按钮的方法。
项目中使用的是Element UI 和Vue
一、通过addEventListener方法,监听按钮的点击事件,进行全局控制
思路:addEventListener方法可以监听页面中的事件,并且会有一个回调函数,我们可以通过这个回调函数来进行按钮的控制。我这里是把监听的方法放在全局的mixins,这样可以简单的实现对按钮的全局控制。下面说一下这种方法的优缺点:
优点:只需要写一遍就可以进行全局的控制,实现方便,改动地方少
缺点:只能控制时间段内按钮是禁用的,过了这个时间按钮将会解除禁用,比如可以控制1秒内只能点击一次,不能在后端数据响应后启用按钮。如果按钮不冒泡,可能监听不到,如点击事件的.stop方法,这个我没有试过,有兴趣的大家可以试一下。
话不多说,上代码。
export default { mounted() { this.$nextTick(() => { this.handleClick(this) }, 0) }, methods: { /** * 处理点击事件 * @param context */ handleClick(context) { const _this = this if (context.$root.$el) { context.$root.$el.addEventListener('click', function(e) { if (_this.forbidButton(e.target)) { // 一秒之后允许点击 setTimeout(() => { _this.allowButton(e.target) }, 1000) } }) } }, /** * 禁止按钮的点击操作,禁止按钮的所有子节点都禁止
* 我这里之所以这样写是因为项目中的按钮有些包裹了span标签,也有的有点击事件但不是按钮,这样用禁用disabled就无效了,这里用了样式pointerEvents来进行禁止点击事件,
* 设置为none即可禁用点击事件等。光禁用此节点是不行的,父节点如果在按钮内或者父节点是按钮也要禁用,这里我担心会有多层嵌套所以使用递归来一劳永逸的解决,
* 为了不让递归太多,只要检测到是div即退出递归。 * @param node * @returns {*} */ forbidButton(node) { if (node && node.type === 'button') { node.style.pointerEvents = 'none' this.setButtonPointerEvents(node, 'none') return true } else if (node && node.type === 'div') { // 如果检测到div,则退出递归 return false } else if (node) { return this.forbidButton(node.parentElement) } else { return false } }, /** * 设置是否允许点击事件 * @param node * @param isDisabled */ setButtonPointerEvents(node, isDisabled) { for (let i = 0; i < node.children.length; i++) { node.children[i].style.pointerEvents = isDisabled } }, /** * 允许按钮的点击操作,允许按钮的所有子节点都允许点击,同禁用一样,把禁用时禁用的都解除 * @param node * @returns {*} */ allowButton(node) { if (node && node.type === 'button') { node.style.pointerEvents = 'auto' this.setButtonPointerEvents(node, 'auto') } else if (node && node.type === 'div') { return false } else if (node) { return this.allowButton(node.parentElement) } else { return false } } } }
上面是mixin中的代码,我是直接在App.js文件中引入的mixin,这样也就相当于全局使用了。
可以这样写:
import btnControl from './btnControl .js' // 导入刚才写的js文件
mixins:['btnControl'] // 引用mixin
二、通过指令控制
通过vue中的自定义指令进行按钮的控制
优点:可以获取点击事件的返回值,即可以在后端响应之后再解除按钮的禁用
缺点:每个按钮都需要加指令
话不多说,这里直接上主要代码:
const btnControl = {} btnControl.install = Vue => { /** 限制按钮点击事件的指令,在结果返回以前不能继续点击*/ Vue.directive('btn-control', { /** * @param el 可以用来操作dom,此例中操作的是按钮 * @param bind bind点value获得绑定的值,如v-my-directive="1 + 1" 中,绑定值为 2 , * 本例中指令绑定的值返回一个promise 对象,所以有.then
* 需要注意的一点是,这个zhi */ inserted: function(el, bind) { el.addEventListener('click', () => { el.disabled = true bind.value().then(res => { // 返回结果之后解除禁止 el.disabled = false }) }) } }) } export default btnControl
需要注意的是这里把按钮原来的点击事件直接换成指令即可,就是说原来的@click直接去掉换成v-btn-control指令
新建
注意,create方法是要有返回值的,并且需要是一个promise
create() { return this.axios.post(url, data) .then((res) => { if (res.data.errcode == 0) { // success } else { // fail } }).catch((err) => { // console.log(err); }); }
此方法借鉴 https://blog.csdn.net/qq_33594380/article/details/85055518 此博客
三、自己封装组件,控制按钮的点击事件
自己封装组件控制按钮的点击事件可以做更多的事情,利于以后的扩展,如果以后有别的对按钮的需要了,也便于及时调整
优点:扩展性好,也可以实时控制按钮的禁用与启用
缺点:每个按钮都需要修改,且按钮需要改动的地方较第二种方法多一些
我这里投了个巧,因为用的是element组件,所以我直接在element组件按钮的基础上进行的封装。
封装的主要代码
可以直接传递loading的值来控制按钮的loading状态,也可以通过回调函数来实时控制按钮的loading和disabled等状态,而且如果有别的需求也可以自己加。
使用方法如下:
使用时把封装好的按钮在main.js文件中全局引入后,需要把原来的el-button换成自己封装的按钮my-button就可以,如果没有特殊属性或操作,可以全部替换成自己封装的按钮。全局引入方法及封装细节参考 https://www.cnblogs.com/muzishijie/p/11291295.html 。
这里简单分为三种使用方法:
1.直接通过loading属性控制按钮,loading状态时也是不允许点击的,但是有些情况有loading不适合,因为loading会对按钮大小有些影响,这时可以用disabled。
确定
在方法中只要把loading变为false就可以解除对按钮的限制
confirm() { this.axios.post(url, data).then((res) => { this.loading = false }).catch((err) => {
this.loading = false // console.log(err); }) },
2.使用loadOn属性,这种方式不需要增加loading变量,直接使按钮在点击后直接变为laoding状态了
确定
在方法中直接调用callback()回调函数解除loading状态,不传参数默认解除状态,传参数用法见方法三
confirm(callback) { this.axios.post(url, data).then((res) => { callback() }).catch((err) => { callback() }) },
3.如果不想在按钮中加属性,可以直接在方法中用回调函数来控制,方法如下,按钮中没有加多余的属性
确定
在方法中给callback()回调函数传参数来控制按钮的loading状态,也可以传disabled控制使按钮为禁用状态。
注意:取消loading状态的位置要放对,自己写的方法中可能会有其他的逻辑验证,如果设置了loading但是解除loading的地方没有执行,这样就会一直处于loading状态。
confirm(callback) {
if (valid) {
callback({ loading: true }) // 控制按钮为loading状态
this.axios.post(url, data).then((res) => {
callback() // 解除按钮loading状态
}).catch(() => {
callback()
})
}
}
基本就是这么多了,表达的有些欠缺,请多多包涵。
如果有错误的地方或需要改进的地方,希望各位大哥多给些建议,小弟在此谢过!