一、用遮罩层处理

uni.showLoading({ title: '加载中',mask:true });//防止连续二次点击

处理后:用uni.hideLoading()清除遮罩层

 

二、@click.once

但只能点击一次,如果不是点击马上跳转的话,或者有检测条件的话,就不能用该方法

三、用组件

以下以封装一个button为例,详细说明异步处理防二次点击的方法,该组件除了防二次点击外,还包含uni-app原生button组件的功能:

3.1 子组件里的HTML:

<template>

    <button

        :size="size"

        :type="type"

        :plain="plain"

        :disabled="disabledData"

        :loading="loadingData"

        :form-type="formType"

        :open-type="openType"

        :hover-class="hoverClass"

        :hover-start-time="hoverStartTime"

        :hover-stay-time="hoverStayTime"

        

        :class="className"

        :style="optionstyle"

        @click="clickButton"

    >

        <text>{{text}}</text>

    </button>

</template>

 

3.2 子组件里的JS:

<script>

export default {

       props: {

            className: String ,//类名

            

            options: Object, //设置按钮的文字颜色、背景颜色、文字大小、弧度

            /*  例如:

                :options="{cl:'#666',bc:'#fff',fs:'28',lh:'2.2',bd:'var(--mainColor)',br:'10',pl:'10',pr:'10'}",

                cl:文字颜色(默认值#333333),

                bc:背景颜色(默认值#ffffff),

                fs:文字大小(单位rpx,默认值28),

                lh:X倍行距,例如lh:'2.2'就是2.2倍行距(默认值normal),可以传px,如:lh:'44px',lh:'44rpx'

                bd:border线颜色,例如:bd:'var(--mainColor)'(border颜色为主题色),当不传bd值时,默认值border=0。当传bd值时,border默认是1px,

                bdSet:自定义border线(当有bdSet值时,bd值会失效),例如:bdSet:2px solid var(--mainColor)

                br:弧度(默认值0),

                pl:padding-left,默认值0,单位:rpx

                pr:padding-right,默认值0,单位:rpx

            */

            

            text:{//按钮里的文字,必传

                type: String,

                default: ''

            },

            time:{//防二次点击的默认时间,默认单位是毫秒。

                type: Number,

                default: ''

            },

            clickBtn:Function,//接收外面的方法,因为存在异步的要求,所以不用this.$emit('clickBtn')来传递值

            

            

            size: {//按钮的大小:default:默认大小; mini:小尺寸

             type: String,

             default: 'default'

            },

            type: {//按钮的样式类型:default:白色; warn:红色; primary:微信小程序为绿色,App、H5、百度小程序、支付宝小程序为蓝色,头条小程序为红色,QQ小程序为浅蓝色

             type: String,

             default: 'default'

            },

            plain: {//按钮是否镂空,背景色透明。

             type: Boolean,

             default: false

            },

            disabled: {//是否禁用

             type: Boolean,

             default: false

            },

            loading: {//名称前是否带 loading 图标

             type: Boolean,

             default: false

            },

            formType: {//用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件

             type: String,

             default: ''

            },

            openType: {//开放能力。contact:打开客服会话,share:触发用户转发,getUserInfo:获取用户信息(手机号、头像、昵称等),getPhoneNumber:获取用户手机号,launchApp:打开APP,openSetting:打开授权设置页

             type: String,

             default: ''

            },

            hoverClass: {//指定按钮按下去的样式类。当 hover-class="none" 时,没有点击态效果

             type: String,

             default: 'button-hover'

            },

            hoverStartTime: {//按住后多久出现点击态,单位毫秒

             type: Number,

             default: 20

            },

            hoverStayTime: {//手指松开后点击态保留时间,单位毫秒

             type: Number,

             default: 70

            },

        },

 

data() {

return {

                allowClick:true,//记录是否允许点击按钮(防二次点击用)

                disabledData:this.disabled,//点击后,异步请求完后,才允许点击

                loadingData:this.loading,//

            };

},

 

computed: {

            // 由于:style只能绑定一个值(用官方写法,只有最后一个值会生效),所以要将所有值集中处理

optionstyle() {

                let optionsArr = []

                

                // 处理按钮里的文字颜色

                let color1 = ''

                if (this.options&&this.options.cl) {

                    color1 = `color:${this.options.cl}`

                    optionsArr.push(color1)

                }else{

                    color1 = "color:#333333"

                    optionsArr.push(color1)

                }

                

                // 处理按钮背景颜色

                if(!this.plain){//如果没有设置透明,才显示背景

                    let backgroundColor1 = ''

                    if (this.options&&this.options.bc) {

                        backgroundColor1 = `background-color:${this.options.bc}`

                        optionsArr.push(backgroundColor1)

                    }else{

                        backgroundColor1 = "background-color:#ffffff"

                        optionsArr.push(backgroundColor1)

                    }

                    

                }

                

                // 处理按钮文字大小

                let fontSize1 = ''

                if (this.options&&this.options.fs) {

                    fontSize1 = `font-size:${this.options.fs}rpx`

                    optionsArr.push(fontSize1)

                }else{

                    fontSize1 = "font-size:28rpx"

                    optionsArr.push(fontSize1)

                }

                

                // 处理行距

                let lineHeight1 = ''

                if (this.options&&this.options.lh) {

                    lineHeight1 = `line-height:${this.options.lh}`

                    optionsArr.push(lineHeight1)

                }else{

                    lineHeight1 = "line-height:normal"

                    optionsArr.push(lineHeight1)

                }

                

                // 处理border线

                let border1 = ''

                if(this.options&&this.options.bdSet){

                    border1 = `border:${this.options.bdSet}`

                    optionsArr.push(border1)

                }else if (this.options&&this.options.bd) {

                    border1 = `border:1px solid ${this.options.bd}`

                    optionsArr.push(border1)

                }else{

                    border1 = "border:0"

                    optionsArr.push(border1)

                }

                

                // 处理border线弧度

                let borderRadius1 = ''

                if (this.options&&this.options.br) {

                    if (this.options.br.indexOf("%")>-1) {

                        borderRadius1 = `border-radius:${this.options.br}`

                    }else{

                        borderRadius1 = `border-radius:${this.options.br}rpx`

                    }

                    optionsArr.push(borderRadius1)

                }else{

                    borderRadius1 = "border-radius:0"

                    optionsArr.push(borderRadius1)

                }

                

                // 处理padding-left

                let paddingLeft = ''

                if (this.options&&this.options.pl) {

                    paddingLeft = `padding-left:${this.options.pl}rpx`

                    optionsArr.push(paddingLeft)

                }else{

                    paddingLeft = "padding-left:0"

                    optionsArr.push(paddingLeft)

                }

                

                // 处理padding-left

                let paddingRight = ''

                if (this.options&&this.options.pr) {

                    paddingRight = `padding-right:${this.options.pr}rpx`

                    optionsArr.push(paddingRight)

                }else{

                    paddingRight = "padding-right:0"

                    optionsArr.push(paddingRight)

                }

 

                return optionsArr.toString().replace(/,/g, ';')

},

},

 

methods: {

            async clickButton(){

                this.disabledData=true

                this.loadingData = true

                if(this.time){

                    await this.clickBtn()

                    setTimeout(()=>{

                        this.disabledData=false

                        this.loadingData = false

                    },this.time)

                }

                if(!this.time){

                    await this.clickBtn()

                    this.disabledData=false

                    this.loadingData = false

                }

            },

        }

};

</script>

 

<style lang="stylus" scoped>

    // 备注:rpx传值的话要写在HTML内联里,不然会出现先换算,再拿值,就会rpx值永远也不会根据屏幕大小来换算

    // button{

        // &[loading]:before {width: 25px;height: 25px;};

    // }

</style>

 

3.3 父组件里引用:

    一、有定时器的,如:

        HTML里:

        底部透明的“搜索”按钮,定时器::time="1000"异步方法完成后再等1秒才能点击按钮,loading时间为:1秒+异步方法的运行时间

         <ls-button class="w-auto flex-center" :text="'搜索'" :plain="true"  :clickBtn="searchSeller" :time="1000" :options="{cl:'#ffffff',lh:'44px',pr:'10'}"/>

        

        JS里(注意:要用Promise异步处理,来防止重复点击问题--就算是设置了time,也要用Promise异步处理):

        searchSeller(){

            return new Promise((resolve, reject) => {

                appShopList.loadListByPage( this,this.params, {complete(){resolve(true)}} )

            })

         },

 

    二、没有定时器的,如:

        HTML里:

        显示灰色字、白色背景、28rpx字体、2.2倍行距、绿色边框、10rpx圆角 的“搜索”按钮

        <ls-button :className="'w-100'" :text="'搜索'" :clickBtn="searchSeller" :options={cl:'#666',bc:'#fff',fs:'28',lh:'2.2',bd:'var(--mainColor)',br:'10'}/>

 

四、用指令

快速点击按钮会重复多次调用接口,防止出现这样的情况

 

全局定义,方便调用

新建plugins.js

export default {

  install (Vue) {

    // 防重复点击(指令实现)

    Vue.directive('preventReClick', {

      inserted (el, binding) {

        el.addEventListener('click', () => {

          if (!el.disabled) {

            el.disabled = true

            setTimeout(() => {

              el.disabled = false

            }, binding.value || 3000)

          }

        })

      }

    })

  }

}

 

main.js引用

import preventReClick from '@/utils/plugins.js';Vue.use(preventReClick);

 

按钮调用直接加v-preventReClick

<el-button type="prismary" style="width:100%;" @click="handleSubmit" v-preventReClick></el-button>