650 lines
20 KiB
Vue
650 lines
20 KiB
Vue
<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>
|