work·

解决 uni.upload 多文件上传限制:使用 fetch + FormData 实现 H5 环境下的多文件上传

探讨如何在 H5 环境下通过 fetch + FormData 解决 uni.upload 无法在一个接口上传多个文件的问题

在使用 uni-app 开发过程中,我们经常 会遇到文件上传的需求。然而,uni.uploadFile 方法存在一个限制:它无法在一个请求中上传多个文件。本文将介绍如何在 H5 环境下使用 fetch + FormData 来解决这个问题。

问题描述

在开发中,我们常常需要一次性上传多个文件,例如:

  1. 上传多张图片作为产品展示
  2. 上传多个文档文件
  3. 同时上传头像和背景图

但是,uni-app 提供的 uni.uploadFile API 只能上传单个文件,即使多次调用也可能导致后端处理复杂。

解决方案:使用 fetch + FormData

在 H5 环境中,我们可以使用原生的 fetchFormData 来实现多文件上传。

1. 基础实现

// 在 H5 环境下上传多文件
function uploadMultipleFiles(files: File[], uploadUrl: string): Promise<any> {
  // 检查是否在 H5 环境中
  if ((globalThis as any).__PLATFORM__ !== 'h5') {
    console.warn('此方法仅支持 H5 环境');
    return Promise.reject(new Error('此方法仅支持 H5 环境'));
  }

  const formData = new FormData();
  
  // 将多个文件添加到 FormData 中
  files.forEach((file: File, index: number) => {
    // 可以使用相同字段名,后端会接收为数组
    formData.append('files', file, file.name);
    // 或者使用不同字段名
    // formData.append(`file_${index}`, file, file.name);
  });

  // 添加其他参数
  formData.append('userId', '123');
  formData.append('category', 'images');

  return fetch(uploadUrl, {
    method: 'POST',
    body: formData
  })
  .then(response => response.json()) // 注意:fetch 的 then 需先进行 json 处理
  .then(data => {
    console.log('上传成功:', data);
    return data;
  })
  .catch(error => {
    console.error('上传失败:', error);
    throw error;
  });
}

2. 在 Vue 组件中的使用示例

<template>
  <div class="upload-container">
    <input 
      type="file" 
      ref="fileInput" 
      multiple 
      accept="image/*" 
      @change="handleFileSelect"
    />
    <button @click="uploadFiles" :disabled="!selectedFiles.length">
      上传选中的 {{ selectedFiles.length }} 个文件
    </button>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const fileInput = ref(null);
const selectedFiles = ref([]);

const handleFileSelect = (event) => {
  // 获取用户选择的文件列表
  const files = Array.from(event.target.files);
  selectedFiles.value = files;
};

const uploadFiles = async () => {
  if (!selectedFiles.value.length) {
    alert('请先选择文件');
    return;
  }

  // 检查是否在 H5 环境
  if (process.client && typeof window !== 'undefined' && window.fetch) {
    try {
      const result = await uploadMultipleFiles(selectedFiles.value, '/api/upload');
      console.log('上传结果:', result);
      alert('上传成功');
    } catch (error) {
      console.error('上传失败:', error);
      alert('上传失败: ' + error.message);
    }
  } else {
    alert('此功能仅在 H5 环境下可用');
  }
};

// 多文件上传函数
async function uploadMultipleFiles(files, uploadUrl) {
  const formData = new FormData();
  
  // 将多个文件添加到 FormData 中
  files.forEach(file => {
    formData.append('files', file, file.name);
  });

  // 添加其他参数
  formData.append('timestamp', Date.now().toString());

  const response = await fetch(uploadUrl, {
    method: 'POST',
    body: formData
  });

  // 注意:fetch 的 then 需先进行 json 处理
  if (response.ok) {
    const result = await response.json();
    return result;
  } else {
    throw new Error(`上传失败,状态码: ${response.status}`);
  }
}
</script>

3. 处理上传进度

// 带进度的上传实现
interface UploadProgressCallback {
  (progress: number): void;
}

interface FileUploadResponse {
  [key: string]: any; // 根据实际后端响应类型调整
}

function uploadMultipleFilesWithProgress(
  files: File[], 
  uploadUrl: string, 
  onProgress?: UploadProgressCallback
): Promise<FileUploadResponse> {
  return new Promise((resolve, reject) => {
    if ((globalThis as any).__PLATFORM__ !== 'h5') {
      reject(new Error('此方法仅支持 H5 环境'));
      return;
    }

    const formData = new FormData();
    
    files.forEach(file => {
      formData.append('files', file, file.name);
    });

    const xhr = new XMLHttpRequest();

    // 监听上传进度
    xhr.upload.addEventListener('progress', (event: ProgressEvent) => {
      if (event.lengthComputable) {
        const percentComplete = (event.loaded / event.total) * 100;
        onProgress && onProgress(percentComplete);
      }
    });

    xhr.addEventListener('load', () => {
      if (xhr.status >= 200 && xhr.status < 300) {
        try {
          const result = JSON.parse(xhr.response);
          resolve(result);
        } catch (error) {
          reject(new Error('响应解析失败'));
        }
      } else {
        reject(new Error(`上传失败: ${xhr.statusText}`));
      }
    });

    xhr.addEventListener('error', () => {
      reject(new Error('网络错误'));
    });

    xhr.addEventListener('abort', () => {
      reject(new Error('上传被取消'));
    });

    xhr.open('POST', uploadUrl);
    xhr.send(formData);
  });
}

注意事项

1. 环境限制

  • 此解决方案仅适用于 H5 环境,在小程序和 App 环境中无法使用
  • 需要检测当前运行环境,避免在非 H5 环境下执行

2. Fetch 响应处理

  • 使用 fetch 时,需要先将响应转换为 JSON 格式
  • then 中先进行 response.json() 处理,然后再进行后续操作

3. FormData 使用

  • FormData 允许我们轻松地构建表单数据
  • 可以通过 append() 方法添加多个同名字段,后端会接收到文件数组

总结

虽然 uni.uploadFile 有其局限性,但在 H5 环境下,我们可以利用原生的 fetchFormData 来实现多文件上传功能。需要注意的是:

  1. 此方法仅适用于 H5 环境
  2. fetch 响应需要先进行 JSON 处理
  3. 需要适当处理错误和上传进度
  4. 后端需要支持接收多文件数组

通过这种方式,我们可以在 H5 环境中实现更灵活的多文件上传功能。

(注:文档内容由 Copilot 生成)

Built with qbimz • © 2026