全局控制按钮点击次数的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()
})
}
}

基本就是这么多了,表达的有些欠缺,请多多包涵。

如果有错误的地方或需要改进的地方,希望各位大哥多给些建议,小弟在此谢过!

相关