如何实现拖拽上传和多文件上传

拖拽上传与多文件上传集成方案(Vue3 + TypeScript)

  1. 拖拽上传支持:允许用户通过拖拽文件到指定区域进行上传。
  2. 多文件上传支持:支持同时上传多个文件。
  3. 并发控制:限制同时上传的文件数量。
  4. 进度更新:实时显示每个文件的上传进度。
  5. 状态管理:管理文件的上传状态(待上传、上传中、已完成、错误)。
  6. 错误处理:处理上传过程中的错误,并提供相应的反馈。
一、核心功能实现
  1. 拖拽区域交互设计

<template>
  <div id="upload-area" class="upload-area" @dragover.prevent @drop.prevent="handleDrop">
    <p>拖拽文件到此处或点击选择文件</p>
    <input type="file" id="file-input" webkitdirectory multiple hidden @change="handleFileInput" />
  </div>
  <div id="file-list" class="file-list">
    <div v-for="fileItem in fileList" :key="fileItem.id" class="file-item">
      <span>{{ fileItem.file.name }}</span>
      <div class="progress">
        <div class="progress-bar" :style="{ width: fileItem.progress + '%' }"></div>
      </div>
      <span>{{ fileItem.status }}</span>
    </div>
  </div>
</template>

  • 使用@dragover.prevent阻止浏览器默认拖拽行为‌
  • 同时支持multiple多选文件和webkitdirectory文件夹选择‌拖拽事件处理 

2‌.拖拽事件处理 

这段代码主要用于处理文件拖放事件(DragEvent),并递归地读取拖放的文件和文件夹中的所有文件,最后将这些文件上传

  1. 获取拖放的文件项:从 DragEvent 对象中获取文件项集合。
  2. 初始化文件数组:用于存储所有读取到的文件。
  3. 定义递归遍历函数:遍历文件和文件夹,读取文件并添加到文件数组中。
  4. 遍历拖放的文件项:将每个文件项转换为 FileSystemEntry 对象,并调用递归遍历函数处理。
  5. 上传文件:调用 uploadFiles 函数上传所有读取到的文件。
const handleDrop = (e: DragEvent) => {
  //获取拖放事件中的文件项集合
  const items = e.dataTransfer?.items;
  if (!items) return;

  const files: File[] = [];
  const traverseEntries = async (entry: any) => {
    if (entry.isFile) {
      //如果当前项是文件,调用 getFileFromEntry 获取文件并添加到 files 数组中。
      const file = await getFileFromEntry(entry);
      files.push(file);
    } else if (entry.isDirectory) {
        //如果当前项是文件夹,创建一个 DirectoryReader 对象,读取文件夹中的所有项,并递归调用 traverseEntries 处理每个项。
      const reader = entry.createReader();
      const entries = await readAllEntries(reader);
      await Promise.all(entries.map(traverseEntries));
    }
  };

  Array.from(items).forEach(item => {
    //获取文件项对应的 FileSystemEntry 对象。
    const entry = item.webkitGetAsEntry();
    if (entry) traverseEntries(entry);
  });

 addFiles(files);
}‌;
const getFileFromEntry = (entry: any): Promise<File> => {
  return new Promise((resolve, reject) => {
    entry.file((file: File) => {
      resolve(file);
    }, reject);
  });
};
//readAllEntries:读取 DirectoryReader 对象中的所有项。
//reader.readEntries:异步读取文件夹中的项,直到读取完毕。
//entries.push(...batch):将读取到的项添加到 entries 数组中。
//resolve(entries):读取完毕后,通过 Promise 返回所有项。
const readAllEntries = (reader: any): Promise<any[]> => {
  return new Promise((resolve, reject) => {
    const entries: any[] = [];
    const readEntries = () => {
      reader.readEntries((batch: any[]) => {
        if (batch.length === 0) {
          resolve(entries);
        } else {
          entries.push(...batch);
          readEntries();
        }
      }, reject);
    };
    readEntries();
  });
};

 

二、多文件上传管理
  1. 文件列表状态维护

 


//id: 文件的唯一标识符,通常由文件内容生成的哈希值表示,确保每个文件在列表中是唯一的。

//file表示文件对象,包含了文件的基本信息(如名称、大小、类型等)。

//progress表示文件上传的进度,范围为 0 到 100 的数字。

//status:表示文件的上传状态,有以下几种可能:

//pending':等待上传。
//uploading':正在上传。
//done':上传完成。
//error':上传失败

interface FileItem {

  id: string;
  file: File;
  progress: number;
  status: 'pending' | 'uploading' | 'done' | 'error';
}

const fileList = ref<FileItem[]>([]);
const handleFileInput = (e) => {
  const files = e.target.files;
  addFiles(files);
};


const addFiles = (newFiles: File[]) => {
  newFiles.forEach(file => {
    //过滤掉已经上传过的文件
    if (!fileList.value.some(f => f.id === generateFileHash(file))) {
      fileList.value.push({
        id: generateFileHash(file),
        file,
        progress: 0,
        status: 'pending'
      });
    }
  });
}‌;
//读取文件内容并生成其 SHA-256 哈希值,作为文件的唯一标识符。
const generateFileHash = (file: File): string => {
  const reader = new FileReader();
  return new Promise<string>((resolve, reject) => {
    reader.onload = (e) => {
      const arrayBuffer = e.target?.result as ArrayBuffer;
      const hash = crypto.createHash('sha256').update(arrayBuffer).digest('hex');
      resolve(hash);
    };
    reader.onerror = reject;
    reader.readAsArrayBuffer(file);
  });
};

 ‌2.并发上传控制

//定义了允许的最大并发上传数为 5。超出此限制时,等待已有任务完成后再继续。
const MAX_CONCURRENT = 5;
//使用 Vue 的 ref 创建一个响应式数组,用于存储当前正在进行的上传任务(每个任务是一个 Promise)。
const uploadPool = ref<Promise<any>[]>([]);

const startUpload = async () => {
  const pendingFiles = fileList.value.filter(f => f.status === 'pending');
  
  for (const fileItem of pendingFiles) {
//如果当前上传任务数量达到最大并发限制(MAX_CONCURRENT),则等待任意一个任务完成后再继续。

    if (uploadPool.value.length >= MAX_CONCURRENT) {
      await Promise.race(uploadPool.value);
    }

    const promise = axios.post('/upload', createFormData(fileItem), {
      onUploadProgress: (e) => {
        fileItem.progress = Math.round((e.loaded / e.total) * 100);
      }
    }).finally(() => {
     //无论上传成功或失败,都会执行 finally 回调,从 uploadPool 中移除该任务。
      uploadPool.value.splice(uploadPool.value.indexOf(promise), 1);
    });
     //uploadPool.value.push(promise)
//将当前上传任务的 Promise 添加到 uploadPool 中。
//fileItem.status = 'uploading'
//更新文件状态为 'uploading',表示该文件正在上传。
    uploadPool.value.push(promise);
    fileItem.status = 'uploading';
  }
}‌;
const createFormData = (file) => {
  const formData = new FormData();
  formData.append('file', file);
  return formData;
};

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值