一、文件上传
<el-upload
ref="localUpload"
class="avatar-uploader"
action="#"
multiple
:accept="acceptType"
:on-change="handleChange"
:show-file-list="false"
:auto-upload="false">
<i class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
<el-button v-if="!isWatch" type="primary" @click="confirmAdd">保存</el-button>
二、获取文件
handleChange(file) {
// 获取文件的路径(此处的路径为安全路径)
let path = document.getElementsByClassName('el-upload__input')[0].value;
path = path.substring(0, path.lastIndexOf('\\') + 1);
const oid = this.newId = uuidV4().replace(/-/g, '');
// 获取文件类型
this.acceptType = file.raw.type;
// 创建文件预览路径
const url = URL.createObjectURL(file.raw);
// 判断文件是否上传过,根据图片的路径以及图片的大小判断
const historyObj = _.find(this.localStorageUrlList, item => item.path === (path + file.name) && file.size === item.size);
if (historyObj) {
this.$set(historyObj, 'url', url);
this.urlList.push(historyObj);
// 获取分片未全部上床的记录
if (historyObj.status === 'exception') {
// 从未上传成功分片数继续分片上传
this.uploadByPieces(file.raw, Math.ceil(file.raw.size / 1024 / 1024), historyObj.oid, historyObj.successNumber);
}
} else {
this.urlList.push({
url, // 文件预览路径
size: file.size, // 文件大小
path: path + file.name, // 文件本地路径
oid, // 文件唯一标识,用户分片合并
status: 'success'
});
// 文件分片数向上取整
this.uploadByPieces(file.raw, Math.ceil(file.raw.size / 1024 / 1024), oid);
}
}
三、文件分片
uploadByPieces(file, pieces, oid, start = 0) {
const list = [];
const urlObj = _.find(this.urlList, item => item.oid === oid);
for (let i = start; i < pieces; i++) {
const chunkSize = 1024 * 1024; // 文件分片大小
const start = i * chunkSize; // 文件分片开始位置
const end = Math.min(file.size, start + chunkSize); // 文件分片结束位置
const blob = file.slice(start, end); // 文件分片内容
const formData = new FormData();
formData.append('identifier', oid); // 文件唯一标识
formData.append('file', blob); // 文件二进制内容
formData.append('number', i);
list.push({ formData, status: '未上传' });
if (i === pieces - 1) {
urlObj.list = list;
urlObj.pieces = pieces;
}
}
}
四、文件多任务上传
confirmAdd() {
const localStorageObj = {};
localStorageObj['form'] = this.dataSourceForm;
localStorageObj['acceptType'] = this.acceptType;
Promise.all(this.urlList.map(item => {
return this.limitedRequest(localStorageObj, item);
})).then(resp => {
const list = resp.filter(item => item.status === 'exception');
localStorageObj['urlList'] = resp;
if (list.length > 0) {
setUploadList(JSON.stringify(localStorageObj));
} else {
setUploadList(JSON.stringify(localStorageObj));
this.mergeFile(localStorageObj);
}
});
}
limitedRequest(localStorageObj, obj, maxNum = 10) {
const _this = this;
const list = obj.list;
const pool = [];
const initSize = Math.min(list.length, maxNum);
let index = obj.successNumber || 0;
for (let i = 0; i < initSize; i++) {
pool.push(run(list.splice(0, 1)));
}
function r() {
if (list.length === 0) {
return Promise.resolve();
}
return run(list.splice(0, 1));
}
function run(item) {
return new Promise((resolve, reject) => {
uploadSliceFile(item[0].formData, _this.abortController).then(() => {
_this.$set(obj, 'successNumber', index);
_this.$set(obj, 'status', 'success');
_this.$set(obj, 'percent', (((index + 1) / obj.pieces) * 100).toFixed(2));
index++;
resolve(r());
}).catch(err => {
console.log(err);
// 上传错误停止所有已发起的请求,以防分片上传错误重复,须在需要停止的接口中添加参数signal: abortController.signal
_this.abortController.abort();
_this.$set(obj, 'successNumber', index);
_this.$set(obj, 'status', 'exception');
reject(err);
});
});
}
return Promise.allSettled(pool).then(() => {
return obj;
});
}
五、文件分片合并
mergeFile(localStorageObj) {
const { task_id, name, oid } = this.dataSourceForm;
const type = this.acceptType.split('/')[1];
Promise.all(this.urlList.map(item => {
if (!item.isMerged) {
return mergeFile(item.oid, type).then(resp => {
item.isMerged = true;
item.name = resp.file_name;
return resp.file_name;
});
} else {
return item.name;
}
})).then(resp => {
this.$message({
message: '合并成功',
type: 'success'
});
}).catch(() => {
// 上传进度保存在本地
setUploadList(JSON.stringify(localStorageObj));
});
}