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>

相关推荐
Asize1 小时前
AI 协作开发新范式:我用 SDD 做了个排版 npm 包
前端·人工智能
kyriewen2 小时前
我把今年流传的前端 AI 面试题整理了一遍——4 类场景题+回答框架(附速查表)
前端·面试·程序员
计算机魔术师3 小时前
国产多模态模型正面硬刚Opus旗舰:差距从30%缩到3%
前端
风骏时光牛马3 小时前
程序员进阶:深度思考,解锁职场成长的底层逻辑
前端
IT_陈寒3 小时前
用了Proxy才发现以前的JavaScript白写了
前端·人工智能·后端
爱丶不疚4 小时前
Eval: Agent 说的 Eval 是什么?从单测、TDD 到 Sentry 聊起
前端·ai编程·vibecoding
求道於盲4 小时前
python中的类型标注
前端
计算机魔术师4 小时前
从硅谷测试到全球铺开,ChatGPT广告的10亿美元秘密
前端
专业抄代码选手5 小时前
08|Fiber 上的 `useState`:状态终于属于具体组件
前端·javascript·react.js
默_笙5 小时前
😭 Vibe Coding 翻车实录:AI 编程为什么必须先写"剧本"
前端·javascript