<a-upload
name="file"
:accept="fileAcceptType"
:multiple="false"
:show-upload-list="false"
:before-upload="file => { beforeFileUpload(file) }"
:customRequest="file => { selfFileUpload(file) }"
@change="info => { handleFileChange(info) }"
>
<a-button> <a-icon type="upload" /> 上传文件 </a-button>
</p>
</a-upload>
<a-table
:columns="columns"
:data-source="fileList"
rowKey="num"
>
<span
slot="size"
slot-scope="text, record"
><span>{{text?text+"kb":'-'}}</span>
<a-progress
:percent="record.uploadPercent/1"
v-if="(record.uploadLoading || record.uploadStatus) && record.status=='上传中'"
/>
</span>
<span
slot="status"
slot-scope="text, record"
>
<span :class="text=='上传成功'?'success':(text=='上传失败'?'fail':'')">{{text}}</span>
</span>
<span
slot="action"
slot-scope="text, record"
>
<a
:disabled="disabled"
@click="del(record)"
>删除</a>
</span>
</a-table>
beforeFileUpload(file) {
console.log("beforeFileUpload", file);
const mimeType = file.type.toLowerCase();
// 检查MIME类型是否以特定字符串开头
let isDoc =
mimeType.startsWith("application/zip") ||
mimeType.startsWith("application/x-zip");
if (!isDoc) {
this.$message.warning("上传文件格式不正确,请上传正确的文件!");
}
let isLt1G = file.size < this.fileAcceptSize;
if (!isLt1G) {
this.$message.warning(
`封面文件过大,请上传不超过${this.fileAcceptSizeStr}的文件!`
);
}
const isLt0 = file.size == 0;
if (isLt0) {
this.$message.warning("上传文件为空,请检查!");
}
console.log("beforeFileUpload", isDoc && !isLt0 && isLt1G);
return isDoc && !isLt0 && isLt1G;
},
selfFileUpload(file) {
let type = file.file.type;
const mimeType = type.toLowerCase();
// 检查MIME类型是否以特定字符串开头
let isDoc =
mimeType.startsWith("application/zip") ||
mimeType.startsWith("application/x-zip")
let isLt1G = file.file.size < this.fileAcceptSize;
const isLt0 = file.file.size == 0;
if (isDoc && !isLt0 && isLt1G) {
this.file = file;
// 加载文件
if (this.zipProcessor) {
this.zipProcessor = null;
}
let that = this;
this.zipProcessor = new ZipProcessor({
maxSize: that.fileAcceptSize,
maxCount: 100,
viewUnabled: true,
validateFileType: function (ext, fullFileName) {
// 定义允许的类型
const allowedTypes = ["png", "jpg", "mp3", "mp4"];
return allowedTypes.includes(ext);
},
onStatusUpdate: function (fileName, status) {
console.log(`文件 ${fileName} 状态更新为: ${status}`);
// 更新页面上对应文件的状态显示
if (that.fileList && that.fileList.length > 0) {
that.fileList.forEach((element) => {
if (element.name == fileName) {
element.status = status;
}
});
}
},
onLoadComplete: function (results) {
console.log("所有文件加载完毕。结果:", results);
that.fileList = [...results];
// 设置序号
that.fileList.forEach((item, index) => {
item.orderNo = index + 1;
});
if (that.fileList.length > 0) {
// 开始上传按钮启用
that.disabled = false;
that.btnText = "开始上传";
}
},
onComplete: function (results) {
console.log("所有文件处理完毕。结果:", results);
that.finalStatus = true;
},
onError: function (error) {
console.error("处理过程中发生错误:", error);
},
});
if (this.file.file) {
that.zipProcessor.processZip(this.file.file); // 开始处理
}
} else {
this.file = null;
}
},
handleFileChange(info) {},
async onStartUpload() {
// 开始上传
let that = this;
that.uploading = true;
that.disabled = true;
that.btnText = "上传中";
for (let i = 0; i < this.fileList.length; i++) {
// viewList
const fileType = this.fileList[i].type;
const fileName = this.fileList[i].name;
const fileEntry = this.fileList[i].file;
// --- 上传逻辑 ---
try {
// 读取文件内容(如果需要上传到服务器, 如果显示的包含不可上传的,需要特殊处理一下)
// 这里要处理三种文件类型,且需要加入上传进度条
if (
fileType == "jpg" ||
fileType == "png" ||
fileType == "mp3" ||
fileType == "mp4"
) {
let config = {
timeout: 120000, //设置超时时长
onUploadProgress: function (progress) {
this.fileList[i].uploadPercent = parseFloat(
(progress.loaded * 100) / progress.total
).toFixed(2);
}.bind(this),
};
that.fileList[i].uploadLoading = true; // record.uploadStatus
that.fileList[i].status = "上传中";
const formData = new FormData();
formData.append("bookId", that.bookId);
formData.append("title", fileName || "");
const fileData = await fileEntry.async("blob");
if (fileType == "jpg" || fileType == "png") {
// 上传图片
//formData.append("description", "test");
formData.append("imageFile", new File([fileData], fileName));
console.log("formData", formData);
// 异步,一条条上传,否则服务器压力过大
let picRes = await createPic(formData, config);
let data = picRes.data;
if (data.success) {
that.fileList[i].uploadLoading = false;
that.fileList[i].uploadStatus = true;
that.fileList[i].status = "上传成功";
} else {
that.fileList[i].uploadLoading = false;
that.fileList[i].uploadStatus = false;
that.fileList[i].status = "上传失败";
}
} else if (fileType == "mp3") {
// 上传音频
} else if (fileType == "mp4") {
// 上传视频
}
}
} catch (uploadError) {
// console.error(`上传文件 ${fileName} 时失败:`, uploadError);
}
// --- 模拟上传逻辑结束 ---
if (i == this.fileList.length - 1) {
this.uploading = false;
this.btnText = "上传结束";
that.finalStatus = true;
}
}
},
ZipProcessor.js:
// zipProcessor.js
import { filetype } from "@/config/filetype";
import JSZip from "jszip";
/**
* ZIP 文件处理器类
*/
class ZipProcessor {
/**
* 构造函数
* @param {Object} config - 配置对象
* @param {Function} config.validateFileType - 用于验证单个文件类型的函数,接收文件名作为参数,返回布尔值
* @param {Function} [config.onProgress] - 可选的进度回调函数,接收 (current, total) 参数
* @param {Function} [config.onStatusUpdate] - 可选的状态更新回调函数,接收 (fileName, status) 参数
* @param {Function} [config.onLoadComplete] - 可选的加载文件完成回调函数,接收 (results) 参数
* @param {Function} [config.onComplete] - 可选的处理完成回调函数,接收 (results) 参数
* @param {Function} [config.onError] - 可选的错误处理回调函数,接收 (error) 参数
* @param {boolean} [config.viewUnabled] - 是否显示格式错误的文档
* @param {number} [config.maxCount] - 包含文件的最大文件数量
* @param {number} [config.maxSize] - zip最大文件大小
*/
constructor(config) {
this.validateFileType = config.validateFileType;
if (typeof this.validateFileType !== "function") {
throw new Error("validateFileType is required and must be a function.");
}
this.onProgress = config.onProgress || (() => {});
this.onStatusUpdate = config.onStatusUpdate || (() => {});
this.onLoadComplete = config.onLoadComplete || (() => {});
this.onComplete = config.onComplete || (() => {});
this.onError =
config.onError ||
((error) => {
console.error("ZipProcessor Error:", error);
});
this.viewUnabled = config.viewUnabled ? config.viewUnabled : false;
this.maxCount = config.maxCount ? config.maxCount : 100;
this.maxSize = config.maxSize ? config.maxSize : 1024 * 1024 * 1024;
this.fileList = []; // 符合条件的文件
this.results = []; // 全部文件
this.viewList = []; //config.postFiles ? config.postFiles : [];
this.zipContent = undefined;
}
getFileNameFromPath(fullName) {
// 查找最后一个 '/' 或 '\' 的位置
const lastSlashIndex = Math.max(
fullName.lastIndexOf("/"),
fullName.lastIndexOf("\\"),
);
// 如果找到了路径分隔符,则返回其后的部分;否则返回原字符串
return fullName.substring(lastSlashIndex + 1);
}
/**
* 处理上传的 ZIP 文件
* @param {File} zipFile - 用户选择的 ZIP 文件
*/
async processZip(zipFile) {
if (!zipFile || !zipFile.name.toLowerCase().endsWith(".zip")) {
this.onError(new Error("请选择一个zip文件"));
return;
}
try {
//const JSZip = window.JSZip;
if (!JSZip) {
this.onError(
new Error(
"JSZip library is not loaded. Please include JSZip before using ZipProcessor.",
),
);
return;
}
let _this = this;
const fileReader = new FileReader();
// 转换文件为ArrayBuffer
fileReader.readAsArrayBuffer(zipFile);
const decoder = new TextDecoder("gbk");
// 监听完成事件
fileReader.onload = async function fn() {
const zip = new JSZip();
_this.zipContent = await zip.loadAsync(fileReader.result, {
decodeFileName: (bytes) => {
return decoder.decode(bytes);
},
});
const files = Object.keys(_this.zipContent.files).filter(
(name) => !_this.zipContent.files[name].dir,
); // 过滤掉目录
const totalFiles = files.length;
console.log("totalFiles", totalFiles, _this.maxCount);
if (totalFiles > _this.maxCount) {
this.onError(new Error("zip包含文件数量超过" + this.maxCount));
return;
}
// const results = [];
console.log(`开始处理 ZIP 文件,共发现 ${totalFiles} 个文件。`);
for (let i = 0; i < files.length; i++) {
console.log("files[i]", files[i]);
const fileName = files[i];
const fileEntry = _this.zipContent.files[fileName];
console.log("fileEntry", fileEntry);
// 获取文件扩展名并验证
const lastDotIndex = fileName.lastIndexOf(".");
let extension = "";
if (lastDotIndex > 0) {
extension = fileName.substring(lastDotIndex + 1).toLowerCase();
}
// 更新当前文件状态为 "checking"
let realName = _this.getFileNameFromPath(fileName);
let currentFile = {
num: i + 1,
realName: fileName,
name: realName,
type: extension,
status: "checking",
size: fileEntry._data.uncompressedSize
? Math.round(fileEntry._data.uncompressedSize / 1024)
: "",
uploadPercent: 0, // 上传百分比
uploadLoading: false, // 上传加载
uploadStatus: false, // 上传成功
file: fileEntry,
};
_this.results.push(currentFile);
const isValidType = _this.validateFileType(extension, fileName);
if (!isValidType) {
console.warn(`文件 ${fileName} 类型不符合要求,跳过处理。`);
currentFile.status = "文件类型错误";
_this.onStatusUpdate(fileName, "skipped_invalid_type");
// this.onProgress(i + 1, totalFiles);
} else {
// 符合类型条件的文档push正确的文件列表
currentFile.status = "待上传"; // check-finish
_this.fileList.push(currentFile);
}
// this.onProgress(i + 1, totalFiles);
}
_this.viewList = _this.viewUnabled ? _this.results : _this.fileList;
_this.onLoadComplete(_this.viewList);
};
} catch (error) {
this.onError(error);
}
}
}
export default ZipProcessor;