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>

相关推荐
剪刀石头布啊6 小时前
vue-react为何要虚拟dom
前端
剪刀石头布啊6 小时前
ahooks常用的一些库,我们使用一下
前端
剪刀石头布啊6 小时前
从git更新本地提交记录到更新已经推送到远端记录
前端
云浪7 小时前
Go 并发实战:从零实现一个高并发安全的任务调度器
javascript·后端·go
Flynt8 小时前
axios 被投毒半年后,我把 npm 的"安全防线"实测了一遍:最信任的 SHA512 是安慰剂
前端·npm·node.js
excel8 小时前
Base62 和 Base64 有什么区别?
前端
风骏时光牛马10 小时前
大模型底层架构与AI源码深度剖析
前端
掘金挖土11 小时前
前端手摸手跑路之 AI 应用开发(九)
前端·后端
中文呀13 小时前
三维 GIS 离线缓存实战:逆向 CesiumOfflineCache,并亲手为它补上"自动更新"能力
前端