Vue + Element Plus 实战:大文件切片上传 + 断点续传

好的!下面是一篇结合 Element Plus 的 Vue 3 大文件切片上传 + 断点续传完整实战教程,UI 用的是 Element Plus,结构清晰,语气亲和,适合你直接复制使用。


整理不易,如果本文对你有帮助,欢迎点个【赞 👍】+【收藏 ⭐】+【关注 🧡】

📦 Vue + Element Plus 实战:大文件切片上传 + 断点续传

今天我们来用 Vue 3 + Element Plus,实战开发一个“支持大文件切片上传、断点续传”的文件上传组件,适合上传视频、压缩包、设计图等大文件场景,带完整前端逻辑和 UI 演示👇


🧩一、功能亮点

✅ 文件切片(Blob 分片)
✅ 并发上传,支持进度条
✅ 秒传校验(文件 hash)
✅ 断点续传(跳过已上传)
✅ 合并通知(服务端拼接)


✨二、最终效果图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
(上传进度 + 支持断点续传)


🛠️三、完整前端实现(Element Plus 版本)

1️⃣ 安装依赖

npm install element-plus spark-md5

2️⃣ 组件模板(UploadChunk.vue)

<template>
  <el-card>
    <el-upload
      :show-file-list="false"
      :before-upload="handleBeforeUpload"
      :http-request="handleUpload"
      drag
      accept="*"
    >
      <i class="el-icon-upload" />
      <div class="el-upload__text">将文件拖到此处,或 <em>点击上传</em></div>
    </el-upload>

    <el-progress
      v-if="uploadProgress > 0"
      :percentage="uploadProgress"
      status="success"
      style="margin-top: 20px"
    />
  </el-card>
</template>

3️⃣ 脚本逻辑(切片、上传、断点续传)

<script setup>
import { ref } from 'vue'
import SparkMD5 from 'spark-md5'
import { ElMessage } from 'element-plus'

const uploadProgress = ref(0)
const CHUNK_SIZE = 2 * 1024 * 1024 // 2MB

let file = null
let fileHash = ''
let fileChunks = []

// 1. 预处理文件
const handleBeforeUpload = async (rawFile) => {
  file = rawFile
  fileChunks = createChunks(file)
  fileHash = await calculateHash(fileChunks)
  return false // 阻止默认上传
}

// 2. 分片
const createChunks = (file) => {
  const chunks = []
  let cur = 0
  while (cur < file.size) {
    chunks.push(file.slice(cur, cur + CHUNK_SIZE))
    cur += CHUNK_SIZE
  }
  return chunks
}

// 3. 计算 hash
const calculateHash = (chunks) => {
  return new Promise((resolve) => {
    const spark = new SparkMD5.ArrayBuffer()
    let count = 0
    const reader = new FileReader()

    const loadNext = () => {
      if (count >= chunks.length) {
        resolve(spark.end())
        return
      }
      reader.readAsArrayBuffer(chunks[count])
    }

    reader.onload = (e) => {
      spark.append(e.target.result)
      count++
      loadNext()
    }

    loadNext()
  })
}

// 4. 获取已上传切片(断点续传)
const getUploadedList = async (hash) => {
  const res = await fetch(`/uploaded-list?hash=${hash}`)
  return res.ok ? await res.json() : []
}

// 5. 上传逻辑
const handleUpload = async () => {
  const uploaded = await getUploadedList(fileHash)

  const uploadChunk = (chunk, index) => {
    const form = new FormData()
    form.append('file', chunk)
    form.append('filename', file.name)
    form.append('chunkIndex', index)
    form.append('hash', fileHash)

    return fetch('/upload-chunk', {
      method: 'POST',
      body: form,
    })
  }

  const requests = fileChunks.map((chunk, index) => {
    if (uploaded.includes(index)) return null
    return uploadChunk(chunk, index)
  }).filter(Boolean)

  let completed = uploaded.length
  const total = fileChunks.length

  for (const req of requests) {
    await req
    completed++
    uploadProgress.value = Math.floor((completed / total) * 100)
  }

  // 通知合并
  await fetch('/merge-chunks', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ filename: file.name, hash: fileHash }),
  })

  ElMessage.success('上传成功 🎉')
  uploadProgress.value = 0
}
</script>

📡四、服务端接口规范(参考 Node)

接口说明:

接口描述
GET /uploaded-list?hash=xxx返回已上传切片 index 列表
POST /upload-chunk接收切片文件,保存到 /tmp/hash/index
POST /merge-chunks合并切片为完整文件,保存在 /uploads/

服务端可用 fs-extramulter 等处理上传,你需要支持以下流程:

  • 创建临时目录保存切片 /tmp/<hash>/index
  • 按顺序读取切片合并 fs.appendFileSync
  • 合并完成后清理切片

🧠五、总结回顾

功能实现点
切片上传Blob.slice() 分片,前端循环上传
秒传前端计算 hash,服务端校验是否上传过
断点续传获取已上传列表,跳过上传
合并通知上传完毕后请求服务端合并接口
UI 展示Element Plus 上传组件 + 进度条反馈
<think>我们正在使用Vue3和ElementPlus来实现大文件分片上传功能。根据引用内容,我们需要处理以下几个关键点:1.使用ElementPlus的`<el-upload>`组件作为基础的上传组件,但需要自定义上传行为(因为默认不支持分片)。2.大文件切割成多个小块(分片)。3.逐个或并发上传这些分片到服务器。4.服务器需要支持合并这些分片。此外,引用中还提到了断点续传,但用户当前的问题是实现分片上传,因此我们主要关注分片上传的实现。步骤:1.创建文件上传组件(使用`<el-upload>`),并设置`auto-upload`为false,以便手动控制上传过程。2.当用户选择文件后,我们获取文件对象,并开始分片处理。3.将文件切成固定大小的分片(例如每片5MB)。4.为每个分片生成一个唯一的标识(可以使用文件MD5+分片索引,但为了简化可以先使用文件名+分片索引)。5.逐个上传分片到服务器(可以使用并发控制,避免同时上传过多分片)。6.所有分片上传完成后,通知服务器合并文件。注意:服务器端需要提供两个接口:-上传分片的接口(接收分片数据)-合并分片的接口(当所有分片上传完成后,请求合并)由于用户问题只关注前端实现,因此我们主要描述前端代码。代码结构示例:模板部分:```html<template><el-uploadaction="":auto-upload="false":on-change="handleFileChange":show-file-list="false"><el-buttontype="primary">选择文件</el-button></el-upload><el-button@click="startUpload">开始上传</el-button></template>```脚本部分:```javascriptimport{ref}from'vue';importaxiosfrom'axios';//假设使用axiosexportdefault{setup(){constselectedFile=ref(null);//处理文件选择consthandleFileChange=(file)=>{selectedFile.value=file.raw;};//开始上传conststartUpload=async()=>{if(!selectedFile.value){return;}//分片大小,例如5MBconstchunkSize=5*1024*1024;constfile=selectedFile.value;consttotalChunks=Math.ceil(file.size/chunkSize);//生成一个唯一标识,这里简单使用文件名+时间戳,实际中可以用更复杂的方法(如MD5)constfileIdentifier=file.name+'_'+Date.now();//循环处理每个分片for(letindex=0;index<totalChunks;index++){conststart=index*chunkSize;constend=Math.min(start+chunkSize,file.size);constchunk=file.slice(start,end);//创建FormData对象,用于上传分片constformData=newFormData();formData.append('file',chunk);formData.append('chunkIndex',index);formData.append('totalChunks',totalChunks);formData.append('identifier',fileIdentifier);formData.append('filename',file.name);try{//上传分片awaitaxios.post('/api/upload/chunk',formData,{headers:{'Content-Type':'multipart/form-data'}});console.log(`分片${index}上传成功`);}catch(error){console.error(`分片${index}上传失败`,error);//错误处理,比如重试或中断break;}}//所有分片上传完成,请求合并try{awaitaxios.post('/api/upload/merge',{identifier:fileIdentifier,filename:file.name,totalChunks:totalChunks});console.log('文件合并成功');}catch(error){console.error('文件合并失败',error);}};return{handleFileChange,startUpload};}};```注意:1.上述代码是基本示例,实际中可能需要处理并发控制(比如同时最多上传5个分片)、断点续传(记录已上传分片)、错误重试等。2.文件唯一标识(identifier)的生成,理想情况下应该使用文件内容的hash(如MD5),这样可以避免相同文件重复上传,并且有利于断点续传。但计算文件hash可能会比较耗时(尤其大文件),所以需要权衡。关于断点续传的扩展:-可以在开始上传前,先向服务器请求已上传分片列表(根据identifier),然后只上传上传分片。-合并时,服务器检查是否所有分片上传完成。根据引用[2]和[3],我们可以进一步完善代码,例如添加进度条、暂停功能等,但用户当前问题只要求实现分片上传,因此以上代码已满足基本要求。参考文献:[^1]:vue+elementPlus分片上传大视频文件[^2]:Vue3+ElementPlus前端实现分片上传[^3]:Vue3如何实现大文件分片上传断点续传?[^4]:VUE-大文件分片及断点上传相关问题:1.如何优化大文件分片上传的性能?2.如何实现断点续传功能?3.如何计算文件的MD5,并用于分片上传?4.分片上传时如何实现并发控制?5.如何显示上传进度?</think>### Vue + Element Plus 大文件分片上传实现方法 以下是实现大文件分片上传的核心步骤和代码示例: #### 1. 核心实现步骤 1. **文件切片处理**:将大文件分割成固定大小的小块 2. **分片上传**:逐个上传切片到服务器 3. **合并请求**:所有切片上传完成后通知服务器合并 4. **进度跟踪**:实时显示上传进度 5. **断点续传**:记录已上传切片,实现续传能力 #### 2. 前端实现代码(Vue3 + Element Plus) ```html <template> <el-upload action="" :auto-upload="false" :on-change="handleFileChange" :show-file-list="false" > <el-button type="primary">选择大文件</el-button> </el-upload> <el-button v-if="currentFile" type="success" @click="startUpload" :disabled="isUploading" > 开始上传 </el-button> <el-progress v-if="isUploading" :percentage="uploadProgress" status="success" class="mt-4" /> </template> <script setup> import { ref } from 'vue'; import axios from 'axios'; import { ElMessage } from 'element-plus'; // 文件相关状态 const currentFile = ref(null); const chunkSize = ref(5 * 1024 * 1024); // 5MB 每片 const totalChunks = ref(0); const uploadedChunks = ref(0); const uploadProgress = ref(0); const isUploading = ref(false); // 文件选择处理 const handleFileChange = (file) => { currentFile.value = file.raw; }; // 开始上传 const startUpload = async () => { if (!currentFile.value) return; isUploading.value = true; uploadProgress.value = 0; try { // 1. 初始化上传(获取文件唯一标识) const { fileId } = await initUpload(currentFile.value); // 2. 文件切片 const chunks = createFileChunks(currentFile.value); totalChunks.value = chunks.length; // 3. 分片上传 for (let i = 0; i < chunks.length; i++) { await uploadChunk(fileId, chunks[i], i, chunks.length); uploadedChunks.value++; uploadProgress.value = Math.round((uploadedChunks.value / chunks.length) * 100); } // 4. 合并请求 await mergeChunks(fileId, currentFile.value.name); ElMessage.success('文件上传成功!'); } catch (error) { ElMessage.error(`上传失败: ${error.message}`); } finally { isUploading.value = false; } }; // 创建文件切片 const createFileChunks = (file) => { const chunks = []; let start = 0; while (start < file.size) { const end = Math.min(start + chunkSize.value, file.size); chunks.push(file.slice(start, end)); start = end; } return chunks; }; // 初始化上传(获取文件唯一标识) const initUpload = async (file) => { const response = await axios.post('/api/upload/init', { name: file.name, size: file.size, type: file.type }); return response.data; // 返回 { fileId: '唯一标识' } }; // 上传单个分片 const uploadChunk = async (fileId, chunk, index, total) => { const formData = new FormData(); formData.append('fileId', fileId); formData.append('chunk', chunk); formData.append('index', index); formData.append('total', total); await axios.post('/api/upload/chunk', formData, { headers: { 'Content-Type': 'multipart/form-data' } }); }; // 合并所有分片 const mergeChunks = async (fileId, fileName) => { await axios.post('/api/upload/merge', { fileId, fileName }); }; </script> ``` #### 3. 服务器端关键接口 1. **初始化接口** (`POST /api/upload/init`) - 接收:文件名、大小、类型 - 返回:文件唯一标识 `fileId` 2. **分片上传接口** (`POST /api/upload/chunk`) - 接收:文件标识、分片数据、分片索引、总分片数 - 存储:临时保存分片文件 3. **合并接口** (`POST /api/upload/merge`) - 接收:文件标识、原始文件名 - 处理:将所有分片按顺序合并为完整文件 #### 4. 优化建议 1. **断点续传**: ```javascript // 初始化时获取已上传分片 const { uploadedIndexes } = await initUpload(file); // 上传时跳过已上传分片 if (!uploadedIndexes.includes(i)) { await uploadChunk(fileId, chunks[i], i, chunks.length); } ``` 2. **并发控制**: ```javascript // 使用Promise.all控制并发数 const concurrentLimit = 3; const uploadQueue = []; for (let i = 0; i < chunks.length; i++) { if (uploadQueue.length >= concurrentLimit) { await Promise.race(uploadQueue); } const task = uploadChunk(fileId, chunks[i], i, chunks.length) .then(() => { uploadQueue.splice(uploadQueue.indexOf(task), 1); }); uploadQueue.push(task); } ``` 3. **文件校验**: ```javascript // 上传前计算文件hash const calculateHash = (file) => { return new Promise((resolve) => { const reader = new FileReader(); reader.readAsArrayBuffer(file); reader.onload = ({ target }) => { crypto.subtle.digest('SHA-256', target.result).then(hash => { resolve(Array.from(new Uint8Array(hash)).map(b => b.toString(16).padStart(2, '0')).join('')); }); }; }); }; ``` #### 5. 注意事项 1. 服务器需要配置足够大的临时存储空间 2. 设置合理的分片大小(通常 2-10MB) 3. 添加文件类型和大小验证 4. 定期清理未完成的临时分片 5. 使用 Web Worker 计算文件 hash 避免界面卡顿 > **实现参考**:Element Plus 的 `<el-upload>` 组件提供了基础上传能力,但分片上传需要自定义实现。核心逻辑是文件切片处理和分片并发控制[^1][^2]。服务器端需要实现分片接收和合并逻辑,可参考分片上传协议设计[^3][^4]。 --- ### 相关问题 1. **如何实现断点续传功能?** - 记录已上传分片索引 - 上传前查询服务器已接收的分片 - 跳过已上传部分继续传输 2. **分片上传如何保证文件完整性?** - 前端计算文件整体 hash - 服务器合并后校验 hash - 使用分片校验码(如 CRC32) 3. **大文件上传如何优化用户体验?** - 添加进度条和预估时间 - 支持暂停/继续操作 - 后台静默上传 - 上传速度限制控制 4. **服务器端如何设计分片合并逻辑?** - 按索引顺序合并二进制数据 - 使用文件流提高内存效率 - 设置合并超时和重试机制 - 分布式存储系统的分片处理 5. **如何解决大文件上传的内存问题?** - 使用流式处理替代完整加载 - 前端使用 Blob.slice() 切割文件 - 服务器使用流式 API 接收数据 - 分片清理机制释放内存
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值