前端文件上传方案:别再只用input type=file了

前端文件上传方案:别再只用input type=file了

前端文件上传方案:别再只用input type=file了

毒舌时刻

这代码写得跟网红滤镜似的——仅供参考。

各位前端同行,咱们今天聊聊前端文件上传。别告诉我你还在用原生的input上传大文件,那感觉就像在用小水管灌满游泳池——慢得让人绝望。

为什么你需要文件上传方案

最近看到一个项目,上传100MB的文件直接卡死浏览器,没有任何进度提示,我差点当场去世。我就想问:你是在做上传还是在做浏览器杀手?

反面教材

<!-- 反面教材:原生文件上传 --> <input type="file" onchange="uploadFile(this.files[0])" /> <script> function uploadFile(file) { const formData = new FormData(); formData.append('file', file); // 直接上传,没有进度,没有断点续传 fetch('/api/upload', { method: 'POST', body: formData }); } </script> 

毒舌点评:这代码,我看了都替你的用户着急。原生上传大文件,你是想让用户等到天荒地老吗?

前端文件上传的正确姿势

1. 分片上传

// 正确姿势:分片上传 class ChunkUploader { constructor(file, options = {}) { this.file = file; this.chunkSize = options.chunkSize || 1024 * 1024; // 1MB this.chunks = Math.ceil(file.size / this.chunkSize); this.uploadedChunks = 0; } async upload() { const promises = []; for (let i = 0; i < this.chunks; i++) { const start = i * this.chunkSize; const end = Math.min(start + this.chunkSize, this.file.size); const chunk = this.file.slice(start, end); promises.push(this.uploadChunk(chunk, i)); } await Promise.all(promises); await this.mergeChunks(); } async uploadChunk(chunk, index) { const formData = new FormData(); formData.append('chunk', chunk); formData.append('index', index); formData.append('total', this.chunks); formData.append('filename', this.file.name); await fetch('/api/upload/chunk', { method: 'POST', body: formData }); this.uploadedChunks++; this.onProgress(this.uploadedChunks / this.chunks); } onProgress(progress) { console.log(`上传进度: ${(progress * 100).toFixed(2)}%`); } async mergeChunks() { await fetch('/api/upload/merge', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ filename: this.file.name, chunks: this.chunks }) }); } } // 使用 const uploader = new ChunkUploader(file, { chunkSize: 1024 * 1024 }); uploader.upload(); 

2. 断点续传

// 正确姿势:断点续传 class ResumableUploader { constructor(file) { this.file = file; this.chunkSize = 1024 * 1024; this.uploadedChunks = new Set(); } async init() { // 获取已上传的分片 const response = await fetch(`/api/upload/status?filename=${this.file.name}`); const { uploadedChunks } = await response.json(); this.uploadedChunks = new Set(uploadedChunks); } async upload() { const chunks = Math.ceil(this.file.size / this.chunkSize); for (let i = 0; i < chunks; i++) { if (this.uploadedChunks.has(i)) { console.log(`分片${i}已上传,跳过`); continue; } const start = i * this.chunkSize; const end = Math.min(start + this.chunkSize, this.file.size); const chunk = this.file.slice(start, end); await this.uploadChunk(chunk, i); this.uploadedChunks.add(i); // 保存进度到本地 localStorage.setItem('uploadProgress', JSON.stringify([...this.uploadedChunks])); } await this.mergeChunks(); localStorage.removeItem('uploadProgress'); } async uploadChunk(chunk, index) { // 上传逻辑... } } 

3. 拖拽上传

// 正确姿势:拖拽上传 import { useCallback } from 'react'; function DragUpload({ onUpload }) { const handleDrop = useCallback((e) => { e.preventDefault(); const files = Array.from(e.dataTransfer.files); files.forEach(file => onUpload(file)); }, [onUpload]); const handleDragOver = useCallback((e) => { e.preventDefault(); }, []); return ( <div className="drag-upload" onDrop={handleDrop} onDragOver={handleDragOver} > <p>拖拽文件到此处上传</p> <input type="file" multiple onChange={(e) => { Array.from(e.target.files).forEach(file => onUpload(file)); }} /> </div> ); } 

毒舌点评:早这么写,你的上传早就做好了。别告诉我你还在用原生input,那你还是趁早去用FTP吧。

实战技巧:文件上传指南

1. 上传优化策略

  1. 分片上传:大文件切分上传
  2. 断点续传:支持暂停恢复
  3. 并发控制:限制同时上传数量
  4. 进度显示:实时显示上传进度

2. 最佳实践

// ✅ 显示上传进度 const xhr = new XMLHttpRequest(); xhr.upload.onprogress = (e) => { const progress = (e.loaded / e.total) * 100; console.log(`${progress}%`); }; // ✅ 图片预览 const preview = URL.createObjectURL(file); // ✅ 文件类型检查 const allowedTypes = ['image/jpeg', 'image/png']; if (!allowedTypes.includes(file.type)) { alert('不支持的文件类型'); return; } 

最后想说的

文件上传不是小事,是用户体验的关键。别再只用input type=file了——优化一下,你的上传会更专业。

文件上传就像快递,原生input像平邮,优化后的上传像顺丰。别让用户等平邮,给他们顺丰的体验。

Read more

写给技术管理者的低代码手册系列文章(1)——从软件工程视角理解低代码的价值、边界与演进路径

自 2014 年提出以来,低代码已逐步进入 ICT 技术成熟期,并开始深度嵌入企业核心系统建设体系。对 CIO、总架构师及技术管理者而言,关键问题已不再是“是否引入低代码”,而是如何将其纳入既有架构体系与工程治理框架,并确保其对系统长期演进产生正向影响。 为此,我们通过阅读大量文献,结合实践案例,编写了这本手册,希望能为您带来更全面、更客观的低代码技术介绍,尝试解答直接决定低代码项目的可持续性的重点问题: * 低代码解决的是哪些长期存在的工程问题? * 其能力边界与适用前提在哪里? * 如何与既有开发体系、架构体系协同? * AI 参与开发后,低代码的工程角色如何变化? * 技术管理者应如何构建配套治理机制? 手册按“背景 → 概念 → 原理 → 场景 → 管理 → 前瞻”的顺序展开,形成完整认知闭环,建议您按顺序阅读,以建立系统视角;亦可根据实际职责,重点研读相关部分。 一句话总结: 本手册面向承担架构设计、平台规划与技术治理责任的管理者, 旨在提供一套可长期参考的低代码认知框架。 第一部分 低代码诞生的背景 企业软件的复杂度并非源于单一技术选择,而是伴随

H5-Dooring低代码可视化编辑器:5分钟上手,零代码打造专业H5页面

H5-Dooring低代码可视化编辑器:5分钟上手,零代码打造专业H5页面 【免费下载链接】h5-DooringMrXujiang/h5-Dooring: h5-Dooring是一个开源的H5可视化编辑器,支持拖拽式生成交互式的H5页面,无需编码即可快速制作丰富的营销页或小程序页面。 项目地址: https://gitcode.com/gh_mirrors/h5/h5-Dooring 还在为H5页面开发而头疼?传统方式下,一个简单的营销页面都需要前端工程师耗费数小时编写HTML、CSS和JavaScript代码。但现在,有了H5-Dooring这款开源免费的低代码可视化编辑器,一切变得像搭积木一样简单有趣! 场景化应用:从营销页面到数据大屏的全能解决方案 营销活动页面:节日促销的快速制作秘籍 想象一下,临近中秋,你的公司需要紧急制作一个节日促销页面。传统方式下,你需要找前端工程师加班加点,但现在,你只需要: 第一步:在项目管理首页点击"新建页面",选择中秋主题模板 第二步:拖拽组件搭建页面结构 - Banner、商品列表、优惠券模块 第三步:配置组件内容 -

【论文阅读】Vision-skeleton dual-modality framework for generalizable assessment of Parkinson’s disease ga

【论文阅读】Vision-skeleton dual-modality framework for generalizable assessment of Parkinson’s disease ga

论文题目:《Vision-skeleton dual-modality framework for generalizable assessment of Parkinson’s disease gait》 论文链接:https://doi.org/10.1016/j.media.2025.103727  代码链接:https://github.com/FJNU-LWP/PD-gait-VSDF 视觉-骨架双模态框架:通过视频实现帕金森病步态的泛化评估 研究背景介绍 帕金森病评估与帕金森病评分量表(MDS-UPDRS) 帕金森病步态评估 研究内容 总体方法流程 关键点视觉 Transformer (KVT) 图像块嵌入 (Patches embedding) 位置与连接嵌入 (Positions and connections embedding) 关键点自注意力 (Keypoints Self-Attention,

15. DAPP react界面-web3.js库-Metemask调用和显示-调用合约方法

15. DAPP react界面-web3.js库-Metemask调用和显示-调用合约方法

测试Solidity ERC20合约 - web3.js结合Metemask调用合约方法 * 1. 环境配置和合约代码 * 2. 编写调试代码 * 3. 测试 * 3.1 MetaMask连接hardhat node * 3.2 MetaMask连接sepolia 一. 系列视频 二. 系列文章 1. Remix编写、编译、部署、测试Solidity ERC20合约 - 基础篇 2. Remix编写、编译、部署、测试Solidity ERC20合约 - 进阶篇 3. Hardhat编写、编译、部署、测试Solidity ERC20合约 - 基础篇 4. JSON-RPC调用区块链方法 5. JSON-RPC调用合约方法