You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

需求:多文件上传时为每个文件添加进度条与中止选项

我之前处理过好几个类似的多文件上传带独立进度追踪的场景,咱们一步步来解决你的问题:

核心问题分析

你现在遇到的多文件只有第一个保存的情况,大概率是因为共用了同一个XMLHttpRequest对象来处理所有文件——当你循环发送请求时,后面的请求会覆盖前面的,最终只有第一个完成上传。要实现每个文件独立的进度条和中止功能,必须给每个文件分配单独的请求实例。

方案实现(推荐每个文件单独发请求)

这种方式不用改后端原有的单文件上传接口,还能保证每个文件的上传状态完全独立,某个文件失败/中止不影响其他文件。

1. 前端代码调整

首先在页面里准备文件选择器和上传容器,然后为每个选中的文件创建独立的请求和UI元素:

<!-- HTML部分 -->
<input type="file" id="fileUploader" multiple>
<div id="uploadStatusContainer"></div>
// JavaScript部分
const fileUploader = document.getElementById('fileUploader');
const statusContainer = document.getElementById('uploadStatusContainer');
const activeXhrs = []; // 存储每个文件的XHR,用于中止操作

fileUploader.addEventListener('change', (e) => {
  const selectedFiles = Array.from(e.target.files);
  
  selectedFiles.forEach((file, idx) => {
    // 1. 为当前文件创建专属的UI元素
    const uploadItem = document.createElement('div');
    uploadItem.className = 'upload-item';
    uploadItem.innerHTML = `
      <span class="file-name">${file.name}</span>
      <div class="progress-wrapper">
        <div class="progress-bar" style="width: 0%;"></div>
      </div>
      <button class="abort-btn" data-file-index="${idx}">中止</button>
    `;
    statusContainer.appendChild(uploadItem);
    
    // 2. 创建独立的XMLHttpRequest实例
    const xhr = new XMLHttpRequest();
    activeXhrs.push(xhr);
    
    // 3. 监听上传进度
    xhr.upload.addEventListener('progress', (progressEvent) => {
      if (progressEvent.lengthComputable) {
        const uploadPercent = Math.round((progressEvent.loaded / progressEvent.total) * 100);
        uploadItem.querySelector('.progress-bar').style.width = `${uploadPercent}%`;
        uploadItem.querySelector('.progress-bar').textContent = `${uploadPercent}%`;
      }
    });
    
    // 4. 处理上传完成/失败状态
    xhr.addEventListener('load', () => {
      const progressBar = uploadItem.querySelector('.progress-bar');
      if (xhr.status === 200) {
        progressBar.style.backgroundColor = '#2ecc71';
        uploadItem.querySelector('.abort-btn').disabled = true;
      } else {
        progressBar.style.backgroundColor = '#e74c3c';
        progressBar.textContent = '上传失败';
      }
    });
    
    // 5. 绑定中止按钮事件
    uploadItem.querySelector('.abort-btn').addEventListener('click', () => {
      xhr.abort();
      const progressBar = uploadItem.querySelector('.progress-bar');
      progressBar.style.backgroundColor = '#f39c12';
      progressBar.textContent = '已中止';
      uploadItem.querySelector('.abort-btn').disabled = true;
    });
    
    // 6. 发送上传请求
    const formData = new FormData();
    formData.append('file', file);
    
    xhr.open('POST', '/your-upload-api-endpoint', true);
    xhr.send(formData);
  });
});

2. 后端代码(无需改动,保持原单文件接口即可)

如果你的后端原本是处理单文件上传的Spring MVC接口,直接复用就行:

@PostMapping("/your-upload-api-endpoint")
public ResponseEntity<String> handleSingleFileUpload(@RequestParam("file") MultipartFile file) {
    try {
        // 你的文件保存逻辑:比如写入数据库
        byte[] fileBytes = file.getBytes();
        // fileService.saveFile(file.getOriginalFilename(), fileBytes, file.getContentType());
        return ResponseEntity.ok("文件上传成功:" + file.getOriginalFilename());
    } catch (IOException e) {
        e.printStackTrace();
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body("文件上传失败:" + file.getOriginalFilename());
    }
}

关键注意事项

  • 不要复用XHR对象:每个文件必须有自己的XMLHttpRequest,否则会出现请求覆盖的问题,导致只有第一个文件上传成功。
  • 存储XHR实例:用数组保存每个文件的XHR,这样中止时能精准找到对应的请求。
  • 动态生成UI:每个文件的进度条和按钮要绑定到对应的请求,避免操作混乱。

如果你的需求是必须一次请求上传所有文件(不推荐,因为无法单独追踪每个文件进度),那后端需要调整为接收MultipartFile[]参数,但这种方式下前端只能拿到整个请求的进度,无法实现单个文件的进度和中止。

内容的提问来源于stack exchange,提问作者mantelinga r

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 06:33:04