当前位置: 首页 > article >正文

Vue3 + Element Plus 防止按钮重复点击的解决方案

在 Vue3 和 Element Plus 项目中,防止按钮重复点击是一个常见的需求,特别是在表单提交、支付等场景下。以下是几种实现方式:

1. 使用 Element Plus 的 loading 状态

Element Plus 的按钮组件本身就支持 loading 状态,这是最简单的方式:

vue

复制

下载

<template><el-button type="primary" :loading="loading" @click="handleSubmit">提交</el-button>
</template><script setup>
import { ref } from 'vue';const loading = ref(false);const handleSubmit = async () => {loading.value = true;try {// 执行异步操作await submitForm();} finally {loading.value = false;}
};
</script>

2. 自定义指令实现防重复点击

可以创建一个全局指令来实现防重复点击:

javascript

复制

下载

// directives.js
import { Directive } from 'vue';export const preventReClick: Directive = {mounted(el, binding) {el.addEventListener('click', () => {if (!el.disabled) {el.disabled = true;setTimeout(() => {el.disabled = false;}, binding.value || 2000);}});}
};// main.js
import { preventReClick } from './directives';
app.directive('prevent-reclick', preventReClick);

使用方式:

vue

复制

下载

<el-button v-prevent-reclick="1000" @click="handleClick">提交</el-button>

3. 使用装饰器(适用于组合式 API)

可以创建一个可组合函数来防止重复点击:

javascript

复制

下载

// composables/usePreventReClick.js
import { ref } from 'vue';export function usePreventReClick() {const isClicking = ref(false);const preventReClick = async (fn) => {if (isClicking.value) return;isClicking.value = true;try {await fn();} finally {isClicking.value = false;}};return {isClicking,preventReClick};
}

使用方式:

vue

复制

下载

<script setup>
import { usePreventReClick } from './composables/usePreventReClick';const { isClicking, preventReClick } = usePreventReClick();const handleSubmit = () => {preventReClick(async () => {// 执行提交逻辑await submitForm();});
};
</script><template><el-button :loading="isClicking" @click="handleSubmit">提交</el-button>
</template>

 ts,usePreventReClick.ts

import { ref } from "vue";type AsyncFunction = () => Promise<void>;/*** 防止重复点击 hook* @returns*/
export function usePreventReClick() {const isClicking = ref(false);const preventReClick = async (fn: AsyncFunction) => {if (isClicking.value) {return;}isClicking.value = true;try {await fn();} finally {isClicking.value = false;}};return {isClicking,preventReClick};
}

使用方式:

<script setup lang="ts" name="MaterialOut">
......
import { usePreventReClick } from "@/hooks/usePreventReClick";// 防止重复点击
const { preventReClick } = usePreventReClick();// 保存
const onSaveClick = async () => {// 防止重复点击preventReClick(async () => {await store.fetchSaveData();ElMessage.success("保存成功!");});
};// 记账
const onJzClick = async () => {// 防止重复点击preventReClick(async () => {// 检查数据合法性// 1、领取人员不能为空if (!ckMaster.value.llPersonId) {ElMessage.error("请选择领取人员!");// 模拟点击,调用 el-cascader 的公开方法来展开下拉框cascaderRef.value?.togglePopperVisible(true);return;}// 2、必须有出库明细if (ckDetail.value.length === 0) {ElMessage.error("请点击【新增】,增加出库明细!");return;}// 循环遍历出库明细for (let i = 0; i < ckDetail.value.length; i++) {// 3、明细数量必须大于 0if (ckDetail.value[i].amount <= 0) {ElMessage.error("数量必须大于 0!");document.getElementById(`input-amount-${ckDetail.value[i].id}`)?.focus();(document.getElementById(`input-amount-${ckDetail.value[i].id}`) as HTMLInputElement)?.select();return;}// 4、明细数量不能大于库存数量if (ckDetail.value[i].amount > ckDetail.value[i].stockAmount) {ElMessage.error("数量不能大于库存数量!");document.getElementById(`input-amount-${ckDetail.value[i].id}`)?.focus();(document.getElementById(`input-amount-${ckDetail.value[i].id}`) as HTMLInputElement)?.select();return;}// 5、用途不能为空if (!ckDetail.value[i].purpose) {ElMessage.error("用途不能为空!");document.getElementById(`input-purpose-${ckDetail.value[i].id}`)?.focus();(<HTMLInputElement>document.getElementById(`input-purpose-${ckDetail.value[i].id}`))?.select();return;}}// 记账await store.fetchJzData();// 重新加载数据await store.fetchData(ckMaster.value.ckNo);initPageData();ElMessage.success("记账成功!");});
};
......
</script>

4. 封装高阶组件

如果需要更复杂的控制,可以封装一个高阶组件:

vue

复制

下载

<!-- PreventReClickButton.vue -->
<template><el-button v-bind="$attrs" :loading="loading" @click="handleClick"><slot></slot></el-button>
</template><script setup>
import { ref } from 'vue';const props = defineProps({onClick: {type: Function,required: true},delay: {type: Number,default: 1000}
});const loading = ref(false);const handleClick = async () => {if (loading.value) return;loading.value = true;try {await props.onClick();} finally {setTimeout(() => {loading.value = false;}, props.delay);}
};
</script>

使用方式:

vue

复制

下载

<PreventReClickButton type="primary" :onClick="submitForm"
>提交
</PreventReClickButton>

ts:BasePreventReClickButton.vue

/** * 防止重复点击按钮组件(防抖按钮组件) */
<script setup lang="ts" name="BasePreventReClickButton">
import { ref } from "vue";const props = withDefaults(defineProps<{onClick: () => Promise<void> | void;delay?: number;}>(),{delay: 0}
);// 加载标识
const loading = ref(false);// 点击事件
const handleClick = async (): Promise<void> => {if (loading.value) return;loading.value = true;try {await props.onClick();} finally {let delay = props.delay;if (delay < 0) delay = 0;setTimeout(() => {loading.value = false;}, delay);}
};
</script><template><!-- v-bind="$attrs" 绑定父组件传递的所有属性 --><!-- 设置当前组件的个性属性,可以覆盖父组件属性 :loading="loading" :disabled="loading" @click="handleClick" --><el-button v-bind="$attrs" :loading="loading" :disabled="loading" @click="handleClick"><!-- 插槽 --><slot></slot></el-button>
</template><style scoped lang="scss"></style>

使用方式:MaterialIn.vue

<script setup lang="ts" name="MaterialIn">
......
import BasePreventReClickButton from "@/components/base/BasePreventReClickButton.vue";// 保存
const onSaveClick = async () => {await store.fetchSaveData();ElMessage.success("保存成功!");
};// 记账
const onJzClick = async () => {// 检查数据合法性// 1、供应厂商不能为空if (!rkMaster.value.supplier) {ElMessage.error("请选择或输入供应厂商!");document.getElementById("input-supplier")?.focus();return;}// 2、必须有入库明细if (rkDetail.value.length === 0) {ElMessage.error("请点击【新增】,增加入库明细!");return;}// 循环遍历入库明细for (let i = 0; i < rkDetail.value.length; i++) {// 3、明细数量必须大于 0if (rkDetail.value[i].amount <= 0) {ElMessage.error("数量必须大于 0!");document.getElementById(`input-amount-${rkDetail.value[i].id}`)?.focus();(document.getElementById(`input-amount-${rkDetail.value[i].id}`) as HTMLInputElement)?.select();return;}// 4、用途不能为空if (!rkDetail.value[i].purpose) {ElMessage.error("用途不能为空!");document.getElementById(`input-purpose-${rkDetail.value[i].id}`)?.focus();(<HTMLInputElement>document.getElementById(`input-purpose-${rkDetail.value[i].id}`))?.select();return;}}// 记账await store.fetchJzData();// 重新加载数据await store.fetchData(rkMaster.value.rkNo);setInputRMB();ElMessage.success("记账成功!");
};
......
</script><template>
......<BasePreventReClickButtonclass="header-btn"type="primary"plain:disabled="rkMaster.stage === 1 || !rkMaster.rkNo":onClick="onSaveClick">保存</BasePreventReClickButton><BasePreventReClickButtonclass="header-btn"type="primary"plain:disabled="rkMaster.stage === 1 || !rkMaster.rkNo":onClick="onJzClick":delay="0">记账</BasePreventReClickButton>
......
</template>

总结

以上方法各有优缺点,根据项目需求选择:

  1. 简单场景:直接使用 Element Plus 的 loading 状态

  2. 全局控制:使用自定义指令

  3. 组合式 API:使用可组合函数

  4. 复杂组件:封装高阶组件

相关文章:

Vue3 + Element Plus 防止按钮重复点击的解决方案

在 Vue3 和 Element Plus 项目中&#xff0c;防止按钮重复点击是一个常见的需求&#xff0c;特别是在表单提交、支付等场景下。以下是几种实现方式&#xff1a; 1. 使用 Element Plus 的 loading 状态 Element Plus 的按钮组件本身就支持 loading 状态&#xff0c;这是最简单…...

测试工程师学LangChain之promptTemplate 实战笔记

一、引言:大模型时代的测试自动化革命 2025 年,随着大模型(如 DeepSeek)在自动化测试领域的广泛应用,Prompt 编写已成为测试工程师的核心技能之一。 为什么? 大模型输出的质量 90% 取决于输入的 PromptLangChain 的 PromptTemplate 提供了参数化 Prompt 的标准化方案Ope…...

OpenCV计算机视觉实战(9)——阈值化技术详解

OpenCV计算机视觉实战&#xff08;9&#xff09;——阈值化技术详解 0. 前言1. 全局阈值与自适应阈值2. Otsu 算法3. 实战案例&#xff1a;文档扫描中的二值化处理4. 算法对比小结系列链接 0. 前言 在图像处理领域&#xff0c;阈值化 (Binarization) 技术就像一把魔术剪刀&…...

【Tauri2】049——upload

前言 这篇就看看一个简单地插件——upload Upload | Taurihttps://tauri.app/plugin/upload/upload的英文意思是“上传&#xff08;程序或信息&#xff09;”。 看来是用来上传文件的。 支持移动端 正文 安装 pnpm tauri add upload 在前后端都会安装&#xff0c;即 .plug…...

4、数据标注的武林秘籍:Label-Studio vs CVAT vs Roboflow

开篇痛点&#xff1a;90%的模型效果取决于数据质量 "标注3小时&#xff0c;训练5分钟"——这是很多AI工程师的真实写照。上周有位读者训练YOLOv12时发现&#xff0c;同样的代码&#xff0c;换批数据mAP直接跌了15%&#xff0c;根本原因是标注不规范&#xff01;本文…...

MATLAB项目实战:阻尼振动与数据拟合项目

关键技能点说明: 函数定义与匿名函数 使用匿名函数定义微分方程:damped_osc = @(t, Y) [...] 自定义拟合模型函数:model = @(b, t) b(1).*exp(...) 符号计算(可选) 使用符号数学工具箱求解析解:dsolve、diff、simplify 符号表达式数值化:subs + double 数值算法实现 ODE…...

74道Node.js高频题整理(附答案背诵版)

简述 Node. js 基础概念 &#xff1f; Node.js是一个基于Chrome V8引擎的JavaScript运行环境。它使得JavaScript可以在服务器端运行&#xff0c;从而进行网络编程&#xff0c;如构建Web服务器、处理网络请求等。Node.js采用事件驱动、非阻塞I/O模型&#xff0c;使其轻量且高效…...

Linux 基础IO(上)

目录 前言 重谈文件 文件操作 1.打开和关闭 2.对文件打开之后操作 理解文件fd 1.文件fd的分配规则与重定向 2.理解shell中的重定向 3.关于Linux下一切皆文件 关于缓冲区 1.为什么要有缓冲区 2.缓冲区刷新策略的问题 3.缓冲区的位置 前言 本篇到了我们linux中的文件…...

如何加载私钥为 SecKeyRef

本文介绍如何在 iOS/macOS 下将私钥加载为 SecKeyRef&#xff0c;涵盖 PEM 格式的 ECC 密钥读取、X9.63 数据构建、以及与 Keychain 的集成。 1. 使用 SecKeyCreateWithData 加载私钥 Apple 提供的 SecKeyCreateWithData 方法可以直接将密钥数据加载为 SecKeyRef 对象。 SecK…...

@Pushgateway自定义脚本推送数据

文章目录 Pushgateway 自定义脚本推送数据1. 目的2. 适用范围3. 前提条件4. 操作流程4.1 确定指标类型和格式4.2 编写推送脚本方法一:使用 curl 命令行推送方法二:使用 Python 脚本推送方法三:使用 Python 客户端库推送4.3 设置定时任务4.4 验证数据5. 高级配置5.1 使用基本…...

kubernate解决 “cni0“ already has an IP address different from 10.244.0.1/24问题

问题 NetworkPlugin cni failed to set up pod “coredns-5d4b4db-jkmnl_kube-system” network: failed to set bridge addr: “cni0” already has an IP address different from 10.244.0.1/24 解决方案 这个问题通常是由于Flannel网络插件残留配置导致的IP地址冲突。以下…...

el-tree拖拽事件,限制同级拖拽,获取拖拽后节点的前后节点,同级拖拽合并父节点name且子节点加入目标节点里

node-drag-start:开始拖拽节点时触发​​(按下鼠标按钮),无论是否允许放置,此事件都会触发。 allow-drop 返回 true 才能触发@node-drag-end="handleDragend"、@node-drop="handleDrop"; (1)allow-drop:动态控制​​是否允许放置; (2)node-dr…...

day62—DFS—太平洋大西洋水流问题(LeetCode-417)

题目描述 有一个 m n 的矩形岛屿&#xff0c;与 太平洋 和 大西洋 相邻。 “太平洋” 处于大陆的左边界和上边界&#xff0c;而 “大西洋” 处于大陆的右边界和下边界。 这个岛被分割成一个由若干方形单元格组成的网格。给定一个 m x n 的整数矩阵 heights &#xff0c; hei…...

《Python基础》第2期:环境搭建

在开始编写 Python 代码前&#xff0c;还需要搭建 Python 的开发环境。 电脑是没办法直接读懂 Python 代码的&#xff0c;而是需要一个解释器&#xff0c;实时把代码翻译成字节码&#xff0c;字节码再转换成 0 和 1&#xff0c;电脑就能读懂了。 Python 的运行过程就是翻译一行…...

WSL 安装 Debian 12 后,Linux 如何安装 curl , quickjs ?

在 WSL 的 Debian 12 系统中安装 curl 非常简单&#xff0c;你可以直接使用 APT 包管理器从官方仓库安装。以下是详细步骤&#xff1a; 1. 更新软件包索引 首先确保系统的包索引是最新的&#xff1a; sudo apt update2. 安装 curl 执行以下命令安装 curl&#xff1a; sudo…...

[CSS3]vw/vh移动适配

vw/vh 目标: 能够使用vw单位设置网页元素的尺寸 相对单位相对视口的尺寸计算结果.vw全称viewport width; 1vw1/100视口宽度 vh全称viewport height; 1vh1/100视口高度 体验vw和vh单位 <!DOCTYPE html> <html lang"en"> <head><meta charset…...

Python进阶与常用库:探索高效编程的奥秘

一、文件与目录操作&#xff1a;os模块 os模块是Python标准库中用于与操作系统交互的核心工具&#xff0c;提供了丰富的文件和目录操作方法。通过os&#xff0c;开发者可以轻松实现文件路径处理、环境变量获取、目录管理等功能。 1.1 核心功能与方法 以下是os模块中常用的方…...

nt!MiDispatchFault函数分析之nt!MiCompleteProtoPteFault函数的作用

nt!MiDispatchFault函数分析之nt!MiCompleteProtoPteFault函数的作用 第一部分&#xff1a; // // PTE is still in transition state, same protection, etc. // ASSERT (Pfn1->u4.InPageError 0); if (Pfn1->u2.ShareCount 0) { MI_REMO…...

YOLOX 的动态标签分类(如 SimOTA)与 Anchor-free 机制解析2025.5.29

YOLOX 的动态标签分类&#xff08;如 SimOTA&#xff09;与 Anchor-free 机制是其核心改进中的两个关键部分&#xff0c;它们在目标检测中的作用和实现方式存在显著差异。以下从原理、实现细节及效果三个方面进行详细对比&#xff1a; 一、核心原理与目标 1. Anchor-free 机制…...

打卡day42

DAY 42 Grad-CAM与Hook函数 知识点回顾 回调函数lambda函数hook函数的模块钩子和张量钩子Grad-CAM的示例 作业&#xff1a;理解下今天的代码即可 1、回调函数 回调函数&#xff08;Callback Function&#xff09;是一种特殊的函数&#xff0c;它作为参数传递给另一个函数&am…...

小白的进阶之路系列之八----人工智能从初步到精通pytorch综合运用的讲解第一部分

PyTorch Tensors 通过大量实例学习编程应用是最有效的方法。 本篇是PyTorch综合运用,旨在让读者通过一行行代码亲自掌握Pytorch工具包的各种功能,有利于大家部署自己的神经网络人工智能计算工程。 首先,载入torch库。 import torch我们来看看一些基本的张量操作。首先,…...

724.寻找数组的中心下标前缀和

题目链接&#xff1a; https://leetcode.cn/problems/find-pivot-index/ 这道题目我们可以使用暴力解法&#xff0c;就一个下标前数组之和&#xff0c;再求一个下标后数组之和&#xff0c;时间复杂度达到n方&#xff0c;我们来写一下&#xff1a; int pivotIndex(vector<in…...

软考-系统架构设计师-第十六章 层次式架构设计理论与实践

层次式架构设计理论与实践 16.2 表现层框架设计16.3 中间层框架设计16.4 数据访问层设计16.5 数据架构规划与设计16.6 物联网层次架构设计 软件体系结构为软件系统提供了结构、行为和属性的高级抽象&#xff0c;由构成系统的元素描述这些元素的相互作用、指导元素集成的模式以及…...

甘特图 dhtmlxGantt.js UA实例

摘要&#xff1a;本文介绍了一个基于AngularJS的排产资源占用甘特图系统&#xff0c;包含前端界面展示和后端控制逻辑。系统通过HTML模板实现甘特图展示区域、查询条件表单和数据绑定&#xff0c;使用JavaScript控制器处理数据查询、甘特图初始化和交互逻辑。主要功能包括&…...

Docker学习笔记:基础知识

本文是自己的学习笔记 1、什么是Docker2、Docker的架构设计2.1、镜像&#xff08;Image&#xff09;2.2、容器&#xff08;Container&#xff09;2.3、仓库&#xff08;Repository)2.4、Docker使用场景案例 1、什么是Docker Docker是基于Go语言实现的云开源项目。它的角色是作…...

5.2 初识Spark Streaming

在本节实战中&#xff0c;我们初步探索了Spark Streaming&#xff0c;它是Spark的流式数据处理子框架&#xff0c;具备高吞吐量、可伸缩性和强容错能力。我们了解了Spark Streaming的基本概念和运行原理&#xff0c;并通过两个案例演示了如何利用Spark Streaming实现词频统计。…...

uv:一个现代化的 Python 依赖管理工具

在 Python 的生态系统中&#xff0c;依赖管理和 Python 版本管理一直是开发者关注的核心问题。传统的工具如 pip、poetry 和 pyenv 虽然功能强大&#xff0c;但在性能和使用体验上仍有改进空间。uv 是由 Python 核心开发者开发的 现代化依赖管理工具&#xff0c;旨在提供更快、…...

Python趣学篇:交互式词云生成器(jieba + Tkinter + WordCloud等)

名人说&#xff1a;路漫漫其修远兮&#xff0c;吾将上下而求索。—— 屈原《离骚》 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 一、为什么要做词云&#xff1f;让文字"活"起来&#xff01;二、核心…...

理解解释器架构:原理、组成与运行机制全解析

目录 前言1. 什么是解释器架构2. 解释器的基本组成2.1 被解释执行的程序2.2 解释器引擎2.3 解释器内部状态2.4 程序执行的当前状态2.5 存储器模型 3. 解释器的工作原理3.1 解析源代码3.2 初始化运行环境3.3 逐条执行语法结构3.4 维护程序状态3.5 内存管理与变量作用域 4. 举例&…...

2025华为OD机试真题+全流程解析+备考攻略+经验分享+Java/python/JavaScript/C++/C/GO六种语言最佳实现

华为OD全流程解析&#xff0c;备考攻略 快捷目录 华为OD全流程解析&#xff0c;备考攻略一、什么是华为OD&#xff1f;二、什么是华为OD机试&#xff1f;三、华为OD面试流程四、华为OD薪资待遇及职级体系五、ABCDE卷类型及特点六、题型与考点七、机试备考策略八、薪资与转正九、…...