Vue 大文件分片上传组件实现解析

Vue 大文件分片上传组件实现解析

一、功能概述

1.1本组件基于 Vue + Element UI 实现,主要功能特点:

  1. 大文件分片上传:支持 2MB 分片切割上传
  2. 实时进度显示:可视化展示每个文件上传进度
  3. 智能格式校验:支持文件类型、大小、特殊字符校验
  4. 文件预览删除:已上传文件可预览和删除
  5. 断点续传能力:网络中断后可恢复上传
  6. 失败自动重试:分片级失败重试机制(最大3次)

用户选择文件 → 前端校验 → 分片切割 → 并行上传 → 合并确认 → 完成上传

二、核心实现解析

2.1 分片上传机制

// 分片切割逻辑
const chunkSize = 2 * 1024 * 1024 // 2MB分片
const totalChunks = Math.ceil(file.size / chunkSize)for (let chunkNumber = 1; chunkNumber <= totalChunks; chunkNumber++) {const start = (chunkNumber - 1) * chunkSizeconst end = Math.min(start + chunkSize, file.size)const chunk = file.slice(start, end)// 构造分片数据包const formData = new FormData()formData.append('file', chunk)formData.append('chunkNumber', chunkNumber)formData.append('totalChunks', totalChunks)
}

2.2 断点续传实现

// 使用Map存储上传记录
uploadedChunksMap = new Map() // 上传前检查已传分片
if (!uploadedChunks.has(chunkNumber)) {// 执行上传
}// 上传成功记录分片
uploadedChunks.add(chunkNumber)

2.3 智能重试机制

const maxRetries = 3 // 最大重试次数
const baseDelay = 1000 // 基础延迟// 指数退避算法
const delay = Math.min(baseDelay * Math.pow(2, retries - 1) + Math.random() * 1000, 10000
)

三、关键代码详解

3.1 文件标识生成

createFileIdentifier(file) {// 文件名 + 大小 + 时间戳 生成唯一IDreturn `${file.name}-${file.size}-${new Date().getTime()}`
}

3.2 进度计算原理

// 实时更新进度
this.$set(this.uploadProgress, file.name, Math.floor((uploadedChunks.size / totalChunks) * 100))

3.3 文件校验体系

handleBeforeUpload(file) {// 类型校验const fileExt = file.name.split('.').pop()if (!this.fileType.includes(fileExt)) return false// 特殊字符校验if (file.name.includes(',')) return false// 大小校验(MB转换)return file.size / 1024 / 1024 < this.fileSize
}

四、服务端对接指南

4.1 必要接口清单

在这里插入图片描述

五、性能优化建议

5.1 并发上传控制

// 设置并行上传数
const parallelUploads = 3 
const uploadQueue = []for (let i=0; i<parallelUploads; i++) {uploadQueue.push(uploadNextChunk())
}await Promise.all(uploadQueue)

5.2 内存优化策略

复制
// 分片上传后立即释放内存
chunk = null
formData = null

5.3 秒传功能实现

// 计算文件哈希值
const fileHash = await calculateMD5(file)// 查询服务器是否存在相同文件
const res = await checkFileExist(fileHash)
if (res.exist) {this.handleUploadSuccess(res)return
}

六、错误处理机制

6.1 常见错误类型

在这里插入图片描述

七、完整版代码

7.1 代码

<template><div class="upload-file"><el-uploadmultiple:action="'#'":http-request="customUpload":before-upload="handleBeforeUpload":file-list="fileList":limit="limit":on-error="handleUploadError":on-exceed="handleExceed":on-success="handleUploadSuccess":show-file-list="false":headers="headers"class="upload-file-uploader"ref="fileUpload"v-if="!disabled"><!-- 上传按钮 --><el-button size="mini" type="primary">选取文件</el-button><!-- 上传提示 --><div class="el-upload__tip" slot="tip" v-if="showTip">请上传<template v-if="fileSize">大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b></template><template v-if="fileType.length > 0">格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b></template>的文件</div></el-upload><!-- 文件列表 --><transition-groupclass="upload-file-list el-upload-list el-upload-list--text"name="el-fade-in-linear"tag="ul"><li:key="file.url"class="el-upload-list__item ele-upload-list__item-content"v-for="(file, index) in fileList"><el-link:href="`${baseUrl}${file.url}`":underline="false"target="_blank"><span class="el-icon-document"> {{ getFileName(file.name) }} </span></el-link><div class="ele-upload-list__item-content-action"><el-link:underline="false"@click="handleDelete(index)"type="danger"v-if="!disabled">删除</el-link></div></li></transition-group><!-- 上传进度展示 --><divv-for="(progress, fileName) in uploadProgress":key="fileName"class="upload-progress"><div class="progress-info"><span class="file-name">{{ fileName }}</span><span class="percentage">{{ progress }}%</span></div><el-progress :percentage="progress" :show-text="false"></el-progress></div></div>
</template><script>
import { getToken } from "@/utils/auth";
import { uploadFileProgress } from "@/api/resource";
export default {name: "FileUpload",props: {// 值value: [String, Object, Array],// 数量限制limit: {type: Number,default: 5,},// 大小限制(MB)fileSize: {type: Number,default: 5,},// 文件类型, 例如['png', 'jpg', 'jpeg']fileType: {type: Array,default: () => ["doc","docx","xls","xlsx","ppt","pptx","txt","pdf",],},// 是否显示提示isShowTip: {type: Boolean,default: true,},// 禁用组件(仅查看文件)disabled: {type: Boolean,default: false,},},data() {return {number: 0,uploadList: [],baseUrl: process.env.VUE_APP_BASE_API,uploadFileUrl: process.env.VUE_APP_BASE_API + "/common/upload", // 上传文件服务器地址headers: {Authorization: "Bearer " + getToken(),},fileList: [],uploadProgress: {}, // 存储文件上传进度uploadedChunksMap: new Map(), // 新增:存储每个文件的已上传分片记录};},watch: {value: {handler(val) {if (val) {let temp = 1;// 首先将值转为数组const list = Array.isArray(val) ? val : this.value.split(",");// 然后将数组转为对象数组this.fileList = list.map((item) => {if (typeof item === "string") {item = { name: item, url: item };}item.uid = item.uid || new Date().getTime() + temp++;return item;});} else {this.fileList = [];return [];}},deep: true,immediate: true,},},computed: {// 是否显示提示showTip() {return this.isShowTip && (this.fileType || this.fileSize);},},methods: {// 上传前校检格式和大小handleBeforeUpload(file) {// 校检文件类型if (this.fileType && this.fileType.length > 0) {const fileName = file.name.split(".");const fileExt = fileName[fileName.length - 1];const isTypeOk = this.fileType.indexOf(fileExt) >= 0;if (!isTypeOk) {this.$modal.msgError(`文件格式不正确,请上传${this.fileType.join("/")}格式文件!`);return false;}}// 校检文件名是否包含特殊字符if (file.name.includes(",")) {this.$modal.msgError("文件名不正确,不能包含英文逗号!");return false;}// 校检文件大小if (this.fileSize) {const isLt = file.size / 1024 / 1024 < this.fileSize;if (!isLt) {this.$modal.msgError(`上传文件大小不能超过 ${this.fileSize} MB!`);return false;}}// this.$modal.loading("正在上传文件,请稍候...");this.number++;return true;},// 文件个数超出handleExceed() {this.$modal.msgError(`上传文件数量不能超过 ${this.limit} 个!`);},// 上传失败handleUploadError(err) {// 确保在上传错误时移除进度条if (err.file && err.file.name) {this.$delete(this.uploadProgress, err.file.name);}this.$modal.msgError("上传文件失败,请重试");this.$modal.closeLoading();},// 上传成功回调handleUploadSuccess(res, file) {if (res.code === 200) {this.uploadList.push({ name: res.fileName, url: res.fileName });this.uploadedSuccessfully();} else {this.number--;this.$modal.closeLoading();this.$modal.msgError(res.msg);this.$refs.fileUpload.handleRemove(file);this.uploadedSuccessfully();}},// 删除文件handleDelete(index) {this.fileList.splice(index, 1);this.$emit("input", this.listToString(this.fileList));},// 上传结束处理uploadedSuccessfully() {if (this.number > 0 && this.uploadList.length === this.number) {this.fileList = this.fileList.concat(this.uploadList);this.uploadList = [];this.number = 0;this.$emit("input", this.listToString(this.fileList));this.$modal.closeLoading();}},// 获取文件名称getFileName(name) {// 如果是url那么取最后的名字 如果不是直接返回if (name.lastIndexOf("/") > -1) {return name.slice(name.lastIndexOf("/") + 1);} else {return name;}},// 对象转成指定字符串分隔listToString(list, separator) {let strs = "";separator = separator || ",";for (let i in list) {strs += list[i].url + separator;}return strs != "" ? strs.substr(0, strs.length - 1) : "";},// Create unique identifier for filecreateFileIdentifier(file) {return `${file.name}-${file.size}-${new Date().getTime()}`;},async customUpload({ file }) {try {const chunkSize = 2 * 1024 * 1024;const totalChunks = Math.ceil(file.size / chunkSize);const identifier = this.createFileIdentifier(file);const maxRetries = 3;const baseDelay = 1000;// 获取或创建该文件的已上传分片记录if (!this.uploadedChunksMap.has(identifier)) {this.uploadedChunksMap.set(identifier, new Set());}const uploadedChunks = this.uploadedChunksMap.get(identifier);this.$set(this.uploadProgress, file.name,Math.floor((uploadedChunks.size / totalChunks) * 100));for (let chunkNumber = 1; chunkNumber <= totalChunks; chunkNumber++) {// 如果分片已上传成功,跳过if (uploadedChunks.has(chunkNumber)) {continue;}let currentChunkSuccess = false;let retries = 0;while (!currentChunkSuccess && retries < maxRetries) {try {const start = (chunkNumber - 1) * chunkSize;const end = Math.min(start + chunkSize, file.size);const chunk = file.slice(start, end);const formData = new FormData();formData.append('file', chunk);formData.append('identifier', identifier);formData.append('totalChunks', totalChunks);formData.append('chunkNumber', chunkNumber);formData.append('fileName', file.name);const res = await uploadFileProgress(formData);if (res.code !== 200) {throw new Error(res.msg || '上传失败');}uploadedChunks.add(chunkNumber);this.$set(this.uploadProgress, file.name,Math.floor((uploadedChunks.size / totalChunks) * 100));currentChunkSuccess = true;// 所有分片上传完成if (uploadedChunks.size === totalChunks) {const successRes = {code: 200,fileName: res.fileName,url: res.url,};// 清理该文件的上传记录this.uploadedChunksMap.delete(identifier);// 立即移除进度条this.$delete(this.uploadProgress, file.name);this.handleUploadSuccess(successRes, file);return;}} catch (error) {retries++;// if (retries === maxRetries) {//   throw new Error(`分片 ${chunkNumber} 上传失败,已重试 ${maxRetries} 次`);// }const delay = Math.min(baseDelay * Math.pow(2, retries - 1) + Math.random() * 1000, 10000);// this.$message.warning(`分片 ${chunkNumber} 上传失败,${retries}秒后重试...`);await new Promise(resolve => setTimeout(resolve, delay));}}if (!currentChunkSuccess) {throw new Error(`分片 ${chunkNumber} 上传失败`);}}} catch (error) {// 确保在错误时也移除进度条this.$delete(this.uploadProgress, file.name);this.uploadedChunksMap.delete(identifier); // 清理上传记录this.$modal.closeLoading();this.$modal.msgError(error.message || '上传文件失败,请重试');}},},
};
</script><style scoped lang="scss">
.upload-file-uploader {margin-bottom: 5px;
}
.upload-file-list .el-upload-list__item {border: 1px solid #e4e7ed;line-height: 2;margin-bottom: 10px;position: relative;
}
.upload-file-list .ele-upload-list__item-content {display: flex;justify-content: space-between;align-items: center;color: inherit;
}
.ele-upload-list__item-content-action .el-link {margin-right: 10px;
}.upload-progress {margin: 10px 0;padding: 8px 12px;background-color: #f5f7fa;border-radius: 4px;.progress-info {display: flex;justify-content: space-between;align-items: center;margin-bottom: 8px;.file-name {color: #606266;font-size: 14px;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;max-width: 80%;}.percentage {color: #409eff;font-size: 13px;font-weight: 500;}}.el-progress {margin-bottom: 4px;}
}
</style>

7.2使用说明

<FileUpload v-model="fileUrls":limit="3":fileSize="10":fileType="['pdf','docx']"
/>

该组件为Vue应用提供了一个可靠的大文件上传解决方案,结合分块、断点续传和进度显示,显著提升了用户体验和上传成功率。适合集成到需要处理大文件或弱网环境的系统中

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/78800.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

「逻辑推理」AtCoder AT_abc401_d D - Logical Filling

前言 这次的 D 题出得很好&#xff0c;不仅融合了数学逻辑推理的知识&#xff0c;还有很多细节值得反复思考。虽然通过人数远高于 E&#xff0c;但是通过率甚至不到 60%&#xff0c;可见这些细节正是出题人的侧重点。 题目大意 给定一个长度为 N N N 的字符串 S S S&#…

腾讯后台开发 一面

一、手撕 合并升序链表 合并两个排序的链表_牛客题霸_牛客网 顺时针翻转矩阵 顺时针旋转矩阵_牛客题霸_牛客网 二、八股 1、静态变量和实例变量 public class House {public static String buildDate "2024-10-27"; // 静态变量public String color; // 实…

Unity 动画

Apply Root Motion 勾选的话就会使用动画片段自带的位移 Update Mode &#xff08;动画重新计算骨骼位置转向缩放的数值&#xff09;&#xff1a; Normal &#xff1a; 随Update走&#xff0c;每次Update都计算Animate Physics &#xff1a;与 fixed Update() 同步&#xff0…

NDT和ICP构建点云地图 |【点云建图、Ubuntu、ROS】

### 本博客记录学习NDT&#xff0c;ICP构建点云地图的实验过程&#xff0c;参考的以下两篇博客&#xff1a; 无人驾驶汽车系统入门&#xff08;十三&#xff09;——正态分布变换&#xff08;NDT&#xff09;配准与无人车定位_settransformationepsilon-CSDN博客 PCL中点云配…

基于HTML + jQuery + Bootstrap 4实现(Web)地铁票价信息生成系统

地铁票价信息表生成系统 1. 需求分析 1.1 背景 地铁已经成为大多数人出行的首选,北京地铁有多条运营线路, 截至 2019 年 12 月,北京市轨道交通路网运营线路达 23 条、总里程 699.3 公里、车站 405 座。2019 年,北京地铁年乘客量达到 45.3 亿人次,日均客流为 1241.1 万人次…

EtherNet/IP 转 Modbus 协议网关

一、产品概述 1.1 产品用途 SG-EIP-MOD-210 网关可以实现将 Modbus 接口设备连接到 EtherNet/IP 网 络中。用户不需要了解具体的 Modbus 和 EtherNet/IP 协议即可实现将 Modbus 设 备挂载到 EtherNet/IP 接口的 PLC 上&#xff0c;并和 Modbus 设备进行数…

PostgreSQL:逻辑复制与物理复制

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…

腾讯云COS与ZKmall 开源商城的存储集成方案

ZKmall 开源商城与腾讯云对象存储&#xff08;COS&#xff09;的集成&#xff0c;可通过云端资源托管、自动化数据同步、高性能存储架构实现本地存储负载降低与访问效率提升。以下是基于搜索结果的集成路径与核心优化点&#xff1a; 一、存储架构升级&#xff1a;本地与云端协同…

HTML — 浮动

浮动 HTML浮动&#xff08;Float&#xff09;是一种CSS布局技术&#xff0c;通过float: left或float: right使元素脱离常规文档流并向左/右对齐&#xff0c;常用于图文混排或横向排列内容。浮动元素会紧贴父容器或相邻浮动元素的边缘&#xff0c;但脱离文档流后可能导致父容器高…

【软件测试学习day1】软件测试概念

前言 本篇学习&#xff0c;测试相关基础概念、常见的开发模型测和测试模型&#xff0c;搞懂4个问题&#xff1a; 什么是需求什么是 bug什么是测试用例开发模型和测试模型 目录 1. 什么是需求 1.1 为什么要有需求 1.2 测试人员眼里的需求 1.3 如何深入了解需求 2. 测试用例…

Flutter常用组件实践

Flutter常用组件实践 1、MaterialApp 和 Center(组件居中)2、Scaffold3、Container(容器)4、BoxDecoration(装饰器)5、Column(纵向布局)及Icon(图标)6、Column/Row(横向/横向布局)+CloseButton/BackButton/IconButton(简单按钮)7、Expanded和Flexible8、Stack和Po…

刘火良FreeRTOS内核实现与应用学习之7——任务延时列表

在《刘火良FreeRTOS内核实现与应用学习之6——多优先级》的基础上&#xff1a;关键是添加了全局变量&#xff1a;xNextTaskUnblockTime &#xff0c;与延时列表&#xff08;xDelayedTaskList1、xDelayedTaskList2&#xff09;来高效率的实现延时。 以前需要在扫描就绪列表中所…

图像预处理-插值方法

一.插值方法 当我们对图像进行缩放或旋转等操作时&#xff0c;需要在新的像素位置上计算出对应的像素值。 而插值算法的作用就是根据已知的像素值来推测未知位置的像素值。 1.1 最近邻插值 CV2.INTER_NEAREST 其为 warpAffine() 函数的参数 flags 的其一&#xff0c;表示最近…

智能配电保护:公共建筑安全的新 “防火墙”

安科瑞刘鸿鹏 摘要 随着城市建筑体量的不断增长和电气设备的广泛使用&#xff0c;现代建筑大楼的用电安全问题日益突出。传统配电方式面临监测盲区多、响应滞后、火灾隐患难发现等问题。为提升建筑电气系统的安全性和智能化水平&#xff0c;智慧用电系统应运而生。本文结合安…

如何解决DDoS攻击问题 ?—专业解决方案深度分析

本文深入解析DDoS攻击面临的挑战与解决策略&#xff0c;提供了一系列防御技术和实践建议&#xff0c;帮助企业加强其网络安全架构&#xff0c;有效防御DDoS攻击。从攻击的识别、防范措施到应急响应&#xff0c;为网络安全工作者提供了详细的操作指引。 DDoS攻击概览&#xff1a…

构建灵活的接口抽象层:支持多种后端数据存取的实战指南

构建灵活的接口抽象层:支持多种后端数据存取的实战指南 引言 在现代软件开发中,数据存取成为业务逻辑的核心组成部分。然而,由于后端数据存储方式的多样性(如关系型数据库、NoSQL数据库和文件存储),如何设计一套能够适配多种后端数据存取的接口抽象层,成为技术团队关注…

OpenCV 图形API(23)图像和通道合成

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 1.算法描述 在OpenCV的G-API模块中&#xff0c;图像和通道合成&#xff08;composition&#xff09;函数允许用户对图像进行复杂的操作&#xff0c;如合并…

帝国cms导航淘客新闻下载多功能网站源码 二次元风格自适应附教程

一、本模板使用帝国cms7.5 utf8版本&#xff0c;二次元导航新闻下载工具淘客自适应响应式帝国cms模板。 1、网站后台有3个系统模型&#xff0c;新闻系统模型&#xff0c;下载系统模型&#xff0c;导航系统模型&#xff0c;商城系统模型&#xff0c;可以根据自己的需求不同&…

本地部署大模型(ollama模式)

分享记录一下本地部署大模型步骤。 大模型应用部署可以选择 ollama 或者 LM Studio。本文介绍ollama本地部署 ollama官网为&#xff1a;https://ollama.com/ 进入官网&#xff0c;下载ollama。 ollama是一个模型管理工具和平台&#xff0c;它提供了很多国内外常见的模型&…

C# virtual 和 abstract 详解

简介 在 C# 中&#xff0c;virtual 和 abstract 关键字都用于面向对象编程中的继承和多态&#xff0c;它们主要用于方法、属性和事件的定义&#xff0c;但在用法上存在一些重要的区别。 virtual 关键字 virtual 表示可重写的方法&#xff0c;但可以提供默认实现&#xff0c;…