前言
写这个文章主要是最近在学习Vue3,然后想起来自己工作也两年了,平常工作都是直接 Cv,要不就是直接找插件,还有各种UI框架自带的组件,很少再有那个耐心自己去造轮子,所以就趁着学习vue3的间隙,顺便来回顾一下以外造过的轮子,这次的轮子就是一个基础的上传文件,另外暂时不做后端那部分,因为没时间。
明确功能
先来看看这个组件要支持哪些功能
支持自定义样式
支持限制文件类型
支持文件限制校验
支持多选并且可以限制数目
支持文件切片上传
支持定义切片大小
支持自定义上传文件字段
支持传输额外的参数
开始造
初始化一个上传的组件
首先先初始化一个Vue文件,然后将我们需要的可以直接在这个阶段完成的一些简单功能直接先完成,看一下最初的结构:
<template>
<div>
<slot name="upload_button">
<div class="button-container">
<button class="button" @click="onClick">上传文件</button>
</div>
</slot>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'upload',
props: {
// 限制文件类型
type: {
type: String,
default: null,
},
// 大小单位mb
size: {
type: Number,
default: null,
},
// 切片文件的大小
chunkSize:{
type: Number,
default: null,
},
// 默认1,多选文件个数
multiple: {
type: Number,
default: 1,
},
// 是否切片上传
zone: {
type: Boolean,
default: false,
},
// 额外的参数
data: {
type: Object,
default: () => Object(),
},
// 上传文件的字段名称
upKey: {
type: String,
default: 'file',
},
},
methods: {
// 上传文件
onClick() {
const input = document.createElement('input');
input.type = 'file';
if (this.multiple > 1) input.multiple = true; // 大于1的时候再开启,主要是用户体验的问题
if (this.type) input.accept = this.type; // 需要限制类型的时候添加限制类型
input.onchange = this.onChange;
input.click();
},
// 选择到的文件
onChange(event: any) {
const e = event.target;
const { files } = e; // 拿到所有的文件
},
},
});
</script>
<style scoped lang="scss">...</style>
那么现在,这个组件已经支持功能有:
- 支持自定义样式
- 支持限制文件类型
这就完成了最初的初始化,接下来我们就要来添加这个组件的功能,让它更能支撑起我们的需求了。
文件限制校验
这里有一个小细节,上面的代码中有一段const { files } = e;
拿到所有的文件,所以我们需要循环对文件进行校验,那为什么要这么做了,因为后面我们需要做多选的功能,所以就没有必要再去判断是否拿单独的一个文件了,接下来在methods,中添加一个validate
函数,这个函数要支持 验证文件大小,验证选择文件是否超出的校验功能;然后将这个函数添加到onChange
中就可以了。
// 不符合就返回直接返回错误新消息,不然就是false
validate(files: any) {
let result: any = false;
// 限制文件的大小
const size = this.size * 1024;
// 多选时判断文件是否超出限制
if (files.length > this.multiple) {
result = `只能选择${this.multiple}个文件`;
return result;
}
// 校验文件
for (let i = 0; i < files.length; i++) {
if (this.size && size <= files[i].size) {
result = `文件大小不能超过${this.size}MB!`;
break;
}
}
return result;
},
最后在onChange
中添加一个普通上传的功能,先定义一个用来上传文件的函数,然后将其添加在onChange的末尾就可以了,最后的代码如下面
onChange(event: any) {
const e = event.target;
const { files } = e; // 拿到所有的文件
// 校验文件是否符合规格
const validate = this.validate(files);
if (validate) return alert(validate);
// 普通文件上传
files.forEach((item) => {
const data = { [this.upKey]: item };
this.upload(data);
});
return true;
},
/**
* @description 上传文件
* @param {object} file
*/
async upload(file: any) {
// 合并额外参数跟数据
file = Object.assign(file, this.data);
const formData: any = new FormData();
// 将数据添加到表单当中
for (const fileKey in file) {
if (file?.hasOwnProperty(fileKey)) {
formData.append(fileKey, file[fileKey]);
}
}
// 发送到后端
// request.post('/api').then((result) => {
// console.log(result);
// });
},
这样,最基础的文件上传就已经完成了,那我们继续完善接下来的功能
最后就是切片上传啦
这里完成最后的四个工作
- 支持文件切片上传
- 支持定义切片大小
- 支持自定义上传文件字段
- 支持传输额外的参数
那么这里需要了解一下,其实文件切片是怎么做的呢?所谓的文件切片,其实就是数据切割,因为在计算机里面,其实所有的东西都是数据而已,所以把File类型的文件转换成数据进行切割就可以了,切割的大小的话,其实就是数据的大小。
那么大概了解了一下文件切割的操作之后,接下来就是明确切割之后需要传输的字段有什么了。 - file:保存分片的数据
- filename:文件名称
- chunkKey:切片标示
file跟filename就不用说了,chunkKey的切片标示,主要是为了做断点续传的功能,因为用户的网络不一定什么时候就会断掉,而且要是用户传输到一半就关闭了,那么这个文件就会有一部分传输不过去,所有需要标示当前是第几块切片,后端那边才可以继续拼接。
在这里我们使用zone
来决定是否使用切片上传这个功能,先添加一个专门做分片上传的函数。
// 分片上传
zoneUpload(files) {
const chunks: any = [];
// 多个文件上传需要 {file,filename,chunkKey}
files.forEach((item) => {
// 将文件切片
const chunk = this.zoneFile(item);
chunks.push(chunk);
});
// 开始上传
chunks.forEach((chunk) => {
chunk.forEach(async (item, index) => {
item.chunkKey = index;// 添加文件下标
item.endKey = item.length - 1;// 添加结束下标
await this.upload(item);
});
});
},
上面的代码有zoneFile
切割函数,完善这个函数
// 切片函数将文件转换为blob,并且切开
zoneFile(file: any) {
const files: any = [];
const filename = Base64.encode(file.name);
// 每个切片大小
const zoneSize: number = this.chunkSize * 1024 * 1024;
const { size } = file;
// 根据文件大小切片总数
let totalPieces: number = Math.ceil(size / zoneSize);
let index = 0; // 开始下标
const chunk: any = { filename };
while (totalPieces) {
chunk[this.upKey] = file.slice(index, index + zoneSize);
files.push(chunk);
totalPieces -= 1;
index += zoneSize;
}
return files;
},
然后修改一下onChange
函数,将切片上传的功能也添加上去,就完成我们本次的工作了,最后onChange
函数的代码如下:
onChange(event: any) {
const e = event.target;
const { files } = e; // 拿到所有的文件
// 校验文件是否符合规格
const validate = this.validate(files);
if (validate) return alert(validate);
// 需要切片的时候
if (this.zone) {
this.zoneUpload(files);
return false;
}
// 普通文件上传
files.forEach((item) => {
const data = { [this.upKey]: item };
this.upload(data);
});
return true;
},
到这里,一个最基础版本的上传文件的功能的完成了,具体的源代码github
如果有需要改进的地方,欢迎提出,毕竟有交流才有进步,哈哈哈