组件:
components>fastloader.vue
<template>
<div class="uploadBox">
<template v-if="state.file != null">
<div class="fileName">{{ state.file?.name }}</div>
<!-- {uploadBtn} -->
<div v-if="state.initMd5Status" class="initMd5loading">
正在校验md5中<img src="../../assets/images/loading_black.gif" />
</div>
<img
v-if="!state.initMd5Status && state.uploadDone"
src="../../assets/images/upload_done.svg"
class="done"
/>
<div
v-if="!state.initMd5Status && !state.uploadDone"
class="uploadStartOrStop"
>
<!-- {upStatusIcon} -->
<img
v-if="state.uploadStatus"
src="../../assets/images/upload_stop.svg"
:class="`iconfont icon-kaishi1 play`"
@click="StopUploadChunks"
/>
<img
v-else
src="../../assets/images/upload_play.svg"
:class="`iconfont icon-kaishi1 play`"
@click="handleUpload"
/>
<svg
class="circleBg"
width="25"
height="25"
viewBox="0 0 25 25"
xmlns="http://www.w3.org/2000/svg"
>
<circle
cx="12"
cy="12"
r="8"
fill="none"
stroke="#eee"
stroke-width="3.5"
></circle>
<circle
cx="12"
cy="12"
r="8"
class="mycircle"
transform="translate(0,24) rotate(-90)"
:style="`stroke-dasharray:${state.currentUploadProgress} 51`"
></circle>
</svg>
</div>
<img
src="../../assets/images/upload_sel_delete_icon.svg"
class="uploaddeleteIcon"
@click="deleteSelFile"
/>
</template>
<div class="uploadBtn" v-else>
<!-- {this.props.children} -->
<span style="color: #999999; font-size: 12px">上传:</span>
<img
src="../../assets/images/upload_sel_icon.png"
class="uploadBtnIcon"
/>
<input type="file" @change="handleFileChange($event)" />
</div>
<!-- <div>{{ state.currentUploadProgress }}</div> -->
</div>
</template>
<script>
import { post, postCancel } from "../../api/http";
import { API } from "../../api/index";
import { message, Table, Progress } from "ant-design-vue";
import SparkMD5 from "spark-md5";
import { reactive, ref } from "@vue/reactivity";
export default {
name: "upload",
props: {
directionType: String,
},
setup(context, props) {
console.log(context, props, "!!!!!!!!!!!!!!!!!!!");
const state = reactive({
file: null,
chunkData: [],
uploadStatus: false,
uploadDone: false,
uploadInfos: null,
currentUploadProgressIndex: 0,
currentUploadProgress: 0,
initMd5Status: false,
requestData: [],
location: "",
});
const filemd5init = (files) => {
return new Promise((resolve, reject) => {
var blobSlice = File.prototype.slice,
file = files,
chunkSize = 10 * 1024 * 1000, // Read in chunks of 2MB
chunks = Math.ceil(file.size / chunkSize),
currentChunk = 0,
spark = new SparkMD5.ArrayBuffer(),
fileReader = new FileReader();
fileReader.onload = function (e) {
//console.log('read chunk nr', currentChunk + 1, 'of', chunks);
spark.append(e.target.result); // Append array buffer
currentChunk++;
if (currentChunk < chunks) {
loadNext();
} else {
//console.log('finished loading');
resolve({
md5: spark.end(),
});
}
};
fileReader.onerror = function (e) {
reject(e);
//console.warn('oops, something went wrong.');
};
function loadNext() {
let start = currentChunk * chunkSize,
end =
start + chunkSize >= file.size ? file.size : start + chunkSize;
fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
}
loadNext();
});
};
const timerNum = ref(0);
let timer;
const mergeRequest = async () => {
let params = new URLSearchParams();
params.append("fileName", state.file.name);
params.append("identifier", state.file._hashmd5);
params.append("locationType", "LOCAL");
params.append("refProjectId", state.file.name);
params.append("size", state.file.size);
params.append("directionType", context.directionType);
console.log(params, "!!!!!!333333");
post(API.uploader.mergeFile, {
data: params,
}).then((res) => {
timer = setInterval(async () => {
post(API.uploader.selectFileStatus, {
data: params,
}).then((res) => {
if (res.data.fileStatus == "PUSH_FINISH") {
//清除暂停信息
state.uploadDone = true;
state.uploadInfos = null;
state.uploadStatus = false;
state.currentUploadProgressIndex = 0;
state.currentUploadProgress = 0;
state.location = res.data.relativePath;
props.emit("uploadSuccess", res.data, state.file, state.location);
clearInterval(timer);
} else if (res.data.fileStatus == "PUSH_FAILURE") {
state.uploadDone = false;
state.uploadInfos = null;
state.uploadStatus = false;
state.currentUploadProgressIndex = 0;
state.currentUploadProgress = 0;
}
});
}, timerNum.value * 1000);
});
};
const StopUploadChunks = () => {
//暂停所有请求
state.uploadStatus = false;
const index = state.currentUploadProgressIndex;
const alllength = state.requestData.length;
for (let i = Number(index) + 1; i < alllength; i++) {
state.requestData[i].cancel();
}
// for (let i = 0; i < state.requestData.length; i++) {
// state.requestData[i].cancel();
// }
//将暂停的上传信息存到localstorage 暂时先做临时的,关闭浏览器刷新页面就全部重新上传
//保存的是暂停时候的循环的当前上传文件
// let currentFileInfos = new Map();
// currentFileInfos.set(arrdata[i].filename, arrdata[i].chunkNumber);
// this.setState({
// uploadInfos: currentFileInfos
// })
};
const deleteSelFile = () => {
state.file = null;
state.chunkData = [];
state.uploadStatus = false;
state.uploadDone = false;
state.uploadInfos = null;
state.currentUploadProgressIndex = 0;
state.currentUploadProgress = 0;
timer && clearInterval(timer);
console.log(state, "state");
StopUploadChunks();
};
const uploadChunks = async (arrdata) => {
// if (arrdata.length === 0) {
// state.uploadInfos = null;
// state.uploadStatus = false;
// state.uploadDone = true;
// console.log("nmmmmmmmmmmb");
// }
let promiseReq = [];
for (let i = 0; i < arrdata.length; i++) {
let formDataorigin = new FormData();
formDataorigin.append("identifier", arrdata[i].identifier);
formDataorigin.append("chunkNumber", arrdata[i].chunkNumber);
formDataorigin.append("chunkSize", arrdata[i].chunkSize);
formDataorigin.append("currentChunkSize", arrdata[i].currentChunkSize);
formDataorigin.append("totalSize", arrdata[i].totalSize);
formDataorigin.append("filename", arrdata[i].filename);
formDataorigin.append("relativePath", arrdata[i].relativePath);
formDataorigin.append("totalChunks", arrdata[i].totalChunks);
formDataorigin.append("file", arrdata[i].file);
formDataorigin.append("directionType", context.directionType);
// let fetchReq = this.request.uploadChunkFile(formDataorigin);
console.log(formDataorigin, "!!!!!!");
//请求数据
let fetchReq = postCancel(API.uploader.uploadChunkFile, {
data: formDataorigin,
});
//收集axios对象
promiseReq.push(
fetchReq.send().then((res) => {
if (res.status == 200) {
state.currentUploadProgressIndex =
state.currentUploadProgressIndex + 1;
state.currentUploadProgress =
(state.currentUploadProgressIndex / state.chunkData.length) *
51;
return Promise.resolve(res);
} else {
return Promise.reject(res);
}
})
);
let tmpReqArr = state.requestData;
tmpReqArr.push(fetchReq.abort);
state.requestData = tmpReqArr;
}
//进行并发请求
Promise.all(promiseReq)
.then((values) => {
console.log(values);
mergeRequest();
})
.catch((reason) => {
console.log(reason);
});
};
const createFileChunk = (file, size) => {
const fileChunkList = [];
let cur = 0;
while (cur < file.size) {
fileChunkList.push({ file: file.slice(cur, cur + size) });
cur += size;
}
return fileChunkList;
};
const handleUpload = async () => {
if (!state.file) return;
//console.log("文件:", this.state.file);
//如果存在暂停信息那么就从暂停项开始截取上传数组并且继续传 否则全部重新赋予上传数组
// if (this.state.uploadInfos) {
// let currentFileData = this.state.chunkData.slice(parseInt(this.state.uploadInfos.get(this.state.file?.name)))
// this.setState({
// // chunkStopData: currentFileData,
// uploadStatus: true
// }, async () => {
// //console.log("续传数组", currentFileData)
// await this.uploadChunks(currentFileData);
// })
// } else {
const fileChunkList = createFileChunk(state.file, 2048000);
let data = fileChunkList.map(({ file }, index, array) => ({
chunk: file,
hash: state.file.name + "-" + index, // 文件名 + 数组下标
identifier: state.file._hashmd5,
chunkNumber: index + 1,
chunkSize: file.size,
currentChunkSize: file.size,
totalSize: state.file.size,
filename: state.file.name,
relativePath: state.file.name,
totalChunks: fileChunkList.length,
file: file,
}));
state.chunkData = data;
state.uploadStatus = true;
// //console.log("开始数组", data)
// //判断穿过的文件跳过(截取未上传的startindex->last)
let formDataorigin = new FormData();
formDataorigin.append("identifier", data[0].identifier);
formDataorigin.append("chunkNumber", data[0].chunkNumber);
formDataorigin.append("chunkSize", data[0].chunkSize);
formDataorigin.append("currentChunkSize", data[0].currentChunkSize);
formDataorigin.append("totalSize", data[0].totalSize);
formDataorigin.append("filename", data[0].filename);
formDataorigin.append("relativePath", data[0].relativePath);
formDataorigin.append("totalChunks", data[0].totalChunks);
formDataorigin.append("file", data[0].file);
formDataorigin.append("directionType", context.directionType);
//可能存在过此文件.如果存在则需要进行妙传功能
console.log(formDataorigin, "!!!!!!22222", data);
post(API.uploader.uploadChunkFile, {
data: formDataorigin,
})
.then((res) => {
if (res.data.ifAllExist) {
state.uploadInfos = null;
state.uploadStatus = false;
state.uploadDone = true;
// props.uploadSuccess(null, state.file, res.location);
console.log(props, "props");
props.emit("uploadSuccess", null, state.file, res.data.location);
return;
}
//如果存在那么就进行跳过,并且截取未上传的文件,然后开始上传
if (res.data.chunkInfoDto?.chunkNumber) {
let currentFileData = state.chunkData.slice(
parseInt(res.data.chunkInfoDto.chunkNumber)
);
state.currentUploadProgressIndex =
res.data.chunkInfoDto.chunkNumber + 1;
state.currentUploadProgress =
(state.currentUploadProgressIndex / state.chunkData.length) * 51;
uploadChunks(currentFileData);
} else {
console.log("不存在走全部", data);
state.currentUploadProgressIndex =
state.currentUploadProgressIndex + 1;
state.currentUploadProgress =
(state.currentUploadProgressIndex / state.chunkData.length) * 51;
uploadChunks(data.slice(1));
}
})
.catch((res) => {
message.error(res);
});
};
const handleFileChange = async (e) => {
console.log(e);
const [file] = e.target.files;
if (!file) return;
state.initMd5Status = true;
state.file = file;
const timerReqNum = file.size / 1024 / 1024 / 1024;
if (timerReqNum <= 0.5) {
timerNum.value = 3;
} else if (timerReqNum >= 0.5 && timerReqNum <= 1) {
timerNum.value = 15;
} else if (timerReqNum >= 1 && timerReqNum <= 2) {
timerNum.value = 30;
} else if (timerReqNum >= 2) {
timerNum.value = 40;
}
console.log(file);
//校验md5
// let md5data: any = await this.filemd5init(file);
file._hashmd5 = file.name + file.lastModified;
state.initMd5Status = false;
state.file = file;
};
return {
state,
filemd5init,
mergeRequest,
StopUploadChunks,
deleteSelFile,
uploadChunks,
createFileChunk,
handleUpload,
handleFileChange,
};
},
};
</script>
<style lang="less" scope>
.mycircle {
stroke: #1890ff;
fill: rgba(0, 0, 0, 0);
stroke-width: 2.8px;
stroke-dasharray: 0 51;
}
.uploadBox {
display: flex;
justify-content: flex-start;
align-items: center;
.fileName {
margin-left: 10px;
font-size: 14px;
font-weight: 200;
color: #8c8c8c;
}
.initMd5loading {
display: flex;
justify-content: flex-start;
align-items: center;
font-size: 12px;
margin-left: 10px;
color: #8c8c8c;
img {
margin-left: 5px;
width: 20px;
height: 20px;
background-size: cover;
}
}
.done {
width: 20px;
height: 20px;
margin-left: 10px;
}
.uploadStartOrStop {
width: 25px;
height: 25px;
margin-left: 10px;
position: relative;
.play {
width: 9px;
height: 9px;
color: #1890ff;
position: absolute;
left: 50%;
top: 50%;
z-index: 2;
transform: translate(-50%, -50%);
display: flex;
justify-content: center;
align-items: center;
}
.circleBg {
position: absolute;
left: 50%;
top: 50%;
z-index: 1;
transform: translate(-50%, -50%);
}
}
}
.tips {
text-anchor: middle;
dominant-baseline: middle;
font-size: 30px;
font-family: Arial;
}
.uploadBtn {
position: relative;
width: 100px;
height: 30px;
border-radius: 18px;
border: 2px solid #eee;
font-size: 15px;
font-weight: bold;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
.uploadBtnIcon {
width: 17px;
height: 14px;
background-size: cover;
}
input {
position: absolute;
right: 0;
top: 0;
opacity: 0;
width: 100%;
height: 100%;
cursor: pointer;
z-index: 1;
}
}
.uploaddeleteIcon {
width: 20px;
height: 16px;
margin-left: 10px;
}
</style>
使用:
Fastloader.vue:
通过传不同的directionType来选择不同的类型
<template>
<div class="Fastloader">
<UploadBigFile
class="uploadSlot"
directionType="METEORO_GRIB2"
@uploadSuccess="uploadSuccess"
></UploadBigFile>
</div>
</template>
<script>
// @ is an alias to /srca
import Fastloader from "./Fastloader";
export default Fastloader;
</script>
<style lang="less" scoped>
.Fastloader {
margin-top: 20px;
}
</style>
Fastloader.js:
import UploadBigFile from '../../../../components/fastloader/fastloader';
export default {
name: "Fastloader",
components: {
UploadBigFile
},
setup(props) {
const uploadSuccess = (e, file, location) => {
console.log("啊啊啊啊啊哦哦哦哦哦哦", e, file, location)
}
return {
uploadSuccess
}
}
};
效果:
上传前:
选择文件:
上传中:
上传完成: