您之前可能与文件上传组件交互过,可能是在更新个人资料图片或将文档上传到云时。尽管文件上传组件都有不同的设计,但它们在桌面上往往更直观。通常,它们允许您将文件拖放到特定区域(通常称为拖放区域)并预览它们。
在本文中,我们将学习如何使用 Vue 创建我们自己的拖放文件上传组件。我们将能够在最终上传之前预览所选文件并删除任何文件。要继续操作,您可以在 GitHub 上访问完整的代码。完成后,它看起来像下面的gif:
构建一个简单的放置区 <inputtype=file>
有趣的是,带有file
类型的<input />
本机 HTML支持拖放,但默认情况下,它只接受单个文件。要接受多个文件,我们可以轻松添加 multiple
属性(即),<input type = "file" multiple />
稍微拉伸其宽度,并添加边框和填充:
从美学上讲,你会同意这还不够好。相反,我们将构建一个以类似方式工作的拖放文件上传器;我们仍然会有文件输入,但其可见性将被隐藏。然后,我们将添加一个 visible
标签,以便拖动到文件输入。我们还将添加另一个自定义事件,该事件将更改拖动状态并允许我们显示和删除所选文件。
创建高级放置区
首先,我们将通过运行以下命令创建一个新的 Vue 应用程序:
npm init vue@latest dropfile
在您喜欢的文本编辑器中打开新项目,然后在 src/component
目录中创建一个空DropFile.vue
文件。接下来,让我们将此组件导入到我们的入口文件中。打开App.vue
并用以下代码替换其内容:
<template>
<div id="app">
<DropFile />
</div>
</template>
<script>
import DropFile from "./components/DropFile.vue";
export default {
name: "App",
components: {
DropFile,
},
};
</script>
首先,我们将组织所有与 CSS
相关的代码。在 src/assets
目录中创建一个新 dropfile.css
文件,并将以下代码粘贴到其中:
.main {
display: flex;
flex-grow: 1;
align-items: center;
height: 100vh;
justify-content: center;
text-align: center;
}
.dropzone-container {
padding: 4rem;
background: #f7fafc;
border: 1px solid #e2e8f0;
}
.hidden-input {
opacity: 0;
overflow: hidden;
position: absolute;
width: 1px;
height: 1px;
}
.file-label {
font-size: 20px;
display: block;
cursor: pointer;
}
.preview-container {
display: flex;
margin-top: 2rem;
}
.preview-card {
display: flex;
border: 1px solid #a2a2a2;
padding: 5px;
margin-left: 5px;
}
.preview-img {
width: 50px;
height: 50px;
border-radius: 5px;
border: 1px solid #a2a2a2;
background-color: #a2a2a2;
}
接下来,将 DropFile.vue
文件中的内容替换为以下代码:
<template>
<div class="main">
<div
class="dropzone-container"
@dragover="dragover"
@dragleave="dragleave"
@drop="drop"
>
<input
type="file"
multiple
name="file"
id="fileInput"
class="hidden-input"
@change="onChange"
ref="file"
accept=".pdf,.jpg,.jpeg,.png"
/>
<label for="fileInput" class="file-label">
<div v-if="isDragging">Release to drop files here.</div>
<div v-else>Drop files here or <u>click here</u> to upload.</div>
</label>
</div>
</div>
</template>
<script>
export default {
data() {
return {
isDragging: false,
files: [],
};
},
methods: {
onChange() {
this.files.push(...this.$refs.file.files);
},
dragover(e) {
e.preventDefault();
this.isDragging = true;
},
dragleave() {
this.isDragging = false;
},
drop(e) {
e.preventDefault();
this.$refs.file.files = e.dataTransfer.files;
this.onChange();
this.isDragging = false;
},
},
};
</script>
<style scoped src="@/assets/dropfile.css"></style>
在这里,我们创建了两个响应式状态: isDragging
表示用户尝试将文件拖到我们的放置区时的状态, 并且files
是一个用于保存选定或放置文件的数组。然后,我们将自定义 ref
附加到主文件输入,以便在 Vue
实例中轻松访问它。我们还添加了一个 onChange
事件,该事件基本上使用附加到输入的文件更新我们的files
数组。
之后,我们创建了dragover
、 dragleave
和 drop
方法,将它们附加到保存主文件输入的容器中。因此, drop
事件和方法将捕获丢弃的文件并将其绑定到我们的文件输入,利用我们之前创建的自定义 ref
。
dragover
和 dragleave
方法还允许我们根据需要更改状态isDragging
。最后,我们使用条件渲染v-if
和 v-else
来检查每个状态的状态 isDragging
并显示自定义消息。如果我们此时运行我们的应用程序,我们应该有以下输出:
尽管我们尚不可见丢弃的文件,但它们实际上在后台的某个地方。要对此进行测试onChange()
方法内,请登录到 this.files
的控制台。每当删除或手动选择文件时,数组都应记录到控制台中,每个文件都包含文件名、大小、上次修改日期和其他相关信息。
改进拖动状态
为了提升拖放体验,我们可以修改拖放区边框以反映其活动状态。更新dropzone
容器样式以包含以下边框样式:
.dropzone-container {
/* . . . */
border: 2px dashed;
border-color: #9e9e9e;
}
接下来,更新 dropzone-container
标记以在拖动状态期间有条件地设置其边框样式:
<div
:style="isDragging && 'border-color: green;'"
>
. . .
通过此新更新,您的放置区应如下所示:
列出已删除的文件
预览所选和删除的文件非常简单。我们只需要遍历我们的文件数组。为此,请紧跟在上一个代码中的 </label>
标记之后添加以下代码:
<!-- Note: Only add the code block below -->
<div class="preview-container mt-4" v-if="files.length">
<div v-for="file in files" :key="file.name" class="preview-card">
<div>
<p>
{{ file.name }}
</p>
</div>
<div>
<button
class="ml-2"
type="button"
@click="remove(files.indexOf(file))"
title="Remove file"
>
<b>×</b>
</button>
</div>
</div>
</div>
在上面的代码中,我们使用条件渲染来检查我们的 files
数组是否有有效的长度。然后,我们循环遍历其所有内容,同时在段落中显示每个文件名。
删除文件
在前面的代码块中,您会注意到我们还为每个可迭代项添加了一个按钮,在将当前文件的索引作为其参数传递时调用 remove()
方法。如果我们此时运行我们的应用程序,我们应该看到所选文件名按预期显示,以及一个用于删除它们的按钮。
但是,删除按钮尚不起作用。若要解决此问题,请将所有以前的方法附加到新 remove()
方法:
// ..
remove(i) {
this.files.splice(i, 1);
},
在此阶段,一切都应按预期工作。我们应该能够手动选择文件,拖放它们,查看它们的名称,也可以删除它们。下面的 gif 显示了输出的预览:
预览选定的图像文件
使我们的拖放区组件更加直观的另一个功能是能够预览选定的图像文件。我们可以通过使用本机 URL.createObjectURL()method:
生成任意 URL
来轻松实现这一点。
// ..
generateURL(file) {
let fileSrc = URL.createObjectURL(file);
setTimeout(() => {
URL.revokeObjectURL(fileSrc);
}, 1000);
return fileSrc;
},
建议在使用 URL.createObjectURL()
该方法创建 URL 后始终撤销 URL,以避免可能的内存丢失。我们添加了额外的超时,以便在一秒后自动执行此操作。
接下来,将显示所有选定或删除的文件名称的段落 <p>
标签替换为以下代码:
<!-- . . . -->
<img class="preview-img" :src=generateURL(file) " />
<p>
{{ file.name }}
</p>
<!-- . . . -->
有了这个,我们就可以一切了!我们现在可以轻松删除、选择删除甚至预览选定的文件:
显示文件大小
如上一节所述,我们还可以直接访问每个选定的文件大小及其上次修改日期。默认情况下,文件大小以字节为单位显示,但是,我们可以轻松地除以一千以转换为KB
。将以下代码添加到显示文件名的文件部分:
<p>
{{ file.name }} -
{{ Math.round(file.size / 1000) + "kb" }}
</p>
现在,每个选定的文件大小将显示为其名称:
防止重复文件
目前,我们的文件拖放区允许我们删除重复文件。为了避免这种情况,我们可以通过遍历传入文件并检查数组 files
中是否已存在其中任何一个来增强功能。如果发现重复项,我们可以显示相应的消息。
若要实现此目的,请使用以下代码更新onChange
该方法:
// . . .
onChange() {
const self = this;
let incomingFiles = Array.from(this.$refs.file.files);
const fileExist = self.files.some((r) =>
incomingFiles.some(
(file) => file.name === r.name && file.size === r.size
)
);
if (fileExist) {
self.showMessage = true;
alert("New upload contains files that already exist");
} else {
self.files.push(...incomingFiles);
}
},
通过此新更新,我们的放置区将根据名称和大小检查用户是否正在尝试上传已存在的文件。
将文件上载到服务器
由于所选文件已附加到 files
状态,因此将它们上载到服务器轻而易举。尽管有几种不同的策略可以实现此目的,但使用 FormData
往往是一种更常见的方法。
下面的代码显示了我们如何利用 FormData
Axios
将文件发送到 API
或服务器进行处理的示例:
// . . .
uploadFiles() {
const files = this.files;
const formData = new FormData();
files.forEach((file) => {
formData.append("selectedFiles", file);
});
axios({
method: "POST",
url: "http://path/to/api/upload-files",
data: formData,
headers: {
"Content-Type": "multipart/form-data",
},
});
},
然后,我们可以在后端使用其他内置函数或框架来根据需要处理文件。
总结
在本文中,我们学习了如何使用 Vue 创建一个极简但交互式的拖放文件上传器。我们的文件上传组件让我们可以浏览所选文件的名称和大小,预览图像文件,甚至在上传之前删除文件。