前言:原生文件上传按钮带来的困扰
许多前端工程师都曾面临这样的场景——产品经理递来一份设计稿,上面是一个精致到发光的按钮:圆角、渐变、悬停动画,样样齐全。你信心满满地打开浏览器,看到那个灰头土脸的 ……心里忍不住嘀咕:这玩意儿到底怎样才能变好看?

别担心,这个问题有办法解决!
今天就以 Vue 3 为例来攻克这个老大难问题。而且不只是选择单个文件,连一键选取文件夹这种操作也能轻松实现——没错,就是那种在网盘里常见的“上传整个文件夹”功能,前端自己就能完成。
核心思路:偷梁换柱法
说白了就一招:把原生 input 隐藏起来,用自定义按钮去“骗”它执行操作。
就好比家里装了声控灯,你一拍手它就亮,但背后其实藏着个物理开关。我们就是那个拍手的人,而 .click() 就是我们的掌声。
复制代码
<button @click="triggerUpload('file')"> 选择 PPT/PDF 文件button>
<button @click="triggerUpload('folder')"> 选择文件夹button>
<input ref="fileInputRef" type="file" accept=".ppt,.pptx,.pdf" style="display: none" />
<input ref="folderInputRef" type="file" webkitdirectory style="display: none" />
复制代码const triggerUpload = (type) => {
if (type === 'file') fileInputRef.value.click(); // 看,这不就点上了嘛!
else if (type === 'folder') folderInputRef.value.click();
};
黑科技:webkitdirectory 是什么神仙属性?
这是本次的重点!加上这个属性后,用户选中的不再是单个文件,而是整个文件夹。
它的返回值非常神奇——event.target.files 会把文件夹里所有子孙文件都平铺成一个数组,不管你目录嵌套了多少层。
划重点:每个文件对象上都有一个 webkitRelativePath 属性,它能告诉你这个文件在本地文件夹里的完整相对路径。
举个例子:你选了 我的项目 文件夹,里面有 图片/logo.png,那么 webkitRelativePath 就是 我的项目/图片/logo.png。
后端拿到这个路径,就能在服务器上原封不动地重建目录结构——是不是很酷?
血泪教训:解决 @change 不触发的千古难题
有没有遇到过这种尴尬:
- 第一次选了
报告.pptx,正常触发上传; - 上传失败了,或者你取消后想重新选一次;
- 再次点击同一个
报告.pptx; - @change 居然不执行了!
原因很简单:浏览器的“鸡贼”逻辑——它发现 input 的 value 没变化(还是同一个文件路径),就认为你没选新文件,所以不触发 change 事件。
解决办法:每次处理完文件后,手动清空 value!
复制代码const onFileSelected = (event) => {
const files = Array.from(event.target.files);
// ... 处理文件逻辑 ...
// 