全局控制按钮点击次数的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()
})
}
}
基本就是这么多了,表达的有些欠缺,请多多包涵。
如果有错误的地方或需要改进的地方,希望各位大哥多给些建议,小弟在此谢过!