vue3 实现自定义文件上传复选框、图像预览、文件上传
序言
最近在倒腾一个有关文件上传的小项目,使用到了 koa
搭建服务器, vue3
来完成页面开发。过程中磕磕绊绊,但最后总算是完成了功能实现,并且对 node
环境下的文件上传流程有了一定了解。故此,写下这篇笔记以记录摸索的过程。
本文是前端部分,后端部分已更新
后端篇
自定义文件选择框
在开始考虑做文件上传的功能的时候,就一直好奇原生的 input
那么丑(此处包括下文的 input
都是指 type='file'
的情况),并且,我们都知道 input
是个非常特殊的 html标签 ,它允许被修改的样式其实很有限,可我们见到的文件上传选择框各式各样,它是如何被做成五花八门的炫酷的样式的呢?
其实,对于 input
样式的自定义,我们需要放弃在 input
元素身上直接动刀子的想法,这个就是改不了!!!但我们可以转换思路,既然不能改 input
的样式,那么我们就把它藏起来,然后用个好看的组件来代替不久好了吗?
<template>
<div>
<input
ref="fileInput"
type="file"
placeholder="选择你的文件"
style="display:none;"
multiple
>
<div class="file-input">
<i class="select-file iconfont"></i>
</div>
</div>
</template>
<style lang="scss" scoped>
.file-input {
display: flex;
flex-direction: row;
padding: 12px;
.select-file {
width: 100px;
height: 100px;
color: #eee;
font-size: 70px;
line-height: 100px;
text-align: center;
background-color: white;
border: 1px solid #ddd;
border-radius: 8px;
}
}
</style>
因此,我们给 input
设置上 display:none
,那么在页面上,这个 input
就不可见了。我们另外加了一个元素(这里使用 iconfont
来展示图标),给它设置上想要的样式,一样,页面上就只剩下我们期望的文件上传选择框:
但这样还没完。对于文件选择框,当我们点击后,会弹系统提供的文件选择的弹窗,但单机我们自定义的组件,并没有这个效果。
其实要实现这个效果,也很简单,我们点击默认样式的 input
的时候,其实就是触发了这个 input
元素的 click
事件。那么我们只需要在用户点击了我们自定义的文件选择框的组件的时候,手动地触发本我们隐藏了的 input
的 click
的事件就可以了:
<script lang="ts" setup>
import { onMounted, ref } from "vue"
const fileInput = ref<HTMLInputElement>()
/** 点击模拟的文件选择触发按钮,触发真实的文件选择的响应事件 */
const clickFileIput = () => {
fileInput.value?.click()
}
</script>
<template>
<!-- ... -->
<i class="select-file iconfont" @click="clickFileIput"></i>
<!-- ... -->
</template>
<style lang="scss" scoped>
/* ... */
</style>
上面的代码中,我们给自定义的文件选择框添加了一个点击事件,然后在这个点击事件当中,我们获取了 ref
中的 input
的元素,然后执行 click
这个事件方法,此时,点击后便会弹出系统的文件选择弹窗了。
而用户选择并确认了文件后,会触发 input
上的 change
事件,然后,我们可以在 input
元素上拿到一个 files
的属性,这个属性中保存的便是用户这次选择的文件封装的对象。
这就便于我们手动地对用户选择文件这一行为进行管理。
比方说,用户每次选择文件并确认后,浏览器地行为是替换 input
上的 files
属性,这会导致用户每次选择的结果并没有关联,如果选择的文件很多,那么用户就必须一次性选中需要需要的文件。这会降低用户的体验。
我们期望的是,用户可以分多次选择所有文件的子集,而我们的代码帮用户完成将子集合并成全集的过程,所以我们来优化一下代码:
<script lang="ts" setup>
// ...
const fileMap = ref(new Map<string, File>())
/**
* 当 input[type='file'] 变化,即用户选择了文件的使用,将其以文件名为 key ,暂时保存在 fileMap 中;
* 注意, input 上的 files 属性,只保存用户打开了文件选择框,并且选择了文件后,这一时刻的文件
* 也就是说,用户每次选择执行的都是 replace 的操作,因此,我们使用 map 来维护用户多次选择的所有文件,以及一些手动删除的操作
*/
const onFileChange = function(this: HTMLInputElement) {
const files = this.files || [];
for (const file of files) {
fileMap.value.set(file.name, file)
}
}
onMounted(() => {
fileInput.value?.addEventListener("change", onFileChange)
})
// ...
</script>
在这里,我们先来定义一个 fileMap
来保存用户选择的文件,文件名作为 key
,将每个 File
对象作为 value。
然后,我们在页面渲染之后,为 input
的对象添加上 change
事件的监听器 onFileChange
(当然,你也可以直接在 input
元素上写上 @change
事件),而在这个监听器中,我们拿到
input
元素上的 files
,然后根据名字保存到 fileMap
中。
这样一来,用户便能分多次选择自己想要上传的文件。
如果需要添加删除的功能,只需要设法移除 fileMap
中保存的文件即可。
实际上,在原生的
input
上,我们每次选择了文件后,会展示我们选择的文件的文件名清单,在当前的代码中,我们选择了文件之后,什么都不会发生。这是因为我想在下文中介绍一下图片选择预览的实现,因此没有单独实现。如果你期望有文件名的清单出现,那么只需要将下文的图片预览的逻辑替换为文字展示即可。
图片预览
既然已经做了自定义文件选择框,不妨顺便也实现一下图片预览好了。
我们来思考一下,实现图片预览大致需要哪几个步骤:
- 知道用户选择了哪些图片
- 获取图片源数据
- 展示图片
这里概括的较为笼统,我们来一步一步详细介绍。
首先,我们如何知道用户选择了哪些文件?其实这一步,在上文中已经介绍到了,我们可以使用一个 fileMap
来保存用户选择的文件,其 键值 是文件名,其 属性值 是 File
对象,所以这一步我们已经完成了。
那么关于图片源数据呢?实际上,这个 File
对象,我们可以打印出来看一下:
实际上,它已经包含了一个文件的所有基本描述信息,因此,如果它能被转换成一个 blob
对象,那么我们就可以操作它真实的数据了。但实际上,我们并不需要转换成 blob
对象,因为 File
这个类就是继承自 Blob
的,这个我们展开方才打印的对象的原型链便可见一斑:
所以,第二步也迎刃而解了。
最后就到了如何展示图片,我们首先会想到的便是使用 img
标签。没错,这是正确的。但是,我们也知道, img
标签需要设置 src
的属性,才能指定需要展示的图片。
这样一来,问题就转换成了如何将我们选中的图片转换成 img
标签可用的 src
属性了。而 src
这个属性的属性值,是一个 URL
对象,因此,我们只需要想方法使用选中的 File
构建一个 URL
对象即可。
这点,浏览器其实已经为我们做好了工具。
我们可以在全局环境上拿到一个 URL
的类,而这个类上有一个 createObjectURL
方法,接受一个 Blob
实例作为参数,返回一个 URL
对象。这正好就是我们想要的。我们只需要将我们存在 fileMap
中的每个文件描述取到,然后构建成为一个 URL
,然后为对应的 img
的 src
属性设置上这个 URL
即可。
大致代码可以参考如下:
<script lang="ts" setup>
import { defineComponent, onMounted, ref } from "vue"
interface FileWithURL {
file: File;
url: string;
}
const fileInput = ref<HTMLInputElement>()
const fileMap = ref(new Map<string, FileWithURL>())
/**
* 当 input[type='file'] 变化,即用户选择了文件的使用,将其以文件名为 key ,暂时保存在 fileMap 中;
* 注意, input 上的 files 属性,只保存用户打开了文件选择框,并且选择了文件后,这一时刻的文件
* 也就是说,用户每次选择执行的都是 replace 的操作,因此,我们使用 map 来维护用户多次选择的所有文件,以及一些手动删除的操作
*/
const onFileChange = function(this: HTMLInputElement) {
const files = this.files || [];
for (const file of files) {
const url = URL.createObjectURL(file)
fileMap.value.set(file.name, { file, url })
}
}
/** 点击模拟的文件选择触发按钮,触发真实的文件选择的响应事件 */
const clickFileIput = () => {
fileInput.value?.click()
}
onMounted(() => {
fileInput.value?.addEventListener("change", onFileChange)
})
</script>
<template>
<div>
<input
ref="fileInput"
type="file"
placeholder="选择你的文件"
style="display:none;"
multiple
>
<div class="file-input">
<div
v-for="item in fileMap.values()"
:key="item.file.name"
class="img-preview"
>
<img :src="item.url">
</div>
<i class="select-file iconfont" @click="clickFileIput"></i>
</div>
</div>
</template>
<style lang="scss" scoped>
.file-input{
display: flex;
flex-direction: row;
padding: 12px;
.img-preview {
position: relative;
margin: 0 6px;
img {
width: 100px;
height: 100px;
}
}
.select-file {
width: 100px;
height: 100px;
color: #eee;
font-size: 70px;
line-height: 100px;
text-align: center;
background-color: white;
border: 1px solid #ddd;
border-radius: 8px;
}
}
</style>
文件上传
做完了一些花里胡哨的交互后,我们回归正题。
对于普通的前后端数据交互,我们一般都是使用 JSON
,因此我们会设置 http
的 content-type
为 application/json
。
但是,当我们需要上传图片或者其它文件的时候, JSON
是不适合的,我们需要使用到 二进制流
来替代。
而要想一个 http
的请求体表现为 二进制流
,我们需要设置 content-type
为 multipart/form-data
。
但实际上,我们并不需要手动去设置这个请求头部,这个后面会介绍到。
对于我们需要上传的文件的处理,我们需要使用到 FormData
这个对象来构造我们的请求表单。
// ...
const onSubmit = () => {
const formData = new FormData();
for (const extendFile of fileMap.value.values()) {
formData.append(extendFile.file.name, extendFile.file)
}
axios.post("your server address", formData)
}
// ...
这里我们定义来一个 onSubmit
的方法来管理我们的数据提交。
在这个方法中,我们从 fileMap
中遍历取出所有的文件对象,通过调用 formData
的 append
这个方法把每个文件添加到我们的表单对象中去,然后使用 axios
来发送我们的请求。
实际上到此,前端需要处理的事情已经差不多了。如果你尝试选择一些文件然后发送这个请求,你会看到请求头部 content-type
很正确地设置为了 multipart
(以及 boundary
,这个是流中用于分割不同部分数据的),而且我们可以查看控制台中请求的负载,发现文件正确地被编译成了 二进制流
。
首先先来说说数据的部分,在 MDN
中提到这么一句话:
如果送出时的编码类型被设为 "multipart/form-data",它会使用和表单一样的格式。
所以如果设置好 content-type
,那么我们就无需关系 FormData 到 二进制 的转换过程。
那么,又是谁帮我们完成了请求头部的设置呢?
由于这里我使用到了 axios
,那么要么是 axios
进行了头部设置,要么就是浏览器帮我们完成了头部设置。
因此,我从 axios
的源码下手,在 adapter/xhr.js
的文件中找到了这样几行代码:
if (utils.isFormData(requestData)) {
delete requestHeaders['Content-Type']; // Let the browser set it
}
那么答案很明显了,这是浏览器的默认行为。
到此为止,我们基本完成了上传文件这一功能前端的部分,接下来就剩下后端如果接收我们发送的文件数据,然后保存到服务器的工作了。
总结
- 自定义文件上传选择框是通过隐藏
input
,使用自己设计的组件作为替代展示,然后在合适的时机手动地触发input
的click
事件 - 图片预览需要将文件对象转换成为一个
URL
对象,然后设置给img
标签的src
属性 - 文件上传需要使用
FormData
封装我们的文件数据,然后设置content-type
为multipart/form-data
,但是后者浏览器会帮我们完成
推荐阅读
-
vue 实现文件预览和文件上传、下载、预览 - 多图、模型、dwg 图纸、文档(word、excel、ppt、pdf)
-
node+express+multer 实现单个或多个图像文件、视频文件的上传
-
使用 Java 实现跨服务器的后端文件传输(上传图像和其他文件)
-
Java 整合 minio 实现文件预览上传和下载
-
vue3 加上 antd Upload 组件实现文件上传,并结合 Modal 组件实现文件预览功能。
-
vue3 实现自定义文件上传复选框、图像预览、文件上传
-
41 个下载免费 3D 模型的最佳网站-使用说明:使用权限可能因型号而异。因此,在下载文件之前,请仔细检查每个下载页面上的许可证和使用权限。 17. Clara.io Clara.io 是一个创建 3D 内容的全球平台,也是一个培养新 3D 艺术家的社区。Clara.io 提供+100,000个免费的3D模型,包括OBJ,Blend,STL,FBX,DAE,Babylon.JS,Three.JS格式,用于 Clara.io,Unity 3D,Blender,Sketchup,Cinema 4D,3DS Max和Maya。 使用说明:免费,标准和专业帐户仅供个人使用,如果您需要将 clara.io 用于商业用途,请与销售团队联系。 18. 3DExport 3DExport是一个市场,您可以在其中购买和销售用于CG项目的3D模型,3D打印模型和纹理。它提供15 +不同的3D格式供下载,如3DS MAX(.max),Cinema4D(.c4d),Maya(.mb,.ma),Lightwave(.lwo),Softimage(.xsi),Wavefront OBJ(.obj),Autodesk FBX(.fbx)等。它还提供15种不同的语言! 使用说明:免费下载仅供个人和非商业用途。 19. 3D Warehouse 3D Warehouse是一个开放的库,允许用户共享和下载SketchUp 3D模型,用于建筑,设计,施工和娱乐!任何人都可以免费制作,修改和重新上传内容到3D仓库,您可以找到任何您能想到的东西,如家具,电子产品,室内产品等。 使用说明:3D Warehouse中的所有模型都是免费的,因此任何人都可以下载文件以用于SketchUp甚至其他软件,如AutoCAD,Revit和ArchiCAD。 20. CadNav.com CadNav是CGI平面设计师和CAD / CAM / CAE工程师的在线3D模型库,我们提供超过50000 +免费3D模型和CAD模型下载。在CadNav网站上,您可以下载高质量的多边形网格3D模型,3D CAD实体对象,纹理,Vray材料,3D作品,CAD图纸等。 使用说明:免费下载仅供个人和非商业用途。 21. All3dfree.net 就像网站名称一样,它提供免费的3D模型,还包括Vray材料,CAD块,2d和3d纹理集合,无需注册即可免费下载。它是不断更新的,因此您可以查找或请求3DS,MAX,C4D,skp,OBJ,FBX,MTL等格式的模型。 使用说明:所有资源均不允许用于商业用途,否则您将承担责任。 22. Hum3D 自2005年以来,Hum3D帮助来自3多个国家的80D艺术家节省3D建模时间,并制作逼真的3D模型,用于电影,视频游戏,AR应用程序和可视化。所有模型均由首席3D艺术家进行验证,他们检查其是否符合专业要求和最新的3D建模标准。 使用说明:免费下载仅供个人和非商业用途。 23. Artist-3D.com 艺术家-3D 库存的免费 3D 模型下载按通用类别排序。它为人体解剖学、汽车、家具、火箭、卫星等模型提供 AutoDesk 3DS Max 格式。您还可以在浏览他们的网站时找到教程和类似类型的建模。 使用说明:使用权限可能因型号而异。因此,在下载文件之前,请仔细检查每个下载页面上的许可证和使用权限。 24. Free the models 就像本网站的标题一样,它为3d应用程序和3d游戏引擎提供免费的内容模型。您可以为您的任何项目找到许多有趣且有用的模型!它提供3ds,wavefront,bryce,poser,lightwave,md2和unity3d格式的模型。还有一个很棒的纹理集合,可以在您最喜欢的建模和渲染程序中使用。 使用说明:您从这里下载的所有内容都可以免费使用,除非它不能包含在另一个免费的网络或CD收藏中,也不能单独出售。否则,您可以在商业游戏,3D应用程序或渲染作品中使用它。您不必提供信用,但如果您这样做,那就太好了。 25. Resources.blogscopia 本网站由一家名为Scopia的公司创建。他们制作3D图像和视频,您可以找到许多为CGI工作的信息架构设计的模型,所有这些都可以在现实生活中使用。您可以免费下载它们,但是,如果您想一次下载它们,您可以支付 3 到 9 欧元。 使用说明:您可以免费下载模型部分的所有文件。每个压缩文件都包含您也可以在此处找到的许可证。基本上,您可以对文件执行任何操作。唯一的限制是不归属于Scopia的重新分发。 26.ambientCG 1000+公共领域PBR材料适合所有人!环境CG是使用许多不同的方法和资产类型创建的,例如照片纹理(PBR),贴花(PBR),图集(PBR),照片纹理(普通),物质存档(SBSAR),雕刻画笔,3D模型和地形。您可以在所有项目中*使用它们! 使用说明:在 ambientCG 上提供下载的所有 PBR 材料、画笔、照片和 3D 模型均根据知识共享 CC0 1.0 通用许可提供。您可以复制、修改、分发和执行作品,即使是出于商业目的,也无需征得许可。信用将不胜感激。 不要满足于平庸的大理石纹理 - 立即使用我们的免费PBR大理石纹理升级您的3D设计。 27.Pixar One Twenty Eight 这是一个提供官方动画行业经典纹理的网站:皮克斯,创建于 1993 年,该纹理库包括 128 个重复纹理,现在免费提供。 它包含您来到的纹理,包括砖块和动物毛皮。肯定会有一些你可以使用的东西。 使用说明:皮克斯动画工作室的《Pixar One Twenty Eight》根据知识共享署名4.0国际许可协议进行许可。即使出于商业目的,您也可以重新混合、调整和构建您的作品,只要您以相同的条款对新创作进行信用和许可。 访问数以千计的免费纹理并提升您的设计游戏 - 立即开始下载! 28. 3DXO 即使有近 620 个免费贴纸可供下载,3DXO 也不是最大的资源,但它的内容非常有用,不需要注册。无论是简单的墙壁或地板,还是一些奇怪的小东西,您都需要的纹理都可以在此网站上看到。 使用说明:使用权限可能因型号而异。因此,在下载文件之前,请仔细检查每个下载页面上的许可证和使用权限。 29. 3DModelsCC0 3DModelsCC0 与其他产品的不同之处在于它包含超过 250+ 个高质量 3D 模型,并且本网站上的所有内容都是免费的,完全是公共领域!使用我们的模型时无需信用或归属! 使用说明:为每个人提供完全免费的公共领域内容。 30.Sketch up texture club Sketchup Texture Club是一个非营利性的教育和信息门户网站,由3D社区的图像促进协会管理,特别强调面向学生和建筑和室内设计专业人士的可视化和渲染技术,以及所有正在学习3D可视化的人。 使用说明:您无需支付版税或使用费。纹理可以免费下载和使用。不允许将纹理作为竞争产品出售或重新分发,即使图像被修改也是如此。 31. FlippedNormals FlippedNormal 是一个提供计算机图形和 3D 资产的市场,您可以找到许多用于雕刻、建模、纹理、概念艺术、3D 模型、游戏资产或课程的高级资产! 使用说明:使用权限可能因型号而异。因此,在下载文件之前,请仔细检查每个下载页面上的许可证和使用权限。 32. NASA 3D NASA 3D网站是一个在线门户,提供与太空和各种NASA任务相关的大量三维模型和模拟。该网站是用户友好的,并提供有关每个型号的详细信息。该网站允许用户探索和下载几种不同格式的模型,包括 OBJ、STL 和 FBX,只需单击下载按钮即可。 使用说明: 要下载模型,只需单击模型页面上的下载按钮并选择所需的格式。 33. 3DAGOGO (Astroprint) 3DAGOGO 是一个提供广泛 3D 模型的网站,包括角色、车辆和建筑物。3DAGOGO 的独特功能之一是它专注于适合 3D 打印的模型,使其成为希望创建物理原型或模型的设计师的绝佳资源。要使用 3DAGOGO,设计师只需在网站上搜索他们正在寻找的模型类型,然后下载 STL 格式的文件。 使用说明: 要使用 3DAGOGO,只需搜索所需的 3D 模型类型并下载 STL 格式的文件。根据需要自定义模型,并确保在将其用于商业目的之前检查使用权限。 34. FreeCAD FreeCAD是一款了不起的3D建模软件,可让您在计算机上创建令人难以置信的3D设计。该软件可免费下载和使用,它提供了广泛的工具和功能,可用于创建用于各种目的的3D模型。 该网站易于浏览,您可以找到开始使用FreeCAD的所有必要信息。此外,该网站还提供一系列教程和指南,可帮助您了解 3D 建模的来龙去脉。 使用说明: 要下载模型,请访问网站并从库中选择所需的模型。该网站还提供了一系列使用该软件的教程和指南。 35. Pinshape Pinshape是一个提供一系列3D打印模型的网站。网站上提供的型号质量很高,因此您可以确保您的最终印刷产品看起来很棒。该网站提供了广泛的模型,包括从家居用品到小雕像和珠宝的所有物品。 但这还不是Pinshape所能提供的全部!该网站还允许用户上传和共享自己的3D模型。这意味着您不仅可以下载出色的模型,还可以通过分享自己的设计为社区做出贡献。此外,Pinshape 提供了一系列自定义选项,因此您可以调整和调整模型以满足您的特定需求。 使用说明: 要下载模型,请在网站上创建一个帐户,搜索所需的模型,然后单击下载按钮。该网站还为每种型号提供了一系列定制选项。 36.Yeggi Yeggi 提供了大量免费的 3D 模型,您可以下载各种格式的模型,例如 STL、OBJ 和 FBX。该网站易于使用,您可以按关键字、类别或特定网站搜索模型。 Yeggi 对于任何寻找 3D 模型的人来说都是一个很好的资源。它提供了大量的模型集合,从日常物品到复杂的机械,以及介于两者之间的一切。该网站的收藏量在不断增长,每天都有新的型号增加。 使用说明: 要下载模型,请在网站上搜索所需的模型,然后单击下载按钮。该网站还提供指向托管模型的原始网站的链接。 37. Open3DModel 来自开放3D模型的图像 Open3DModel具有各种类别的模型,包括建筑,车辆和角色。无论您需要建筑物,汽车还是人的3D模型,都可以在此网站上找到。 该网站易于浏览,您可以按类别或关键字搜索模型。每个模型都附带预览图像和详细信息,例如文件格式、大小和多边形数量。此信息可以帮助您选择适合您需求的模型。 使用说明: 要下载模型,请访问网站,从库中选择所需的模型,然后单击下载按钮。 使用最好的 3D 资产管理工具简化您的 3D 制作流程。立即试用它们,将您的 3D 项目提升到一个新的水平! 38. 3DExport 对于那些为其 3D 设计项目寻找 3D 模型、纹理和其他资源的人来说,该平台是一个很好的资源。该网站有大量模型可供选择,包括 3D 打印对象、游戏资产等。用户可以按类别、文件格式或价格范围浏览,以找到适合其项目的完美资源。此外,3DExport 还提供一系列教程和其他 3D 资源,以帮助用户提高技能并创建更令人印象深刻的设计。 使用说明: 要使用 3DExport,只需创建一个帐户并浏览可用型号。您可以按类别、格式和价格进行搜索,以找到所需的型号。找到喜欢的模型后,只需下载它并开始在您的项目中使用它。 39.Blend Swap Blend Swap是一个社区驱动的市场,提供与Blender软件兼容的各种免费3D模型。该平台允许用户共享和下载模型、纹理和其他资产,以便在他们的项目中使用。 使用说明: 创建免费帐户后,您可以浏览社区上传的大量3D模型。当您找到要使用的一个时,只需下载它并将其导入您选择的 3D 软件即可。 40. 3DShook 3DShook 是一个高级 3D 模型市场,提供一系列用于建筑、游戏等各个行业的高质量模型。该平台提供基于订阅的模型,具有不同的定价计划,允许用户访问一系列模型。 使用说明: 注册免费帐户后,只需浏览3D模型库,选择您喜欢的模型,然后以您需要的格式下载它们。 41. Smithsonian X 3D 史密森尼 X 3D 对于正在寻找历史文物和文物的高质量 3D 模型的设计师来说,这是一个独特的资源。该平台提供了大量3D模型,这些模型是根据史密森尼博物馆和研究中心中的真实物体扫描创建的。 使用说明:
-
在 vue3 中通过 element-plus 的上传组件实现文件上传
-
南邮OJ Web任务大揭秘:层层挑战剖析 1. 挑战一:迷宫般的目录探索 题目作者似乎穷举了所有可能的目录组合,最终在404.php中的