大文件上传是前端开发中常见的需求之一,特别是在需要处理较大的Excel表格数据、高清图片、视频或其他大型文件时。优化大文件上传不仅可以提升用户体验,还能有效减轻服务器负担。本文将深入探讨大文件上传的几种常见优化技术,包括 文件切片与并发上传、断点续传、云服务上传、后台处理优化、安全性考虑和用户体验优化。
一、前言
在现代Web应用中,用户上传大文件已成为常见需求。然而,直接上传大文件会面临诸多挑战,例如网络不稳定导致上传中断、长时间上传导致用户体验差、服务器压力大等。因此,优化大文件上传性能显得尤为重要。
二、优化方案
1. 文件切片与并发上传
1.1 文件切片原理
文件切片(Chunking)将大文件切割成多个小片段,然后分别上传。可以利用HTML5中的File API和Blob对象,通过FileReader读取文件内容(读取文件内容到JavaScript中,并支持多种格式的输出,如文本、DataURL),然后使用XMLHttpRequest或fetch API发送 post 每个小片段,并在服务器端将它们合并成完整的文件。
---将一个大文件切割成多个小文件,分别上传,然后在服务端组合(需要唯一的标识确定顺序)。这种方式可以提高上传速度和可靠性,但需要额外的前后端开发和维护工作。
这种方法可以提高上传效率和稳定性,并且支持断点续传。
1.2 实现步骤
- 前端切片:利用
Blob
对象的slice
方法将文件切片(计算每个分片的哈希值+序号)。 - 并发上传:使用
Promise.all
实现多个切片并发上传。 - 合并请求:切片上传完成后,通知服务器合并这些切片。
每个切片通常需要包含以下信息:
- 切片的序号:用于标识切片的顺序,如第1片、第2片等。
- 切片的大小:切片的字节大小。
- 总切片数:整个文件切割后的总切片数量。
- 文件唯一标识符:用于区分不同文件的标识,如文件名、文件MD5哈希等。
- 切片内容:切片的实际数据。
- 切片的起始和结束字节:可选信息,表示切片在原始文件中的位置。
谁来合并切片?何时合并切片?如何合并切片?
- 谁来合并切片:通常是服务器来处理切片的合并。当所有切片上传完成后,服务器会进行合并操作。
- 何时合并切片:合并可以在接收到最后一个切片之后,或者通过特定的API调用(如merge请求)来触发合并操作。
- 如何合并切片:服务器将所有切片按照顺序读取,逐个拼接成完整的文件,通常会将切片存储在一个临时位置,在合并后再移动到最终存储位置。
注意以下几点:
大文件切片上传时,切片数量取决于几个关键因素:文件总大小、每个切片的大小(即切片大小),以及任何特定于应用或服务的限制。计算切片数量的过程包括确定合理的切片大小,然后根据文件总大小来计算需要多少个这样大小的切片。
文件分片大小的选择:过小的分片会增加上传请求的数量,而过大的分片可能会导致上传过程中的内存和网络压力增加;一般分片不要超过10M
重试机制:对于上传失败的分片,可以设置重试次数,并在重试失败后提示用户 (根据业务情况而定)
上传进度的显示:可以使用XMLHttpRequest的upload事件或fetch API的ProgressEvent来获取上传进度,并将其显示给用户。
错误处理和恢复:如果上传过程中出现错误,需要及时捕获并给出错误提示,同时确保可以从错误处恢复并继续上传。
服务器端处理:在服务器端需要相应的接口来接收和处理分片上传的文件,并在上传完成后将其合并成完整的文件。同时,需要处理断点续传的逻辑,以保证上传进度的准确性。
1.3 计算文件哈希值
- 使用 Web Workers 来计算每个分片的哈希值,以避免阻塞主线程。(可以根据业务方向进行选择)
- 使用 spark-md5 库来计算 MD5 哈希值
适用于小文本的简单方法
function calculateHash(text) {
const hash = SparkMD5.hash(text);
console.log(hash); // 输出hash值
return hash;
}
// 示例使用
const text = "这是一段测试文本";
calculateHash(text);
适用于大文本的异步方法(使用Web Worker)
对于非常大的文本,直接在主线程上计算hash可能会导致UI冻结。这时,可以使用Web Worker来异步计算。
-
创建Web Worker脚本 (
worker.js
):self.onmessage = function(e) { const hash = SparkMD5.ArrayBuffer.hash(e.data, true); // 使用ArrayBuffer处理大文件数据流 postMessage(hash); // 将hash值发送回主线程 };
-
在主线程中调用Web Worker:
function calculateHashWithWorker(text) { const worker = new Worker('worker.js'); // 创建worker实例 worker.onmessage = function(e) { console.log(e.data); // 输出hash值 worker.terminate(); // 完成计算后终止worker }; worker.postMessage(text); // 发送文本数据到worker } // 示例使用大文本数据(例如,从文件读取) const largeText = "重复很多次的文本..."; // 这里可以是很大的字符串或者文件内容流 calculateHashWithWorker(largeText);
注意事项:
-
确保在处理大文件时使用
ArrayBuffer
,这样可以更有效地利用内存和减少阻塞。SparkMD5.ArrayBuffer.hash()
方法可以处理二进制数据流。 -
使用Web Worker时,确保处理好跨域资源共享(CORS)问题,尤其是在加载外部脚本时。
-
对于实际应用中的文件上传场景,通常还需要将文件分割成小块(chunk),分别计算每个块的hash,然后上传这些块的hash到服务器进行比对,以实现秒传功能。这通常涉及后端逻辑的支持。
2. 断点续传
断点续传(Resumable Uploads)将大文件分成多个小片段,每个小片段上传成功后记录其上传进度,若中断或失败(如网络故障、页面关闭等)后可从上次记录的进度继续上传,避免重新上传整个文件。可以使用XMLHttpRequest或fetch API发送每个小片段,同时在服务器端保留上传进度信息。这通常通过记录已上传的分片索引/hash来实现。
2.1 实现步骤
- 前端记录进度:使用
localStorage
记录已上传成功的切片信息。这种方式不依赖于服务端,实现起来也比较方便,缺点在于如果用户清除了本地文件,会导致上传记录丢失。 - 断点续传:上传时检查哪些切片未上传,继续上传未完成的部分。在上传前,向服务器查询已上传的分片,只上传未完成的分片
续传时,客户端如何知晓已上传过哪些切片?
- 状态查询:断点续传 & 秒传原理 客户端在开始上传之前,向服务器发送请求,询问该文件的上传状态(请求中包含文件的唯一标识符),服务端响应该文件已上传分片,客户端再将未上传分片上传即可;
如果没有需要上传的分片就是秒传;
如果有需要上传的分片就是断点续传; - 服务器响应:服务器会返回一个包含已上传切片编号或切片信息的列表,客户端据此判断哪些切片已经上传,从而跳过已上传的部分,只上传未完成的切片。
分段上传 与 断点续传 比较:
分段上传和断点续传都是为了优化大文件上传的过程,提高上传的效率和可靠性。它们的实现方式不同,适用情况也略有差异。下面是两者的区别:
分段上传:将一个大文件切割成多个小部分进行上传,每个小部分的大小可以根据具体需求来确定。这种方法可以减少单个请求的数据量,提高上传效率,并且可以在上传失败后只重新上传失败的部分,而不需要重新上传整个文件。常见的例子包括百度网盘、腾讯微云等。
断点续传:当文件上传过程中出现网络断开或其他异常情况时,可以通过记录已上传的部分,下次从上次上传的位置继续上传。这种方法可以保证上传的连续性,避免上传失败后需要重新上传整个文件,同时也可以提高上传效率。常见的例子包括云存储、视频网站等。
尽管它们的实现方式不同,但是它们有一些相似之处:
都需要使用特殊的上传方式或协议来支持分段上传或断点续传。例如,HTTP协议默认并不支持断点续传,需要在服务器端做特殊处理。
都需要在前端和后端做相应的处理来支持。在前端,需要将大文件切割成多个小部分并发送到后端;在后端,需要接收和保存上传的文件片段,并在最后将它们合并成一个完整的文件。
分段上传和断点续传都是为了解决大文件上传的问题,提高上传效率和可靠性。不同的场景和需求可能需要选择不同的方法来实现。
3. 秒传功能
-
通俗的说,你把要上传的东西上传,服务器会先做MD5校验,如果服务器上有一样的东西,它就直接给你个新地址,其实你下载的都是服务器上的同一个文件,想要不秒传,其实只要让MD5改变,就是对文件本身做一下修改(改名字不行),例如一个文本文件,你多加几个字,MD5就变了,就不会秒传了。
已经上传过的文件,并且在后端已经拼接完成,如果再次上传的话后端不做处理,直接返回拼接好的文件的信息即可,一般主要后端实现。
4. WebSocket 上传
使用WebSocket协议进行文件上传。WebSocket提供了全双工通信,可以实时传输数据,适合处理大文件上传。通过WebSocket,可以将文件拆分成多个分片,并逐个发送到服务器端。
通过WebSocket协议实现实时的双向数据传输,适用于大文件或大量数据的实时传输,但需要特殊的服务器支持。
5. 基于WebWorker的并行处理
- 使用WebWorker来并行计算文件的分片hash值,可以显著提高大文件处理的速度。
WebWorker 实际上是运行在浏览器后台的一个单独的线程,因此可以执行一些耗时的操作而不会阻塞主线程。WebWorker 通过与主线程之间传递消息实现通信,这种通信是双向的。WebWorker不能访问 DOM 不能获取dom对象,也不能使用像 window 对象这样的浏览器接口对象,但可以使用一些WebWorker 标准接口和 Navigator 对象的部分属性和方法。
1、主线程
主线程创建 worker 实例,向子线程通过 postMessage 发送消息,通过 onmessage 监听子线程返回的数据。
/*
在主线程中 通过 new Worker(线上地址服务器资源) 传入文件url来实现
返回 worker实例对象,该对象是主线程和其他线程的通讯桥梁
如果js文件是ES6 module的规范的话,那么new Worker的时候就需要说明类型
const worker = new Worker('http://localhost:5000/worker.js', {type:'module'})
*/
const worker = new Worker('http://localhost:5000/worker.js')
// 监听子线程返回的数据
worker.onmessage = function (e) {
console.log('Fibonacci result:', e.data)
doSomething();
}
function doSomething() {
// 执行任务
worker.postMessage('Work done!');
}
// 主线程通过 postMessage 向子线程发送消息
worker.postMessage('Hello World!')
// Worker 完成任务以后,主线程就可以把它关掉
worker.terminate();
Worker()构造函数的参数是一个脚本文件,该文件就是 Worker 线程所要执行的任务。由于 Worker 不能读取本地文件,所以这个脚本必须来自网络。如果下载没有成功(比如404错误),Worker 就会默默地失败
2、Worker 子线程 self
子线程 worker.js
// worker.js
self.onmessage = (e) => {
console.log('主线程发送过来的数据',e.data)
// 处理数据
self.postMessage(// 向主线程发送消息,主线程通过onmessage 接收)
}
//还可以通过监听 message 事件来接收消息
self.addEventListener('message', function(e) {
console.log('收到消息:' + e.data);
})
// self代表子线程自身,即子线程的全局对象。因此,等同于下面两种写法。
// 写法一
this.addEventListener('message', function (e) {
this.postMessage('You said: ' + e.data);
}, false);
// 写法二
addEventListener('message', function (e) {
postMessage('You said: ' + e.data);
}, false);
self.close() // 用于在 Worker 内部关闭自身。
除了使用self.addEventListener()指定监听函数,也可以使用self.onmessage指定。监听函数的参数是一个事件对象,它的data属性包含主线程发来的数据。self.postMessage()方法用来向主线程发送消息。
3、Worker 加载脚本
Worker 内部如果要加载其他脚本,有一个专门的方法importScripts()。
importScripts('script1.js');
// 该方法可以同时加载多个脚本
importScripts('script1.js', 'script2.js');
4、错误处理
主线程可以监听 Worker 是否发生错误。如果发生错误,Worker 会触发主线程的error事件。
worker.onerror(function (event) {
console.log([
'ERROR: Line ', e.lineno, ' in ', e.filename, ': ', e.message
].join(''));
});
// 或者
worker.addEventListener('error', function (event) {
// ...
});
5、关闭 Worker
使用完毕,为了节省系统资源,必须关闭 Worker。
// 主线程
worker.terminate();
// Worker 线程
self.close();
6、数据通信
主线程与 Worker 之间的通信内容,可以是文本,也可以是二进制数据。需要注意的是,这种通信是拷贝关系,即是传值而不是传址,Worker 对通信内容的修改,不会影响到主线程。事实上,浏览器内部的运行机制是,先将通信内容串行化,然后把串行化后的字符串发给 Worker,后者再将它还原。
主线程与 Worker 之间也可以交换二进制数据,比如 File、Blob、ArrayBuffer 等类型,也可以在线程之间发送。下面是一个例子。
// 主线程
var uInt8Array = new Uint8Array(new ArrayBuffer(10));
for (var i = 0; i <>uInt8Array.length; ++i) {
uInt8Array[i] = i * 2; // [0, 2, 4, 6, 8,...]
}
worker.postMessage(uInt8Array);
// Worker 线程
self.onmessage = function (e) {
var uInt8Array = e.data;
postMessage('Inside worker.js: uInt8Array.toString() = ' + uInt8Array.toString());
postMessage('Inside worker.js: uInt8Array.byteLength = ' + uInt8Array.byteLength);
};
7、限制
- 同源限制
分配给 Worker 线程的文件必须与主线程的文件同源 - DOM 限制
Worker 线程不能直接操作 DOM,但Worker 线程可以使用navigator对象和location对象部分属性和方法。 - 通信限制
不再同一个上下文,不能直接通信,须通过 postMessage 、 onmessage完成 - 条数限制
浏览器能创建webworker线程 基本上都在20条以内,每条线程大概5M左右 - 文件限制
线程不能打开本地文件,只能打开网络文件(服务器)在Web Workers中直接访问本地文件系统是不可能的,因为出于安全和隐私的考虑,浏览器不允许Web Workers访问本地文件系统。Web Workers主要用于执行后台任务,如数据处理、大量计算等,它们运行在浏览器的主线程之外,但不能直接访问DOM或本地文件系统。
然而,你可以通过以下几种方式来处理与本地文件相关的需求:
1. 使用 File API 和 Blob如果你的应用需要处理用户上传的文件,你可以使用 HTML 的
<input type="file">
元素让用户选择文件,然后通过 File API 来读取文件内容。例如:// 在主线程中 document.getElementById('fileInput').addEventListener('change', function(event) { const file = event.target.files[0]; const reader = new FileReader(); reader.onload = function(e) { const fileContent = e.target.result; postMessage(fileContent); // 发送到worker }; reader.readAsText(file); // 或者 readAsDataURL, readAsBinaryString 等,根据需要 }); // 在Worker中 self.onmessage = function(e) { const fileContent = e.data; // 处理文件内容 };
2. 使用 XMLHttpRequest 或 Fetch API 加载文件
如果你的应用需要加载服务器上的文件(而不是本地文件),你可以在主线程中使用
XMLHttpRequest
或fetch
API 加载文件,然后将数据发送到 Worker。例如:// 在主线程中 fetch('path/to/your/file').then(response => response.text()).then(data => { postMessage(data); // 发送到worker }); // 在Worker中 self.onmessage = function(e) { const fileContent = e.data; // 处理文件内容 };
- 脚本限制
Worker 线程不能执行alert()方法和confirm()方法,但可以使用 XMLHttpRequest 对象发出 AJAX 请求。 - 有些东西是无法通过主线程传给子线程的,如 方法、dom节点、一些对象里的特殊设置(freeze、getter、setter,所以vue的响应式对象是不能传递的)
- 模块的引入问题
如果js文件是ES6 module的规范的话,那么new Worker的时候就需要说明类型
const worker = new Worker('http://localhost:5000/worker.js', {type:'module'})
8、应用场景
- 大量的后台计算
- 一般不会用到 worker,因为浏览器主线程很少会出现很复杂从而导致阻塞的操作。如果真的遇到了,可以用 worker 来创建一个子线程进行处理,提高用户体验
- 随着webgl、canvas等能力的加入,web前端有越来越多的可视化操作。如 在线滤镜、在线绘图、web游戏等。这些东西是非常耗时计算的。
6. 通过第三方服务上传
比如使用云存储服务(如七牛、阿里云OSS等)进行文件上传,即使是大文件也可以快速上传,同时也可以享受到云存储服务的稳定性和安全性。
针对不同的场景和业务需求,可以选择适合自己的上传方案
7. 使用第三方库
有许多开源的第三方库可以简化大文件上传的过程,例如Plupload、FineUploader和Uppy等。这些库提供了丰富的API和功能,可以处理分片上传、断点续传、上传进度显示等复杂的操作。
8. 压缩传输数据
- 对传输的数据进行压缩处理,减少传输时间和带宽消耗
9. 分布式存储
- 使用分布式存储系统,提高文件存储和访问的性能和可扩展性,减轻单个服务器的负载压力。
10. 后台处理优化
- 分片接收与合并:服务器需要支持接收分片请求,并在所有分片上传完成后合并文件。可以利用中间件或服务端程序语言实现这一逻辑。
11. 安全性考虑
- 文件类型校验:在前端和后端都应对文件类型进行校验,确保上传的文件类型符合预期。
- 文件大小限制:限制单个文件和总上传文件的大小,防止恶意用户上传过大的文件造成服务器压力。
12. 用户体验优化
- 进度显示:通过显示上传进度条,让用户了解上传进度,提升用户体验。
- 网络波动处理:考虑到用户可能在网络不稳定的环境中上传文件,可以增加失败重试机制。
文件切片完整实例
后端代码(Node.js + Express)
安装依赖
npm init -y
npm install express multer fs
创建服务器文件(server.js)
const express = require('express');
const multer = require('multer');
const fs = require('fs');
const path = require('path');
const bodyParser = require('body-parser');
const app = express();
const upload = multer({ dest: 'uploads/' });
app.use(bodyParser.json());
// 路由:处理文件切片上传
app.post('/upload', upload.single('chunk'), (req, res) => {
const { index, fileName } = req.body;
const chunkPath = path.join(__dirname, 'uploads', `${fileName}-${index}`);
fs.renameSync(req.file.path, chunkPath);
res.status(200).send('Chunk uploaded');
});
// 路由:合并切片
app.post('/merge', (req, res) => {
const { totalChunks, fileName } = req.body;
const filePath = path.join(__dirname, 'uploads', fileName);
const writeStream = fs.createWriteStream(filePath);
for (let i = 0; i < totalChunks; i++) {
const chunkPath = path.join(__dirname, 'uploads', `${fileName}-${i}`);
const data = fs.readFileSync(chunkPath);
writeStream.write(data);
fs.unlinkSync(chunkPath);
}
writeStream.end();
res.status(200).send('File merged');
});
app.listen(3000, () => {
console.log('Server started on http://localhost:3000');
});
前端代码(index.html + script.js)
1.创建HTML文件(index.html)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>大文件上传</title>
</head>
<body>
<input type="file" id="fileInput">
<progress id="progressBar" value="0" max="100"></progress>
<button onclick="uploadFile()">上传文件</button>
<script src="script.js"></script>
</body>
</html>
2.创建JavaScript文件(script.js)
const fileInput = document.getElementById('fileInput');
const progressBar = document.getElementById('progressBar'); // 进度条
const chunkSize = 5 * 1024 * 1024; // 5MB 每次最大切片的长度
// 接口1:每个切片要发送的ajax
const uploadChunk = async (chunk, index, fileName) => {
const formData = new FormData();
formData.append('chunk', chunk);
formData.append('index', index);
formData.append('fileName', fileName);
// 发送切片上传请求
await fetch('/upload', {
method: 'POST',
body: formData
});
updateProgressBar(index);
};
const updateProgressBar = (index) => {
const uploadedChunks = JSON.parse(localStorage.getItem('uploadedChunks')) || [];
if (!uploadedChunks.includes(index)) {
uploadedChunks.push(index);
progressBar.value = (uploadedChunks.length / totalChunks) * 100;
localStorage.setItem('uploadedChunks', JSON.stringify(uploadedChunks));
}
};
// 上传文件按钮事件
// 接口2:将文件分片成promise请求数组
const uploadFile = async () => {
const file = fileInput.files[0];
const totalChunks = Math.ceil(file.size / chunkSize);
const uploadedChunks = JSON.parse(localStorage.getItem('uploadedChunks')) || [];
const promises = [];
for (let i = 0; i < totalChunks; i++) {
if (!uploadedChunks.includes(i)) {
const chunk = file.slice(i * chunkSize, (i + 1) * chunkSize);
promises.push(uploadChunk(chunk, i, file.name));
}
}
// 多个切片并发上传
await Promise.all(promises);
// Promise.allSettled()当你需要所有Promise都结束(不管成功还是失败)时才继续执行后续操作。
// 接口3:promise.all执行结束后,请求一个是否合并成功的接口
await fetch('/merge', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ totalChunks, fileName: file.name })
});
localStorage.removeItem('uploadedChunks');
alert('文件上传成功');
};
Promise.all()
当你需要所有Promise都成功时才继续。Promise.allSettled()当你需要所有Promise都结束(不管成功还是失败)时才继续执行后续操作。
思路:我们首先获取文件对象,并设置每个分片的大小(这里可以设置为1MB-5MB)。然后使用slice方法将文件切割成多个分片,并通过FormData对象将分片数据和其他参数(如当前分片索引、总分片数)一同发送到服务器端。
启动后端服务器
3.在浏览器中打开前端页面
将index.html
文件在浏览器中打开,选择文件并点击“上传文件”按钮即可看到文件上传进度。
node server.js
三、插件
目前成熟的大文件上传方案 目前社区已经存在一些成熟的大文件上传解决方案,也许并不需要我们手动去实现一个简陋的大文件上传库,但是了解其原理还是十分有必要的。
推荐的前端vue组件:vue-simple-uploader,支持vue2,vue3
vue-simple-uploader是基于simple-Uploader.js封装的大文件上传组件,具有以下优点:
- 支持单文件、多文件、文件夹上传;支持拖拽文件、文件夹上传
- 可暂停、继续上传
- 错误处理
- 支持“秒传”,通过文件判断服务端是否已存在从而实现“秒传”
- 分块上传
- 支持进度、预估剩余时间、出错自动重试、重传等操作
安装与配置
npm install vue-simple-uploader --save
然后在你的main.js
中引入并使用它:
import Vue from 'vue';
import uploader from 'vue-simple-uploader';
Vue.use(uploader);
接下来,配置上传选项,这些选项可以根据你的后端接口和业务需求进行调整:
options: {
target: ' http://localhost:8080', // SpringBoot后台接收文件夹数据的接口
simultaneousUploads: 10, // 支持同时上传数量
autoStart: false, // 自动上传
panelShow: false,
allowDuplicateUploads: false, // 上传过得文件不可以再上传
testChunks: false, // 是否分片-不分片
chunkSize: '102400000000', // 块大小
// query参数是带有数据的post的额外参数,policy、OSSAccessKeyId和signature是获取到的后端签名返回,
query: (file) => {
return {
name: file.name,
key: file.key,
policy,
OSSAccessKeyId: accessId,
signature,
success_action_status: 200, // success_action_status需设置为 200
};
},
}
常用方法与事件
vue-simple-uploader提供了多种方法和事件,以便于开发者根据需要进行自定义处理:
- assignBrowse:将非组件按钮绑定为上传按钮。
- getSize:获取上传文件的总大小。
- progress:获取上传进度。
- addFile:手动添加文件到上传队列。
事件处理包括但不限于:
- fileAdded:文件添加到上传队列时触发。
- filesAdded:多文件添加时触发。
- fileSuccess:文件上传成功时触发。
- complete:所有文件上传完成时触发。
- fileError:文件上传失败时触发。
代码实现
以下是vue-simple-uploader
组件的一个基本使用示例,包括组件声明、事件绑定和样式配置:
<template>
<!-- 定义Uploader组件 -->
<uploader
:key="uploader_key" <!-- 使用key确保组件在数据更新时重新渲染 -->
:options="options" <!-- 绑定配置项 -->
class="uploader-example" <!-- 添加自定义类名 -->
@file-added="onFileAdded" <!-- 文件添加时触发的事件 -->
@file-success="onFileSuccess" <!-- 文件上传成功时触发的事件 -->
@upload-start="uploadStr" <!-- 开始上传时触发的事件 -->
@complete="uploadEnd" <!-- 所有文件上传完成时触发的事件 -->
@file-error="fileError" <!-- 文件上传失败时触发的事件 -->
>
<!-- 定义不支持上传的提示 -->
<uploader-unsupport></uploader-unsupport>
<!-- 定义拖拽区域 -->
<uploader-drop>
<!-- 定义上传按钮,使用Element UI的按钮组件 -->
<el-button class="uploaders-btn">
<uploader-btn class="btn" :directory="true"> <!-- 设置为目录上传 -->
<el-icon><Notification /></el-icon> <!-- 使用Element UI的图标组件 -->
<span>上传文件夹</span> <!-- 按钮文本 -->
</uploader-btn>
</el-button>
</uploader-drop>
</uploader>
</template>
<script>
import md5 from "js-md5";
export default {
data() {
return {
// 用于刷新组件的key,每次上传时更改其值以刷新组件状态
uploader_key: new Date().getTime(),
// 配置项,根据后端接口和业务需求进行配置
options: {
//目标上传 URL,默认POST
target: "/api/file/uploadFile", // 后端接收数据的接口
//上传文件时文件内容的参数名,对应chunk里的Multipart对象名,默认对象名为file
ileParameterName: 'upfile',
//失败后最多自动重试上传次数
maxChunkRetries: 3,
query: (file, res, status) => {
// 返回上传所需的额外参数
return {
filePath: "",
identifier: md5(file.uniqueIdentifier),
parentUserFileId: this.firstId,
sourceMenuId: this.findId,
uuid: this.uuid,
};
},
headers: {
"Blade-Auth": "bearer " + getToken(), // 认证信息
},
testChunks: true, // 不分片上传
//分块大小(单位:字节)
chunkSize: '2048000',
},
fileStatusText: {
success: '上传成功',
error: '上传失败',
uploading: '上传中',
paused: '暂停',
waiting: '等待上传'
}
};
},
created() {
// 组件创建时初始化options
this.options = {
// ... 具体配置
};
},
methods: {
// 文件添加到上传队列时的处理函数
onFileAdded(file) {
console.log("文件添加到队列:", file);
// 每次添加文件时生成新的uuid
this.uuid = new Date().getTime();
},
// 文件上传成功时的处理函数
onFileSuccess(rootFile, file, response, chunk) {
console.log("文件上传成功:", file, response);
// 根据服务器返回的response处理业务逻辑
},
// 文件上传失败时的处理函数
fileError(rootFile, file, response, chunk) {
console.error("文件上传失败:", file, response);
// 显示错误信息
this.$message.error("文件夹上传失败");
},
// 开始上传时的处理函数
uploadStr() {
this.loadingFile = true; // 设置加载状态
},
// 所有文件上传完成时的处理函数
uploadEnd() {
this.loadingFile = false; // 重置加载状态
},
},
};
</script>
<style lang="scss" scoped>
/* 自定义样式 */
.uploader-example {
.uploaders-btn {
/* 按钮样式 */
}
.btn {
/* 上传按钮内的图标和文本样式 */
}
}
</style>