Vue3 + Nodejs 实战 ,文件上传项目--实现文件批量上传(显示实时上传进度)

news2024/11/25 13:15:20

目录

技术栈

 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
    }
    //修改保存文件的默认name
    const 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,否则使用kb
      size: 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
  //先遍历所有的循环找到没有上传的文件的index
  tableData.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(() => {
        //进度条归0
        percentage.value = 0
        handleUpload()
        clearTimeout(timer)
      }, 1000)
    } else {
      //返回的是一个promise
      tableData.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.loaded
        const total = progressEvent.total
        const percentCompleted = Math.round((loaded * 100) / total)
        //在这里改变进度条的值
        percentage.value = percentCompleted
        console.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.token
  return 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()
}

到此该一键上传文件并且实时显示进度条的功能就实现了。

下一篇是完成大文件的切片上传,敬请关注等候。

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

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

相关文章

城市消防无人机控制系统的设计

目录 摘 要......................................................................................................................... 2 第一章 绪论.............................................................................................................…

Qt 5.12.12 静态编译(MinGW)

前置准备 系统环境 版本 Windows 11 专业版 版本 22H2 安装日期 ‎2023/‎6/‎18 操作系统版本 22621.2428 体验 Windows Feature Experience Pack 1000.22674.1000.0依赖工具 gcc Qt 5.12.12 安装 MinGW 后自动安装 https://download.qt.io/archive/qt/5.12/5.12.12/qt-ope…

【C/C++数据结构 - 2】:稳定性与优化揭秘,揭开插入排序、希尔排序和快速排序的神秘面纱!

文章目录 排序的稳定性插入排序插入排序的优化 希尔排序快速排序 排序的稳定性 稳定排序&#xff1a;排序前2个相等的数在序列中的前后位置顺序和排序后它们2个的前后位置顺序相同。&#xff08;比如&#xff1a;冒泡、插入、基数、归并&#xff09; 非稳定排序&#xff1a;排…

【Linux】自旋锁 以及 读者写者问题

自旋锁 以及 读者写者问题 一、自旋锁1、其他常见的各种锁2、自旋锁相关的API函数 二、读者写者问题1、读者与写者的关系2、读写锁的API函数3、用伪代码理解读写锁的原理4、读写锁的演示使用 一、自旋锁 1、其他常见的各种锁 悲观锁&#xff1a;在每次取数据时&#xff0c;总是…

Docker 构建Python镜像时,pip使用国内地址的dockerfile模版

一、问题现象 构建镜像时&#xff0c;使用pip命令打包报错&#xff1a; 二、问题根因 因国内无法访问pip的配置文件中的仓库地址 三、解决办法 这个办法同样适用于&#xff1a;物理机&#xff0c;这个地址是阿里云的 pip config set global.index-url http://mirrors.aliy…

<C++> IO流

C语言的输入与输出 在C语言当中&#xff0c;我们使用最频繁的输入输出方式就是scanf与printf&#xff1a; scanf&#xff1a; 从标准输入设备&#xff08;键盘&#xff09;读取数据&#xff0c;并将读取到的值存放到某一指定变量当中。printf&#xff1a; 将指定的数据输出到…

idea自动封装方法

例如 package com.utils;import java.lang.reflect.Field; import java.sql.*; import java.util.ArrayList; import java.util.List; import java.util.ResourceBundle;/*** author hrui* date 2023/10/13 13:49*/ public class DBUtils {private static ResourceBundle bund…

【网络编程】Linux网络编程基础与实战第二弹——Socket编程

Socket编程套接字概念套接字通讯原理 网络编程接口网络字节序sockaddr数据结构socket函数bind函数listen函数accept函数connect函数 ) Socket编程 套接字概念 Socket本身有“插座”的意思&#xff0c;在Linux环境下&#xff0c;用于表示进程间网络通信的特殊文件类型。本质为…

多机器人三角形编队的实现

文章目录 前言一、机器人编队前的准备二、配置仿真环境2.编写机器人编队.cpp文件 三、三角形编队测试 前言 前阵子一直想要实现多机器人编队&#xff0c;找到了很多开源的编队代码&#xff0c;经过好几天的思索&#xff0c;终于实现了在gazebo环境中的TB3三角形机器人编队。 一…

prostate数据集下载

1. prostatex 下载地址&#xff1a;https://wiki.cancerimagingarchive.net/pages/viewpage.action?pageId23691656 比赛&#xff1a;https://prostatex.grand-challenge.org/ 这个下载的是一个tcia文件&#xff0c;参考这篇文章打开该文件 2. promise12 地址&#xff1a;…

阿里健康大药房七周年峰会:两大变革叠加 风往何处吹

10月11日&#xff0c;2023数字医药产业论坛暨阿里健康大药房7周年活动在杭州举行。 作为一年一度的医药圈峰会&#xff0c;大会现场集聚了数百家全球知名医药健康企业、经济学者、学术智库等各界领袖、专家&#xff0c;针对健康行业新趋势、新技术、新场景分享产业见解和经验&…

Redis HyperLogLog的使用

Redis HyperLogLog知识总结 一、简介二、使用 一、简介 Redis HyperLogLog是一种数据结构&#xff0c;用于高效地计算基数&#xff08;集合中唯一元素的数量&#xff09;。它的主要作用是用于在内存中高效地存储和计算大量数据的基数&#xff0c;而无需完全存储所有的数据。Hy…

XMind思维导图软件forMac/win:让你的大脑更高效地运转

XMind 是一款非常实用的思维导图软件&#xff0c;它可以帮助用户更好地组织思维、提高工作效率。 您是否曾经遇到过这样的问题&#xff1a;在工作中需要处理大量的信息、任务和项目&#xff0c;但却又不知道该如何下手&#xff1f;这种情况很常见&#xff0c;但是&#xff0c;…

简单好用的解压缩软件:keka 中文 for mac

Keka是一款功能全面、易于使用的文件压缩和解压缩软件&#xff0c;为Mac用户提供了便捷的文件管理工具。它支持多种压缩格式&#xff0c;具有快速解压和强大的压缩功能&#xff0c;让您能够轻松地处理各种文件压缩需求。 隐私非常重要 安全共享只需设置密码并创建高度加密的文…

虚幻引擎:如何实现骨骼重定向

前言&#xff1a; 为什么需要做骨骼重定向&#xff0c;因为当前角色素材没有对应的动画&#xff0c;这时候我们可以找个身高体型差不多的带有动画素材的另一个角色来做重定向&#xff0c;这样我们就可以得到我们需要的动画素材了。 1.首先创建两个骨骼的IK绑定 2.然后给两个骨骼…

Java Kids-百倍提速【Mac IOS】

引言&#xff1a;当今社会&#xff0c;创新和提升效率已经成为了大家普遍的追求。无论是个人生活还是企业经营&#xff0c;我们都希望能够以更高的效率完成任务&#xff0c;节省时间和资源。因此&#xff0c;提速成为了一种时代的要求&#xff0c;而"Java Kids 百倍提速&q…

Hadoop3教程(四):HDFS的读写流程及节点距离计算

文章目录 &#xff08;55&#xff09;HDFS 写数据流程&#xff08;56&#xff09; 节点距离计算&#xff08;57&#xff09;机架感知&#xff08;副本存储节点选择&#xff09;&#xff08;58&#xff09;HDFS 读数据流程参考文献 &#xff08;55&#xff09;HDFS 写数据流程 …

SpringBoot+原生HTML+MySQL开发的电子病历系统源码

电子病历系统源码 电子病历编辑器源码 云端SaaS服务 电子病历系统&#xff0c;采用 “所见即所得、一体化方式”&#xff0c;协助医生和护士准确、标准、快捷实现病历书写、修改、审阅、打印、体温单浏览、医嘱管理等&#xff0c;是提供病历快速简洁化完成的一系列综合型医生病…

MyCat分片水平拆分

场景 在业务系统中 , 有一张表 ( 日志表 ), 业务系统每天都会产生大量的日志数据 , 单台服务器的数据存 储及处理能力是有限的 , 可以对数据库表进行拆分。 准备 准备三台服务器&#xff0c;具体的结构如下&#xff1a; 并且&#xff0c;在三台数据库服务器中分表创建一…

启山智软/JAVA商城

一、项目介绍 启山网上商城采用目前流行的JAVA spring cloud架构开发&#xff0c;前端使用的是目前最流行的TypeScript、VUE3、uniapp、element-plus、pinia技术&#xff0c;后端采用的是JAVA、SpringBoot、spring cloud技术&#xff0c;数据库采用的是MSQ&#xff0c;采用前后…