前端文件上传实战

前端文件上传实战
Vue 3 + React 实现单文件和多文件上传

文件上传这个功能,说简单也简单,说复杂也真是能玩出不少花样。

记得我第一次写文件上传的时候,就是一个 <input type="file"> 配上 FormData,完事儿。但后来需求就开始不对劲了——要限制文件大小、要显示上传进度、要支持拖拽、要做文件类型校验…

得,那咱就一步步来,把常见的文件上传场景都覆盖一遍。

一、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 的字段名和后端的 @RequestParam("files") 或者 @ModelAttribute("files") 对得上,不然收不到文件。

二、Vue 3 + Element Plus 实现

原生写是能写,但样式得自己调,交互效果也得自己写。

说实话,一般项目我都会直接用组件库,省心。Element Plus 的 Upload 组件功能挺完善的,该有的都有。

2.1 单文件上传

用 Element Plus 写,代码确实少很多。

<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 多文件上传

多文件其实就是加个 multiple 属性,再加个 limit 限制数量。

<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> 

on-exceed 这个回调挺实用的,用户选了 10 个文件,你只让传 5 个,这时候就得提示一下用户。

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 这个钩子非常有用,返回 false 就能阻止上传。用它来做文件校验,再合适不过了。

三、React + Ant Design 实现

说完 Vue,再来看看 React 那边。Ant Design 的 Upload 组件也很好用。

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> ) } 

Ant Design 的写法跟 Element Plus 有点区别,它是 props 风格的回调,不过用起来都挺直观的。

3.2 多文件上传

import { Upload, message, Button } from 'antd' function MultiUpload() { // 超出数量限制了 const handleExceed = () => { message.warning('最多只能上传 5 个文件') } // 文件状态变化的时候 const handleChange = (info) => { // 如果手动删了一些文件,导致数量又合规了,这里其实不用特别处理 // Ant Design 会自动处理上传列表 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 拖拽上传

Ant Design 专门提供了 Dragger 组件来做拖拽。

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> ) } 

拖拽区域的样式是封装好的,不用自己写 CSS,挺好的。

3.4 手动上传

Ant Design 默认是选中文件就上传,如果想手动上传,需要做点配置。

import { Upload, Button, message } from 'antd' import { UploadOutlined } from '@ant-design/icons' 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} // 返回 false 表示阻止自动上传 beforeUpload={() => false} multiple > <Button icon={<UploadOutlined />}>选择文件</Button> </Upload> <Button type="primary" onClick={handleUpload} style={{ marginTop: 16 }}> 开始上传 </Button> </div> ) } 

这里有个小坑:fileList 里的每个 file 对象,它的 originFileObj 才是原始的 File 实例。直接用 file 可能会出问题。

四、进度监听

上传大文件的时候,用户肯定想知道上传进度,不然还以为页面卡死了。

Vue 3 进度监听

Element Plus 的 el-upload 组件没有直接提供进度回调,需要用原生 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,就更简单了,axios 封装了进度事件。

import axios from'axios'constuploadWithAxios=(file)=>{const formData =newFormData() 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 // 带上 cookie 等凭证 onProgress={(event, file) => { // event.percent 是 0-100 的数字 const percent = Math.round(event.percent) console.log(`当前进度: ${percent}%`) }} > <Button>上传</Button> </Upload> 

withCredentials 这个属性记得加上,不然跨域上传的时候 cookie 之类的凭证不会带上。

五、总结对比

写了这么多,来张表总结一下各方案的特点:

功能Vue 原生Vue + Element PlusReact 原生React + Ant Design
单文件上传input + fetchel-uploadinput + fetchUpload
多文件上传multiple + forEachel-upload + multiplemultiple + forEachUpload + multiple
拖拽上传drag eventsel-upload + dragdrag eventsUpload.Dragger
进度监听xhr.onprogress需二次封装xhr.onprogressonProgress
文件限制手动校验:limit/:before-upload手动校验maxCount/beforeUpload
手动上传控制时机:auto-upload=“false”控制时机beforeUpload={() => false}
上传样式自己写 CSS开箱即用自己写 CSS开箱即用

总的来说,如果你的项目已经用了 Element Plus 或 Ant Design,直接用它们的 Upload 组件是最省事的。如果你是原生开发,那就得自己多写点代码,但胜在依赖少、灵活性高。

文件上传这个功能,看起来简单,里面的门道还挺多的。实际项目中,根据业务需求选择合适的方案就好。

Read more

Visual Studio 使用 GitHub Copilot 与 IntelliCode 辅助编码 【AI辅助开发系列】

Visual Studio 使用 GitHub Copilot 与 IntelliCode 辅助编码 【AI辅助开发系列】

🎀🎀🎀【AI辅助编程系列】🎀🎀🎀 1. Visual Studio 使用 GitHub Copilot 与 IntelliCode 辅助编码 2. Visual Studio 安装和管理 GitHub Copilot 3. Visual Studio 使用 GitHub Copilot 扩展 4. Visual Studio 使用 GitHub Copilot 聊天 5. Visual Studio 使用 GitHub Copilot 协助调试 6. Visual Studio 使用 IntelliCode AI 辅助代码开发 7. Visual Studio 玩转 IntelliCode AI辅助开发

孙珍妮AI绘画教程:用Z-Image-Turbo快速生成精美人像

孙珍妮AI绘画教程:用Z-Image-Turbo快速生成精美人像 1. 这不是普通AI画图,是“孙珍妮专属风格”的一键生成 你有没有试过输入“孙珍妮”三个字,却得到一张模糊、失真、甚至完全不像的图片?不是模型不行,而是缺了关键一环——风格锚定。 Z-Image-Turbo本身已是当前开源文生图模型中速度与质量兼顾的标杆:8步推理、1024×1024高清输出、中英双语文本渲染能力出色。但要让它稳定生成“孙珍妮”风格的人像——那种明眸皓齿、气质清冷又带点古典韵味的视觉表达——光靠通用提示词远远不够。 这就是【Z-Image-Turbo】依然似故人_孙珍妮镜像的价值所在:它不是简单套壳,而是在Z-Image-Turbo原生架构上,注入了经过精细调优的LoRA权重,专门学习并固化了孙珍妮面部结构、神态特征、光影偏好与服饰审美逻辑。你可以把它理解为给模型装上了一副“孙珍妮专用滤镜”,而且这副滤镜不降低速度、不牺牲细节、不增加部署门槛。 更重要的是,这个镜像已经完成全部工程封装:Xinference服务自动加载、Gradio界面开箱即用、无需配置CUDA环境、不用写一行启动脚本。你点开

【Claude Code解惑】深度评测:Claude Code vs. GitHub Copilot CLI,谁才是终端之王?

【Claude Code解惑】深度评测:Claude Code vs. GitHub Copilot CLI,谁才是终端之王?

深度评测:Claude Code vs. GitHub Copilot CLI,谁才是终端之王? 目录 1. 引言与背景 2. 原理解释(深入浅出) 3. 10分钟快速上手(可复现) 4. 代码实现与工程要点 5. 应用场景与案例 6. 实验设计与结果分析 7. 性能分析与技术对比 8. 消融研究与可解释性 9. 可靠性、安全与合规 10. 工程化与生产部署 11. 常见问题与解决方案(FAQ) 12. 创新性与差异性 13. 局限性与开放挑战 14. 未来工作与路线图 15. 扩展阅读与资源 16. 图示与交互 17. 术语表与速查表 18. 互动与社区 0.

AIGC时代大模型幻觉问题深度治理:技术体系、工程实践与未来演进

AIGC时代大模型幻觉问题深度治理:技术体系、工程实践与未来演进

文章目录 * 一、幻觉问题的多维度透视与产业冲击 * 1.1 幻觉现象的本质特征与量化评估 * 1.2 产业级影响案例分析 * 二、幻觉问题的根源性技术解剖 * 2.1 数据污染的复合效应 * 2.1.1 噪声数据类型学分析 * 2.1.2 数据清洗技术实现 * 2.2 模型架构的先天缺陷 * 2.2.1 注意力机制的局限性 * 2.2.2 解码策略的博弈分析 * 2.3 上下文处理的边界效应 * 三、多层次解决方案体系构建 * 3.1 数据治理体系升级 * 3.1.1 动态数据质量监控 * 3.1.2 领域知识图谱构建 * 3.