前端文件上传方案:别再只用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.1 问题现象 操作步骤预期结果实际结果1. 选择三级目录"规章制度"--2. 上传文件 合同.pdf上传成功上传成功3. 再次选择同一目录--4. 上传另一个 合同.pdf提示"已存在同个名称的文件"无任何提示,新文件被忽略 用户困惑:明明上传了新文件,但列表里只有第一次上传的内容。 1.2 问题影响 * 用户误以为上传成功,实际新文件丢失 * 无法通过上传覆盖更新文件 * 用户体验差,容易造成数据丢失 1.3 修复历程 时间操作结果12-11创建问题-12-11AI分析并修复提交前端+后端代码12-12合并代码验证通过12-25验收关闭功能正常 激活次数:0次(一次修复成功) 二、问题分析 2.

从Web到全平台:Capacitor打包工具实战指南

作为前端开发者,你是否曾面临这样的困境:好不容易用React、Vue或Angular开发完Web应用,却被要求适配iOS和Android端?学习原生开发成本太高,找原生团队协作又耗时费力。今天要给大家介绍的Capacitor,正是解决这个痛点的利器——由Ionic团队打造的现代跨平台打包工具,能让Web开发者零原生基础也能构建全平台应用。 一、为什么选Capacitor?先看它的核心优势 在接触具体用法前,我们得先搞清楚:Capacitor凭什么成为Web转原生的优选?对比传统方案,它的优势太明显了: 1. 零框架侵入,适配所有Web项目 不同于某些强绑定框架的工具,Capacitor对前端技术栈完全无要求。不管你是用React写的管理系统、Vue开发的移动端页面,还是原生HTML/CSS/JS写的项目,都能直接接入打包。我曾把一个基于Vue3的官网快速打包成APP,整个过程没改一行业务代码。 2. 现代WebView加持,性能接近原生 Capacitor在iOS端采用WKWebView,Android端使用Chromium WebView,这俩都是各平台性能最优的Web

Chromium WebRTC 在 AI 辅助开发中的实战优化与避坑指南

最近在做一个AI辅助的实时协作项目,用到了Chromium的WebRTC模块来处理音视频通信。项目上线初期,当AI推理任务(比如实时背景虚化、手势识别)和WebRTC的编解码、传输同时进行时,延迟抖动非常明显,GPU也经常被“打满”,用户体验很糟糕。这促使我深入研究了WebRTC的底层,并尝试用AI的思路去优化它,最终将端到端延迟降低了近30%。这里把整个实战优化过程和踩过的坑记录下来,希望能给遇到类似问题的朋友一些参考。 1. 背景痛点:当WebRTC遇上AI推理 在传统的视频会议场景中,WebRTC的自适应码率(GCC算法)和抗丢包(NACK、FEC)机制已经相当成熟。然而,在AI辅助开发场景下,比如实时虚拟背景、语音降噪、内容审核等,情况变得复杂很多: * 实时性要求更高:AI处理本身需要时间(推理延迟),这直接叠加在了视频采集、编码、传输、解码、渲染的链路上。用户能明显感觉到“说话”和“画面/效果响应”之间的迟滞。 * GPU资源竞争白热化:WebRTC的视频编码(特别是硬件编码)

Clawdbot Web Chat平台从零开始:Qwen3-32B模型加载、API路由、UI定制完整流程

Clawdbot Web Chat平台从零开始:Qwen3-32B模型加载、API路由、UI定制完整流程 1. 为什么需要这个平台?——一句话说清价值 你是不是也遇到过这样的问题:想快速搭一个能直接对话大模型的网页聊天界面,但又不想从零写前后端、不熟悉模型服务部署、更不想被云API调用限制和费用卡脖子? Clawdbot Web Chat 就是为这类需求而生的轻量级解决方案。它不依赖复杂框架,不强制绑定特定云服务,核心能力就三件事:把本地跑起来的 Qwen3-32B 模型“接进来”、把 API 请求“转过去”、把聊天页面“换上新皮肤”。 整个过程不需要写一行模型推理代码,也不用配置 Nginx 反向代理规则——所有关键链路都已预置,你只需要改几个配置项、启动两个服务、打开浏览器,就能拥有一个专属的、响应快、无延迟、完全可控的大模型对话入口。 2. 环境准备:三步完成基础搭建 2.1 确认系统与依赖 Clawdbot