携手创作,共同成长!这是我参与「掘金日新计划 · 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' }]),
)
}
这样就行了,现在上传两个图片试试
可以看到成功上传,多文件上传没问题