在项目中你可能经常使用文件上传的组件,使用它的场景一般是在应用程序中更新你的个人资料中的头像或者在社交软件中分享图片,或者是将文件上传至云端…虽然同为文件上传组件,但是它们都拥有不同的设计。
然而在大多的桌面应用中,允许你将文件拖放到一片设定好特定区域中并在上传之前可以预览文件的文件上传组件往往更直观。
通过这篇文章,我们将学习如何用Vue3以及Tailwindcss创建属于我们自己的拖放式文件上传组件;它可以实现以下功能:
- 可以通过自行选择文件或者拖动文件将文件存放至上传区域
- 可以预览选定的文件
- 在最后上传前随时删除任何文件。
以下是我们在代码编写完成后的组件预览:
使用<input type='file' />
构建一个简单的放置区域
有趣的是,原生的HTML标签 <input />
的文件类型支持拖放,但是默认只接受一个文件。我们可以在input标签内添加multiply
属性来接受一个以上的文件,把上传区域的宽度拉长一点,添加一些边框,它就可以正常工作:
但是这样做会显得不太美观,input
标签的自带样式仍然会存在于上传区域中。然而,我们现在实现的拖放式文件上传组件也会以类似的方式工作,即我们仍将有一个文件输入,但可以将其可见性隐藏。然后我们将添加一个可见的文字标识,通过它可以将文件拖动到上述的文件上传区域。接着我们会添加一些其他的自定义事件,以改变拖动状态,并允许我们显示和删除选定的文件。
创建一个自定义的上传区域
让我们开始创建一个新的vue3项目来完成这一个功能实现。如果你还没有准备好必须的运行环境,先安装node,然后使用以下命令创建一个全新的vue项目:
npm create vite@latest
(使用vite)
设置项目名后,下面的选项选择Vue
和JavaScript
即可
在编写代码之前,我们先要安装一个用于编写css的框架Tailwind CSS。Tailwind CSS 是一个由js编写的CSS 框架 他是基于postcss去解析的,由于本文主要是介绍文件上传组件,这两个开发工具不在这进行过多介绍,想要深入了解的读者可以移步至它们的官网地址:
这里主要介绍如何在项目中安装并使用Tailwindcss:
安装TailwindCSS以及其依赖项
npm install -D tailwindcss postcss autoprefixer
生成配置文件
npx tailwindcss init -p
修改配置文件tailwind.config.js
由于使用3.0及以上版本,使用官方文档的配置(2.6版本)会导致tailwindCSS不生效,故需要使用以下配置:
module.exports = {
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
theme: {
extend:{},
},
plugins: [],
}
修改src中的style.css
@tailwind base;
@tailwind components;
@tailwind utilities;
这个文件后面会用到,现在先保持初始配置。
上述配置完成后,tailwindcss就可以使用了。
接下来在src/component
目录下新建文件DropFile.vue
下一步,将这个空模板文件导入到App.vue
中,将原有的内容替换掉:
<template>
<DropFile />
</template>
<script setup>
import DropFile from './components/DropFile.vue'
</script>
现在让我们编写css代码,由于使用了tailwind CSS的缘故,我将组件的所有css代码使用tailwind CSS语法写在了公共样式文件style.css
文件夹中。
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.main {
@apply flex grow items-center justify-center h-screen text-center;
}
.dropzone-container {
@apply p-16 bg-[#f7fafc] border border-solid border-[#e2e8f0];
}
.hidden-input {
@apply opacity-0 overflow-hidden absolute w-1 h-2;
}
.file-label {
@apply text-xl block cursor-pointer;
}
.preview-container {
@apply flex mt-8;
}
.preview-card {
@apply flex border border-solid border-[#a2a2a2] p-2 ml-2;
}
.preview-img {
@apply w-14 h-14 rounded-md border border-solid border-[#a2a2a2] bg-[#a2a2a2];
}
}
编写完css代码后,为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="fileInputRef"
accept=".pdf,.jpg,.jpeg,.png"
/>
<label for="fileInput" class="file-label">
<div v-if="isDragging">释放以将文件放到此处。</div>
<div v-else>将文件拖到此处或单击此处上传。</div>
</label>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const isDragging = ref(false)
const files = ref([])
const fileInputRef = ref(null)
function onChange() {
files.value = [...fileInputRef.value.files]
}
function dragover(e) {
e.preventDefault()
isDragging.value = true
}
function dragleave() {
isDragging.value = false
}
function drop(e) {
e.preventDefault()
fileInputRef.value.files = e.dataTransfer.files
onChange()
isDragging.value = false
}
在代码中,我们创建了两个响应式变量:isDragging
和files
isDragging
表示用户是否已经将文件拖放入文件区。files
是一个数组,用于保存所选择的文件。
然后我们在用于文件输入的input标签中添加了一个ref
,以使它在我们的Vue示例中容易被访问。此外,我们还添加了一个onChange
函数,该函数用于监听我们在文件区中选择的文件来更新files
数组。
之后我们创建了dragover
、dragleave
和drop
方法。drop
方法在文件被拖拽到文件区并释放时被调用。它将使用拖拽操作的所有文件赋值给ref
,利用这个自定义的ref
通过onChange
函数将选择的文件添加到Files
文件列表中。
dragover
和dragleave
方法也让我们根据需要改变isDragging
的状态。最后我们使用v-if
和v-else
来检查isDragging
的状态,然后为每个状态显示一个自定义的消息。
尽管被丢弃的文件在页面上并没有显示,但是他们实际上就在后台的某个地方。为了测试这一点,我们在onChange()
方法中把files
里面的内容显示在控制台。每当你手动选择或者丢弃一个文件时,控制台应该会显示files
数组的变化。其中每个文件都包含文件名、大小、修改日期以及其他与文件有关的信息。
展示文件区里的所有文件
想要预览已经被选择的文件非常简单:我们只需要使用v-for
遍历文件数据,将结果展示在页面上。要实现这一点,请在上面的代码中的</label>
标签之后添加以下代码:
<!-- . . . -->
</label>
<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>
移除文件区里的文件
在上面的代码中,眼尖的同学会注意到我们还为循环中的每一项添加了一个按钮,它调用了remove()
方法,同时将当前文件的索引作为其参数进行传递。在这种情况下,我们此时运行项目,我们应该能够看到文件区内的所有文件的文件名会按预期显示,每一个文件还有一个删除按钮,但是,删除按钮并未起作用。
要解决此问题,在script
中添加remove()
方法,如下所示:
remove(i) {
files.value.splice(i, 1);
},
这些操作完成以后,移除文件操作应该按预期进行。此时我们应该能够手动选择文件,拖放文件,查看文件区内所有文件的名称,还可以移除文件区内的文件。输出预览如下:
预览上传的图片文件
我们可以添加一个实现图片文件的预览的功能。通过使用URL.createObjectURL()
方法生成任意URL,并将我们的文件对象作为其参数来传递,从而轻松实现这一功能。如果要实现预览的效果,需要在已有的方法列表中添加generateURL()
方法,如下所示:
// ..
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>
<!-- . . . -->
我们成功了!我们现在可以轻松地删除、选择,甚至预览选定的文件:
上传至服务器
将文件区里的所有文件上传到服务器也是轻而易举的事,因为它们存在于files
文件数组里面;尽管有不同的方案来实现这一点,但利用FormData往往是一种更常见的方法。
下面是如何利用FormData和Axios将文件发送到API或服务器进行处理的示例:
// . . .
multipleFilesUpload() {
const formData = new FormData();
files.value.forEach((file) => {
formData.append("selectedFiles", file);
});
axios({
method: "POST",
url: "http://api/file/multiple_upload/",
data: formData,
headers: {
"Content-Type": "multipart/form-data",
},
});
},
然后,我们可以根据需要在后端使用其他内置功能或框架来处理文件上传。
后面有时间会写一个后端处理文件上传的文章,敬请期待!
写在最后
我们已经介绍了如何使用Vue创建一个极简但实用的拖放文件上传组件。我们的文件上传组件可以让我们浏览选定文件的名称和大小,预览图像文件,甚至在上传前随意删除文件。
在遵循本文的教程编写属于自己的代码时,难免会出现错误。为了省去不必要的麻烦,GitHub上也提供了本教程的完整代码。
感谢您的阅读!