在使用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>