Vue 3 + React 实现单文件和多文件上传
文件上传是前端开发中的常见功能,涵盖限制文件大小、显示上传进度、支持拖拽及文件类型校验等场景。
一、Vue 3 原生实现
1.1 单文件上传
使用原生方式实现单文件上传。
<template>
<div>
<!-- 文件选择框 -->
<input type="file" @change="handleFile" />
<!-- 上传按钮 -->
<button @click="upload" :disabled="!file">上传</button>
<!-- 预览文件名 -->
<p v-if="file">已选择:{{ file.name }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const file = ref(null)
// 监听文件选择事件
const handleFile = (e) => {
file.value = e.target.files[0]
}
// 上传文件到服务器
const upload = async () => {
if (!file.value) return
const formData = new FormData()
formData.append('file', file.value)
await fetch('/api/upload', {
method: 'POST',
body: formData
})
}
</script>
注意细节:
e.target.files[0]获取第一个选中的文件,如需多选需添加multiple属性。FormData对象会自动设置Content-Type边界值。
1.2 多文件上传
改动不大,主要处理多个文件列表。
<template>
<div>
<input type="file" multiple @change="handleFiles" />
<button @click="upload" :disabled="!files.length">批量上传</button>
<ul>
<li v-for="f in files" :key="f.name">{{ f.name }}</li>
</ul>
</div>
</template>
<script setup>
import { ref } from 'vue'
const files = ref([])
// 获取选中的多个文件
const handleFiles = (e) => {
files.value = Array.from(e.target.files)
}
// 批量上传多个文件
const upload = async () => {
if (!files.value.length) return
const formData = new FormData()
// 注意:这里要用相同的字段名 'files',后端才能用 List/MultipartFile[] 接收
files.value.forEach(f => formData.append('files', f))
await fetch('/api/upload/multiple', {
method: 'POST',
body: formData
})
}
</script>
注意后端接收时,前端 append 的字段名需与后端参数名一致。
二、Vue 3 + Element Plus 实现
使用组件库可简化样式和交互逻辑。
2.1 单文件上传
<template>
<el-upload action="/api/upload" :on-success="handleSuccess" :on-error="handleError" :show-file-list="false">
<el-button type="primary">选择文件</el-button>
</el-upload>
</template>
<script setup>
// 上传成功回调
const handleSuccess = (res) => {
ElMessage.success('上传成功')
}
// 上传失败回调
const handleError = () => {
ElMessage.error('上传失败')
}
</script>
action 为必填项。:show-file-list="false" 用于隐藏已上传文件列表。
2.2 多文件上传
<template>
<el-upload
action="/api/upload"
:file-list="fileList"
:on-success="handleSuccess"
:on-remove="handleRemove"
:on-exceed="handleExceed"
:limit="5"
multiple
>
<el-button type="primary">选择文件</el-button>
<template #tip>
<div>支持 jpg、png、pdf 格式,单个文件不超过 10MB</div>
</template>
</el-upload>
</template>
<script setup>
import { ref } from 'vue'
const fileList = ref([])
// 文件数量超出限制了
const handleExceed = () => {
ElMessage.warning('最多只能上传 5 个文件')
}
// 手动移除了某个文件
const handleRemove = (file) => {
console.log('移除了:', file.name)
}
// 单个文件上传成功的回调
const handleSuccess = (res, file) => {
ElMessage.success(`${file.name} 上传成功`)
}
</script>
2.3 拖拽上传
<template>
<el-upload
action="/api/upload"
drag
:auto-upload="false"
:on-change="handleFileChange"
>
<el-icon><upload-filled /></el-icon>
<div>将文件拖到此处,或<em>点击上传</em></div>
</el-upload>
</template>
<script setup>
import { UploadFilled } from '@element-plus/icons-vue'
// 文件被添加或者被移除的时候触发
const handleFileChange = (file) => {
console.log('选中文件:', file.name)
}
</script>
:auto-upload="false" 关闭自动上传,通常配合用户确认操作。
2.4 手动上传(上传前校验)
<template>
<el-upload
ref="uploadRef"
action="/api/upload"
:auto-upload="false"
:before-upload="beforeUpload"
:on-change="handleChange"
>
<el-button type="success">选取文件</el-button>
<template #tip>
<el-button type="primary" @click="submitUpload">开始上传</el-button>
</template>
</el-upload>
</template>
<script setup>
import { ref } from 'vue'
const uploadRef = ref(null)
// 上传前的校验,返回 false 可以取消上传
const beforeUpload = (rawFile) => {
// 检查文件大小,10MB
const isLt10M = rawFile.size / 1024 / 1024 < 10
if (!isLt10M) {
ElMessage.error('文件大小不能超过 10MB')
return false
}
return true
}
// 文件被添加或移除时的回调
const handleChange = (file) => {
console.log('当前文件:', file.name)
}
// 手动调用上传
const submitUpload = () => {
uploadRef.value.submit()
}
</script>
beforeUpload 钩子可用于文件校验。
三、React + Ant Design 实现
3.1 单文件上传
import { Upload, message } from 'antd'
function SingleUpload() {
// 上传成功
const handleSuccess = (res) => {
message.success('上传成功')
}
// 上传失败
const handleError = () => {
message.error('上传失败')
}
return (
<Upload
action="/api/upload"
onSuccess={handleSuccess}
onError={handleError}
showUploadList={false}
>
<button>选择文件</button>
</Upload>
)
}
3.2 多文件上传
import { Upload, message, Button } from 'antd'
function MultiUpload() {
// 超出数量限制了
const handleExceed = () => {
message.warning('最多只能上传 5 个文件')
}
// 文件状态变化的时候
const handleChange = (info) => {
if (info.fileList.length > 5) {
handleExceed()
return
}
info.fileList.forEach(file => {
if (file.status === 'done') {
message.success(`${file.name} 上传成功`)
} else if (file.status === 'error') {
message.error(`${file.name} 上传失败`)
}
})
}
return (
<Upload
action="/api/upload"
multiple
maxCount={5}
onChange={handleChange}
>
<Button>选择文件</Button>
</Upload>
)
}
maxCount 属性可直接限制最大数量。
3.3 拖拽上传
import { Upload, message } from 'antd'
import { InboxOutlined } from '@ant-design/icons'
function DragUpload() {
// 拖拽释放后的回调
const handleDrop = ({ fileList }) => {
console.log('拖拽进来的文件:', fileList)
}
return (
<Upload.Dragger
action="/api/upload"
multiple
drag
onDrop={handleDrop}
>
<p className="ant-upload-drag-icon">
<InboxOutlined />
</p>
<p>点击或拖拽文件到此区域上传</p>
</Upload.Dragger>
)
}
3.4 手动上传
import { Upload, Button, message } from 'antd'
import { UploadOutlined } from '@ant-design/icons'
import { useState } from 'react'
function ManualUpload() {
const [fileList, setFileList] = useState([])
// 选完文件后,不触发上传,只更新列表
const handleChange = ({ fileList: newFileList }) => {
setFileList(newFileList)
}
// 手动触上传
const handleUpload = () => {
if (fileList.length === 0) {
message.warning('请先选择文件')
return
}
const formData = new FormData()
fileList.forEach(file => {
// originFileObj 才是原始的 File 对象
formData.append('files', file.originFileObj)
})
fetch('/api/upload', {
method: 'POST',
body: formData
}).then(() => {
message.success('上传成功')
setFileList([])
})
}
return (
<div>
<Upload
fileList={fileList}
onChange={handleChange}
beforeUpload={() => false}
multiple
>
<Button icon={<UploadOutlined />}>选择文件</Button>
</Upload>
<Button
type="primary"
onClick={handleUpload}
style={{ marginTop: 16 }}
>
开始上传
</Button>
</div>
)
}
注意:fileList 中的 originFileObj 才是原始 File 实例。
四、进度监听
Vue 3 进度监听
Element Plus 未直接提供进度回调,需使用 XMLHttpRequest 或 axios。
<script setup>
import { ref } from 'vue'
const progress = ref(0)
const uploading = ref(false)
const uploadWithProgress = () => {
const fileInput = document.querySelector('input[type="file"]')
const file = fileInput.files[0]
if (!file) return
const formData = new FormData()
formData.append('file', file)
const xhr = new XMLHttpRequest()
// 监听上传进度
xhr.upload.onprogress = (e) => {
if (e.lengthComputable) {
const percent = Math.round((e.loaded / e.total) * 100)
progress.value = percent
console.log(`上传进度:${percent}%`)
}
}
// 上传完成
xhr.onload = () => {
if (xhr.status === 200) {
ElMessage.success('上传成功')
}
uploading.value = false
}
xhr.open('POST', '/api/upload')
xhr.send(formData)
uploading.value = true
}
</script>
<template>
<div>
<input type="file" @change="handleFile" />
<button @click="uploadWithProgress" :disabled="uploading">
{{ uploading ? `上传中 ${progress}%` : '上传' }}
</button>
<el-progress :percentage="progress" v-if="uploading" />
</div>
</template>
若使用 axios:
import axios from 'axios'
const uploadWithAxios = (file) => {
const formData = new FormData()
formData.append('file', file)
axios.post('/api/upload', formData, {
onUploadProgress: (progressEvent) => {
const percent = Math.round((progressEvent.loaded / progressEvent.total) * 100)
console.log(`进度:${percent}%`)
}
})
}
React + Ant Design 进度属性
Ant Design 的 Upload 组件支持 onProgress。
<Upload
action="/api/upload"
withCredentials
onProgress={(event, file) => {
// event.percent 是 0-100 的数字
const percent = Math.round(event.percent)
console.log(`当前进度:${percent}%`)
}}
>
<Button>上传</Button>
</Upload>
withCredentials 用于跨域上传时携带凭证。
五、总结对比
| 功能 | Vue 原生 | Vue + Element Plus | React 原生 | React + Ant Design |
|---|---|---|---|---|
| 单文件上传 | input + fetch | el-upload | input + fetch | Upload |
| 多文件上传 | multiple + forEach | el-upload + multiple | multiple + forEach | Upload + multiple |
| 拖拽上传 | drag events | el-upload + drag | drag events | Upload.Dragger |
| 进度监听 | xhr.onprogress | 需二次封装 | xhr.onprogress | onProgress |
| 文件限制 | 手动校验 | :limit/:before-upload | 手动校验 | maxCount/beforeUpload |
| 手动上传 | 控制时机 | :auto-upload="false" | 控制时机 | beforeUpload={() => false} |
| 上传样式 | 自己写 CSS | 开箱即用 | 自己写 CSS | 开箱即用 |
建议根据项目技术栈选择方案。若已使用组件库,直接使用其 Upload 组件最为便捷;原生开发则依赖少、灵活性高。

