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>

相关推荐
老神在在0012 小时前
Token身份验证完整流程
java·前端·后端·学习·java-ee
利刃大大2 小时前
【Vue】指令修饰符 && 样式绑定 && 计算属性computed && 侦听器watch
前端·javascript·vue.js·前端框架
多仔ヾ2 小时前
Vue.js 前端开发实战之 08-Vue 开发环境
vue.js
源码获取_wx:Fegn08953 小时前
计算机毕业设计|基于springboot + vue景区管理系统(源码+数据库+文档)
java·vue.js·spring boot·后端·课程设计
踢球的打工仔3 小时前
typescript-接口的基本使用(一)
android·javascript·typescript
徐小夕@趣谈前端4 小时前
NO-CRM 2.0正式上线,Vue3+Echarts+NestJS实现的全栈CRM系统,用AI重新定义和实现客户管理系统
前端·javascript·人工智能·开源·编辑器·echarts
catino4 小时前
图片、文件上传
前端
Mr Xu_4 小时前
Vue3 + Element Plus 实现点击导航平滑滚动到页面指定位置
前端·javascript·vue.js
小王努力学编程4 小时前
LangChain——AI应用开发框架(核心组件1)
linux·服务器·前端·数据库·c++·人工智能·langchain