🔥前端文件上传常见场景 -- 多文件上传

4,278 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第23天,点击查看活动详情

前端开发中,常见的文件上传场景有:

  • 单个文件上传
  • 多个文件上传
  • 目录上传
  • 拖拽上传
  • 剪贴板上传
  • 大文件分块上传

本篇文章来讲一下多文件上传的实现

往期回顾:🔥前端文件上传常见场景 -- 单文件上传

多文件上传

多文件上传,无非就是input标签加上一个multiple属性,并且我们需要添加新的后端接口去处理多个文件的上传,以及对应前端接口的封装要由原来的uploadFileRef.value.files[0]变为uploadFileRef.value.files传给后端接口

修改input标签,添加multiple属性

在实现之前,我们先将单文件上传的组件样式美化一下,将原生的input标签隐藏,转成由一个按钮来触发上传文件窗口的弹出,这个可以通过按钮点击事件触发时去触发input的点击事件即可

然后加上multiple属性,即可让其支持多文件选择上传了

<input
  multiple
  display-none
  type="file"
  accept="image/*"
  ref="uploadFileRef"
  @input="handleInputFileConfirm"
/>
<button btn @click="uploadFileRef!.click()">上传</button>

然后修改handleInputFileConfirm,根据是否具有multiple属性决定调用哪个接口

const handleInputFileConfirm = () => {
  // 没选择文件则不调用接口
  if (!uploadFileRef.value?.files?.length) return

  // 根据是否有 multiple 属性来决定调用哪个接口
  if (uploadFileRef.value.multiple) {
    const files = uploadFileRef.value.files
    multipleUploadFile('file', files)
  } else {
    // 获取选择的第一个文件并调用接口上传
    const file = uploadFileRef.value.files[0]
    singleUploadFile('file', file)
  }
}

封装多文件上传接口

/**
 * @description 多文件上传
 * @param fieldName formData 的字段名 不指定则默认为 file
 * @param files input 中选择的多个文件
 */
export const multipleUploadFile = (fieldName: string, files: FileList) => {
  const formData = new FormData()

  // 将 files 类数组转成数组方便遍历
  const fileArr = Array.from(files)
  fileArr.forEach(file => {
    formData.append(fieldName, file)
  })

  request.post(FileUploadAPI.MULTIPLE_UPLOAD, formData, {
    onUploadProgress: (progressEvent: ProgressEvent) => {
      const uploadedPercent = Math.round(
        (progressEvent.loaded / progressEvent.total) * 100,
      )
      console.log(uploadedPercent)
    },
  })
}

代码和单文件上传差不多,只是多了一个遍历files数组逐个添加到formData中的过程,这明显已经和单文件上传的逻辑有大量重复,那么就可以抽象出一个baseUploadFile函数去复用重复的逻辑

import { FileUploadAPI } from '~/constants'
import { request } from '~/utils'

const baseUploadFile = (fieldName: string, fileOrFiles: File | FileList) => {
  const formData = new FormData()
  let uploadUrl = ''

  if (fileOrFiles instanceof File) {
    // 单文件上传
    formData.set(fieldName, fileOrFiles)
    uploadUrl = FileUploadAPI.SINGLE_UPLOAD
  } else if (fileOrFiles instanceof FileList) {
    // 多文件上传
    // 将 files 类数组转成数组方便遍历
    const fileArr = Array.from(fileOrFiles)
    fileArr.forEach(file => {
      formData.append(fieldName, file)
    })
    uploadUrl = FileUploadAPI.MULTIPLE_UPLOAD
  }

  uploadUrl !== '' &&
    request.post(uploadUrl, formData, {
      // 计算上传进度
      onUploadProgress: (progressEvent: ProgressEvent) => {
        const uploadedPercent = Math.round(
          (progressEvent.loaded / progressEvent.total) * 100,
        )
        console.log(uploadedPercent)
      },
    })
}

/**
 * @description 上传文件 -- 会转成 formData 上传
 * @param fieldName formData 的 字段名 不指定则默认为 file
 * @param file input 中选择的文件
 */
export const singleUploadFile = (fieldName: string, file: File) => {
  baseUploadFile(fieldName, file)
}

/**
 * @description 多文件上传
 * @param fieldName formData 的字段名 不指定则默认为 file
 * @param files input 中选择的多个文件
 */
export const multipleUploadFile = (fieldName: string, files: FileList) => {
  baseUploadFile(fieldName, files)
}

这样代码看起来就清爽多了有没有!

服务端接口扩展多文件上传

基础的处理逻辑仍然和单文件上传接口一致,只是多文件上传接口使用的中间件不是uploadMulter.single了,而是uploadMulter.fields,只需要将前端formData的文件对应的fields指定好即可

import { API_ROUTES } from '~/enums'
import { RegisterRoute } from '~/types/global'
import { generateUniversalResponseData } from '~/utils'
import { uploadMulter } from '~/multer'

export const multiFileUpload: RegisterRoute = router => {
  router.post(
    API_ROUTES.MULTIPLE_UPLOAD,
    async (ctx, next) => {
      try {
        // 交给多文件上传 multer 去处理
        await next()
        ctx.body = generateUniversalResponseData(
          0,
          'upload successfully!',
          null,
        )
      } catch (e) {
        ctx.body = generateUniversalResponseData(
          500,
          `[UPLOAD_FILE_ERROR]: ${e}`,
          null,
        )
      }
    },
    // 使用 fields 对文件处理 可以支持多文件
    // name 要和 formData 的 fieldName 对应
    uploadMulter.fields([{ name: 'file' }]),
  )
}

这样就行了,现在上传两个图片试试

image.png

image.png

可以看到成功上传,多文件上传没问题