- 效果图如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/afbfd814855047a38ebe19a8ac27f0c7.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6YC46YC45ZGA,size_20,color_FFFFFF,t_70,g_se,x_16)
- 文件所在位置:
![在这里插入图片描述](https://img-blog.csdnimg.cn/3ab8b1538aea4953931deaf917f36db5.png)
- view/huge_file_upload/huge_file_upload.vue:(在项目中使用?)
<template>
<div>
<div style="width: 350px">
<FileUploader
:config="config"
@fileUploadFinished="fileUploadFinishedHandle"
@fileDeleteFinished="fileDeleteFinishedHandle"
ref="fileUploader1"
/>
</div>
<sg-button type="primary" @click="clear">重置</sg-button>
</div>
</template>
<script>
import FileUploader from "@/components/FileUploader";
import { IFileConfig } from "@/components/FileUploader/IFileConfig";
const config = new IFileConfig();
config.domId = "file";
config.isMultiple = true;
export default {
name: "HugeFileUpload",
components: {
FileUploader,
},
data() {
return {
config: config,
};
},
methods: {
fileUploadFinishedHandle(e) {
console.log(e);
},
fileDeleteFinishedHandle(e) {
console.log(e);
},
clear() {
this.$refs.fileUploader1.clear();
},
},
mounted() {},
};
</script>
<style lang="scss" scoped></style>
- components/FileUploader/api/index.js:
import request from "@/utils/axios";
/**
* 上传分片
* @returns
*/
export function fileSliceUpload(data) {
return request({
url: "/ami/ma01-02-056/minio-file/fileSliceUpload",
method: "POST",
data,
});
}
/**
* 合并文件分片
* @returns
*/
export function mergeSlice(data) {
return request({
url: "/ami/ma01-02-056/minio-file/mergeSlice",
method: "POST",
data,
});
}
/**
* 小文件上传,不分片
* @returns
*/
export function fileUpload(data) {
return request({
url: "/ami/ma01-02-056/minio-file/fileUpload",
method: "POST",
data,
});
}
/**
* 删除文件
* @returns
*/
export function deleteFile(data) {
return request({
url: "/ami/ma01-02-056/minio-file/deleteFile",
method: "POST",
data,
});
}
- components/FileUploader/assets/图片(图片所在位置)
![在这里插入图片描述](https://img-blog.csdnimg.cn/91e03562ba1f4dca99ad01194c179e1c.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6YC46YC45ZGA,size_20,color_FFFFFF,t_70,g_se,x_16)
- components/FileUploader/IFileConfig.js:(文件限制封装)
export class IFileConfig {
constructor() {}
// id
domId = "";
// 是否多文件
isMultiple = false;
// 文件类型
fileAccept = ".zip, .rar";
// 切片长度
sliceLength = 1024 * 1024 * 5; // 5M
// 文件大小限制
maxFileSize = 1024 * 1024 * 1024 * 5; // 5G
// 禁止删除上传成功的文件
cantDeleteServerFile = false
}
- components/FildeUploader/index.vue:(上传文件的主要逻辑)
<template>
<div>
<div class="uploader-container">
<input
v-if="config.isMultiple"
type="file"
name="file"
multiple="multiple"
:id="config.domId"
style="display: none"
@change="fileChosen"
:accept="config.fileAccept"
/>
<input
v-else
type="file"
name="file"
:id="config.domId"
style="display: none"
@change="fileChosen"
:accept="config.fileAccept"
/>
<img
src="./assets/upload.png"
class="upload-img-btn"
alt=""
@click="openFileWindow()"
v-if="uploadStatus === 'n'"
/>
<div class="file-list" v-else>
<sg-row v-for="(item, index) in fileList" :key="'01_' + index" class="file-item">
<sg-col :span="2">
<img src="./assets/uploaded.png" style="width: 16px; height: 16px; margin-top: 13px" alt="" />
</sg-col>
<sg-col :span="19">
<div style="white-space: nowrap; text-overflow: ellipsis; overflow: hidden">
{{ item.file.name }}
</div>
</sg-col>
<sg-col :span="3" style="text-align: right">
<span v-if="uploadStatus === 'u' || uploadStatus === 'f'">{{ item.percent }}%</span>
<span v-if="item.isDelServerFile">0%</span>
<img
src="./assets/del.png"
style="width: 15px; height: 16px; margin: 0 auto; margin-top: 13px; cursor: pointer"
alt=""
@click="delFile(index, item)"
v-if="(uploadStatus === 'w' || uploadStatus === 's') && !item.isDelServerFile && delImgBtnFlag"
/>
</sg-col>
</sg-row>
</div>
<div style="width: 100px; margin: 0 auto">
<sg-button type="primary" :loading="true" v-if="uploadStatus === 'u'">上传中</sg-button>
<sg-button
type="primary"
@click="uploadBtnClickHandle()"
:disabled="uploadStatus === 'n'"
v-if="uploadStatus === 'n' || uploadStatus === 'w'"
>上传文件</sg-button
>
<sg-button type="primary" @click="reUpload()" v-if="uploadStatus === 'f'">断点续传</sg-button>
</div>
<div style="text-align: center; margin-top: 20px">注:限上传3个文件,单个文件最大为{{ maxFileSize }}G</div>
</div>
</div>
</template>
<script>
import { fileSliceUpload, mergeSlice, fileUpload, deleteFile } from "./api";
import { deepClone } from "@/utils/util";
import { IFileConfig } from "./IFileConfig";
const uuid = () => {
const temp_url = URL.createObjectURL(new Blob());
const uuid = temp_url.toString();
URL.revokeObjectURL(temp_url);
return uuid.substr(uuid.lastIndexOf("/") + 1);
};
export default {
name: "HugeFileUpload",
props: {
config: {
type: IFileConfig,
default: () => {},
},
},
data() {
return {
fileIdForData: "",
fileDom: null,
fileList: [],
uploadImgBtnFlag: true,
maxFileSize: 5,
delImgBtnFlag: true,
bytesPerPiece: this.config.sliceLength,
taskList: [],
sliceUrlList: {},
fileSliceList: [],
uploadStatus: "n",
fileIdList: {},
mergeResult: [],
};
},
methods: {
openFileWindow() {
this.fileDom.click();
},
fileChosen() {
if (this.fileDom.files && this.fileDom.files.length) {
const len = this.fileDom.files.length < 4 ? this.fileDom.files.length : 3;
for (let i = 0; i < len; i++) {
const f = this.fileDom.files[i];
if (f.size <= this.config.maxFileSize) {
let blob = deepClone(this.fileDom.files[i]);
this.fileList.push({
file: blob,
percent: 0,
filename: blob.name,
isDelServerFile: false,
});
}
}
this.uploadStatus = "w";
this.fileDom.value = "";
}
},
delFile(index, item) {
if (this.uploadStatus === "w") {
this.fileList.splice(index, 1);
if (this.fileList.length === 0) {
this.uploadStatus = "n";
}
} else {
this.deleteFile(this.fileIdList[item.filename] * 1, item);
}
},
deleteFile(formData, fileInfo) {
fileInfo.isDelServerFile = true;
deleteFile(formData).then(
(res) => {
if (res.status === 200) {
this.fileList = this.fileList.filter((item) => item.filename !== fileInfo.filename);
delete this.fileIdList[fileInfo.filename];
if (this.fileList.length === 0) {
this.uploadStatus = "n";
}
this.$emit("fileDeleteFinished", { fileInfo, res });
} else {
console.log(res);
fileInfo.isDelServerFile = false;
}
},
(err) => {
console.log(err);
fileInfo.isDelServerFile = false;
}
);
},
mergeSlice(fileSliceList, totalPieces, filesize, filename) {
return new Promise((resolve) => {
let count = 0;
for (const item of fileSliceList) {
if (item.status === "1") {
count++;
}
}
if (count == totalPieces) {
console.log("merge happened");
const param = {
fileSize: Math.ceil(filesize / 1024),
fullName: filename,
sliceList: this.sliceUrlList[filename],
sliceNum: totalPieces,
};
mergeSlice(param).then(
(res) => {
if (res.status === 200) {
resolve(res);
} else {
resolve({
type: "ErrorOrException",
msg: res,
});
}
},
(err) => {
resolve({
type: "ErrorOrException",
msg: err,
});
}
);
} else {
resolve({
type: "ErrorOrException",
msg: null,
});
}
});
},
fileSliceUpload(formData) {
return new Promise(function (resolve) {
fileSliceUpload(formData).then(
(res) => {
if (res.status === 200) {
resolve(res);
} else {
resolve({
type: "ErrorOrException",
msg: res,
});
}
},
(err) => {
resolve({
type: "ErrorOrException",
msg: err,
});
}
);
});
},
fileUpload(formData) {
return new Promise(function (resolve) {
fileUpload(formData).then(
(res) => {
if (res.status === 200) {
resolve(res);
} else {
resolve({
type: "ErrorOrException",
msg: res,
});
}
},
(err) => {
resolve({
type: "ErrorOrException",
msg: err,
});
}
);
});
},
cutFileIntoPieces(blob, bytesPerPiece, totalPieces) {
const uuidfolder = uuid();
let start = 0;
let end;
let index = 0;
const filesize = blob.size;
while (start < filesize) {
end = start + bytesPerPiece;
if (end > filesize) {
end = filesize;
}
const chunk = blob.slice(start, end);
const sliceIndex = uuidfolder + "_" + index;
const formData = new FormData();
formData.append("sliceFile", chunk);
formData.append("sliceName", sliceIndex);
formData.append("sliceNo", index);
formData.append("sliceNum", totalPieces);
formData.append("sliceUrl", "");
this.fileSliceList.push({
formData,
sliceIndex,
index,
status: "0",
});
start = end;
index++;
}
},
createTasks() {
if (this.fileList && this.fileList.length) {
for (const item of this.fileList) {
const blob = item.file;
const filesize = blob.size;
const filename = blob.name;
if (filesize <= this.bytesPerPiece) {
const formData = new FormData();
formData.append("multipartFile", blob);
this.taskList.push({
type: "small",
status: "0",
filename,
formData,
callback: this.fileUpload,
});
} else {
let totalPieces = Math.ceil(filesize / this.bytesPerPiece);
this.fileSliceList = [];
this.cutFileIntoPieces(blob, this.bytesPerPiece, totalPieces);
if (this.fileSliceList && this.fileSliceList.length) {
for (const slice of this.fileSliceList) {
this.taskList.push({
type: "slice",
status: "0",
slice,
filename,
totalPieces,
callback: this.fileSliceUpload,
});
}
const t = [];
for (const item of this.fileSliceList) {
t.push({
index: item.index,
status: "0",
});
}
this.taskList.push({
type: "merge",
status: "0",
filename,
fileSliceList: t,
totalPieces,
filesize,
callback: this.mergeSlice,
});
}
}
}
}
},
async runTask() {
const setPercent = (filename, percent) => {
const file = this.fileList.filter((item) => item.filename === filename);
if (file && file.length) {
file[0].percent = percent;
}
};
for (const task of this.taskList) {
if (task.status === "0") {
console.log(task);
if (task.type === "slice") {
const res = await task.callback(task.slice.formData);
if (!res.type) {
task.status = "1";
const t = this.taskList.filter((item) => item.type === "merge" && item.filename === task.filename);
if (t && t.length) {
const s = t[0].fileSliceList.filter((fs) => fs.index === task.slice.index);
if (s && s.length) {
s[0].status = "1";
}
}
const sib = this.taskList.filter(
(item) => item.type === "slice" && item.filename === task.filename && item.status === "1"
);
const p = sib && sib.length ? sib.length : 0;
setPercent(task.filename, parseInt((p / task.totalPieces) * 90));
const {
data: {
data: { sliceUrl },
},
} = res;
if (!this.sliceUrlList[task.filename]) {
this.sliceUrlList[task.filename] = [];
}
this.sliceUrlList[task.filename].push({
sliceFile: null,
sliceName: task.slice.sliceIndex,
sliceNo: task.slice.index,
sliceUrl,
});
}
} else if (task.type === "merge") {
const res = await task.callback(task.fileSliceList, task.totalPieces, task.filesize, task.filename);
if (!res.type) {
task.status = "1";
setPercent(task.filename, 100);
this.fileIdList[task.filename] = res.data.fileId;
this.mergeResult.push(res);
}
} else if (task.type === "small") {
const res = await task.callback(task.formData);
console.log(res);
if (!res.type) {
task.status = "1";
setPercent(task.filename, 100);
this.fileIdList[task.filename] = res.data.data.fileId;
this.mergeResult.push(res);
}
}
}
}
const t = this.taskList.filter((task) => task.status === "0");
if (t && t.length) {
this.uploadStatus = "f";
} else {
this.uploadStatus = "s";
this.taskList = [];
this.fileSliceList = [];
this.sliceUrlList = {};
this.$emit("fileUploadFinished", deepClone(this.mergeResult));
this.mergeResult = [];
if (this.config.cantDeleteServerFile) {
this.delImgBtnFlag = false;
}
}
},
uploadBtnClickHandle() {
this.uploadStatus = "u";
this.createTasks();
this.runTask();
},
clear() {
this.fileList = [];
this.uploadImgBtnFlag = true;
this.delImgBtnFlag = true;
this.taskList = [];
this.sliceUrlList = {};
this.fileSliceList = [];
this.mergeResult = [];
this.uploadStatus = "n";
},
reUpload() {
this.uploadStatus = "u";
this.runTask();
},
},
created() {
},
mounted() {
this.fileDom = document.getElementById(this.config.domId);
this.maxFileSize = parseInt((this.config.maxFileSize * 1) / 1024 / 1024 / 1024);
},
watch: {
},
};
</script>
<style lang="scss" scoped>
.uploader-container {
border: 1px dashed #bacfe7;
height: 250px;
border-radius: 5px;
background: #f4f7fa;
.upload-img-btn {
width: 80px;
height: 80px;
margin: 40px auto;
cursor: pointer;
}
.file-list {
height: 160px;
.file-item {
margin: 5px 8px;
background: #fff;
height: 44px;
line-height: 44px;
padding: 0 10px;
}
}
}
</style>