文章目录
- 概要
- 编写这个组件的原因
- 组件的HTML
- 逻辑模块
- 父组件传的数据
- Data的变量
- 1. 文件上传前
- 2. 利用watch 监听
- 大文件初始化
- 大文件合并的请求
- 循环上传
- 组件的郑示例
- 注意点
- 优化后的完整代码
概要
提示:批量上传大于5Mb的小文件或者批量上传 大于5Mb文件使用切片上传
编写这个组件的原因
后端不能处理5Mb 以上的文件
组件的HTML
试图模块相对简单,没有书写复杂的删除文件的操作 之展示上传后的文件名称
<template>
<div>
<!-- 上传组件 -->
<el-upload
action
:auto-upload="false"
:show-file-list="false"
:on-change="handleChange"
:accept="accept"
multiple
ref="upload"
>
<el-button size="small" type="primary">点击上传</el-button>
</el-upload>
<!-- {{ FileAllListArray }} -->
<ul>
<li v-for="(item, index) in FileAllArrayLst" :key="index">
{{ item.name }}
</li>
</ul>
</div>
</template>
逻辑模块
引入了 spark-md5
版本是 spark-md5": "^3.0.2
如果使用这个组件的话 npm i 下载一下
import SparkMD5 from "spark-md5";
import axios from "axios";
import { getToken, getIsVersion } from "@/utils/auth";
父组件传的数据
这个就不做过多的解释了有开发经验的应该都可以看懂
props: {
accept: {
type: String,
default: ".doc,.docx",
},
FileAllListArray: {
type: Array,
default: ()=>[],
},
type: {
type: Number,
default: null,
},
Url: {
type: Object,
default: ()=>({
GeTinitUpload: window.service.url + "/system/oss/initUpload", // 初始化
PosetUploadPart: window.service.url + "/system/oss/uploadPart", // 上传 切片
PostCompleteUpload: window.service.url + "/system/oss/completeUpload", // 合并切片
uploadDan: window.service.url + "/system/oss/upload", // 小于 5mb
}),
}
},
Data的变量
因为当时赶项目比较急所有没有写具体的作用 主要的变量还是标记了 下面是 Js 逻辑模块
data() {
return {
loadings: false,// 加载页面开关
percent: 0,
videoUrl: "",
upload: true,
percentCount: 0,
chunkSizes: "",
chunkAll: 0,
fileId: "", // 文件ID
fileName: "", // 文件名称
FileArray: [], // 上传选中的文件
FileAllArray: [], // 文件列表
FileAllArrayLst: [], // 文件列表
chunkList: [],
// FileIdlist: [],
// fileIndes: [],
FileAZon5M: [], //上传的所有的 file 文件 5 MB
FileDaYu5M: [], //上传的所有的 file 文件 大于 5 MB
batch: false,
asde: [],
asde5: [],
loading: "",
};
},
1. 文件上传前
首先 调用的是 Upload 的 :on-change="handleChange"
事件 用于获取上传后的File文件流的 大小用于区分是否大于或者小于5Mb的文件并且打开上传的 加载页面
// 文件上传前
async handleChange(file) {
this.openFullScreen2();
this.loadings = true;
this.FileAllArray.push(file);
this.FileAllArrayLst.push(file);
// 当前判断: 当上传的文件小于5Mb的时候 将Filer文件放进 FileAZon5M 这个数组里面
if (file.size < 5 * 1024 * 1024) {
this.FileAZon5M.push(file);
} else {
// 当前判断: 当上传的文件大于5Mb的时候 将Filer文件放进 FileDaYu5M 这个数组里面
this.FileDaYu5M.push(file);
}
},
// 文件loading
openFullScreen2() {
this.loading = this.$loading({
lock: true,
text: "文件上传中...",
spinner: "el-icon-loading",
background: "rgba(0, 0, 0, 0.7)",
});
},
2. 利用watch 监听
第一:监听 FileAZon5M
小于 5Mb 的数据 判断想应的情况
第二:监听 FileDaYu5M
存放大于5Mb的数组
watch: {
// 监听存放小于5Mb的数组
FileAZon5M: {
handler(n, o) {
// 循环n这个新数组
n.filter((item) => {
// 当前判断是判断是否是最后一个并且 大于5Mb的不是最后一个 如果符合将小于5Mb的数组清空, 加载动画继续打开
if (item.uid == n[n.length - 1].uid && this.FileDaYu5M.length != 0) {
this.pullXiaoyu5M(item, true);
this.FileAZon5M = []
} else if (
// 当前判断是判断是否是最后一个并且 大于5Mb的也是最后一个 如果符合将小于5Mb的数组清空, 加载动画关闭
item.uid == n[n.length - 1].uid &&
this.FileDaYu5M.length == 0
) {
this.pullXiaoyu5M(item, false);
this.FileAZon5M = []
} else {
this.pullXiaoyu5M(item, true);
}
});
},
},
// 监听存放大于5Mb的数组
FileDaYu5M: {
handler(n, o) {
n.filter((item) => {
console.log(999999)
// 调用初始化 切片的接口
this.initialize(item);
});
},
},
},
大文件初始化
// 切片上传的初始化
initialize(file) {
let i = 0;
// 重新定义 this 预防 指向丢失
let that = this;
// 文件的名称
this.fileName = file.name;
axios({
url: this.Url.GeTinitUpload, // 初始化切片的接口
method: "post",
headers: {
"Content-Type": "multipart/form-data", // 请求头
Authorization: "Bearer " + getToken(), // 让每个请求携带自定义token 请根据实际情况自行修改
},
//参数
params: { fileName: this.fileName },
}).then((res) => {
if (res.data.code != 200) {
this.FileAllArray = [];
this.batch = true;
return;
}
console.log(res.data.msg)
// 切片对应的 文件留的Id
that.fileId = res.data.msg;
if (!file) return;
this.percent = 0;
this.videoUrl = "";
// 获取文件并转成 ArrayBuffer 对象
const fileObj = file.raw;
let buffer;
try {
buffer = this.fileToBuffer(fileObj);
} catch (e) { }
// 将文件按固定大小(3M)进行切片,注意此处同时声明了多个常量
const chunkSize = 5 * 1024 * 1024;
this.chunkSizes = chunkSize;
let chunkList = [], // 保存所有切片的数组
chunkListLength = Math.ceil(fileObj.size / chunkSize); // 计算总共多个切片
this.chunkAll = chunkListLength;
let suffix = /\.([0-9A-z]+)$/.exec(fileObj.name)[1]; // 文件后缀名
// 根据文件内容生成 hash 值
const spark = new SparkMD5.ArrayBuffer();
spark.append(buffer);
const hash = spark.end();
// 生成切片,这里后端要求传递的参数为字节数据块(chunk)和每个数据块的文件名(fileName)
let curChunk = 0; // 切片时的初始位置
for (let i = 0; i < chunkListLength; i++) {
const item = {
chunk: fileObj.slice(curChunk, curChunk + chunkSize),
// fileName: `${hash}_${i}.${suffix}`, // 文件名规则按照 hash_1.jpg 命名
fileName: file.name, // 文件名规则按照 hash_1.jpg 命名
fileId: that.fileId,
};
curChunk += chunkSize;
chunkList.push(item);
}
console.log(chunkList);
this.chunkList = chunkList; // sendRequest 要用到
this.hash = hash; // sendRequest 要用到
this.sendRequest();
});
},
大文件合并的请求
// 发送请求
sendRequest() {
const requestList = []; // 请求集合
let fileIdS = null;
let fileName = null;
this.chunkList.forEach((item, index) => {
console.log(item, "item");
const fn = () => {
const formData = new FormData();
formData.append("file", item.chunk);
formData.append("partNumber", index + 1);
formData.append("partSize", item.chunk.size);
formData.append("uploadId", item.fileId);
return axios({
url: this.Url.PosetUploadPart,
method: "post",
headers: {
"Content-Type": "multipart/form-data",
Authorization: "Bearer " + getToken(), // 让每个请求携带自定义token 请根据实际情况自行修改
},
data: formData,
}).then((res) => {
if (index == this.chunkList.length && res.code == 200) {
complete();
}
if (res.data.code === 0) {
// 成功
if (this.percentCount === 0) {
// 避免上传成功后会删除切片改变 chunkList 的长度影响到 percentCount 的值
this.percentCount = 100 / this.chunkList.length;
}
this.percent += this.percentCount; // 改变进度
console.log( this.percent ,' this.percent ')
this.chunkList.splice(index, 1); // 一旦上传成功就删除这一个 chunk,方便断点续传
}
});
};
fileIdS = item.fileId;
fileName = item.fileName;
requestList.push(fn);
});
let i = 0; // 记录发送的请求个数
// 文件切片全部发送完毕后,需要请求 '/merge' 接口,把文件的 hash 传递给服务器
const complete = (value) => {
let that = this;
axios({
url: this.Url.PostCompleteUpload, // 合并切片接口
method: "post",
headers: {
"Content-Type": "multipart/form-data",
Authorization: "Bearer " + getToken(), // 让每个请求携带自定义token 请根据实际情况自行修改
},
params: { fileName: fileName, uploadId: fileIdS },
}).then((res) => {
this.asde5.push(res.data.data);
if (res.data.code === 200) {
// 请求发送成功
this.FileAllArray = []
// 上传成功 将每个文件 传给父组件
this.$emit("event_Upload", res);
this.$refs.upload.clearFiles()
this.FileArray.shift();
if (that.FileArray.length != 0) {
this.batch = true;
this.circulation(that.FileArray[0]);
}
if (
that.FileArray.length == 0 &&
fileIdS == this.chunkList[this.chunkList.length - 1].fileId
) {
this.FileDaYu5M = []
setTimeout(() => {
this.loading.close();
}, 10000)
}
}
});
};
const send = async () => {
if (!this.upload) return;
if (i >= requestList.length) {
// 发送完毕
complete();
return;
}
await requestList[i]();
i++;
send();
};
send(); // 发送请求
},
循环上传
// 循环上传
circulation(value) {
console.log(value, "value");
let that = this;
this.openFullScreen2();
that.initialize(value);
},
// 将 File 对象转为 ArrayBuffer
fileToBuffer(file) {
return new Promise((resolve, reject) => {
const fr = new FileReader();
fr.onload = (e) => {
resolve(e.target.result);
};
fr.readAsArrayBuffer(file);
fr.onerror = () => {
reject(new Error("转换文件格式发生错误"));
};
});
},
组件的郑示例
<template>
<div>
<!-- 上传组件 -->
<el-upload
action
:auto-upload="false"
:show-file-list="false"
:on-change="handleChange"
:accept="accept"
multiple
ref="upload"
>
<el-button size="small" type="primary">点击上传</el-button>
</el-upload>
<!-- {{ FileAllListArray }} -->
<ul v-if="type != 1">
<li v-for="(item, index) in FileAllArrayLst" :key="index">
{{ item.name }}
</li>
</ul>
<ul v-if="type == 1">
<li
v-for="(item, index) in FileAllListArray"
:key="index"
style="width: 100%; display: flex; justify-content: space-between"
>
<div>{{ item.newName }}</div>
<div @click="FileAllList(item)">X</div>
</li>
</ul>
</div>
</template>
<script>
import SparkMD5 from "spark-md5";
import axios from "axios";
import { getToken, getIsVersion } from "@/utils/auth";
export default {
name: "App3",
props: {
accept: {
type: String,
default: ".doc,.docx",
},
FileAllListArray: {
type: Array,
default: ()=>[],
},
type: {
type: Number,
default: null,
},
Url: {
type: Object,
default: ()=>({
GeTinitUpload: window.service.url + "/system/oss/initUpload", // 初始化
PosetUploadPart: window.service.url + "/system/oss/uploadPart", // 上传 切片
PostCompleteUpload: window.service.url + "/system/oss/completeUpload", // 合并切片
uploadDan: window.service.url + "/system/oss/upload", // 小于 5mb
}),
}
},
data() {
return {
loadings: false,// 加载页面开关
percent: 0,
videoUrl: "",
upload: true,
percentCount: 0,
chunkSizes: "",
chunkAll: 0,
fileId: "", // 文件ID
fileName: "", // 文件名称
FileArray: [], // 上传选中的文件
FileAllArray: [], // 文件列表
FileAllArrayLst: [], // 文件列表
chunkList: [],
// FileIdlist: [],
// fileIndes: [],
FileAZon5M: [], //上传的所有的 file 文件 5 MB
FileDaYu5M: [], //上传的所有的 file 文件 大于 5 MB
batch: false,
asde: [],
asde5: [],
loading: "",
};
},
watch: {
// 监听存放小于5Mb的数组
FileAZon5M: {
handler(n, o) {
// 循环n这个新数组
n.filter((item) => {
// 当前判断是判断是否是最后一个并且 大于5Mb的不是最后一个 如果符合将小于5Mb的数组清空, 加载动画继续打开
if (item.uid == n[n.length - 1].uid && this.FileDaYu5M.length != 0) {
this.pullXiaoyu5M(item, true);
this.FileAZon5M = []
} else if (
// 当前判断是判断是否是最后一个并且 大于5Mb的也是最后一个 如果符合将小于5Mb的数组清空, 加载动画关闭
item.uid == n[n.length - 1].uid &&
this.FileDaYu5M.length == 0
) {
this.pullXiaoyu5M(item, false);
this.FileAZon5M = []
} else {
this.pullXiaoyu5M(item, true);
}
});
},
},
// 监听存放大于5Mb的数组
FileDaYu5M: {
handler(n, o) {
n.filter((item) => {
console.log(999999)
// 调用初始化 切片的接口
this.initialize(item);
});
},
},
},
methods: {
pullXiaoyu5M(item, type) {
console.log(item);
let formData = new FormData();
formData.append("file", item.raw);
axios({
url: this.Url.uploadDan,
method: "post",
headers: {
"Content-Type": "multipart/form-data",
Authorization: "Bearer " + getToken(), // 让每个请求携带自定义token 请根据实际情况自行修改
},
data: formData,
}).then((res) => {
this.asde.push(res.data.data);
this.FileAllArray = []
this.$emit("event_Upload", res);
this.$refs.upload.clearFiles()
this.loadings = false;
this.$message({
type: "success", // success error warning
message: "上传成功",
duration: 2000,
});
if (type) {
} else {
setTimeout(() => {
this.loading.close();
}, 10000);
}
})
},
FileAllList(value) {
this.$emit("FileAllList", value);
},
// 文件loading
openFullScreen2() {
this.loading = this.$loading({
lock: true,
text: "文件上传中...",
spinner: "el-icon-loading",
background: "rgba(0, 0, 0, 0.7)",
});
},
// 文件上传前
async handleChange(file) {
this.openFullScreen2();
this.loadings = true;
this.FileAllArray.push(file);
this.FileAllArrayLst.push(file);
// 当前判断: 当上传的文件小于5Mb的时候 将Filer文件放进 FileAZon5M 这个数组里面
if (file.size < 5 * 1024 * 1024) {
this.FileAZon5M.push(file);
} else {
// 当前判断: 当上传的文件大于5Mb的时候 将Filer文件放进 FileDaYu5M 这个数组里面
this.FileDaYu5M.push(file);
}
},
// 切片上传的初始化
initialize(file) {
let i = 0;
// 重新定义 this 预防 指向丢失
let that = this;
// 文件的名称
this.fileName = file.name;
axios({
url: this.Url.GeTinitUpload, // 初始化切片的接口
method: "post",
headers: {
"Content-Type": "multipart/form-data", // 请求头
Authorization: "Bearer " + getToken(), // 让每个请求携带自定义token 请根据实际情况自行修改
},
//参数
params: { fileName: this.fileName },
}).then((res) => {
if (res.data.code != 200) {
this.FileAllArray = [];
this.batch = true;
return;
}
console.log(res.data.msg)
// 切片对应的 文件留的Id
that.fileId = res.data.msg;
if (!file) return;
this.percent = 0;
this.videoUrl = "";
// 获取文件并转成 ArrayBuffer 对象
const fileObj = file.raw;
let buffer;
try {
buffer = this.fileToBuffer(fileObj);
} catch (e) { }
// 将文件按固定大小(3M)进行切片,注意此处同时声明了多个常量
const chunkSize = 5 * 1024 * 1024;
this.chunkSizes = chunkSize;
let chunkList = [], // 保存所有切片的数组
chunkListLength = Math.ceil(fileObj.size / chunkSize); // 计算总共多个切片
this.chunkAll = chunkListLength;
let suffix = /\.([0-9A-z]+)$/.exec(fileObj.name)[1]; // 文件后缀名
// 根据文件内容生成 hash 值
const spark = new SparkMD5.ArrayBuffer();
spark.append(buffer);
const hash = spark.end();
// 生成切片,这里后端要求传递的参数为字节数据块(chunk)和每个数据块的文件名(fileName)
let curChunk = 0; // 切片时的初始位置
for (let i = 0; i < chunkListLength; i++) {
const item = {
chunk: fileObj.slice(curChunk, curChunk + chunkSize),
// fileName: `${hash}_${i}.${suffix}`, // 文件名规则按照 hash_1.jpg 命名
fileName: file.name, // 文件名规则按照 hash_1.jpg 命名
fileId: that.fileId,
};
curChunk += chunkSize;
chunkList.push(item);
}
console.log(chunkList);
this.chunkList = chunkList; // sendRequest 要用到
this.hash = hash; // sendRequest 要用到
this.sendRequest();
});
},
// 发送请求
sendRequest() {
const requestList = []; // 请求集合
let fileIdS = null;
let fileName = null;
this.chunkList.forEach((item, index) => {
console.log(item, "item");
const fn = () => {
const formData = new FormData();
formData.append("file", item.chunk);
formData.append("partNumber", index + 1);
formData.append("partSize", item.chunk.size);
formData.append("uploadId", item.fileId);
return axios({
url: this.Url.PosetUploadPart,
method: "post",
headers: {
"Content-Type": "multipart/form-data",
Authorization: "Bearer " + getToken(), // 让每个请求携带自定义token 请根据实际情况自行修改
},
data: formData,
}).then((res) => {
if (index == this.chunkList.length && res.code == 200) {
complete();
}
if (res.data.code === 0) {
// 成功
if (this.percentCount === 0) {
// 避免上传成功后会删除切片改变 chunkList 的长度影响到 percentCount 的值
this.percentCount = 100 / this.chunkList.length;
}
this.percent += this.percentCount; // 改变进度
console.log( this.percent ,' this.percent ')
this.chunkList.splice(index, 1); // 一旦上传成功就删除这一个 chunk,方便断点续传
}
});
};
fileIdS = item.fileId;
fileName = item.fileName;
requestList.push(fn);
});
let i = 0; // 记录发送的请求个数
// 文件切片全部发送完毕后,需要请求 '/merge' 接口,把文件的 hash 传递给服务器
const complete = (value) => {
let that = this;
axios({
url: this.Url.PostCompleteUpload, // 合并切片接口
method: "post",
headers: {
"Content-Type": "multipart/form-data",
Authorization: "Bearer " + getToken(), // 让每个请求携带自定义token 请根据实际情况自行修改
},
params: { fileName: fileName, uploadId: fileIdS },
}).then((res) => {
this.asde5.push(res.data.data);
if (res.data.code === 200) {
// 请求发送成功
this.FileAllArray = []
// 上传成功 将每个文件 传给父组件
this.$emit("event_Upload", res);
this.$refs.upload.clearFiles()
this.FileArray.shift();
if (that.FileArray.length != 0) {
this.batch = true;
this.circulation(that.FileArray[0]);
}
if (
that.FileArray.length == 0 &&
fileIdS == this.chunkList[this.chunkList.length - 1].fileId
) {
this.FileDaYu5M = []
setTimeout(() => {
this.loading.close();
}, 10000)
}
}
});
};
const send = async () => {
if (!this.upload) return;
if (i >= requestList.length) {
// 发送完毕
complete();
return;
}
await requestList[i]();
i++;
send();
};
send(); // 发送请求
},
// 循环上传
circulation(value) {
console.log(value, "value");
let that = this;
this.openFullScreen2();
that.initialize(value);
},
// 将 File 对象转为 ArrayBuffer
fileToBuffer(file) {
return new Promise((resolve, reject) => {
const fr = new FileReader();
fr.onload = (e) => {
resolve(e.target.result);
};
fr.readAsArrayBuffer(file);
fr.onerror = () => {
reject(new Error("转换文件格式发生错误"));
};
});
},
},
};
</script>
<style scoped>
.progress-box {
box-sizing: border-box;
width: 360px;
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 10px;
padding: 8px 10px;
background-color: #ecf5ff;
font-size: 14px;
border-radius: 4px;
}
</style>
注意点
在开发中发现 在大小文件混合上传的时候 大文件的合并接口状态与返回的 文件链接不同步导致后面提交上传的时候会丢失,所以 我在大小文件上传成功的接口位置加了一个计时器 延迟这个加载页面的关闭 , 如果不需要 可以自行关闭
setTimeout(() => {
this.loading.close();
}, 10000)
优化后的完整代码
<template>
<div>
<!-- 上传组件 -->
<el-upload action :auto-upload="false" :show-file-list="false" :on-change="handleChange" :accept="accept" multiple>
<el-button size="small" type="primary">点击上传</el-button>
</el-upload>
<ul v-if="type != 1">
<li v-for="(item, index) in FileAllArray" :key="index">
{{ item.name }}
</li>
</ul>
<ul v-if="type == 1">
<li v-for="(item, index) in FileAllListArray" :key="index"
style="width: 100%; display: flex; justify-content: space-between">
<div>{{ item.newName }}</div>
<div @click="FileAllList(item)">X</div>
</li>
</ul>
</div>
</template>
<script>
import SparkMD5 from "spark-md5";
import axios from "axios";
// const Url = {
// GeTinitUpload: window.service.url + "/system/oss/initUpload", // 初始化
// PosetUploadPart: window.service.url + "/system/oss/uploadPart", // 上传 切片
// PostCompleteUpload: window.service.url + "/system/oss/completeUpload", // 合并切片
// uploadDan: window.service.url + "/system/oss/upload", // 小于 5mb
// };
import { getToken, getIsVersion } from "@/utils/auth";
export default {
name: "App3",
filters: {
btnTextFilter(val) {
return val ? "暂停" : "继续";
},
},
props: {
accept: {
type: String,
default: ".doc,.docx",
},
FileAllListArray: {
type: Array,
default: [],
},
type: {
type: Number,
default: null,
},
Url: {
type: Object,
default: {
GeTinitUpload: window.service.url + "/system/oss/initUpload", // 初始化
PosetUploadPart: window.service.url + "/system/oss/uploadPart", // 上传 切片
PostCompleteUpload: window.service.url + "/system/oss/completeUpload", // 合并切片
uploadDan: window.service.url + "/system/oss/upload", // 小于 5mb
},
}
},
data() {
return {
loadings: false,
percent: 0,
videoUrl: "",
upload: true,
percentCount: 0,
chunkSizes: "",
chunkAll: 0,
fileId: "", // 文件ID
fileName: "", // 文件名称
FileArray: [], // 上传选中的文件
FileAllArray: [], // 文件列表
chunkList: [],
// FileIdlist: [],
// fileIndes: [],
FileAZon5M: [], //上传的所有的 file 文件 5 MB
FileDaYu5M: [], //上传的所有的 file 文件 大于 5 MB
batch: false,
asde: [],
asde5: [],
loading: "",
dayu5: [],
};
},
watch: {
FileAZon5M: {
handler(n, o) {
n.filter((item) => {
if (item.uid == n[n.length - 1].uid && this.dayu5.length != 0) {
this.pullXiaoyu5M(item, true);
this.FileAZon5M = []
} else if (
item.uid == n[n.length - 1].uid &&
this.dayu5.length == 0
) {
this.pullXiaoyu5M(item, false);
this.FileAZon5M = []
} else {
this.pullXiaoyu5M(item, true);
}
});
},
},
FileDaYu5M: {
handler(n, o) {
console.log(n,'NNNNNNNNNNNNNNNNNN');
// 大文件初始化切片
this.initialize(n[0])
// n.filter((item) => {
// this.initialize(item);
// });
},
},
},
methods: {
pullXiaoyu5M(item, type) {
let formData = new FormData();
formData.append("file", item.raw);
axios({
url: this.Url.uploadDan,
method: "post",
headers: {
"Content-Type": "multipart/form-data",
Authorization: "Bearer " + getToken(), // 让每个请求携带自定义token 请根据实际情况自行修改
},
data: formData,
}).then((res) => {
this.asde.push(res.data.data);
this.$emit("event_Upload", res);
this.loadings = false;
this.$message({
type: "success", // success error warning
message: "上传成功",
duration: 2000,
});
if (type) {
} else {
this.loading.close();
// setTimeout(() => {
// this.loading.close();
// }, 10000);
}
})
},
FileAllList(value) {
this.$emit("FileAllList", value);
},
// 文件loading
openFullScreen2() {
this.loading = this.$loading({
lock: true,
text: "文件上传中...",
spinner: "el-icon-loading",
background: "rgba(0, 0, 0, 0.7)",
});
},
// 文件上传前
async handleChange(file) {
this.openFullScreen2();
this.loadings = true;
this.FileAllArray.push(file);
if (file.size < 5 * 1024 * 1024) {
this.FileAZon5M.push(file);
} else {
this.FileDaYu5M.push(file);
this.dayu5.push(file)
}
},
// 切片上传的初始化
initialize(file) {
let i = 0;
let that = this;
this.fileName = file.name;
axios({
url: this.Url.GeTinitUpload,
method: "post",
headers: {
"Content-Type": "multipart/form-data",
Authorization: "Bearer " + getToken(), // 让每个请求携带自定义token 请根据实际情况自行修改
},
params: { fileName: this.fileName },
}).then((res) => {
if (res.data.code != 200) {
this.FileAllArray = [];
this.batch = true;
return;
}
that.fileId = res.data.msg;
if (!file) return;
this.percent = 0;
this.videoUrl = "";
// 获取文件并转成 ArrayBuffer 对象
const fileObj = file.raw;
let buffer;
try {
buffer = this.fileToBuffer(fileObj);
} catch (e) { }
// 将文件按固定大小(3M)进行切片,注意此处同时声明了多个常量
const chunkSize = 5 * 1024 * 1024;
this.chunkSizes = chunkSize;
let chunkList = [], // 保存所有切片的数组
chunkListLength = Math.ceil(fileObj.size / chunkSize); // 计算总共多个切片
this.chunkAll = chunkListLength;
let suffix = /\.([0-9A-z]+)$/.exec(fileObj.name)[1]; // 文件后缀名
// 根据文件内容生成 hash 值
const spark = new SparkMD5.ArrayBuffer();
spark.append(buffer);
const hash = spark.end();
// 生成切片,这里后端要求传递的参数为字节数据块(chunk)和每个数据块的文件名(fileName)
let curChunk = 0; // 切片时的初始位置
for (let i = 0; i < chunkListLength; i++) {
const item = {
chunk: fileObj.slice(curChunk, curChunk + chunkSize),
// fileName: `${hash}_${i}.${suffix}`, // 文件名规则按照 hash_1.jpg 命名
fileName: file.name, // 文件名规则按照 hash_1.jpg 命名
fileId: that.fileId,
};
curChunk += chunkSize;
chunkList.push(item);
}
this.chunkList = chunkList; // sendRequest 要用到
this.hash = hash; // sendRequest 要用到
this.sendRequest();
});
},
// 发送请求
sendRequest() {
const requestList = []; // 请求集合
let fileIdS = null;
let fileName = null;
this.chunkList.forEach((item, index) => {
const fn = () => {
const formData = new FormData();
formData.append("file", item.chunk);
formData.append("partNumber", index + 1);
formData.append("partSize", item.chunk.size);
formData.append("uploadId", item.fileId);
return axios({
url: this.Url.PosetUploadPart,
method: "post",
headers: {
"Content-Type": "multipart/form-data",
Authorization: "Bearer " + getToken(), // 让每个请求携带自定义token 请根据实际情况自行修改
},
data: formData,
}).then((res) => {
if (index == this.chunkList.length && res.code == 200) {
complete();
}
if (res.data.code === 0) {
// 成功
if (this.percentCount === 0) {
// 避免上传成功后会删除切片改变 chunkList 的长度影响到 percentCount 的值
this.percentCount = 100 / this.chunkList.length;
}
this.percent += this.percentCount; // 改变进度
this.chunkList.splice(index, 1); // 一旦上传成功就删除这一个 chunk,方便断点续传
}
});
};
fileIdS = item.fileId;
fileName = item.fileName;
requestList.push(fn);
});
let i = 0; // 记录发送的请求个数
// 文件切片全部发送完毕后,需要请求 '/merge' 接口,把文件的 hash 传递给服务器
const complete = (value) => {
let that = this;
axios({
url: this.Url.PostCompleteUpload,
method: "post",
headers: {
"Content-Type": "multipart/form-data",
Authorization: "Bearer " + getToken(), // 让每个请求携带自定义token 请根据实际情况自行修改
},
params: { fileName: fileName, uploadId: fileIdS },
}).then((res) => {
this.asde5.push(res.data.data);
if (res.data.code === 200) {
// 请求发送成功
this.$emit("event_Upload", res);
this.dayu5.shift();
if (that.dayu5.length != 0) {
this.batch = true;
// 循环上传
this.circulation(that.dayu5[0]);
}
if (
that.dayu5.length == 0 &&
fileIdS == this.chunkList[this.chunkList.length - 1].fileId
) {
that.dayu5 = []
this.loading.close();
// setTimeout(() => {
// this.loading.close();
// }, 10000)
}
}
});
};
const send = async () => {
if (!this.upload) return;
if (i >= requestList.length) {
// 发送完毕
complete();
return;
}
await requestList[i]();
i++;
send();
};
send(); // 发送请求
},
// 循环上传
circulation(value) {
let that = this;
this.openFullScreen2();
that.initialize(value);
},
// 按下暂停按钮
handleClickBtn() {
this.upload = !this.upload;
// 如果不暂停则继续上传
if (this.upload) this.sendRequest();
},
// 将 File 对象转为 ArrayBuffer
fileToBuffer(file) {
return new Promise((resolve, reject) => {
const fr = new FileReader();
fr.onload = (e) => {
resolve(e.target.result);
};
fr.readAsArrayBuffer(file);
fr.onerror = () => {
reject(new Error("转换文件格式发生错误"));
};
});
},
},
};
</script>
<style scoped>
.progress-box {
box-sizing: border-box;
width: 360px;
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 10px;
padding: 8px 10px;
background-color: #ecf5ff;
font-size: 14px;
border-radius: 4px;
}
</style>
提示:这里可以添加总结
提供先进的推理,复杂的指令,更多的创造力。