• 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博客

    在路由文件中新增一个接口

    1. //上传文件
    2. router.post('/fileUpload', handler.fileUp)

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

    其实后端这次的代码和上一篇中的上传图片代码相差不大,无非就是进行一些文件大小控制,返回相应等,不过我新增了个保留原始的文件名的方法,用的是fs模块中的重命名方法。

     其中上传的路径放在了public下的file文件夹中了,这个你们可以根据自己的喜好进行更改。

    1. exports.fileUp = (req, res, next) => {
    2. //上传大小小于5Mb的文件
    3. //接收数据
    4. const form = formidable({
    5. multiples: true,
    6. uploadDir: path.join(__dirname, '../../public/file'),
    7. keepExtensions: true
    8. })
    9. form.parse(req, (err, fields, files) => {
    10. if (err) {
    11. next(err)
    12. return
    13. }
    14. //限制上传文件的大小
    15. if (files.file.size > 1024 * 1024 * 5) {
    16. //删除对应的文件
    17. const folderPath = path.join(__dirname, '../../public/file/' + files.file.newFilename) // 文件路径
    18. fs.unlinkSync(folderPath)
    19. res.send({
    20. code: 400,
    21. msg: '上传文件过大'
    22. })
    23. return
    24. }
    25. //修改保存文件的默认name
    26. const folderPath = path.join(__dirname, '../../public/file/' + files.file.newFilename) // 文件路径
    27. let newName = path.join(__dirname, '../../public/file/' + files.file.originalFilename)
    28. //对读取的文件进行重命名
    29. console.log(newName)
    30. fs.rename(folderPath, newName, (err) => {
    31. if (err) {
    32. console.log(err)
    33. return
    34. } else {
    35. console.log('重命名成功')
    36. res.send({
    37. code: 200,
    38. msg: '上传成功'
    39. })
    40. }
    41. })
    42. })
    43. }

    这就是接口函数了

    上一篇中漏讲了一个地方,就是路由要在app.js中进行注册,否则该接口是调用不了的,不过有nodejs基础的应该都能想到这个问题了。

    这样注册以后接口就可以正常访问了,不过注意我这里接口带了/api,所以在前端调用时记得带上/api。 

    2.前端实现

    2.1 实现静态结构

    我想到的是这样一种效果,可以显示文件名,文件的大小,文件的上传状态等等信息

    不过文件的状态这里我做了三种显示效果,是 准备上传--> 上传进度条 --> 上传成功(上传失败),table表格用的是Element-plus的 el-table组件,这里的进度我也没有自己写了,用的是Element-plus的组件,不过你们想自己实现的话也很简单(如果想的话可以自己试试)。

    注意这里的input选择文件框是隐藏的,点击按钮时触发他的点击事件就行。 

    注意这里的input选择框默认是只支持选择单文件的,要想实现多文件选择要加上multiple属性

    1. <style lang="scss" scoped>
    2. p {
    3. font-size: 14px;
    4. color: red;
    5. margin: 10px 0;
    6. }
    7. style>

    2.2 整合上传文件的数据

    通过基本的静态结构,可以看到table中需要一个tableDatao'n的数据,所以hai'yao我的实现思路是选择上传的文件后将需要用到的数据整合到tableData中供table表格展示,然后还要将上传文件需要用到的formData放在一个数组中保存,在之后上传时再用里面的数据。

    定义好需要用到的数据

    1. import { ref } from 'vue'
    2. //存放文件的数组
    3. const fileList = ref([])
    4. //存放table的数据
    5. const tableData = ref([])
    6. //input框的ref
    7. const fileInputRef = ref(null)

    选择文件后整合数据

    1. //触发文件选择事件
    2. const handleBtnClick = () => {
    3. fileInputRef.value.click()
    4. }
    5. //触发文件选择框
    6. const handleFileClick = (e) => {
    7. //遍历选中的所有文件添加到数组中
    8. for (let i = 0; i < e.target.files.length; i++) {
    9. let selectedFile = e.target.files[i]
    10. //将数据整合起来放进数组中
    11. tableData.value.push({
    12. id: tableData.value.length,
    13. name: selectedFile.name,
    14. //判断文件大小,大于0.1mb使用mb,否则使用kb
    15. size: selectedFile.size > 1024 * 1024 ? (selectedFile.size / 1024 / 1024).toFixed(2) + 'mb' : (selectedFile.size / 1024).toFixed(2) + 'kb',
    16. status: '准备上传'
    17. })
    18. fileList.value.push(selectedFile)
    19. }
    20. }

    删除文件的函数

    1. //删除选中的文件
    2. const handleDelete = (index, row) => {
    3. console.log(index)
    4. //根据index和id进行对比,删除其中的元素
    5. tableData.value.splice(index, 1)
    6. fileList.value.splice(index, 1)
    7. }

    2.3 实现一键上传文件

    因为我给table中的数据加了status的文件上传的状态,所以我在点击一键上传后,主要使用该字段来进行判断该上传第几个文件,这里会定义一个上传文件的index索引,然后采用递归的方式循环上传所有的文件,其实一键上传也是一个一个文件的上传。

     定义好需要用到的数据

    1. //正在上传的文件的index
    2. const fileIndex = ref(0)
    3. //一键上传文件
    4. const handleUpload = () => {
    5. fileIndex.value = 0
    6. //先遍历所有的循环找到没有上传的文件的index
    7. tableData.value.forEach((item) => {
    8. if (item.status == '上传成功' || item.status == '上传失败') fileIndex.value++
    9. })
    10. if (tableData.value.length == fileIndex.value) {
    11. return
    12. }
    13. tableData.value[fileIndex.value].status = '正在上传'
    14. //调用上传文件的函数
    15. uploadFile(fileList.value[fileIndex.value]).then((res) => {
    16. if (res) {
    17. tableData.value[fileIndex.value].status = '上传成功'
    18. //重新调用函数
    19. let timer = setTimeout(() => {
    20. //进度条归0
    21. percentage.value = 0
    22. handleUpload()
    23. clearTimeout(timer)
    24. }, 1000)
    25. } else {
    26. //返回的是一个promise
    27. tableData.value[fileIndex.value].status = '上传失败'
    28. }
    29. })
    30. }

    上传文件的函数

    因为这里需要做一个实时的上传文件的进度条显示,然后Element-plus的进度条组件中绑定了一个元素来控制其进度条显示,这里需要获取到真实的上传进度,因为我用的是axios封装的请求,所以可以使用其中的一个回调来获取进度,不过原生的axaj也是可以获取的,这个根据自己的项目来

    1. //进度条
    2. const percentage = ref(0)
    1. //导入axios构造的函数
    2. import { http} from '@/api/http.js'
    3. //上传文件的函数
    4. const uploadFile = async (value) => {
    5. // 创建一个FormData对象来包装文件
    6. const formData = new FormData()
    7. formData.append('file', value)
    8. try {
    9. const res = await http.post('/api/fileUpload', formData, {
    10. headers: {
    11. 'Content-Type': 'multipart/form-data'
    12. },
    13. //监听进度
    14. onUploadProgress: (progressEvent) => {
    15. //进度条
    16. const loaded = progressEvent.loaded
    17. const total = progressEvent.total
    18. const percentCompleted = Math.round((loaded * 100) / total)
    19. //在这里改变进度条的值
    20. percentage.value = percentCompleted
    21. console.log(`上传进度: ${percentCompleted}%`)
    22. }
    23. })
    24. // 等待 Promise 解析
    25. console.log(res)
    26. // 根据 Promise 解析的结果来判断上传是否成功
    27. if (res.code === 200) {
    28. ElMessage({
    29. type: 'success',
    30. message: '上传成功'
    31. })
    32. return true
    33. } else {
    34. ElMessage({
    35. type: 'error',
    36. message: res.msg
    37. })
    38. return false
    39. }
    40. } catch (error) {
    41. return false
    42. }
    43. }

    现在一键上传文件已经完成了,并且可以实时的显示上传的进度,我们现在可以测试一下。

    为了方便的看到上传进度,我们可以将浏览器的网络调低一点(不过这样做了就要把请求拦截器中的超时设置的长一点),设置个30s应该差不多

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

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

    2.4 取消上传 

    在axios中要想取消上传(取消请求),需要用到cancel token,详情可以查看这个axios中文文档|axios中文网 | axios

     在请求拦截器中定义一个token并导出在组件中使用

    1. //axios请求拦截器
    2. import axios from 'axios'
    3. import { ElMessage } from 'element-plus'
    4. const http = axios.create({
    5. baseURL: 'http://127.0.0.1:3000',
    6. timeout: 30000
    7. })
    8. // 创建一个 Cancel Token 对象
    9. const cancelSource = axios.CancelToken.source()
    10. //请求拦截器
    11. http.interceptors.request.use((config) => {
    12. config.cancelToken = cancelSource.token
    13. return config
    14. })
    15. //返回拦截器
    16. http.interceptors.response.use(
    17. (response) => {
    18. return response.data
    19. },
    20. //失败回调
    21. (error) => {
    22. ElMessage({
    23. type: 'error',
    24. message: error.message
    25. })
    26. return Promise.reject(error)
    27. }
    28. )
    29. export { http, cancelSource }

    组件中点击取消按钮触发该函数,不过之前要导入cancelSource

    import { http, cancelSource } from '@/api/http.js'
    1. //取消发送
    2. const cancelUpload = () => {
    3. console.log('取消发送')
    4. // 取消上传
    5. cancelSource.cancel('请求取消')
    6. }

    不过我这样写会出现一个小bug,就是如果取消上传后就不能再次上传了,因为我的axios发送的post用的都是同一个axios构造出来的实例,关闭后其他的的请求也用不了了,就无法再次上传,解决方法很简单就是直接刷新浏览器即可,或者在发送请求时每次都用axios重新构造一个实例,这样就不会互相受到影响了,不过我偷懒了一波就直接采用了刷新浏览器的方式了,你们如果要做的话可以优化一下这个功能。

    1. const free = () => {
    2. //这里我将开启上传设置成了刷新页面,但实际情况下,可以重新创建axios请求示例,其他的请求就不会受到影响了,有兴趣的可以自己实现一下该功能
    3. location.reload()
    4. }

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

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

  • 相关阅读:
    【C++】线程库
    强制20天内开发APP后集体被裁,技术负责人怒用公司官微发文:祝“早日倒闭”
    【ThreadLocal为什么可能内存泄漏?】 —— 每天一点小知识
    Allegro阻抗分析指导书
    内网 Ubuntu 20.04 搭建 docusaurus 项目(或前端项目)的环境(mobaxterm、tigervnc、nfs、node)
    【eBPF-03】进阶:BCC 框架中 BPF 映射的应用 v1.0
    媒体宣传如何助力品牌发展
    4款视频号数据分析平台!
    JS 数组的各个方法汇总
    Stable Diffusion算法、结构全流程概述
  • 原文地址:https://blog.csdn.net/m0_64642443/article/details/133825796