Uniapp和 Vue3项目中防止按钮重复点击的3种方案

方案一:状态锁

核心:通过设置变量 true/false 来控制按钮或者 view 的状态。

这种方式应该最常见。

<template>
  <view>
  <em><!-- 作者:知否技术 --></em>
<em><!--1. 视图 --></em>
<view :class="{ 'disabled': isLoading }" @click="handleSubmit">
   提交
</view>
<em><!--2. 按钮 disabled --></em>
<uv-button :disabled="isLoading" @click="handleSubmit"> 提交 </uv-button>
<em><!--3. 按钮 loading --></em>
<uv-button :loading="isLoading" @click="handleSubmit">提交</uv-button>
 </view>
</template>

<script setup>
  import {ref} from 'vue'
  import orderApi from '@/api/order/order.js';
  const isLoading = ref(false)
  // 提交方法
  const handleSubmit = async () => {
      // 防止重复点击
      if (isLoading.value) return
      // 锁住
      isLoading.value = true
      try {
          const res = await orderApi.handleSubmit(order);
          if (res.code === 200) {
              getReturnFundList();
              uni.showToast({ title: '提交成功' });
          } else {
              uni.showToast({ title: '提交失败' });
          }
      } catch (error) {
          console.error("提交失败:", error);
      } finally {
          // 解锁
          isLoading.value = false;
      }
  }
</script>Code language: HTML, XML (xml)

方案二:全局自定义防重复点击指令

在项目根目录中新建 /directives/preventClick.js :

图片

1. preventClick.js

<em>// 防止重复点击指令</em>
exportconst preventClick = {
<em>// 指令绑定到元素时执行</em>
  mounted(el, binding) {
    <em>// 1. 设置防抖间隔时间,默认1000毫秒</em>
    const delay = binding.value || 1000;
    
    <em>// 2. 创建一个标记,记录当前是否可点击</em>
    el.isDisabledClick = false;
    
    <em>// 3. 保存原始的点击事件处理函数</em>
    const originalClickHandler = el.__originalClickHandler;
    
    <em>// 4. 创建新的点击事件处理函数</em>
    el.__preventClickHandler = (event) => {
      <em>// 如果当前不可点击,直接拦截点击事件</em>
      if (el.isDisabledClick) {
        event.preventDefault(); <em>// 阻止默认行为</em>
        event.stopPropagation(); <em>// 阻止事件冒泡</em>
        return;
      }
      
      <em>// 设置为不可点击状态</em>
      el.isDisabledClick = true;
      
      <em>// 5. 执行原始的点击事件处理函数</em>
      if (originalClickHandler) {
        originalClickHandler(event);
      }
      
      <em>// 6. 设置定时器,在指定时间后恢复点击</em>
      el.preventClickTimer = setTimeout(() => {
        <em>// 恢复点击状态</em>
        el.isDisabledClick = false;
        <em>// 清除定时器引用</em>
        el.preventClickTimer = null;
      }, delay);
    };
    
    <em>// 7. 移除原始的点击事件,添加新的点击事件</em>
    el.removeEventListener('click', originalClickHandler);
    el.addEventListener('click', el.__preventClickHandler);
  },

<em>// 指令从元素解绑时执行</em>
  unmounted(el) {
    <em>// 8. 清除定时器</em>
    if (el.preventClickTimer) {
      clearTimeout(el.preventClickTimer);
      el.preventClickTimer = null;
    }
    
    <em>// 9. 移除事件监听器</em>
    if (el.__preventClickHandler) {
      el.removeEventListener('click', el.__preventClickHandler);
      el.__preventClickHandler = null;
    }
    
    <em>// 10. 清除自定义属性</em>
    el.isDisabledClick = null;
    el.__originalClickHandler = null;
  }
};Code language: JavaScript (javascript)

2. 在 main.js 中全局注册指令

import { createSSRApp } from'vue';
import App from'./App.vue';
<em>// 导入指令</em>
import { preventClick } from'@/directives/preventClick';

exportfunction createApp() {
const app = createSSRApp(App);
<em>// 全局注册指令,指令名为 prevent-click</em>
  app.directive('prevent-click', preventClick);
return { app };
}Code language: JavaScript (javascript)

3.在组件中使用指令

<template>
  <view>
    <em><!-- 方式1:使用默认 1000ms 防抖 --></em>
    <button v-prevent-click @click="handleClick">点击</button>
    <em><!-- 方式2:自定义防抖时间 --></em>
    <button v-prevent-click="2000" @click="handleClick">点击</button>
  </view>
</template>Code language: HTML, XML (xml)

方案三:封装一个防重复点击的 Hook

在项目目录 hooks 新建文件: usePreventReclick.js

图片
import { ref } from'vue'
exportfunction usePreventReclick(delay = 1000) {
const isDisabled = ref(false)
<em>// 执行受保护的操作 fn - 要执行的函数(建议返回 Promise)</em>
<em>// 返回 fn 的结果或 undefined(如果被阻止)</em>
const execute = async (fn) => {
    if (isDisabled.value) {
      console.info('操作被阻止')
      returnundefined
    }
<em>// 设置为 true</em>
    isDisabled.value = true
    try {
      const result = await fn()
      return result
    } catch (error) {
      <em>// 抛出异常</em>
      throw error
    } finally {
      <em>// 延迟后自动重置状态</em>
      setTimeout(() => {
        isDisabled.value = false
      }, delay)
    }
  }
<em>// 解除禁用</em>
const reset = () => {
    isDisabled.value = false
  }
return {
    isDisabled,
    execute,
    reset
  }
}Code language: JavaScript (javascript)

在项目中使用:

<template>
  <view class="container">
    <button 
      :disabled="isLoading" 
      :loading="isLoading"
      @click="handleSubmit"
    >
      {{ isLoading ? '正在提交中...' : '提交订单' }}
    </button>
  </view>
</template>

<script setup>
import { ref } from 'vue'
import { usePreventReclick } from '@/hooks/usePreventReclick'
const isLoading = ref(false)
// 创建防重实例:时间 1500 毫秒
const { isDisabled: isLoading, execute } = usePreventReclick(1500)

const handleSubmit = () => {
  execute(async () => {
    // 模拟后端请求
    await new Promise()
    uni.showToast({ title: '提交成功'})
  }).catch(err => {
    console.error('提交失败:', err)
    uni.showToast({ title: '提交失败'})
  })
}
</script>Code language: HTML, XML (xml)