使用Vue3+ElementPlus前端实现分片上传的全过程

 更新时间:2022年11月9日 17:46  点击:926 作者:Lyrelion

1. 什么是分片上传

将 一个文件 切割为 一系列特定大小的 数据片段,将这些 数据片段 分别上传到服务端;

全部上传完成后,再由服务端将这些 数据片段 合并成为一个完整的资源;

上传过程中,由于外部因素(比如网络波动)导致上传中断,下次上传时会保留该文件的上传进度(断点续传);

2. 上传组件模板

包含三部分:

  • 上传组件,使用 el-upload
  • 进度条组件,使用 el-progress
  • 上传完成状态组件,使用 el-input 自定义

 <el-form-item label="上传附件" prop="uploadFile">
   <el-upload
       v-if="!editForm.inlineAppVersionModel.fileName"
       class="upload-demo"
       drag
       :show-file-list="false"
       :action="APP_MANAGEMENT.uploadFile"
        // 根据项目的接口传递参数
       :data="{
         applicationId: applicationId,
         applicationVersion: applicationVersion,
         bucketName: 'app'
       }"
       // 覆盖默认的http请求
       :http-request="handleFileUpload"
   >
     <el-icon class="el-icon--upload">
       <upload-filled />
     </el-icon>
 
     <div v-if="!progress" class="el-upload__text">
       Drop file here or <em>click to upload</em>
     </div>
 
     // 进度条
     <el-progress
        v-else
        :text-inside="true"
        :stroke-width="24"
        :percentage="progress"
        status="success"
       />
   </el-upload>
 
   // 上传成功之后隐藏上传文件组件
   <div v-else style="display: flex;">
     <el-input v-model="editForm.inlineAppVersionModel.fileName" readonly>
     </el-input>
     <div style="display: flex;">
       <el-button 
           type="primary" 
           :icon="Download" 
           size="small" 
           @click="handleFileDownload" 
          />
       <el-button type="primary" :icon="Delete" size="small" @click="handleFileDel" />
     </div>
   </div>
 </el-form-item>

3. 上传组件逻辑

3.1 基本思路

使用 el-upload 选择文件

选择成功的 回调函数 可以读取文件信息,用于前端校验文件的合法性

前端校验文件合法后,将文件进行切片

通过 请求轮询 把切片传递给后端

3.2 选择上传文件

在这一步,可以获得文件信息

根据文件信息,对文件进行合法性校验

校验成功后,调用文件切片方法

/**
 * @description: 选择上传文件
 * @param file el-upload 返回的参数
 */
const handleFileUpload = async (file: any) => {
  console.log('el-upload 返回的参数 === ', file.file);
 
  // 如果文件合法,则进行分片上传
  if (await checkMirrorFile(file)) {
    // 文件信息
    const files = file.file;
    // 从 0 开始的切片
    const shardIndex = 0;
    // 调用 文件切片 方法
    uploadFileSilce(files, shardIndex);
  // 文件非法,则进行提示
  } else {
    ElMessage.error('请检查文件是否合法!');
  }
};

3.3 校验文件是否合法

校验文件格式

校验文件大小

调用接口,校验磁盘剩余空间大小

/**
 * @description: 校验文件合法性
 */
const checkMirrorFile = async (file) => {
    // 校验文件格式,支持.zip/.tar
    const fileType = file.file.name.split('.')
    if (fileType[fileType.length - 1] !== 'zip' && fileType[fileType.length - 1] !== 'tar') {
        ElMessage.warning('文件格式错误,仅支持 .zip/.tar')
        return false
    }
 
    // 校验文件大小
    const fileSize = file.file.size;
    // 文件大小是否超出 2G
    if (fileSize > 2 * 1024 * 1024 * 1024) {
        ElMessage.warning('上传文件大小不能超过 2G')
        return false
    }
 
    // 调用接口校验文件合法性,比如判断磁盘空间大小是否足够
    const res = await checkMirrorFileApi()
    if (res.code !== 200) {
        ElMessage.warning('暂时无法查看磁盘可用空间,请重试')
        return false
    }
    // 查看磁盘容量大小
    if (res.data.diskDevInfos && res.data.diskDevInfos.length > 0) {
        let saveSize = 0
        res.data.diskDevInfos.forEach(i => {
            // 磁盘空间赋值
            if (i.devName === '/dev/mapper/centos-root') {
                // 返回值为GB,转为字节B
                saveSize = i.free * 1024 * 1024 * 1024
            }
        })
        // 上传的文件大小没有超出磁盘可用空间
        if (fileSize < saveSize) {
            return true
        } else {
            ElMessage.warning('文件大小超出磁盘可用空间容量')
            return false
        }
    } else {
        ElMessage.warning('文件大小超出磁盘可用空间容量')
        return false
    }
}

3.4 文件加密

此处文件上传用 MD5 进行加密,需要安装依赖 spark-md5

npm i spark-md5

/**
 * @description: 文件加密处理
 */
const getMD5 = (file: any): Promise<string> => new Promise((resolve, reject) => {
  const spark = new SparkMD5.ArrayBuffer();
  // 获取文件二进制数据
  const fileReader = new FileReader();
  fileReader.readAsArrayBuffer(file); // file 就是获取到的文件
  // 异步执行函数
  fileReader.addEventListener('load', (e: any) => {
    spark.append(e.target.result);
    const md5: string = spark.end();
    resolve(md5);
  });
  fileReader.addEventListener('error', (e) => {
    reject(e);
  });
});

3.5 合并文件

通过接口合并上传文件,接口需要的参数:

  • 文件名
  • 文件唯一 hash 值

接口合并完成后,前端展示已上传的文件名称

/**
 * @description: 合并文件
 * @param name 文件名
 * @param hash 文件唯一 hash 值
 * @return 命名名称
 */
const composeFile = async (name: string, hash: string) => {
  console.log('开始文件合并');
  const res = await uploadFileMerge({
    applicationId: props.applicationId,
    applicationVersion: props.applicationVersion,
    bucketName: 'app',
    fileName: name,
    hash,
  });
  console.log('后端接口合并文件 ===', res);
  if (res.status === 200 && res.data.code) {
    // 合并成功后,调整已上传的文件名称
    state.editForm.inlineAppVersionModel.fileName = name;
  }
};

3.6 文件切片上传

接口轮询 —— 每次携带一个文件切片给后端;后端接受到切片 并 返回成功状态码后,再进行下一次切片上传

/**
 * @description: 分片函数
 * @param file 文件
 * @param shardIndex 分片数量
 */
const uploadFileSilce = async (file: File, shardIndex: number) => {
      // 文件名
      const { name } = file;
      // 文件大小
      const { size } = file;
      // 分片大小
      const shardSize = 1024 * 1024 * 5;
      // 文件加密
      const hash: string = await getMD5(file);
      // 分片总数
      const shardTotal = Math.ceil(size / shardSize);
 
      // 如果 当前分片索引 大于 总分片数
      if (shardIndex >= shardTotal) {
        isAlive.value = false;
        progress.value = 100;
        // 合并文件
        composeFile(name, hash);
        return;
      }
 
      // 文件开始结束的位置
      const start = shardIndex * shardSize;
      const end = Math.min(start + shardSize, size);
      // 开始切割
      const packet = file.slice(start, end);
      
      // 拼接请求参数
      const formData = new FormData();
      formData.append('file', packet);
      formData.append('applicationId', props.applicationId);
      formData.append('applicationVersion', props.applicationVersion);
      formData.append('bucketName', 'app');
      formData.append('hash', hash);
      formData.append('shardSize', shardSize as unknown as string);
      formData.append('seq', shardIndex as unknown as string);
 
      // 如果 当前分片索引 小于 总分片数
      if (shardIndex < shardTotal) {
        // 进度条保留两位小数展示
        progress.value = Number(((shardIndex / shardTotal) * 100).toFixed(2)) * 1;
        // 调用文件上传接口
        const res = await uploadFile(formData);
        if (res.status !== 200) {
          ElMessage.error('上传失败');
          progress.value = 0;
          return;
        }
        if (res.status === 200 && res.data.code === 200) {
          // 这里为所有切片上传成功后进行的操作
          console.log('上传成功');
        }
        // eslint-disable-next-line no-param-reassign
        shardIndex++;
        // 递归调用 分片函数
        uploadFileSilce(file, shardIndex);
      }
    };

4. 参考文章

4.1 文章链接

前端大文件上传和下载(分片上传)

4.2 参考文章提到的注意事项

4.2.1 nginx 上传大小限制

nginx 默认上传大小为 1MB,若超过 1MB,则需要修改 nginx 配置 解除上传限制

4.2.2 大文件下载

/**
 * @description: 动态创建 a 标签,实现大文件下载
 */
const downloadMirror = async (item) => {
  let t = {
    id: item.id,
  }
  const res = await downloadMirrorApi(t)
  if (res.headers["content-disposition"]) {
    let temp = res.headers["content-disposition"].split(";")[1].split("filename=")[1]
    let fileName = decodeURIComponent(temp)
    // 通过创建a标签实现文件下载
    let link = document.createElement('a')
    link.download = fileName
    link.style.display = 'none'
    link.href = res.data.msg
    document.body.appendChild(link)
    link.click()
    document.body.removeChild(link)
  } else {
    ElMessage({
      message: '该文件不存在',
      type: 'warning',
    })
  }
}

总结

到此这篇关于使用Vue3+ElementPlus前端实现分片上传的文章就介绍到这了,更多相关Vue3+ElementPlus前端分片上传内容请搜索猪先飞以前的文章或继续浏览下面的相关文章希望大家以后多多支持猪先飞!

原文出处:https://blog.csdn.net/Lyrelion/article/details/127404500

[!--infotagslink--]

相关文章

  • 浅谈vue2的$refs在vue3组合式API中的替代方法

    这篇文章主要介绍了浅谈vue2的$refs在vue3组合式API中的替代方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-04-18
  • Vue3 编译流程-源码解析

    今天将从 Vue 的入口文件开始,看看声明了一个 Vue 的单文件之后是如何被 compile-core 编译核心模块编译成渲染函数的。下面小编讲解并附上代码分析展现在文章里,感兴趣的小伙伴不要错过奥...2021-09-25
  • vue3弹出层V3Popup实例详解

    这篇文章主要介绍了vue3弹出层V3Popup实例详解,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-01-04
  • vue3源码剖析之简单实现方法

    源码的重要性相信不用再多说什么了吧,特别是用Vue 框架的,一般在面试的时候面试官多多少少都会考察源码层面的内容,下面这篇文章主要给大家介绍了关于vue3源码剖析之简单实现的相关资料,需要的朋友可以参考下...2021-09-07
  • Vue3中reactive函数toRef函数ref函数简介

    这篇文章主要介绍了Vue3中的三种函数,分别对reactive函数toRef函数以及ref函数原理及使用作了简单介绍,有需要的朋友可以借鉴参考下...2021-09-24
  • vue+element+oss实现前端分片上传和断点续传

    这篇文章主要介绍了vue+element+oss实现前端分片上传和断点续传,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-30
  • 利用Vue3 创建Vue CLI 项目(一)

    这篇文章主要介绍利用Vue3 创建Vue CLI 项目,下面文章内容附有官方文档链接,安装过程,需要的可以参考一下...2021-10-18
  • 浅谈Vue3的几个优势

    这篇文章主要给大家分享的是Vue3的几个优势,Vue3仍然在源码、性能和语法 API 三个大的方面进行了优化,下面我们一起进入文章看看具体详情吧...2021-10-26
  • 利用vue3+ts实现管理后台(增删改查)

    这篇文章主要介绍了利用vue3+ts实现管理后台(增删改查),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-10-30
  • vue3封装放大镜组件的实例代码

    这篇文章主要给大家介绍了关于vue3封装放大镜组件的相关资料,封装之后,使用起来就更简单了,一个组件一行就可以,文中通过示例代码介绍的非常详细,需要的朋友可以参考下...2021-09-20
  • 使用Vue3进行数据绑定及显示列表数据

    这篇文章主要介绍了使用Vue3进行数据绑定及显示列表数据,整篇文章围绕Vue3进行数据绑定及显示列表数据的想换自来哦展开内容,需要的小伙伴可以参考一下...2021-10-23
  • 详解vue3 沙箱机制

    这篇文章主要介绍了详解vue3 沙箱机制,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-04-15
  • 40行代码把Vue3的响应式集成进React做状态管理

    这篇文章主要介绍了40行代码把Vue3的响应式集成进React做状态管理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-05-20
  • Vue3 响应式侦听与计算的实现

    这篇文章主要介绍了Vue3 响应式侦听与计算的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-11-11
  • Vue3 AST解析器-源码解析

    这篇文章我们从 ast 生成时调用的 baseParse 函数分析,再到 baseParse 返回 createRoot 的调用结果,一直到细化的讲解了 parseChildren 解析子节点函数中的其中某一个具体解析器的执行过程。最后通过一个简单模板举例,需要的朋友可以参考下...2021-09-25
  • Vue3.0中Ref与Reactive的区别示例详析

    在vue3中对响应式数据的声明官方给出了ref()和reactive()这两种方式,这篇文章主要给大家介绍了关于Vue3.0中Ref与Reactive区别的相关资料,需要的朋友可以参考下...2021-07-07
  • vue3缓存页面keep-alive及路由统一处理详解

    当我们不想每次跳转路由都会重新加载页面时(重新加载页面很耗时),就可以考虑使用keep-alive缓存页面了,这篇文章主要给大家介绍了关于vue3缓存页面keep-alive及路由统一处理的相关资料,需要的朋友可以参考下...2021-10-17
  • 图文详解Vue3没有代码提示问题的解决办法

    最近在使用Vue.js时候没有自动提示,就很难受,下面这篇文章主要给大家介绍了关于Vue3没有代码提示问题的解决办法,文中通过图文介绍的非常详细,需要的朋友可以参考下...2023-01-17
  • 手摸手教你实现Vue3 Reactivity

    本文主要介绍了手摸手教你实现Vue3 Reactivity,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-08-25
  • vue3如何优雅的实现移动端登录注册模块

    这篇文章主要给大家介绍了关于vue3如何优雅的实现移动端登录注册模块的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-28