<template>
<div>
<input type="file" @change="uploadChange" multiple="multiple" />
<div style="text-align: left">
<div
v-for="(item, index) in waitList"
:key="item.lastModified + item.name"
style="margin-bottom: 20px"
>
<div style="margin: 5px">
<button @click="startUploadFile(item, index)">开始上传</button>
<span>{{ item.name }}</span>
</div>
</div>
<div v-for="item in startList" :key="item.lastModified + item.name">
<span>{{ item.name }}</span>
<div style="width: 600px; height: 4px; background: gray">
<div
:style="{
background: 'blue',
height: '4px',
width: item.precent ? item.precent + '%' : '0px',
}"
></div>
</div>
{{ item.precent }}%
<div v-if="item.uploadStatus !== 'success'">
<div
v-if="item.isStop"
style="margin: 20px"
@click="item.startUpload()"
class="pointer"
>
继续
</div>
<div
v-else
style="margin: 20px"
@click="item.stopUpload()"
class="pointer"
>
暂停
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import axios from "axios";
// axios.defaults.baseURL = 'http://localhost';
import CryptoJs from "crypto-js";
export default {
data() {
return {
chunkSize: 1024 * 1024 * 1, // 50M
fileList: [],
fileInfoList: [],
waitList: [], //存储所有的上传文件
startList: [], //开始上传的分片
endList: [], //已上传的分片
};
},
methods: {
sendFile(file, sourceToken) { //文件不大于的单独上传
let formdata = new FormData();
formdata.append("file", file);
formdata.append("suffix", file.name.split(".").pop().toLowerCase());
axios({
url: "/test/upload",
method: "post",
data: formdata,
headers: { "Content-Type": "multipart/form-data" },
cancelToken: sourceToken,
onUploadProgress: (e) => { //获取上传文件得进度
file.uploadedChunkSize = e.loaded;
file.precent = ((e.loaded / e.total) * 100).toFixed(2);
if (file.precent == 100) {
// this.endUploadFile(file);
}
this.$forceUpdate(); // 刷新视图
},
}).then(
({ data }) => {
console.log(data, "upload/file");
},
(err) => {
console.log(err);
}
);
},
startUploadFile(item, index) { //开始上传
this.waitList.splice(index, 1); //移除上传的文件
this.startList.push(item); //将需要上传的文件,放入数组
this.uploadFile(item); //上传
},
endUploadFile(item, index) {
this.startList.splice(index, 1);
this.endList.push(item);
},
uploadChange(e) {
const files = e.target.files;
this.waitList = [...this.waitList, ...files];
},
async uploadFile(file) {
file.precent = 0; // 百分比进度
file.uploadedChunkSize = 0; // 记录上传切片大小
file.uploadStartTime = new Date().getTime;
file.isStop = false; //文件进度
file.hash = CryptoJs.MD5(file.name + file.uploadStartTime)
.toString()
.toUpperCase(); //文件进度
// 暂停
const {
token,
// cancel
} = axios.CancelToken.source();
const sourceToken = (file.sourceToken = token);
console.log(sourceToken);
file.stopUpload = () => {
file.isStop = true;
this.$forceUpdate();
// cancel();
};
file.startUpload = () => {};
const chunkSize = this.chunkSize;
// 文件> 五倍切片大小 不进行切片上传
if (file.size < chunkSize * 1) {
this.sendFile(file, sourceToken);
} else {
const chunkInfo = await this.cutBlob(file, chunkSize); //文件分片
const chunkArr = chunkInfo.chunkArr;
this.queueReq({ //递归顺序上传切片文件
file,
sourceToken,
chunkArr,
progressCallback: (chunkItem, data) => {
console.log(chunkItem, data);
this.progressCallback(file, chunkItem, data);
},
endCallBack: () => {
this.endCallBack();
file.uploadStatus = "success";
this.$forceUpdate();
},
errorCallBack: (data) => {
file.startUpload = () => {
const {
token,
// cancel
} = axios.CancelToken.source(); //中断请求
const sourceToken = (file.sourceToken = token);
file.isStop = false;
file.stopUpload = () => {
file.isStop = true;
this.$forceUpdate();
// cancel();
};
this.queueReq({ ...data, sourceToken, file });
this.$forceUpdate();
};
this.errorCallBack(data);
},
});
}
},
cutBlob(file, chunkSize) {
const chunkArr = []; // 所有切片缓存数组
const blobSlice =
File.prototype.slice ||
File.prototype.mozSlice ||
File.prototype.webkitSlice; // 切割Api不同浏览器分割处理
const chunkNums = Math.ceil(file.size / chunkSize); // 切片总数
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.addEventListener("loadend", () => {
let startIndex = "";
let endIndex = "";
let contentItem = "";
// 文件切割分片
for (let i = 0; i < chunkNums; i++) {
startIndex = i * chunkSize;
endIndex = startIndex + chunkSize;
endIndex > file.size && (endIndex = file.size);
contentItem = blobSlice.call(file, startIndex, endIndex);
chunkArr.push({
index: i, // 用于合并文件
hash: file.hash, // 区分上传
total: chunkNums, // 分片总文件数
name: file.name, // 文件名称
size: file.size, // 文件大小
chunk: contentItem, // 分片文件
});
}
resolve({
chunkArr,
fileInfo: {
hash: file.hash,
total: chunkNums,
name: file.name,
size: file.size,
},
});
});
reader.addEventListener("error", function _error(err) {
reject(err);
});
});
},
queueReq({
file,
sourceToken,
chunkArr,
result,
progressCallback,
endCallBack,
errorCallBack,
}) {
if (chunkArr.length == 0) {
return endCallBack();
}
const chunkItem = chunkArr.shift();
let formdata = new FormData();
formdata.append("file", chunkItem.chunk);
formdata.append("hash", chunkItem.hash);
formdata.append("index", chunkItem.index);
formdata.append("name", chunkItem.name);
let params = {
suffix: chunkItem.name.split(".").pop().toLowerCase(),
subscript: chunkItem.index,
};
if (result) {
// 第二次发起拿到上次请求的返回
params.noGroupPath = result.data.data;
}
if (file.isStop) {
chunkArr.unshift(chunkItem); //移除已经上传的分片
errorCallBack({
errMessage: { message: "暂停" },
chunkItem,
sourceToken,
chunkArr,
result,
progressCallback,
endCallBack,
errorCallBack,
});
return;
}
this.sendChunk({
chunkItem,
data: formdata,
params,
progressCallback,
cancelToken: sourceToken,
}).then(
(res) => {
this.queueReq({
file,
sourceToken,
chunkArr,
progressCallback,
errorCallBack,
endCallBack,
result: res,
});
},
(err) => {
chunkArr.unshift(chunkItem);
errorCallBack({
errMessage: err,
chunkItem,
sourceToken,
chunkArr,
result,
progressCallback,
endCallBack,
errorCallBack,
});
}
);
},
sendChunk({ chunkItem, data, params, progressCallback, cancelToken }) {
return axios({
url: "/test/upload",
method: "post",
data,
params,
headers: { "Content-Type": "multipart/form-data" },
cancelToken,
onUploadProgress: (e) => {
chunkItem.loaded = e.loaded;
progressCallback(chunkItem, e); // 回调
},
});
},
progressCallback(file, chunkItem, data) {
// console.log(data, "====");
// 上传中回调
const { loaded, total } = data;
file.uploadedChunkSize += loaded < total ? 0 : +loaded;
file.uploadedChunkSize > chunkItem.size &&
(file.uploadedChunkSize = chunkItem.size);
file.precent = ((file.uploadedChunkSize / chunkItem.size) * 100).toFixed(
2
);
this.$forceUpdate(); // 刷新视图
},
endCallBack() {
console.log("上传完成");
},
errorCallBack({ chunkArr, sourceToken }) {
console.log(chunkArr, sourceToken);
},
},
};
</script>
<style>
.pointer {
cursor: pointer;
}
</style>