原来的代码:尚未增加debounce防抖
src\components\common\CommonFileManager.vue
// 触发文件选择 const triggerFileSelect = (event?: Event) => { if (fileInputRef.value) { fileInputRef.value.click(); } }; // 处理文件选择 const handleFileSelect = async (event: Event) => { const input = event.target as HTMLInputElement; selectedFiles.value = input.files; // 检查不通过,直接退出 if (!check()) return; // 上传文件 if (props.uploadToServer && selectedFiles.value) { try { uploading.value = true; const uploadData = new FormData(); // 添加多个文件到 FormData for (const file of selectedFiles.value) { uploadData.append("uploadFiles", file); } // 添加业务类型到 FormData uploadData.append("businessType", props.businessType); // 添加业务主键到 FormData uploadData.append("businessKey", props.businessKey); const result = await FileApi.uploadFiles(uploadData); fileSources.value = result.data; } catch (error) { console.error("上传失败", error); } finally { uploading.value = false; } } // 清空input值,允许重复选择相同文件 if (fileInputRef.value) { fileInputRef.value.value = ""; } }; <!-- 上传按钮 --> <div v-if="actionLayout.includes(`upload`) && fileSources.length < props.maxCount" class="upload-button-container" title="点击上传文件" @click="triggerFileSelect"> <!-- 上传图标 --> <!-- <el-icon> <Plus /></el-icon> --> <!-- 上传按钮(包含上传图标) --> <el-button type="primary" :icon="Plus" circle size="small" /> <input ref="fileInputRef" type="file" multiple style="display: none" @change="handleFileSelect" /> </div>这样都备具一定的防抖功能,连续点击,偶尔才会连续弹出【两个】文件选择窗口
增加防抖代码:
// 触发文件选择 - 防抖处理,1000ms 防抖延迟 const triggerFileSelect = debounce(() => { if (fileInputRef.value) { fileInputRef.value.click(); } }, 1000);应用效果:连续点击,频繁弹出文件选择窗口
这个问题是因为防抖函数(debounce)返回的是一个新的函数引用,而每次组件重新渲染时,都会创建新的防抖函数实例。在 Vue 3 的<script setup>中,当组件重新渲染时,会重新执行setup逻辑,导致debounce创建新的函数实例。
但在你的代码中,更可能的原因是:
问题分析
debounce返回新函数:每次调用debounce()都会创建一个新的防抖函数实例DOM 事件绑定:
.upload-button-container上的@click事件绑定了triggerFileSelect函数频繁双击:
第一次点击:触发防抖函数,设置 1000ms 延迟
在 1000ms 内第二次点击:由于是新的点击事件,会再次执行
triggerFileSelectfileInputRef.value.click()是同步操作,会立即触发文件选择
解决方案
方案1:使用{ leading: true, trailing: false }配置
javascript
const triggerFileSelect = debounce(() => { if (fileInputRef.value) { fileInputRef.value.click(); } }, 1000, { leading: true, // 立即执行第一次 trailing: false // 延迟后不再执行 });方案2:使用节流(throttle)替代防抖
javascript
import { throttle } from 'lodash-es'; const triggerFileSelect = throttle(() => { if (fileInputRef.value) { fileInputRef.value.click(); } }, 1000);方案3:使用自定义防抖逻辑
javascript
import { ref } from 'vue'; // 添加防抖标志位 const isTriggering = ref(false); const triggerFileSelect = () => { if (isTriggering.value) return; isTriggering.value = true; if (fileInputRef.value) { fileInputRef.value.click(); } setTimeout(() => { isTriggering.value = false; }, 1000); };方案4:直接在模板中使用事件修饰符
vue
<!-- 修改模板中的点击事件 --> <div class="upload-button-container" title="点击上传文件" @click="handleUploadClick"> <!-- ... --> </div>
javascript
// 在 script 中 import { ref, onUnmounted } from 'vue'; let clickTimer: NodeJS.Timeout | null = null; const handleUploadClick = () => { if (clickTimer) return; // 如果在防抖时间内,直接返回 // 触发文件选择 if (fileInputRef.value) { fileInputRef.value.click(); } // 设置防抖 clickTimer = setTimeout(() => { clickTimer = null; }, 1000); }; onUnmounted(() => { if (clickTimer) { clearTimeout(clickTimer); } });推荐方案
我建议使用方案1,因为它:
保持使用
lodash的防抖功能{ leading: true }确保第一次点击立即响应{ trailing: false }防止延迟后的重复执行用户体验更好(第一次点击立即响应)
javascript
const triggerFileSelect = debounce(() => { if (fileInputRef.value) { fileInputRef.value.click(); } }, 1000, { leading: true, trailing: false });这样配置后,无论用户如何频繁点击,都只会在第一次点击时立即触发文件选择,后续的点击在 1000ms 内会被忽略,避免了连续弹出文件选择窗口的问题。
最终防抖修改代码:
// 触发文件选择 - 防抖处理,1000ms 防抖延迟,使用 { leading: true, trailing: false } 配置 const triggerFileSelect = debounce( () => { if (fileInputRef.value) { fileInputRef.value.click(); } }, 1000, { leading: true, // 立即执行第一次 trailing: false // 延迟后不再执行 } );