Vue+el-upload配置minIO实现大文件的切片并发上传、上传进度展示、失败重试功能

vue3+el-upload实现切片上传

效果图

初始界面
在这里插入图片描述
上传中的界面
在这里插入图片描述
上传完成的界面
在这里插入图片描述
上传失败的界面
在这里插入图片描述

<template><div><el-uploadclass="BigFileUpload"ref="uploadRef"action="#"drag:show-file-list="false":on-change="handleFileChange":on-exceed="handleExceed":on-error="handleError":http-request="handleUpload":limit="fileLimit":file-list="fileList"><div:class="['BigFileUpload-text',fileList.length > 0 ? 'BigFileUpload-text-hasFileList' : 'BigFileUpload-text-noFileList']"><img class="el-upload__img" src="@/assets/newUI/icon-upload.png" /><div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div></div><template #tip><div class="el-upload__tip">导入规则:只允许上传大小不超过{{ fileSize / 1024 }}GB的文件。</div></template></el-upload><div:class="['BigFileUpload-list',fileList.length > 0 ? 'BigFileUpload-list-hasFileList' : 'BigFileUpload-list-noFileList']"><div class="fileList" v-for="(file, index) in fileList" :key="index"><el-image fit="scale-down" :src="computedFileIcon(file)" class="image file-image"></el-image><div class="file-name">{{ file.name }}</div><div class="file-progress"><div v-if="fileStatus === 'fail'" class="fail"><span class="text">上传失败!</span><span class="btn" @click="handleReTry(file)">点击重试</span></div><div v-if="fileStatus === 'success'" class="success"><span>100%上传完成</span><el-icon @click="handleRemove(file)"><Delete /></el-icon></div><div v-if="fileStatus === 'loading'" class="uploading"><span>{{ percentage }}%上传中</span></div></div></div></div></div>
</template><script setup>
import { ref, getCurrentInstance } from 'vue'
import { watch } from 'vue-demi'
import axios from 'axios'
import SparkMD5 from 'spark-md5'
import { getToken } from '@/utils/auth'
import { getUserAgentInfo } from '@/api/login'
import box from '@/assets/images/knowdge/box.png'
import jpg from '@/assets/images/knowdge/image.png'
import word from '@/assets/images/knowdge/DOC.png'
import xslx from '@/assets/images/knowdge/XLS.png'
import pdf from '@/assets/images/knowdge/PDF.png'
import ppt from '@/assets/images/knowdge/PPT.png'
import video from '@/assets/images/knowdge/video.png'
import mp3 from '@/assets/images/knowdge/video.png'
import zip from '@/assets/images/knowdge/zip.png'
import other from '@/assets/images/knowdge/file.png'
import txt from '@/assets/images/knowdge/TXT.png'
const imgList = {ppt: ppt,doc: word,docx: word,xlsx: xslx,xls: xslx,pdf: pdf,pdfx: pdf,zip: zip,rar: zip,jpg: jpg,jpeg: jpg,png: jpg,webp: jpg,mp3: mp3,mp4: video,txt: txt
}
let timeStamp = 0
const props = defineProps({chunkSize: {type: Number,default: 10 // 默认分片大小 10MB},actionURL: {type: String,required: true // 文件上传接口地址},fileLimit: {type: Number,default: 1},MAX_REQUEST: {// 最大并发数type: Number,default: 5},maxRetries: {// 重试次数type: Number,default: 2},// 大小限制(MB)fileSize: {type: Number,default: 200},// 大小限制(MB)fileListArr: {type: Array,default: []}
})
const { proxy } = getCurrentInstance()
const emit = defineEmits(['upload-success', 'upload-remove'])
const fileList = ref([])
const file = ref(null) // 当前上传的文件
const fileMd5 = ref('') // 文件的 MD5 值
const fileName = ref('') // 文件名称
const fileType = ref('') // 文件类型,带".",例如 :  .mp3
const totalChunks = ref(0) // 文件的分片总数量
const uploadedChunks = ref([]) // 已上传的分片索引
const requestPool = ref([]) // 文件请求池
let chunkCountList = ref([]) // 选择的文件切片总数数组
let MAX_REQUEST = props.MAX_REQUEST // 最大请求数
let httpRequestParams = {}
const percentage = ref(0) // 进度
const fileStatus = ref('') // 当前上传的文件状态loading上传中,success上传成功,fail上传失败
let isAbortRequest = false // 是否放弃请求
// 计算文件类型
const computedFileIcon = fileItem => {const index = fileItem.name.lastIndexOf('.')const ext = fileItem.name.substr(index + 1)return imgList[ext] || other
}
// 计算文件的 MD5
const calculateFileMd5 = file => {return new Promise(resolve => {const reader = new FileReader()const spark = new SparkMD5.ArrayBuffer()reader.onload = e => {spark.append(e.target.result)resolve(spark.end())}reader.readAsArrayBuffer(file)})
}
// 文件选择回调
const handleFileChange = async uploadFile => {console.log(123)// 校验网络状态// if (!navigator.onLine) {//   this.$message.error('请检查网络连接');//   return false; // 阻止上传// }// 校验文件大小if (props.fileSize) {const isLt = file.size / 1024 / 1024 > props.fileSizeif (isLt) {console.log('上传文件已超过4G!')return false}}const index = uploadFile.name.lastIndexOf('.')const ext = uploadFile.name.substr(index + 1)// 文件file.value = uploadFile.raw// 文件名称fileName.value = uploadFile.name// 文件类型fileType.value = ext
}
// 开始上传
const startUpload = async () => {if (!file.value) {alert('请先选择文件!')return}uploadedChunks.value = []isAbortRequest = falsepercentage.value = 0fileStatus.value = 'loading'// 分片上传totalChunks.value = Math.ceil(file.value.size / (props.chunkSize * 1024 * 1024))// 计算文件的 MD5fileMd5.value = await calculateFileMd5(file.value)sliceFile()
}
// 将文件切片
const sliceFile = () => {// 用一个数组保存,一个文件切出来的总数chunkCountList.value.push(totalChunks.value)for (let i = 0; i < totalChunks.value; i++) {const size = props.chunkSize * 1024 * 1024const chunk = file.value.slice(i * size, (i + 1) * size) // 获取切片requestPool.value.push({ chunk, index: i }) // 加入请求池}timeStamp = new Date().getTime()processPool() // 处理请求池
}
// 处理请求池中的切片上传
const processPool = () => {while (requestPool.value.length > 0 && MAX_REQUEST > 0 && !isAbortRequest) {// 取出一个切片const { chunk, index } = requestPool.value.shift()// 接口切片uploadChunk(chunk, index).then(() => {if (requestPool.value.length > 0) {processPool() // 继续处理请求池}}).finally(() => {MAX_REQUEST++ // 释放一个请求槽})MAX_REQUEST-- // 占用一个请求槽}
}
// 上传分片
const uploadChunk = async (chunk, chunkIndex, retries = 0) => {const formData = new FormData()formData.append('fileName', fileName.value)formData.append('fileMD5', fileMd5.value)formData.append('chunkIndex', chunkIndex)formData.append('totalIndex', totalChunks.value)formData.append('file', chunk)formData.append('type', `.${fileType.value}`)formData.append('timeStamp', timeStamp)try {await axios.post(props.actionURL, formData, {headers: {'Content-Type': 'multipart/form-data',Authorization: 'Bearer ' + getToken(),Clientid: getUserAgentInfo()}}).then(res => {uploadedChunks.value.push(chunkIndex)if (res.data.data.status === 3) {console.log('上传完成:')httpRequestParams.onSuccess(res.data)percentage.value = 100fileStatus.value = 'success'emit('upload-success', res.data.data) // 通知父组件上传成功}if (res.data.data.status === 2) {const percent = (uploadedChunks.value.length / totalChunks.value) * 100percentage.value = Math.round(percent)fileStatus.value = 'loading'}if (res.data.data.status === -2) {// 接口出现错误-2console.log('后端合并失败-2:', res.data)isAbortRequest = truerequestPool.value.length = 0MAX_REQUEST = props.MAX_REQUESTfileStatus.value = 'fail'proxy.$refs.uploadRef.onError(new Error(`后端合并失败-2!`))httpRequestParams.onError(new Error(`后端合并失败-2!`))proxy.$refs.uploadRef.abort()throw new Error(`后端合并失败-2!`)}if (res.data.code === 510) {// 后端限流console.log('后端限流:', res.data)isAbortRequest = truerequestPool.value.length = 0props.MAX_REQUESTfileStatus.value = 'fail'httpRequestParams.onError()proxy.$refs.uploadRef.abort()throw new Error(`后端限流!`)}})} catch (error) {// 失败重试// console.log('失败重试:', error)// if (retries < props.maxRetries) {//   console.warn(`分片 ${chunkIndex} 上传失败,正在重试 (${retries + 1}/${props.maxRetries})`)//   await uploadChunk(chunk, chunkIndex, retries + 1) // 重试// } else {isAbortRequest = truerequestPool.value.length = 0MAX_REQUEST = props.MAX_REQUESTfileStatus.value = 'fail'proxy.$refs.uploadRef.abort()httpRequestParams.onError()throw new Error(`分片 ${chunkIndex} 上传失败,重试次数用尽!`)// }}
}
// 文件列表移除文件
const handleRemove = file => {if (props.fileLimit === 1) {proxy.$refs['uploadRef'].clearFiles()fileList.value = []emit('upload-remove')}
}
// 上传失败,重试
const handleReTry = file => {percentage.value = 0uploadedChunks.value = []// 自动触发上传startUpload()
}
// 文件超出个数限制
const handleExceed = () => {proxy.$modal.msgError(`上传文件数量不能超过 ${props.fileLimit} 个!`)
}
// 文件上传失败
const handleError = () => {percentage.value = 0fileStatus.value = 'fail'
}
// 文件上传进度
const handleUpload = async parms => {console.log(parms)httpRequestParams = parms// 文件列表fileList.value = [{name: fileName.value}]console.log(fileList.value, "fileList.value");// 自动触发上传startUpload()
}
watch(() => props.fileListArr,newVal => {if (newVal && newVal.length > 0) {fileList.value = newValfileStatus.value = 'success'}},{ immediate: true }
)
</script><style lang="scss" scoped>
.BigFileUpload {width: 100%;min-width: 440px;:deep .el-upload-dragger {height: 132px;padding: 0;border-radius: 4px 4px 4px 4px;border: 1px dashed #2e75ff;}.BigFileUpload-text {margin-top: 20px;}.el-upload__img {width: 54px;height: 54px;}.el-upload__tip {margin-top: 0;font-family:PingFang SC,PingFang SC;font-weight: 400;font-size: 12px;color: #141d39;line-height: 24px;text-align: left;font-style: normal;text-transform: none;}
}
</style>

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

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

相关文章

Kubespray部署企业级高可用K8S指南

目录 前言1 K8S集群节点准备1.1 主机列表1.2 kubespray节点python3及pip3准备1.2.1. 更新系统1.2.2. 安装依赖1.2.3. 下载Python 3.12源码1.2.4. 解压源码包1.2.5. 编译和安装Python1.2.6. 验证安装1.2.7. 设置Python 3.12为默认版本&#xff08;可选&#xff09;1.2.8. 安装pi…

无人机端部署 AI 模型,实现实时数据处理和决策

在无人机端部署 AI 模型&#xff0c;实现实时数据处理和决策&#xff0c;是提升无人机智能化水平的关键技术之一。通过将 AI 模型部署到无人机上&#xff0c;可以实现实时目标检测、路径规划、避障等功能。以下是实现这一目标的详细方案和代码示例。 一、实现方案 1. 硬件选择…

审批流AntV框架蚂蚁数据可视化X6饼图(注释详尽)

大家好&#xff0c;这次使用的是AntV的蚂蚁数据可视化X6框架&#xff0c;类似于审批流的场景等&#xff0c;代码如下&#xff1a; X6框架参考网址&#xff1a;https://x6.antv.vision/zh/examples/showcase/practices#bpmn 可以进入该网址&#xff0c;直接复制下方代码进行调试…

用于管理 Elasticsearch Serverless 项目的 AI Agent

作者&#xff1a;来自 Elastic Fram Souza 由自然语言驱动的 AI 代理&#xff0c;可轻松管理 Elasticsearch Serverless 项目 - 支持项目创建、删除和状态检查。 这个小型命令行工具让你可以用简单的英语管理你的无服务器 Elasticsearch 项目。它通过AI&#xff08;这里是 Ope…

通过计费集成和警报监控 Elasticsearch Service 成本

作者&#xff1a;来自 Elastic Alexis Charveriat 使用 Elasticsearch 服务计费集成来跟踪、定制和提醒 Elasticsearch 服务费用。 监控和管理你的Elasticsearch服务&#xff08;ESS&#xff09;使用情况和成本对高效运营至关重要。 Elasticsearch服务计费集成提供了一种简化的…

【第12节】C++设计模式(结构型模式)-Proxy(代理)模式

一、问题背景 使用 Proxy 模式优化对象访问 在某些情况下&#xff0c;直接访问对象可能会导致性能问题或安全性问题。Proxy 模式&#xff08;代理模式&#xff09;通过引入一个代理对象来控制对原始对象的访问&#xff0c;从而解决这些问题。以下是几种典型的应用场景&#xf…

​DeepSeek:如何通过自然语言生成HTML文件与原型图?

在当今快节奏的开发与设计环境中&#xff0c;快速生成HTML文件或原型图是每个开发者与设计师的迫切需求。虽然DeepSeek无法直接生成图片&#xff0c;但它却能够通过自然语言生成流程图、原型图以及交互式页面&#xff0c;甚至可以直接输出HTML代码。本文将详细介绍如何与DeepSe…

Python-07PDF转Word

2025-03-04-PDF转Word DeepSeek等大模型从来都不是简单的写一个静态博客这么肤浅&#xff08;太多博主都只讲这个内容了&#xff09;借助全网大神的奇思妙想&#xff0c;拓展我狭隘的思维边界。 文章目录 2025-03-04-PDF转Word [toc]1-参考网址2-学习要点3-核心逻辑4-核心代码 …

【全栈开发】---- 一文掌握 Websocket 原理,并用 Django 框架实现

目录 介绍 底层原理 握手环节详解&#xff1a; 收发数据&#xff08;加密&#xff09; Django 中配置 channels 1、注册 channels 2、在 settings.py 中添加 asgi_application 3、修改 asgi.py 文件 4、routing 5、consumers 实现 聊天室 介绍 WebSocket是一种先进的通信协议&…

XTDrone+Mavros+Gazebo仿真——配置与控制不同的无人机

参考资料为XTDrone官方文档&#xff1a; 配置与控制不同的无人机 语雀XTDrone目前支持多旋翼飞行器&#xff08;multiroto...https://www.yuque.com/xtdrone/manual_cn/vehicle_config# 1、修改无人机机型为solo 以outdoor3.launch为例&#xff0c;讲解如何配置不同的机型 …

快速熟悉JavaScript

目录 1.js的基本认知 2.js的基本语法 2.1 变量的声明 三个关键字的区别 2.2数据类型 2.2.1 基本数据类型 2.2.2 复杂数据类型 2.3对象的属性和方法 2.3.1属性 2.3.2访问方式 2.4.3动态操作 2.4.4方法 2.4字符串的常用属性和方法 2.5运算符 2.6逻辑控制语句 2.7函…

perl初试

我手头有一个脚本&#xff0c;用于从blastp序列比对的结果文件中&#xff0c;进行文本处理&#xff0c; 获取序列比对最优的hit记录 #!/usr/bin/perl -w use strict;my ($blast_out) ARGV; my $usage "This script is to get the best hit from blast output file wit…

确定 Flutter SDK 及其关联的 Gradle 版本与适配的 JDK 版本

最近在编写 Flutter 项目&#xff0c;发现不同的 Flutter SDK 版本有着不一样的 Gradle 版本&#xff0c;然后不同的 Gradle 版本需要不同的 JDK 版本。只有当三者都一致匹配时&#xff0c;才能正常的完成编译 首先&#xff0c;我们可以确定 Flutter SDK 对应的 Gradle 版本。…

unity6 打包webgl注意事项

webgl使用资源需要异步加载 使用localization插件时要注意&#xff0c;webgl不支持WaitForCompletion&#xff0c;LocalizationSettings.InitializationOperation和LocalizationSettings.StringDatabase.GetTable都不能用 web里想要看到具体的报错信息调试开启这两个&#xf…

Three.js 入门(基础材质、贴图、纹理、环境、遮蔽光、透明度、高光贴图)

本篇主要学习内容 : three常用的几种材质环境贴图、贴图、环境光、遮蔽光、透明度、高光贴图&#xff08;纹理贴图&#xff09; 点赞 关注 收藏 学会了 1.three常用的几种材质 1.1&#xff09; 基础网格材质MeshBasicMaterial、漫反射网格材质MeshLambertMaterial、高光…

【大模型安全】大模型的技术风险

【大模型安全】大模型的技术风险 1.DDoS攻击2.常见的传统网络攻击方式3.恶意意图的识别4.AI生成虚假信息传播5.利用AI进行黑客攻击6.模型对抗攻击7.后门攻击8.Prompt攻击9.数据投毒攻击10.模型窃取攻击11.数据窃取攻击 1.DDoS攻击 2023年11月9日凌晨&#xff0c;OpenAI在官网公…

数据库设计方面如何进行PostgreSQL 17的性能调优?

在数据库设计方面&#xff0c;PostgreSQL 17 的性能调优可以从以下几个方面入手&#xff1a; 表结构设计 选择合适的数据类型&#xff1a;根据数据的实际范围和业务需求&#xff0c;选择占用空间小、查询效率高的数据类型。对于固定长度的字符串&#xff0c;如性别字段&#…

C++学习之路,从0到精通的征途:入门基础

目录 一.C的第一个程序 二.命名空间 1.namespace的价值 2.命名空间的定义 3.命名空间使用 三.C的输入与输出 1.<iostream> 2.流 3.std(standard) 四.缺省参数 1.缺省参数的定义 2.全缺省/半缺省 3.声明与定义 ​五.函数重载 1.参数个数不同 2.参数类型不…

PHP配置虚拟主机

虚拟主机: 不是真实存在的主机, 因为一台电脑理论上讲只能作为一个网站: 事实上,一个网站是一个文件夹. 在本地开发中&#xff0c;通过虚拟主机配置可以实现多域名独立访问不同项目目录&#xff08;如 www.project1.test 和 www.project2.test&#xff09;&#xff0c;以 ”XAM…

linux安装Kafka以及windows安装Kafka和常见问题解决

目录 Linux上安装Kafka 当主题删除不了时&#xff0c;可以进入zookeeper&#xff1a; windows上安装kafka 安装Kafka需要有Zookeeper&#xff0c;或者也可以使用kafka自带的zookeeper Linux上安装Kafka 将kafka的安装包上传到/export/server目录并解压&#xff0c;重命名&…