Deng
Deng
解决vue3项目使用【Element Plus】中 el-upload 大文件分片上传 | odjBlog
    欢迎来到odjBlog的博客!

解决vue3项目使用【Element Plus】中 el-upload 大文件分片上传

技术总结及问题解决 odjbin 9个月前 (03-16) 53次浏览 0个评论

版本说明

  • "vue": "^3.3.4",
  • "element-plus": "^2.3.8"

这里主要使用 el-upload 中的:on-change="handleChange" 就可以实现

代码

<template>
  <el-upload :file-list="value"
             action="#"
             drag
             :limit="maxSize"
             with-credentials
             :multiple="multiple"
             :auto-upload="false"
             :show-file-list="showFileList"
             :on-change="handleChange"
  >
    <el-icon class="el-icon--upload">
      <upload-filled/>
    </el-icon>
    <div class="el-upload__text">
      <em>点击上传</em>或者将文件拖拽到虚线框内上传文件
    </div>
  </el-upload>
</template>

<script setup>
import {ElMessage, ElMessageBox, ElNotification} from "element-plus";
import {getToken} from '@/utils/auth'
import {getUploadUrl, uploadSingle} from "@/api/home";
const headers = reactive({
  authorization: getToken(), //token
  'Access-Control-Allow-Origin': "*",
  'access-control-allow-headers': 'token, Origin, X-Requested-With, Content-Type, Accept, Authorization',
  'access-control-allow-methods': ' POST,GET,PUT,DELETE,HEAD,OPTIONS,PATCH',
  'access-control-allow-origin': '*',
  'Access-Control-Origin': 'https://xxx.xx.com'
})
const props = defineProps({
  value: {
    type: Array,
    default: () => {
      return []
    }
  },
  maxSize: {
    type: Number,
    default: 1
  },
  showFileList: {
    type: Boolean,
    default: false
  },
  multiple: {
    type: Boolean,
    default: false
  }
})
const handleChange = (file, fileList) => {
  handleUpload(fileList)
}
/**
 * 判断文件大小, 调取不同接口上传
 * @param fileList 文件列表
 */
const handleUpload = (fileList) => {
  //如果文件小于 1M, 直接上传
  let fd = new FormData();
  fileList.forEach(item => {
    fd.append("file", item.raw);
  })
  fd.append("uid", localStorage.getItem('uid'));//接口参数
  if (fileList[0].size < chunkRef.value) {
    //singleUpload(fd)//小文件直接上传方法
  } else {
// 如果文件大于等于 5MB,分片上传
    uploadByPieces(fileList[0])
  }
}
/**
 * 大文件上传接口
 * @param data 接口参数
 */
const uploadChunk = (data) => {
  return new Promise((resolve, reject) => {
    uploadMultiple(data).then(res => {
      return resolve(res)
    }).catch(error => {
      return reject(error)
    });
  })
}

/**
 * 获取文件分块信息
 * @param file 文件对象,包含文件的原始信息和原始内容
 * @param index 分块索引,用于确定当前是第几个分块
 * @returns {{start: number, chunk: *, end: number}}
 */
const getChunkInfo = (file, index) => {
  // 上传过程中用到的变量
  const chunkSize = chunkRef.value;
  //计算当前分块的起始位置
  let start = index * chunkSize;
  // 计算当前分块的结束位置,确保不超过文件总大小
  let end = Math.min(file.size, start + chunkSize);
  // 从文件中切分出当前分块的内容
  let chunk = file.raw.slice(start, end);
  // 返回当前分块的起始位置、结束位置和分块内容
  return {start, end, chunk};
};

/**
 * 异步处理上传队列中的文件
 * @returns {Promise<void>}
 */
async function processUploadQueue() {
  // 检查是否有文件正在上传,如果有,则不执行任何操作
  if (isUploading) {
    return;
  }
  isUploading = true;
  // 当上传队列中有文件时,循环处理队列中的文件
  while (uploadQueue.length > 0) {
    // 从队列中取出第一个文件的上传信息
    const {resolve, reject, fetchForm} = uploadQueue.shift();
    try {
      const res = await uploadChunk(fetchForm);
      console.log(res, 'vs');
      if (res.code === 200) {
        res.success = true
      }
      // 假设上传成功
      resolve(res.success); // 返回 true 表示成功
    } catch (error) {
      reject(error);
    }
  }
  isUploading = false;
}

/**
 * 读取文件块并添加到上传队列中
 * 该函数将给定文件分割成多个块,并将指定索引的块添加到上传队列中
 * 它使用异步方式读取文件块,并在读取完成后处理上传逻辑
 * @param {File} file - 要上传的文件对象
 * @param {number} index - 当前文件块的索引
 * @param {number} chunkCount - 文件块的总数
 */
const readChunk = (file, index, chunkCount) => {
  return new Promise(async (resolve, reject) => {
    const {chunk} = getChunkInfo(file, index);
    // 创建 FormData 对象以准备上传的数据
    let fetchForm = new FormData();
    fetchForm.append("file", chunk);
    fetchForm.append("index", index);
    fetchForm.append("chunkCount", chunkCount);
    fetchForm.append("fileName", file.name);
    // 将任务添加到队列中
    uploadQueue.push({resolve, reject, fetchForm});
    // 如果队列中只有一个任务,则开始处理
    processUploadQueue();
  });
};

const uploadByPieces = async (file) => {
  const chunkCount = Math.ceil(file.size / (chunkRef.value)); // 总片数
  // 针对单个文件进行 chunk 上传

  // 针对每个文件进行 chunk 处理
  const promiseList = []
  for (let index = 0; index < chunkCount; ++index) {
    promiseList.push(readChunk(file, index, chunkCount))
  }
  // 使用 Promise.all 等待所有 readChunk 调用完成
  Promise.all(promiseList).then((res) => {
    // 所有 readChunk 调用完成后,调用 getUploadUrl
    if (res.every(item => item === true)) {
      ElNotification({
        title: '提示',
        message: '文件上传成功',
        type: 'success'
      })
    }
  }).catch(err => {
    console.error('Error reading chunks:', err);
  });
}

</script>
喜欢 (0)
[]
分享 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
已稳定运行:3年255天2小时13分