news 2026/2/20 2:24:45

vue大文件上传的教程:从原理到实战案例分享

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
vue大文件上传的教程:从原理到实战案例分享

一个大三学生的文件管理系统血泪史(前端篇)

各位看官,我是浙江某高校网络工程专业的大三学生,最近在搞一个"史诗级"项目——文件管理系统。为啥说是史诗级?因为光是需求就快把我整秃噜皮了!

项目背景

我们导师说:“小张啊,现在找工作得有作品,你做个文件管理系统吧,要支持10G大文件上传、加密传输、断点续传、文件夹层级保留,还要兼容IE8!”

我:“老师,您这是要让我重写百度网盘吗?”

导师:“不,我要你超越百度网盘!”

我:“…”

技术选型

经过一番挣扎,我选择了:

  • 前端:Vue3 + 原生JS(因为导师说不能用jQuery)
  • 上传组件:WebUploader(兼容性好) + H5(现代浏览器用)
  • 开发工具:VSCode(因为Sublime Text要钱)
  • 数据库:MySQL(因为导师说这个最简单)
  • 服务器:本地Windows 7 + IE9(学校老机器的配置)

前端实现(血泪版)

1. 兼容性处理

首先得解决IE8兼容问题,我写了这么个神器:

// 检测浏览器并给出友好提示functiondetectBrowser(){constuserAgent=navigator.userAgent;if(userAgent.indexOf('MSIE')>-1||userAgent.indexOf('Trident')>-1){constversion=userAgent.match(/(MSIE |rv:)(\d+)/)[2];if(version<9){alert('检测到您使用的是IE'+version+',本系统需要IE9+或其他现代浏览器!');// 偷偷给IE用户显示个可爱图片document.body.innerHTML=`亲爱的IE用户 请使用Chrome/Firefox/Edge/Safari等现代浏览器`;}}}detectBrowser();

2. 文件上传组件封装

classFileUploader{constructor(options){this.chunkSize=options.chunkSize||5*1024*1024;// 默认5MB分片this.fileInput=document.getElementById(options.inputId);this.uploadBtn=document.getElementById(options.uploadBtnId);this.progressBar=document.getElementById(options.progressId);this.fileList=[];this.initEvents();}initEvents(){this.fileInput.addEventListener('change',(e)=>this.handleFileSelect(e));this.uploadBtn.addEventListener('click',()=>this.startUpload());}handleFileSelect(e){constfiles=Array.from(e.target.files);this.fileList=files.map(file=>({file,chunks:[],uploadedChunks:0,totalChunks:Math.ceil(file.size/this.chunkSize),fileId:this.generateFileId()}));// 显示文件列表(简化版)console.log('已选择文件:',this.fileList.map(f=>f.file.name));}generateFileId(){return'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,function(c){constr=Math.random()*16|0;return(c==='x'?r:(r&0x3|0x8)).toString(16);});}// 加密函数(简化版)encryptData(data){// 实际项目中应该用AES等加密算法// 这里只是演示,返回Base64编码returnbtoa(unescape(encodeURIComponent(data)));}// 分片上传asyncuploadChunk(fileObj,chunkIndex){constfile=fileObj.file;conststart=chunkIndex*this.chunkSize;constend=Math.min(file.size,start+this.chunkSize);constchunk=file.slice(start,end);// 加密分片数据constencryptedChunk=this.encryptData(chunk);// 这里应该用XMLHttpRequest或Fetch API发送到后端// 但导师说后端要我自己写,所以我只能...console.log(`模拟上传分片${chunkIndex+1}/${fileObj.totalChunks}:${file.name}`);// 模拟网络延迟awaitnewPromise(resolve=>setTimeout(resolve,500));return{success:true,chunkIndex};}asyncstartUpload(){if(this.fileList.length===0){alert('请先选择文件!');return;}for(constfileObjofthis.fileList){try{for(leti=0;i<fileObj.totalChunks;i++){constresult=awaitthis.uploadChunk(fileObj,i);if(result.success){fileObj.uploadedChunks++;this.updateProgress(fileObj);// 保存上传进度到localStorage(断点续传)this.saveProgressToStorage(fileObj);}}console.log(`文件${fileObj.file.name}上传完成!`);}catch(error){console.error(`上传文件${fileObj.file.name}时出错:`,error);}}}updateProgress(fileObj){constpercent=(fileObj.uploadedChunks/fileObj.totalChunks*100).toFixed(2);this.progressBar.style.width=percent+'%';this.progressBar.textContent=percent+'%';}saveProgressToStorage(fileObj){constprogressData={fileId:fileObj.fileId,fileName:fileObj.file.name,uploadedChunks:fileObj.uploadedChunks,totalChunks:fileObj.totalChunks,fileSize:fileObj.file.size,lastModified:fileObj.file.lastModified};localStorage.setItem(`uploadProgress_${fileObj.fileId}`,JSON.stringify(progressData));}// 从localStorage恢复上传进度staticrestoreProgress(){constprogressItems={};for(leti=0;i<localStorage.length;i++){constkey=localStorage.key(i);if(key.startsWith('uploadProgress_')){constdata=JSON.parse(localStorage.getItem(key));progressItems[key]=data;}}returnprogressItems;}}// 使用示例document.addEventListener('DOMContentLoaded',()=>{constuploader=newFileUploader({inputId:'fileInput',uploadBtnId:'uploadBtn',progressId:'progressBar'});// 恢复上传进度constprogress=FileUploader.restoreProgress();console.log('恢复的上传进度:',progress);});

3. Vue3组件封装

export default { name: 'FileUploader', data() { return { files: [], chunkSize: 5 * 1024 * 1024, // 5MB uploadProgress: {} }; }, methods: { handleFileChange(e) { const files = Array.from(e.target.files); // 处理文件夹上传(WebKit browsers) if (files.length === 0 && e.target.webkitEntries) { const entries = Array.from(e.target.webkitEntries); this.processDirectoryEntries(entries); return; } this.files = files.map(file => ({ file, name: file.name, size: file.size, uploaded: false, uploadedSize: 0 })); }, processDirectoryEntries(entries) { // 实际项目中需要递归处理文件夹结构 // 这里简化处理,只取文件 const files = []; const processEntry = (entry) => { if (entry.isFile) { entry.file(file => { files.push(file); if (files.length === entries.length) { this.files = files.map(file => ({ file, name: file.name, size: file.size, uploaded: false, uploadedSize: 0 })); } }); } }; entries.forEach(processEntry); }, async startUpload() { if (this.files.length === 0) { alert('请先选择文件或文件夹!'); return; } for (const fileObj of this.files) { try { await this.uploadFile(fileObj); fileObj.uploaded = true; } catch (error) { console.error(`上传文件 ${fileObj.name} 时出错:`, error); } } }, async uploadFile(fileObj) { const file = fileObj.file; const totalChunks = Math.ceil(file.size / this.chunkSize); for (let i = 0; i < totalChunks; i++) { const start = i * this.chunkSize; const end = Math.min(file.size, start + this.chunkSize); const chunk = file.slice(start, end); // 加密分片(实际项目中应该用更安全的加密方式) const encryptedChunk = this.simpleEncrypt(chunk); // 这里应该调用后端API上传分片 // 但导师说后端要我自己写,所以我只能... console.log(`上传分片 ${i + 1}/${totalChunks}: ${file.name}`); // 模拟网络延迟 await new Promise(resolve => setTimeout(resolve, 300)); fileObj.uploadedSize = end; this.updateProgress(); } }, simpleEncrypt(data) { // 超级简单的"加密"(实际项目不能用!) return data; // 这里应该返回加密后的数据 }, updateProgress() { const totalUploaded = this.files.reduce((sum, file) => sum + file.uploadedSize, 0); const totalSize = this.files.reduce((sum, file) => sum + file.size, 0); const percent = Math.round((totalUploaded / totalSize) * 100); this.$refs.progressBar.style.width = percent + '%'; this.$refs.progressBar.nextElementSibling.textContent = percent + '%'; }, restoreUpload() { // 从localStorage恢复上传进度 const progressItems = {}; for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); if (key.startsWith('uploadProgress_')) { const data = JSON.parse(localStorage.getItem(key)); progressItems[key] = data; } } console.log('恢复的上传进度:', progressItems); alert('上传进度已恢复(控制台查看详情)'); }, formatFileSize(bytes) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } }, mounted() { // 检测浏览器兼容性 this.detectBrowser(); }, methods: { detectBrowser() { const userAgent = navigator.userAgent; if (userAgent.indexOf('MSIE') > -1 || userAgent.indexOf('Trident') > -1) { const version = userAgent.match(/(MSIE |rv:)(\d+)/)[2]; if (version < 9) { alert(`检测到您使用的是IE${version},本系统需要IE9+或其他现代浏览器!`); document.body.innerHTML = ` <div style="text-align:center; padding:50px;"> <h1>亲爱的IE用户</h1> <img src="https://http.cat/418.jpg" alt="IE已淘汰"> <p>请使用Chrome/Firefox/Edge/Safari等现代浏览器</p> </div> `; } } } } }; .file-uploader { max-width: 800px; margin: 0 auto; padding: 20px; font-family: Arial, sans-serif; } .upload-area { margin: 20px 0; padding: 20px; border: 2px dashed #ccc; text-align: center; } .progress-container { margin: 20px 0; height: 30px; background: #f0f0f0; border-radius: 15px; position: relative; } .progress-bar { height: 100%; background: #4CAF50; border-radius: 15px; width: 0%; transition: width 0.3s; } .progress-text { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #333; } .file-list { margin-top: 30px; } .file-list ul { list-style: none; padding: 0; } .file-list li { padding: 8px; border-bottom: 1px solid #eee; }

项目趣事

  1. IE8兼容大战:为了兼容IE8,我尝试了各种polyfill,最后发现还是直接显示"请升级浏览器"更实际

  2. 文件夹上传:WebUploader对文件夹上传支持不好,我研究了半天发现原来要用webkitdirectory属性

  3. 断点续传:本来想用IndexedDB存储上传进度,发现IE不支持,最后改用localStorage(虽然有大小限制)

  4. 加密传输:研究了AES加密,发现实现起来太复杂,最后用了简单的Base64编码(导师说这不算真正的加密)

求助与展望

现在前端部分勉强能跑,但后端完全没头绪。群里的小伙伴们也都在喊"后端大佬救命"!

在此我郑重声明:

  1. 不提供后端代码(因为我也不会)
  2. 欢迎大佬加入QQ群374992201指导
  3. 加群送1-99元红包(老板说这是营销策略)
  4. 推荐工作成功者送20%项目提成(虽然现在还没项目)

最后,如果哪位师哥师姐愿意收我为徒,教我后端开发,我愿意:

  • 每天给您请安
  • 帮您写前端代码
  • 毕业设计可以挂您名字
  • 未来第一年工资分您10%

(联系方式:QQ群374992201,群里找我"求带的小张")

将组件复制到项目中

示例中已经包含此目录

引入组件

配置接口地址

接口地址分别对应:文件初始化,文件数据上传,文件进度,文件上传完毕,文件删除,文件夹初始化,文件夹删除,文件列表
参考:http://www.ncmem.com/doc/view.aspx?id=e1f49f3e1d4742e19135e00bd41fa3de

处理事件

启动测试

启动成功

效果

数据库

效果预览

文件上传

文件刷新续传

支持离线保存文件进度,在关闭浏览器,刷新浏览器后进行不丢失,仍然能够继续上传

文件夹上传

支持上传文件夹并保留层级结构,同样支持进度信息离线保存,刷新页面,关闭页面,重启系统不丢失上传进度。

下载示例

点击下载完整示例

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/17 5:52:32

Hunyuan-MT-7B支持REST API吗?未来扩展性前瞻

Hunyuan-MT-7B 支持 REST API 吗&#xff1f;未来扩展性前瞻 在企业全球化加速、内容多语种分发需求激增的今天&#xff0c;机器翻译早已不再是实验室里的技术玩具&#xff0c;而是支撑跨境沟通、产品出海和数字内容本地化的关键基础设施。尤其当大模型席卷自然语言处理领域后&…

作者头像 李华
网站建设 2026/2/16 19:41:04

AR增强现实融合:叠加识别结果于实景画面

AR增强现实融合&#xff1a;叠加识别结果于实景画面 万物识别-中文-通用领域&#xff1a;让机器“看懂”真实世界 在智能硬件与AI融合的浪潮中&#xff0c;AR&#xff08;增强现实&#xff09;技术正从游戏娱乐走向工业检测、教育辅助和零售导购等实际场景。其核心能力之一——…

作者头像 李华
网站建设 2026/2/19 15:28:47

十分钟教学:用云端GPU搭建课堂用物体识别演示系统

十分钟教学&#xff1a;用云端GPU搭建课堂用物体识别演示系统 作为一名高校教师&#xff0c;我最近需要在AI概论课上展示物体识别技术&#xff0c;但学校的实验室缺乏足够的GPU资源。经过一番探索&#xff0c;我发现利用云端GPU可以快速搭建一个稳定且高效的演示系统。本文将分…

作者头像 李华
网站建设 2026/2/16 11:41:49

汽车内饰识别分析:用于二手车评估的视觉辅助

汽车内饰识别分析&#xff1a;用于二手车评估的视觉辅助 引言&#xff1a;从人工验车到智能视觉辅助的演进 在二手车交易市场中&#xff0c;车辆内饰的状态是决定其残值的重要因素之一。传统评估依赖经验丰富的检测人员通过肉眼观察座椅磨损、仪表盘划痕、空调出风口积尘等细节…

作者头像 李华
网站建设 2026/2/14 23:39:34

寻根问祖资料解读:Hunyuan-MT-7B帮助海外华人了解族谱

用AI读懂祖先的语言&#xff1a;Hunyuan-MT-7B如何帮海外华人破解族谱密码 在旧金山的一间书房里&#xff0c;一位第三代华裔青年正凝视着一张泛黄的纸页——那是他祖父从广东带出的家族族谱残卷。上面写着“祖籍南海&#xff0c;迁于香山&#xff0c;世居珠江之畔”。他能认出…

作者头像 李华
网站建设 2026/2/19 21:13:16

AI如何帮你快速掌握单调栈算法?

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 创建一个交互式学习单调栈的AI助手。功能包括&#xff1a;1) 根据用户输入的问题自动生成单调栈的Python/Java实现代码&#xff1b;2) 提供常见单调栈问题的分类讲解&#xff08;如…

作者头像 李华