vue3 + ts 制作指令,防止按钮在固定时间内重复点击,不会影响到表单的校验

在使用elementPlus的form表单提交的时候,直接使用防抖指令,虽然可以防止一定时间内重复提交,但发现表单的必填项校验也会被延迟处理,被测试在测页面的时候提了bug,后面对指令做了一些优化,点击提交的时候不会影响到form表单的默认校验。

代码如下:

1、创建一个 vOnceClick.ts文件,代码如下

复制代码
// utils/vOnceClick.ts
import { type Directive, type DirectiveBinding } from 'vue'

// 定义参数接口
interface OnceClickOptions {
	handler?: Function
	delay?: number
	disabledClass?: string | string[] // 锁定期间添加的样式类
	disabledAttribute?: boolean // 是否设置 disabled 属性
}

// 使用 WeakMap 存储处理器以便清理
const handlerMap = new WeakMap<
	HTMLElement,
	{
		clickHandler: (event: Event) => void
		isLocked: boolean
		timer: ReturnType<typeof setTimeout> | undefined
	}
>()

export const vOnceClick: Directive<HTMLElement, OnceClickOptions | Function | number> = {
	mounted(el: HTMLElement, binding: DirectiveBinding<OnceClickOptions | Function | number>) {
		let handler: Function | undefined
		let delay: number = 2000
		let disabledClass: string | string[] = 'once-click-disabled'
		let disabledAttribute: boolean = true

		// 解析参数
		if (binding.value && typeof binding.value === 'object' && !Array.isArray(binding.value)) {
			const options = binding.value as OnceClickOptions
			handler = options.handler
			delay = options.delay || 1000
			disabledClass = options.disabledClass || disabledClass
			disabledAttribute = options.disabledAttribute !== false // 默认 true
		} else if (typeof binding.value === 'function') {
			handler = binding.value
		} else if (typeof binding.value === 'number') {
			delay = binding.value
		}

		const state = {
			isLocked: false,
			timer: undefined as ReturnType<typeof setTimeout> | undefined
		}

		// 设置元素为禁用状态
		const setDisabled = () => {
			if (disabledAttribute) {
				el.setAttribute('disabled', '')
				el.style.pointerEvents = 'none'
				el.style.cursor = 'not-allowed'
				el.style.opacity = '0.6'
			}

			if (disabledClass) {
				if (Array.isArray(disabledClass)) {
					el.classList.add(...disabledClass)
				} else {
					el.classList.add(disabledClass)
				}
			}
		}

		// 设置元素为启用状态
		const setEnabled = () => {
			if (disabledAttribute) {
				el.removeAttribute('disabled')
				el.style.pointerEvents = ''
				el.style.cursor = ''
				el.style.opacity = ''
			}

			if (disabledClass) {
				if (Array.isArray(disabledClass)) {
					el.classList.remove(...disabledClass)
				} else {
					el.classList.remove(disabledClass)
				}
			}
		}

		const clickHandler = (event: Event) => {
			if (state.isLocked) {
				event.preventDefault()
				event.stopPropagation()
				event.stopImmediatePropagation()
				return
			}

			// 锁定元素
			state.isLocked = true
			setDisabled()

			// 执行处理函数
			if (handler) {
				handler(event)
			}

			// 延迟后解锁
			state.timer = setTimeout(() => {
				state.isLocked = false
				setEnabled()
				state.timer = undefined
			}, delay)
		}

		// 存储状态和处理器
		handlerMap.set(el, {
			clickHandler,
			isLocked: state.isLocked,
			timer: state.timer
		})

		el.addEventListener('click', clickHandler)
	},

	unmounted(el: HTMLElement) {
		const data = handlerMap.get(el)
		if (data) {
			el.removeEventListener('click', data.clickHandler)
			if (data.timer) {
				clearTimeout(data.timer)
			}
			handlerMap.delete(el)
		}
	},

	// 添加 beforeUpdate 钩子,处理参数变化
	beforeUpdate(el: HTMLElement, binding: DirectiveBinding<OnceClickOptions | Function | number>) {
		// 如果值发生变化,需要重置
		const data = handlerMap.get(el)
		if (data && binding.oldValue !== binding.value) {
			// 清除之前的定时器
			if (data.timer) {
				clearTimeout(data.timer)
				data.timer = undefined
			}
			// 重置为可用状态
			data.isLocked = false

			// 移除禁用状态
			el.removeAttribute('disabled')
			el.style.pointerEvents = ''
			el.style.cursor = ''
			el.style.opacity = ''
			el.classList.remove('once-click-disabled')
		}
	}
}

2、在main.ts引入指令

3、在vue页面使用

默认写法:

复制代码
 <button v-once-click="{ handler: test, delay: 2000 }">
    正确
  </button

转参数的写法:

<el-button v-once-click="{ handler: () => test('dataFormRef'), delay: 1000 }" >确认</el-button>

相关推荐
LaughingZhu10 分钟前
Product Hunt 每日热榜 | 2026-05-21
前端·人工智能·经验分享·chatgpt·html
怕浪猫17 分钟前
Electron 开发实战(一):从零入门核心基础与环境搭建
前端·electron·ai编程
小鹏linux1 小时前
Ubuntu 22.04 部署开源免费具有精美现代web页面的Casdoor账号管理系统
linux·前端·ubuntu·开源·堡垒机
前端若水2 小时前
会话管理:创建、切换、删除对话历史
前端·人工智能·python·react.js
Bigger2 小时前
mini-cc:一个轻量级 AI 编程助手的诞生
前端·ai编程·claude
涵涵(互关)2 小时前
Naive-ui树型选择器只显示根节点
前端·ui·vue
BY组态2 小时前
Ricon组态系统最佳实践:从零开始构建物联网监控平台
前端·物联网·iot·web组态·组态
BY组态3 小时前
Ricon组态系统vs传统组态软件:为什么选择新一代Web组态平台
前端·物联网·iot·web组态·组态
SoaringHeart3 小时前
Flutter进阶:OverlayEntry 插入图层管理器 NOverlayZIndexManager
前端·flutter
放下华子我只抽RuiKe53 小时前
React 从入门到生产(四):自定义 Hook
前端·javascript·人工智能·深度学习·react.js·自然语言处理·前端框架