news 2026/9/28 1:52:33

vue2项目中WebUploader怎样实现局域网大文件分块上传?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
vue2项目中WebUploader怎样实现局域网大文件分块上传?

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

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

项目背景

我们导师说:“小张啊,现在找工作得有作品,你做个文件管理系统吧,要支持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/9/27 6:15:41

机器学习与数据挖掘项目~消费者的预测分析(代码+数据集+报告)(设计源文件+万字报告+讲解)(支持资料、图片参考_相关定制)_文章底部可以扫码

机器学习与数据挖掘项目~消费者的预测分析(代码数据集报告)(设计源文件万字报告讲解)&#xff08;支持资料、图片参考_相关定制&#xff09;_文章底部可以扫码 以英国的在线电子零售公司的跨国交易数据。集作为分析样本&#xff0c;通过对该公司的运营指标统计分。析以及构建RM…

作者头像 李华
网站建设 2026/9/27 9:05:57

跨平台环境下,KindEditor如何优化WORD图片复制效率?

企业网站内容管理模块Word/公众号粘贴与文档导入功能实施报告 一、需求背景分析 作为重庆某国企项目负责人&#xff0c;我们在政府类项目开发中遇到了以下核心需求&#xff1a; 内容输入效率需求&#xff1a;需要支持从Word/公众号直接粘贴内容到网站编辑器&#xff0c;并自…

作者头像 李华
网站建设 2026/9/27 4:39:03

网页编辑器KindEditor如何处理WORD文档中的图片粘贴?

《Word一键转存历险记&#xff1a;一个穷学生的CMS升级之路》 寻找解决方案的奇幻旅程 第一天&#xff1a;初探Word粘贴黑科技 作为一名福建某高校的计科大三狗&#xff08;啊不是&#xff0c;学生&#xff09;&#xff0c;我正在给我的CMS新闻管理系统做升级。需求很简单&a…

作者头像 李华
网站建设 2026/9/27 20:31:32

LDO相位补偿:提升动态响应关键方案

目录 一、核心原理&#xff1a;LDO 反馈环路的相位特性 二、通用 LDO 反馈网络补偿拓扑&#xff08;适配可调输出 LDO&#xff09; 1. ADJ 引脚并联补偿电容&#xff08;最常用&#xff09; 原理 适用场景 参数设计 2. 分压电阻串联补偿电容&#xff08;零点补偿&#x…

作者头像 李华
网站建设 2026/9/27 2:26:18

富文本编辑器UEDITOR的ELECTRON插件如何处理图文混排?

企业级富文本编辑器增强方案技术报告 作为广东高新技术企业和软件企业技术负责人&#xff0c;针对当前项目中的富文本编辑器功能增强需求&#xff0c;经过严格技术评估和架构设计&#xff0c;现提交以下技术实施方案。 一、需求分析与技术选型 1.1 核心需求分解 文档导入功能…

作者头像 李华