tool-tech-front/src/components/FileUpload/optimizeToolUpload.vue
2024-11-15 17:12:38 +08:00

650 lines
20 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div>
<el-upload class="upload-demo" ref="upload" action="#"
:before-upload="beforeUpload"
:on-change="onChange"
:on-remove="handleRemove"
:multiple="false"
:on-exceed="handleExceed"
:accept="acceptType"
:file-list="fileList"
:auto-upload="true"
:http-request="uploadFile"
show-file-list>
<el-button slot="trigger" size="small" type="primary" @click="isFolder = false">上传文件</el-button>
<el-button size="small" type="primary" @click="handleUploadFile" >
上传文件夹
</el-button>
<!-- 上传-->
<input v-show="false" ref="fileRef" id="fileFolder" type="file" @change="handleFolderUpload" webkitdirectory
multiple="multiple" />
<div slot="tip" class="el-upload__tip" >只能上传{{acceptType}}格式</div>
</el-upload>
<div v-show="progressFlag" class="head-img">
<el-progress :text-inside="true" :stroke-width="14" :percentage="progressPercent" status="success"></el-progress>
</div>
</div>
</template>
<script>
import axios from 'axios'
import {
getToken
} from "@/utils/auth";
export default {
props: {
uploadUrl: {
type: String,
required: false
},
// 是否多选
isMultiple: {
type: Boolean,
default() {
return false
},
required: false
},
// 文件类型
type: {
type: Array,
default() {
return []
},
required: false
},
//上传类型
acceptType: {
type: String,
required: false
},
// 上传数量
limit: {
type: Number,
default() {
return 1
},
required: false
},
// 文件
dataFile: {
type: [Object, Array, String],
default() {
return ''
},
required: false
},
isDetail: {
type: Boolean,
default: false
},
},
data() {
return {
fileList: [],
progress: 0,
isUploading: false,
progressFlag: false, //进度条初始值隐藏
progressPercent: 0, //进度条初始值
partSize: 5 * 1024 * 1024,
fileDetailName: null,
uploadedNum: 0,
_shardCount: null, //是否最后一片
isFolder: false,
totalFiles: 0, // 总文件数量
uploadedCount: 0, // 已上传文件数量
};
},
watch: {
dataFile: {
handler(newValue, oldValue) {
if (newValue && newValue.length > 0) {
this.fileList = Array.isArray(newValue) ? newValue : [newValue]
this.fileDetailName = this.fileList[0].name
} else {
this.fileList = []
}
},
immediate: true
}
},
methods: {
created(){
this.clearFile()
},
// 上传前
beforeUpload(file) {
/* const fileType = file.type.toLowerCase()
console.info("fileType=======", fileType)
const typeFlag = this.type.some(item => {
return fileType.indexOf(item) != -1
})
if (!typeFlag) {
this.$message.error('上传类型有误,请重新上传')
return false
}*/
},
//文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用
onChange(file, fileList) {
this.fileList = fileList;
},
handleRemove(file, fileList) {
this.fileList = fileList;
},
handleExceed(files, fileList) {
/* this.$message.warning(
"最多之能上传"+ this.limit +"个附件"
); */
},
async uploadFile({ data, file }) {
let self = this
//初始化参数
if (file.size < this.partSize) {
let formData = new FormData()
formData.append("file", file)
self.progressFlag = true;
axios({
url: self.uploadUrl,
method: 'post',
data: formData,
headers: {
'Authorization': 'Bearer ' + getToken(),
'Content-Type': 'multipart/form-data'
},
onUploadProgress: progressEvent => {
// progressEvent.loaded:已上传文件大小
// progressEvent.total:被上传文件的总大小
//进度条
self.progressPercent = ((progressEvent.loaded / progressEvent.total) * 100) | 0;
}
}).then(res => {
setTimeout(() => {
if (res.data.code == 200) {
setTimeout(function () {
//文件夹上传
if(self.isFolder){
self.fileList.push(file)
}
self.$message({
message: '上传成功!',
type: 'success',
duration: '2000'
});
self.progressFlag = false;
self.progressPercent = 0
self.handleResult(res,file);
}, 500);
} else {
self.progressFlag = false;
self.progressPercent = 0
if(self.$refs.upload){
self.$refs.upload.clearFiles();
}
self.$message({
message: '上传失败!',
type: 'error',
duration: '2000'
});
}
}, 1000);
}).catch(error => {
console.error(error)
self.progressFlag = false;
self.progressPercent = 0
if(self.$refs.upload){
self.$refs.upload.clearFiles();
}
self.$message({
message: '上传失败!',
type: 'error',
duration: '2000'
});
})
} else {
self.uploadIdSharding(file);
}
},
uploadIdSharding(file){
let self = this
self.uploadedNum = 0
file._shardCount = Math.ceil(file.size / this.partSize) //总片数
file.uploaded = 0
let formData = new FormData()
formData.append('fileName', file.name)
//大文件上传初始化,返回uploadId initUpload(formData)
axios({
url: process.env.VUE_APP_BASE_API + "/common/initUpload",
method: 'post',
data: formData,
headers: {
'Authorization': 'Bearer ' + getToken(),
'Content-Type': 'multipart/form-data'
},
}).then(function (res) {
if (res.status == 200) {
//从第0块开始上传
file.uploadId = res.data.uploadId
file.objectKey = res.data
file.chunkId = 0
self.uploadByChunk(file)
} else {
this.progress--
self.$message.error(res.msg)
}
}).catch(error => {
self.progressPercent = 0
self.progressFlag = false;
console.error(error)
})
},
//分片上传大文件
uploadByChunk(file) {
this._start = file.chunkId * this.partSize
this._end = Math.min(file.size, this._start + this.partSize)//结束时总大小,和 开始的大小+之前的大小比较
let self = this
let fileData = file.slice(this._start, this._end)
//获取文件块MD5
let reader = new FileReader()
reader.readAsBinaryString(fileData)
let form1 = new FormData()//new一个form的实例可以进行键值对的添加
form1.append('chunkFile', fileData) //slice方法用于切出文件的一部分
form1.append('uploadId', file.uploadId)
form1.append('chunkId', (file.chunkId + 1).toString())
form1.append('shardCount', file._shardCount.toString()) //是否最后一片
self.progressFlag = true;
//上传大文件的Chunk 返回chunk的MD5 uploadChunk(form1)
axios({
url: process.env.VUE_APP_BASE_API + "/common/uploadChunk",
method: 'post',
data: form1,
headers: {
'Authorization': 'Bearer ' + getToken(),
'Content-Type': 'multipart/form-data'
},
}).then(response => {
if (response.status == 200) {
//判断返回的MD5值是否一致一致继续传下一块否则重传本块这里目前前后端MD5一直不一致暂时先注释掉有时间了我再研究一下
// self.md5Str[index] === response.msg ||
if (true) {
// this.$message.success('第' + (index + 1).toString() + '块文件上传成功')
self.uploadedNum++
let percent = Math.floor((self.uploadedNum / file._shardCount) * 100)
self.percentageSend(file, percent)
//如果没上传完成,继续上传一下块
file.chunkId++
if (file.chunkId < file._shardCount) {
this.uploadByChunk(file)
}
} else {
//不一致,重新传本块
this.$message.success('第' + (file.chunkId + 1).toString() + '块文件上传不成功,重新上传')
this.uploadByChunk(file.chunkId)
}
} else {
//出错,跳出循环显示错误
this.progress--
this.$message.error(response.msg)
}
})
},
//接收上传的百分值回调
percentageSend(file, perNum) {
let self = this
self.progressPercent = perNum
//如果上传完成,合并文件
if (perNum === 100) {
let form2 = new FormData()//new一个form的实例可以进行键值对的添加
form2.append('uploadId', file.uploadId)
form2.append('fileName', file.name)
// 大文件上传完成后合并
// 返回文件访问的URL
axios({
url: process.env.VUE_APP_BASE_API + "/common/mergeFile",
method: 'post',
data: form2,
headers: {
'Authorization': 'Bearer ' + getToken(),
'Content-Type': 'multipart/form-data'
},
}).then(res => {
if (res.status == 200) {
setTimeout(function () {
self.$message({
message: '上传成功!',
type: 'success',
duration: '2000'
});
self.progressFlag = false;
self.progressPercent = 0
self.handleResult(res,file);
}, 500);
//文件夹上传
if(self.isFolder){
self.fileList.push(file)
}
} else {
self.progressFlag = false;
self.progressPercent = 0
if(self.$refs.upload){
self.$refs.upload.clearFiles();
}
self.$message({
message: '上传失败!',
type: 'error',
duration: '2000'
});
}
}).catch(error => {
self.progressFlag = false;
self.progressPercent = 0
if(self.$refs.upload){
self.$refs.upload.clearFiles();
}
self.$message({
message: '上传失败!',
type: 'error',
duration: '2000'
});
});
}
},
resetData() {
this.progress = 0;
},
handleResult(res, file) {
let self = this
const data = {
fileName: file.name,
fileSize: file.size,
fileUrl: res.data.url,
filePath: res.data.filePath,
fileOldName: res.data.originalFilename,
fileNewName: res.data.newFileName,
suffixType: res.data.suffixType
}
self.$emit("handleSuccess", data)
self.clearFile()
},
/** 清空文件 **/
clearFile(){
if(this.$refs.upload){
this.$refs.upload.clearFiles();
}
this.fileList = []
},
handleUploadFile() {
document.getElementById("fileFolder").value = null;
this.$refs.fileRef.dispatchEvent(new MouseEvent("click"));
},
/* async handleFolderUpload(event) {
let self = this;
self.isFolder = true
const files = event.target.files;
self.fileslength = files.length;
let filesper = Math.floor( 100 / files.length * 100)/100;
self.progressShow = true;
if (self.fileslength == 0) {
self.progressShow = false;
self.$modal.msg("上传完成0个未完成0个");
}
for (const file of files) {
let temp = {
"data": null,
"file": file
}
await self.uploadFile(temp);
}
}, */
/* // 处理文件夹上传
async handleFolderUpload(event) {
const files = event.target.files;
this.isFolder = true;
this.progressFlag = true;
if (files.length === 0) {
this.$message.warning('没有选择文件');
return;
}
let successCount = 0;
let failCount = 0;
for (const file of files) {
try {
await this.uploadFolderFile({ file });
successCount++;
} catch (error) {
failCount++;
}
}
this.$message.success(`上传完成!成功:${successCount},失败:${failCount}`);
this.progressFlag = false;
this.progressPercent = 0;
}, */
// 单个文件上传
uploadFolderFile({ data, file }) {
let self = this
//初始化参数
if (file.size < this.partSize) {
return new Promise((resolve, reject) => {
const formData = new FormData();
formData.append("file", file);
this.progressFlag = true;
axios({
url: self.uploadUrl,
method: 'post',
data: formData,
headers: {
'Authorization': 'Bearer ' + getToken(),
'Content-Type': 'multipart/form-data'
},
/* onUploadProgress: progressEvent => {
this.progressPercent = ((progressEvent.loaded / progressEvent.total) * 100) | 0;
} */
}).then(res => {
setTimeout(() => {
if (res.data.code === 200) {
// self.progressFlag = false;
// self.progressPercent = 0;
self.handleResult(res,file);
resolve(); // 上传成功后,调用 resolve
} else {
// self.progressFlag = false;
self.$message.error(file.name + '上传失败!');
reject(); // 上传失败时调用 reject
}
}, 500);
}).catch(error => {
// self.progressFlag = false;
// self.progressPercent = 0;
self.$message.error(file.name + '上传失败!');
reject(error); // 上传失败时调用 reject
});
});
}else{
return new Promise((resolve, reject) => {
self.uploadIdShardingFolder(file)
.then(() => {
resolve(); // 上传成功,继续下一个文件
})
.catch(error => {
reject(error); // 上传失败,触发 reject
});
});
}
},
async handleFolderUpload(event) {
let self = this
const files = Array.from(event.target.files);
this.totalFiles = files.length;
this.uploadedCount = 0;
this.progressFlag = true;
let failCount = 0;
for (let i = 0; i < files.length; i++) {
const file = files[i];
const fileExtension = file.name.slice(file.name.lastIndexOf('.')).toLowerCase();
// 校验文件格式
if (!self.acceptType.includes(fileExtension)) {
failCount++;
this.$message.error('文件格式不支持: ' + file.name);
continue;
}
try {
await this.uploadFolderFile(
{
data: null,
file: file
}
);
this.uploadedCount++;
this.updateProgress();
} catch (error) {
failCount++;
}
}
setTimeout(function () {
self.$message.success('上传完成!成功:' + self.uploadedCount+',失败:' + failCount);
self.progressFlag = false;
self.progressPercent = 0;
}, 500);
},
updateProgress() {
if (this.uploadedCount === this.totalFiles) {
this.progressPercent = 100;
} else {
this.progressPercent = Math.min(100, Math.floor((this.uploadedCount / this.totalFiles) * 100));
}
/*
if (this.uploadedCount % 10 === 0 || this.uploadedCount === this.totalFiles) {
this.$message.success(`已上传 ${this.uploadedCount} 个文件`);
} */
},
// 初始化分片上传
uploadIdShardingFolder(file) {
let self = this
return new Promise((resolve, reject) => {
file._shardCount = Math.ceil(file.size / this.partSize); // 总片数
file.uploaded = 0;
const formData = new FormData();
formData.append('fileName', file.name);
// 大文件上传初始化, 返回 uploadId
axios({
url: process.env.VUE_APP_BASE_API + "/common/initUpload",
method: 'post',
data: formData,
headers: {
'Authorization': 'Bearer ' + getToken(),
'Content-Type': 'multipart/form-data'
},
}).then(res => {
if (res.status === 200) {
file.uploadId = res.data.uploadId;
file.chunkId = 0;
this.uploadByChunkFolder(file)
.then(() => resolve()) // 全部分片上传完成,返回 resolve
.catch(error => reject(error)); // 分片上传出错,返回 reject
} else {
reject(new Error('初始化上传失败'));
}
}).catch(error => {
reject(error);
});
});
},
// 分片上传逻辑
uploadByChunkFolder(file) {
let self = this
return new Promise((resolve, reject) => {
const uploadChunk = (chunkId) => {
const _start = chunkId * this.partSize;
const _end = Math.min(file.size, _start + this.partSize);
const fileData = file.slice(_start, _end);
const form1 = new FormData();
form1.append('chunkFile', fileData);
form1.append('uploadId', file.uploadId);
form1.append('chunkId', (chunkId + 1).toString());
form1.append('shardCount', file._shardCount.toString());
axios({
url: process.env.VUE_APP_BASE_API + "/common/uploadChunk",
method: 'post',
data: form1,
headers: {
'Authorization': 'Bearer ' + getToken(),
'Content-Type': 'multipart/form-data'
},
}).then(response => {
if (response.status === 200) {
file.uploadedNum++;
// const percent = Math.floor((file.uploadedNum / file._shardCount) * 100);
// this.percentageSend(file, percent);
if (chunkId + 1 < file._shardCount) {
// 继续上传下一片
uploadChunk(chunkId + 1);
} else {
// 所有分片上传完成,合并文件
this.mergeFileFolder(file).then(() => resolve()).catch(error => reject(error));
}
} else {
reject(new Error('分片上传失败'));
}
}).catch(error => {
reject(error);
});
};
// 从第0块开始上传
uploadChunk(0);
});
},
// 合并文件
mergeFileFolder(file) {
let self = this
return new Promise((resolve, reject) => {
const form2 = new FormData();
form2.append('uploadId', file.uploadId);
form2.append('fileName', file.name);
axios({
url: process.env.VUE_APP_BASE_API + "/common/mergeFile",
method: 'post',
data: form2,
headers: {
'Authorization': 'Bearer ' + getToken(),
'Content-Type': 'multipart/form-data'
},
}).then(res => {
if (res.status === 200) {
self.handleResult(res,file);
resolve(); // 合并成功
} else {
reject(new Error('文件合并失败'));
}
}).catch(error => {
reject(error);
});
});
}
}
};
</script>