一、前言
最近的工作中遇到了一些需要 前端 上传文件的 需求,作为新手程序员的我之前都是 从老的项目中 找到 类似的功能 然后 CV 过来,但我也知道这样肯定是不能取得什么真正进步的,于是我在网上浏览了许多文章,并得到了一些自己的感悟,在此记录下来,当然我所领悟的内容 很有可能会存在 许多误解。还是希望路过的大佬们能够 指正我的问题,让我知道自己错在了什么地方。
在我看过的文章里比较主流的 前端 文件上传 方法,就是使用 FormData 对象来实现附件的上传。
原因是,前端在使用 post 请求提交数据的时候,主要都是 将要传输的数据 编码为 application/json 和 application/x-www-form-urlencoded,两种形式来进行 传输。
但 文件类型,也就是 File 类型和 Blob类型(也就是 二进制对象) 类型 的数据,再进行 传输时,会出现 数据丢失的问题。
传输 File 和 Blob 类型的数据,我们需要将 数据 编码为** multipart/form-data ** 类型。
我们可以通过 FormData 对象,来实现这种编码。
*注:multipart/form-data 的大体 样式,就是 一个 对象中 包含着许多 length为 2 的数组 (其本质类似于键值对)。
这些数组,第一个元素是 键名 key ,第二个是 键值 value,但 他们都是字符串的形式。
大体就是这个样子 [‘key’,‘value’]
*
二、先来了解 FormData
1、构造函数:
const obj = new FormData()
console.log('FormDataobj---', obj)
创造一个 FormData 的实例对象
2、内置方法
①:FormData.append( )
这个方法是,向 FormData 对象中,添加新的数据(当然还要是 key/value 形式的)。
eg:
obj.append('keyName1','value1')
console.log('FormDataobj---', obj.getAll('keyName1'))
现在打印,obj 的话 还是 像刚创建好的时候一样,但我们可以先借助 后面的方法,查看其中的内容
可见,数据被我们添加进去了。
同时,如果添加了 key 相同的数据, 也会再生成一条新的数据,并不会 覆盖掉原本的数据。
obj.append('keyName1','value1')
obj.append('keyName1','value2')
console.log('FormDataobj---', obj.getAll('keyName1'))
②:FormData.delete()
从 FormData 对象中 删除 一个 键值对。(参数就是 删除目标的 key)
③:FormData.entries()
返回一个包含所有键值对的iterator对象(迭代器)。
通过 for…of 形式,就可以遍历这个 iterator对象 中所有的键值对。
const obj = new FormData();
obj.append('keyName1','value1')
obj.append('keyName1','value2')
obj.append('keyName3','value3')
const iteratorObj = obj.entries()
for( var key of iteratorObj ) {
console.log('key---', key)
}
如图所示:
④:FormData.get()
返回在 FormData 对象中与给定键关联的第一个值。也就是 如果一个 key 对应多个 value,
使用这个方法,只会返回第一个 value
obj.append('keyName1','value1')
obj.append('keyName1','value2')
obj.append('keyName1','value3')
obj.append('keyName3','value3')
console.log('key---', obj.get('keyName1'))
⑤:FormData.getAll()
返回一个包含 FormData 对象中与给定键关联的所有值的数组。这次会返回 所有 关联键值
⑥:FormData.has()
返回一个布尔值表明 FormData 对象是否包含某些键。(参数也是 key)
⑦:FormData.keys()
返回一个包含所有键的iterator对象。 (只有 键名,没有键值)
⑧:FormData.values()
返回一个包含所有值的iterator对象。
⑨:FormData.values()
给 FormData 设置属性值,如果FormData 对应的属性值存在则覆盖原值,否则新增一项属性值。(除了 覆盖相同 key 的value,其他 与 .append() 方法相同)
三、使用FormData 来进行文件传输
//事件载体
<div class="" @click="testUpload()" id="input"></div>
---------------------------------------
// 基本方法
testUpload() {
const inputFile = document.createElement("input");
inputFile.type = "file";
inputFile.style.display = "none";
document.body.appendChild(inputFile);
// 手动触发,前面创建的 type 为 file 的input 标签,就可以唤醒 电脑本地的文件夹。
inputFile.click();
// 给新创建的标签 添加的侦听,是用来 侦听,我们 选择要上传的目标文件的 这个事件的。
// 其中选中的 文件,会被保存在我们 创建的 input 标签的 fiels 下。
// 我这里写的仅仅是最基本的 文件传输, 还可以添加各种限制,比如,通过 截取file 的后缀名 加以判断,来限制 支持传输的文件种类。
inputFile.addEventListener("change", function () {
const file = inputFile.files[0];
const formData = new FormData();
// 将选中的文件,通过 FormData() 转码为 multipart/form-data 类型,进行网络 传输
formData.append("file", file);
window.request({
url: "xxx/xxx",
data: formData,
success: (res) => {},
finally: () => {},
});
});
},
四、使用 Element UI 提供的 组件进行 文件传输
<el-upload
class=""
// 传输文件的目标地址
action=""
//支持多选
multiple
// 双向绑定 项目中的目标文件, 选中的文件会存在这里
:file-list="fileList"
//上传文件之前的钩子,参数为上传的文件,若返回 false 或者返回 Promise 且被 reject,则停止上传。
:before-upload="beforeAvatarUpload"
//文件上传成功时的钩子
:on-success="handleSuccess"
:show-file-list="false"
>
// 这里 内嵌的 我估计也是一个 类似于 FormData 的文件上传途径,通过点击实现上传。
<div style="display: flex">
<el-button
style="margin-right: 10px"
type="primary"
plain
>上传</el-button
>
</div>
</el-upload>
-------------------------------------------------------------------------
// 相关方法钩子
// 在此处 对 要传输的文件的类型 , 以及文件大小 做判断
beforeAvatarUpload(file, fileList) {
var testmsg = file.name.substring(file.name.lastIndexOf(".") + 1);
const extension = testmsg === "xls";
const extension2 = testmsg === "xlsx";
const extension3 = testmsg === "psd";
const extension4 = testmsg === "png";
const extension5 = testmsg === "jpg";
const extension6 = testmsg === "docx";
const extension7 = testmsg === "pdf";
const extension8 = testmsg === "txt";
const extension9 = testmsg === "zip";
const extension10 = testmsg === "mp4";
const extension11 = testmsg === "doc";
const extension12 = testmsg === "ppt";
const extension13 = testmsg === "pptx";
const extension14 = testmsg === "rar";
const extension15 = testmsg === "XLS";
const extension16 = testmsg === "XLSX";
const extension17 = testmsg === "PSD";
const extension18 = testmsg === "PNG";
const extension19 = testmsg === "JPG";
const extension20 = testmsg === "DOCX";
const extension21 = testmsg === "PDF";
const extension22 = testmsg === "TXT";
const extension23 = testmsg === "ZIP";
const extension24 = testmsg === "MP4";
const extension25 = testmsg === "DOC";
const extension26 = testmsg === "PPT";
const extension27 = testmsg === "PPTX";
const extension28 = testmsg === "RAR";
const extension29 = testmsg === "jpeg";
const isLt2M = file.size / 1024 / 1024 < 100;
if (
!extension &&
!extension2 &&
!extension3 &&
!extension4 &&
!extension5 &&
!extension6 &&
!extension7 &&
!extension8 &&
!extension9 &&
!extension10 &&
!extension11 &&
!extension12 &&
!extension13 &&
!extension14 &&
!extension15 &&
!extension16 &&
!extension17 &&
!extension18 &&
!extension19 &&
!extension20 &&
!extension21 &&
!extension22 &&
!extension23 &&
!extension24 &&
!extension25 &&
!extension26 &&
!extension27 &&
!extension28 &&
!extension29
) {
await this.$message({
message: "不支持此格式文件!",
type: "warning",
});
throw new Error('Invalid state!');
}
if (!isLt2M) {
await this.$message({
message: "传输文件大小不能超过 100MB!",
type: "warning",
});
throw new Error('Invalid state!');
}
// 全部满足的情况下,返回 1 否则 返回 0 阻止本次 传输
return (
(extension ||
extension2 ||
extension3 ||
extension4 ||
extension5 ||
extension6 ||
extension7 ||
extension8 ||
extension9 ||
extension10 ||
extension11 ||
extension12 ||
extension13 ||
extension14 ||
extension15 ||
extension16 ||
extension17 ||
extension18 ||
extension19 ||
extension20 ||
extension21 ||
extension22 ||
extension23 ||
extension24 ||
extension25 ||
extension26 ||
extension27 ||
extension28 ||
extension29) &&
isLt2M
);
},