Vue3 + Nodejs 实战 ,文件上传项目--实现文件批量上传(显示实时上传进度)
目录
技术栈
1.后端接口实现
2.前端实现
2.1 实现静态结构
2.2 整合上传文件的数据
2.3 实现一键上传文件
2.4 取消上传
博客主页:専心_前端,javascript,mysql-CSDN博客
系列专栏:vue3+nodejs 实战--文件上传
前端代码仓库:jiangjunjie666/my-upload: vue3+nodejs 上传文件的项目,用于学习 (github.com)
后端代码仓库:jiangjunjie666/my-upload-server: nodejs上传文件的后端 (github.com)
欢迎关注
在上一篇中,我们创建好了前端Vue3,后端nodejs的项目,并且实现了一个图片上传的功能,地址在: Vue3 + Nodejs 实战 ,文件上传项目--实现图片上传-CSDN博客 ,该篇实现了文件的批量上传并且显示实时的上传进度。
技术栈
前端:Vue3 Vue-router axios element-plus...
后端:nodejs express...
1.后端接口实现
我们先把后端上传文件的接口写好,后端接收文件我用的并不是原生的js,用的是:formidable,所以没看过上一篇创建项目的一定要去看看喔。Vue3 + Nodejs 实战 ,文件上传项目--实现图片上传-CSDN博客
在路由文件中新增一个接口
//上传文件
router.post('/fileUpload', handler.fileUp)

在处理函数文件中编写接口函数

其实后端这次的代码和上一篇中的上传图片代码相差不大,无非就是进行一些文件大小控制,返回相应等,不过我新增了个保留原始的文件名的方法,用的是fs模块中的重命名方法。
其中上传的路径放在了public下的file文件夹中了,这个你们可以根据自己的喜好进行更改。
exports.fileUp = (req, res, next) => {//上传大小小于5Mb的文件//接收数据const form = formidable({multiples: true,uploadDir: path.join(__dirname, '../../public/file'),keepExtensions: true})form.parse(req, (err, fields, files) => {if (err) {next(err)return}//限制上传文件的大小if (files.file.size > 1024 * 1024 * 5) {//删除对应的文件const folderPath = path.join(__dirname, '../../public/file/' + files.file.newFilename) // 文件路径fs.unlinkSync(folderPath)res.send({code: 400,msg: '上传文件过大'})return}//修改保存文件的默认nameconst folderPath = path.join(__dirname, '../../public/file/' + files.file.newFilename) // 文件路径let newName = path.join(__dirname, '../../public/file/' + files.file.originalFilename)//对读取的文件进行重命名console.log(newName)fs.rename(folderPath, newName, (err) => {if (err) {console.log(err)return} else {console.log('重命名成功')res.send({code: 200,msg: '上传成功'})}})})
}
这就是接口函数了
上一篇中漏讲了一个地方,就是路由要在app.js中进行注册,否则该接口是调用不了的,不过有nodejs基础的应该都能想到这个问题了。

这样注册以后接口就可以正常访问了,不过注意我这里接口带了/api,所以在前端调用时记得带上/api。
2.前端实现
2.1 实现静态结构
我想到的是这样一种效果,可以显示文件名,文件的大小,文件的上传状态等等信息

不过文件的状态这里我做了三种显示效果,是 准备上传--> 上传进度条 --> 上传成功(上传失败),table表格用的是Element-plus的 el-table组件,这里的进度我也没有自己写了,用的是Element-plus的组件,不过你们想自己实现的话也很简单(如果想的话可以自己试试)。
注意这里的input选择文件框是隐藏的,点击按钮时触发他的点击事件就行。
注意这里的input选择框默认是只支持选择单文件的,要想实现多文件选择要加上multiple属性
<template><div class="container"><input type="file" ref="fileInputRef" style="display: none" @change="handleFileClick" multiple /><p>不可选中重复的文件</p><el-button type="primary" @click="handleBtnClick">可选择多个文件</el-button><el-table :data="tableData" style="width: 100%"><el-table-column prop="name" label="文件名" width="400" /><el-table-column prop="size" label="文件大小" width="400" /><!-- 控制显示 --><el-table-column label="文件状态" width="400"><template #default="scope1"><span v-if="scope1.row.status == '准备上传'">{{ scope1.row.status }}</span><el-progress :percentage="percentage" stroke-width="8" :width="100" :duration="1" v-if="scope1.row.status == '正在上传'" /><span v-if="scope1.row.status == '上传成功'" style="color: #67c23a">{{ scope1.row.status }}</span><span v-if="scope1.row.status == '上传失败'" style="color: red">{{ scope1.row.status }}</span></template></el-table-column><el-table-column prop="address" label="操作"><template #default="scope3"><el-button size="small" type="danger" @click="handleDelete(scope3.$index, scope3.row)" :disabled="scope3.row.status == '正在上传'">删除</el-button></template></el-table-column></el-table><el-button type="success" style="margin-top: 20px" @click="handleUpload" :disabled="tableData.length == 0">一键上传</el-button><el-button size="default" style="margin-top: 20px" type="danger" @click="cancelUpload">取消上传</el-button><el-button size="default" style="margin-top: 20px" type="danger" @click="free">开启上传</el-button></div>
</template><style lang="scss" scoped>
p {font-size: 14px;color: red;margin: 10px 0;
}
</style>
2.2 整合上传文件的数据
通过基本的静态结构,可以看到table中需要一个tableDatao'n的数据,所以hai'yao我的实现思路是选择上传的文件后将需要用到的数据整合到tableData中供table表格展示,然后还要将上传文件需要用到的formData放在一个数组中保存,在之后上传时再用里面的数据。
定义好需要用到的数据
import { ref } from 'vue'
//存放文件的数组
const fileList = ref([])
//存放table的数据
const tableData = ref([])
//input框的ref
const fileInputRef = ref(null)
选择文件后整合数据
//触发文件选择事件
const handleBtnClick = () => {fileInputRef.value.click()
}//触发文件选择框
const handleFileClick = (e) => {//遍历选中的所有文件添加到数组中for (let i = 0; i < e.target.files.length; i++) {let selectedFile = e.target.files[i]//将数据整合起来放进数组中tableData.value.push({id: tableData.value.length,name: selectedFile.name,//判断文件大小,大于0.1mb使用mb,否则使用kbsize: selectedFile.size > 1024 * 1024 ? (selectedFile.size / 1024 / 1024).toFixed(2) + 'mb' : (selectedFile.size / 1024).toFixed(2) + 'kb',status: '准备上传'})fileList.value.push(selectedFile)}
}
删除文件的函数
//删除选中的文件
const handleDelete = (index, row) => {console.log(index)//根据index和id进行对比,删除其中的元素tableData.value.splice(index, 1)fileList.value.splice(index, 1)
}
2.3 实现一键上传文件
因为我给table中的数据加了status的文件上传的状态,所以我在点击一键上传后,主要使用该字段来进行判断该上传第几个文件,这里会定义一个上传文件的index索引,然后采用递归的方式循环上传所有的文件,其实一键上传也是一个一个文件的上传。
定义好需要用到的数据
//正在上传的文件的index
const fileIndex = ref(0)//一键上传文件
const handleUpload = () => {fileIndex.value = 0//先遍历所有的循环找到没有上传的文件的indextableData.value.forEach((item) => {if (item.status == '上传成功' || item.status == '上传失败') fileIndex.value++})if (tableData.value.length == fileIndex.value) {return}tableData.value[fileIndex.value].status = '正在上传'//调用上传文件的函数uploadFile(fileList.value[fileIndex.value]).then((res) => {if (res) {tableData.value[fileIndex.value].status = '上传成功'//重新调用函数let timer = setTimeout(() => {//进度条归0percentage.value = 0handleUpload()clearTimeout(timer)}, 1000)} else {//返回的是一个promisetableData.value[fileIndex.value].status = '上传失败'}})
}
上传文件的函数
因为这里需要做一个实时的上传文件的进度条显示,然后Element-plus的进度条组件中绑定了一个元素来控制其进度条显示,这里需要获取到真实的上传进度,因为我用的是axios封装的请求,所以可以使用其中的一个回调来获取进度,不过原生的axaj也是可以获取的,这个根据自己的项目来
//进度条
const percentage = ref(0)
//导入axios构造的函数
import { http} from '@/api/http.js'
//上传文件的函数
const uploadFile = async (value) => {// 创建一个FormData对象来包装文件const formData = new FormData()formData.append('file', value)try {const res = await http.post('/api/fileUpload', formData, {headers: {'Content-Type': 'multipart/form-data'},//监听进度onUploadProgress: (progressEvent) => {//进度条const loaded = progressEvent.loadedconst total = progressEvent.totalconst percentCompleted = Math.round((loaded * 100) / total)//在这里改变进度条的值percentage.value = percentCompletedconsole.log(`上传进度: ${percentCompleted}%`)}})// 等待 Promise 解析console.log(res)// 根据 Promise 解析的结果来判断上传是否成功if (res.code === 200) {ElMessage({type: 'success',message: '上传成功'})return true} else {ElMessage({type: 'error',message: res.msg})return false}} catch (error) {return false}
}
现在一键上传文件已经完成了,并且可以实时的显示上传的进度,我们现在可以测试一下。
为了方便的看到上传进度,我们可以将浏览器的网络调低一点(不过这样做了就要把请求拦截器中的超时设置的长一点),设置个30s应该差不多


选择好文件后就可以点击上传了

可以看到上传进度是实时显示的 ,并且从后端中文件夹的图片传输进度也能看出来

2.4 取消上传
在axios中要想取消上传(取消请求),需要用到cancel token,详情可以查看这个axios中文文档|axios中文网 | axios
在请求拦截器中定义一个token并导出在组件中使用
//axios请求拦截器
import axios from 'axios'
import { ElMessage } from 'element-plus'
const http = axios.create({baseURL: 'http://127.0.0.1:3000',timeout: 30000
})// 创建一个 Cancel Token 对象
const cancelSource = axios.CancelToken.source()
//请求拦截器
http.interceptors.request.use((config) => {config.cancelToken = cancelSource.tokenreturn config
})//返回拦截器
http.interceptors.response.use((response) => {return response.data},//失败回调(error) => {ElMessage({type: 'error',message: error.message})return Promise.reject(error)}
)export { http, cancelSource }
组件中点击取消按钮触发该函数,不过之前要导入cancelSource
import { http, cancelSource } from '@/api/http.js'
//取消发送
const cancelUpload = () => {console.log('取消发送')// 取消上传cancelSource.cancel('请求取消')
}
不过我这样写会出现一个小bug,就是如果取消上传后就不能再次上传了,因为我的axios发送的post用的都是同一个axios构造出来的实例,关闭后其他的的请求也用不了了,就无法再次上传,解决方法很简单就是直接刷新浏览器即可,或者在发送请求时每次都用axios重新构造一个实例,这样就不会互相受到影响了,不过我偷懒了一波就直接采用了刷新浏览器的方式了,你们如果要做的话可以优化一下这个功能。
const free = () => {//这里我将开启上传设置成了刷新页面,但实际情况下,可以重新创建axios请求示例,其他的请求就不会受到影响了,有兴趣的可以自己实现一下该功能location.reload()
}
到此该一键上传文件并且实时显示进度条的功能就实现了。
下一篇是完成大文件的切片上传,敬请关注等候。
相关文章:
Vue3 + Nodejs 实战 ,文件上传项目--实现文件批量上传(显示实时上传进度)
目录 技术栈 1.后端接口实现 2.前端实现 2.1 实现静态结构 2.2 整合上传文件的数据 2.3 实现一键上传文件 2.4 取消上传 博客主页:専心_前端,javascript,mysql-CSDN博客 系列专栏:vue3nodejs 实战--文件上传 前端代码仓库:jiangjunjie…...
狂砸40亿美元,亚马逊向OpenAI竞争对手Anthropic投资
9 月 25 日下午,亚马逊在公司官网发布,向大模型公司 Anthropic 投资 40 亿美元, Anthropic以拥有对标 ChatGPT 的谈天机器人 Claude 而出名。 这项新的战略合作将结合双方在更安全的生成式AI领域的技术和专业知识,加速Anthropic未…...
目标检测YOLO实战应用案例100讲-基于YOLOv5_tiny算法的路面裂缝智能检测
目录 前言 国内外研究现状 公路路面裂缝检测方法现状 基于深度学习检测算法现状...
P5682 [CSP-J2019 江西] 次大值% 运算 set 去重的一道好题
#include <bits/stdc.h> using namespace std; int n, x, len, a[100010], ans; set<int> s; set<int>::iterator asd; int main() {/*a[n-1] 是最大的a[n-2] 可能是次大的a[n]%a[n-1]<a[n-1] 不可能是最大的,可能是次大的a[n-1]%a[n-2]<…...
vue3后台管理框架之API接口统一管理
在开发项目的时候,接口可能很多需要统一管理。在src目录下去创建api文件夹去统一管理项目的接口; 参数请参考mock中的模拟接口 //统一管理咱们项目用户相关的接口import request from @/utils/requestimport type { loginForm, loginResponseData, userInfoReponseData } fro…...
线性表的插入、删除和查询操作
线性表的插入、删除和查询操作 1、定义线性表 定义一个线性结构,有列表默认长度设置为50,列表数量 #include <stdio.h> #define MaxSize 50typedef int Element; typedef struct{Element data[MaxSize];int length; }SqList;2、顺序表插入 插入…...
基于深度学习网络的疲劳驾驶检测算法matlab仿真
目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1疲劳检测理论概述 4.2 本课题说明 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 In_layer_Size [227 227 3]; img_size [224,…...
【文件系统】Linux文件系统的基本存储机制
Linux文件系统是Linux操作系统的重要组成部分,它负责管理计算机存储设备上的文件和目录。Linux文件系统采用类Unix的设计,具有强大的性能和可扩展性,支持多种文件系统类型,如ext4、XFS、Btrfs等。在项目存储架构的设计中ÿ…...
Outlook导入导出功能灰色,怎么解决
下载安装 Outlook 软件后,登陆账号,然后选择“文件” - “导出”,结果发现“导出”按钮是灰色的,根本无法导出。根据官方说法:由于配置没有完成或者office产品没有正确激活。outlook导出键为灰色原因由于配置没有完成或…...
Chrome 同站策略(SameSite)问题
问题产生 问题复现: A项目页面使用 iframe 引用了B项目 B项目登录页面输入账号密码后点击登录 无法跳转 尝试解决: 在B项目修改了跳转方式 但无论是 this.$router.push 还是 window.herf 都无法实现跳转在iframe中使用 sandbox 沙箱属性 同样无法实现跳…...
docker搭建nginx+php-fpm
docker run --name nginx -p 8898:80 -d nginx:1.20.2-alpine# 将容器nginx.conf文件复制到宿主机 docker cp nginx:/etc/nginx/nginx.conf /usr/local/nginx/conf/nginx.conf# 将容器conf.d文件夹下内容复制到宿主机 docker cp nginx:/etc/nginx/conf.d /usr/local/nginx/conf…...
数据结构与算法---单调栈结构
数据结构与算法---单调栈结构 1 滑动窗口问题 1 滑动窗口问题 1 滑动窗口问题 由一个代表题目,引出一种结构 【题目】 有一个整型数组 arr 和一个大小为 w 的窗口从数组的最左边滑到最右边,窗口每次向右边滑一个位置。 例如,数组为[4,3,…...
Python爬虫:某书平台的Authorization参数js逆向
⭐️⭐️⭐️⭐️⭐️欢迎来到我的博客⭐️⭐️⭐️⭐️⭐️ 🐴作者:秋无之地 🐴简介:CSDN爬虫、后端、大数据领域创作者。目前从事python爬虫、后端和大数据等相关工作,主要擅长领域有:爬虫、后端、大数据开发、数据分析等。 🐴欢迎小伙伴们点赞👍🏻、收藏⭐️、…...
Android MediaCodec 框架 基于codec2
系列文章的目的是什么? 粗略: 解码需要哪些基础的服务?标准解码的调用流程?各个流程的作用是什么?解码框架的层次?各个层次的作用? 细化: 解码参数的配置?解码输入数…...
【RocketMQ 系列三】RocketMQ集群搭建(2m-2s-sync)
您好,我是码农飞哥(wei158556),感谢您阅读本文,欢迎一键三连哦。 💪🏻 1. Python基础专栏,基础知识一网打尽,9.9元买不了吃亏,买不了上当。 Python从入门到精…...
Go TLS服务端绑定证书的几种方式
随着互联网的发展,网站提供的服务类型和规模不断扩大,同时也对Web服务的安全性提出了更高的要求。TLS(Transport Layer Security)[1]已然成为Web服务最重要的安全基础设施之一。默认情况下,一个TLS服务器通常只绑定一个证书[2],但…...
【算法与数据结构】--高级算法和数据结构--排序和搜索
一、常见排序算法 以下是一些常见的排序算法,包括冒泡排序、选择排序、插入排序、快速排序和归并排序。每种排序算法的讲解以及附带C#和Java示例: 1.1 冒泡排序 (Bubble Sort) 讲解: 冒泡排序是一种简单的比较排序算法。它多次遍历待排序的…...
【Java】jvm 元空间、常量池(了解)
JDK1.8 以前的 HotSpot JVM 有方法区,也叫永久代(permanent generation)方法区用于存放已被虚拟机加载的类信息,常量、静态遍历,即编译器编译后的代码JDK1.7 开始了方法区的部分移除:符号引用(S…...
Spring Boot自动加载
问:自动装配如何实现的? 答:简单来说就是自动去把第三方组件的Bean装载到IOC容器中,不需要开发人员再去写Bean相关的配置,在springboot应用里面只需要在启动类上去加上SpringBootApplication注解,就可以去实…...
MPNN 模型:GNN 传递规则的实现
首先,假如我们定义一个极简的传递规则 A是邻接矩阵,X是特征矩阵, 其物理意义就是 通过矩阵乘法操作,批量把图中的相邻节点汇聚到当前节点。 但是由于A的对角线都是 0.因此自身的节点特征会被过滤掉。 图神经网络的核心是 吸周围…...
基于FPGA的PID算法学习———实现PID比例控制算法
基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容:参考网站: PID算法控制 PID即:Proportional(比例)、Integral(积分&…...
golang循环变量捕获问题
在 Go 语言中,当在循环中启动协程(goroutine)时,如果在协程闭包中直接引用循环变量,可能会遇到一个常见的陷阱 - 循环变量捕获问题。让我详细解释一下: 问题背景 看这个代码片段: fo…...
376. Wiggle Subsequence
376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...
如何为服务器生成TLS证书
TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...
【C语言练习】080. 使用C语言实现简单的数据库操作
080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
MySQL:分区的基本使用
目录 一、什么是分区二、有什么作用三、分类四、创建分区五、删除分区 一、什么是分区 MySQL 分区(Partitioning)是一种将单张表的数据逻辑上拆分成多个物理部分的技术。这些物理部分(分区)可以独立存储、管理和优化,…...
js 设置3秒后执行
如何在JavaScript中延迟3秒执行操作 在JavaScript中,要设置一个操作在指定延迟后(例如3秒)执行,可以使用 setTimeout 函数。setTimeout 是JavaScript的核心计时器方法,它接受两个参数: 要执行的函数&…...
Python第七周作业
Python第七周作业 文章目录 Python第七周作业 1.使用open以只读模式打开文件data.txt,并逐行打印内容 2.使用pathlib模块获取当前脚本的绝对路径,并创建logs目录(若不存在) 3.递归遍历目录data,输出所有.csv文件的路径…...
SOC-ESP32S3部分:30-I2S音频-麦克风扬声器驱动
飞书文档https://x509p6c8to.feishu.cn/wiki/SKZzwIRH3i7lsckUOlzcuJsdnVf I2S简介 I2S(Inter-Integrated Circuit Sound)是一种用于传输数字音频数据的通信协议,广泛应用于音频设备中。 ESP32-S3 包含 2 个 I2S 外设,通过配置…...
