vue项目实现文件下载进度条

本文介绍了一种改进的文件流下载方法,该方法能够实时显示下载进度,提高用户体验。文章详细阐述了如何使用axios结合Vuex管理下载进度,并通过element-ui在前端展示进度条。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

平时业务中下载文件方式常见的有俩种:

第一种,直接访问服务器的文件地址,自动下载文件;

第二种 ,服务器返回blob文件流,再对文件流进行处理和下载。

一般小文件适用于第一种下载方案,不占用过多服务器资源,而对于体积庞大的文件,常常使用文件流的方式进行传输,如图:

文件流传输成功后通过代码可以立即发起浏览器下载该文件流:

这种方式也有弊端,在文件流传输过程中,用户无法感知文件流的传输状态(进度),会造成一些困扰(无法确定当前下载操作是否已经生效)。针对这种情况,我们可以在页面显示文件流的状态和传输进度,提高页面交互性和友好性。

接下来是具体实现:

封装js方法:

/**
 * @param {Object} data: {url: 文件地址, download: 文件名称}
 */
export function downLoadAll(data) {
  let downProgress = {};
  let uniSign = new Date().getTime() + ''; // 可能会连续点击下载多个文件,这里用时间戳来区分每一次下载的文件,也可以用生成器和迭代器实现文件的唯一标识(见下文)
  axios.get(
    data.url, 
    { responseType: 'blob', headers: { "Content-Type": "application/json; charset=utf-8" },
    onDownloadProgress (progress) {
      downProgress = Math.round(100 * progress.loaded / progress.total) // progress对象中的loaded表示已经下载的数量,total表示总数量,这里计算出百分比
      store.commit('caseInformation/SET_PROGRESS', {path: uniSign, 'progress': downProgress}) // 将此次下载的文件名和下载进度组成对象再用vuex状态管理
    }}).then( (res)=>{ // 文件流传输完成后,开启文件下载
      if(data.downLoad){
        jsFileDownload(res.data,data.downLoad+'.'+data.url.replace(/.+\./,"")); // jsFileDownLoad是用来下载文件流的,下载插件:npm i js-file-download,import引入:import jsFileDownLoad from 'js-file-download'
      } else {
        jsFileDownload(res.data, data.url.split('/')[data.url.split('/').length-1]);
      }
  }).catch((e)=>{
    this.$message.error('该文件无法下载')
  })
}

ps:使用迭代器和生成器实现唯一性:

function* IdGenerator() { // 定义生成器IdGenerator
  let id = 0;
  while(true){ //循环生成无线长度的id序列
    yield ++id;      
  }    
}
const idIterator = IdGenerator(); //定义idIterator迭代器
export function downLoadAll(data) {
  let unSign = idIterator.next().value; //调用迭代器的next()方法获取新值
 ...
}

在浏览器中测试:

nice!这里用迭代器和生成器也可以唯一标识文件。有的朋友可能会产生疑问,这里用while死循环不会发生溢出吗?正常情况下是这样的,但在生成器中没问题!当生成器遇到一条yield语句,它会一直挂起执行,直到下次调用next方法,所以每次调用一次next方法,while循环才会迭代一次并返回下一个id值。

store中的caseInfomation.js:

...

const state = {
  progressList: [], // 文件下载进度列表
  ...
}

const mutations = {
  SET_PROGRESS: (state, progressObj)=>{ // 修改进度列表
    if(state.progressList.length){ // 如果进度列表存在
      if(state.progressList.find(item=>item.path == progressObj.path)){ // 前面说的path时间戳是唯一存在的,所以如果在进度列表中找到当前的进度对象
        state.progressList.find(item=>item.path == progressObj.path).progress = progressObj.progress // 改变当前进度对象的progress
      }
    }else{
      state.progressList.push(progressObj) // 当前进度列表为空,没有下载任务,直接将该进度对象添加到进度数组内
    }
  },
  DEL_PROGRESS: (state, props) => {
    state.progressList.splice(state.progressList.findIndex(item=>item.path == props), 1) // 删除进度列表中的进度对象
  },
  ...
}

页面显示进度弹框代码,downLoadNotice.vue:

<template>

</template>

<script>
  import { mapState } from 'vuex'

  export default {
    name: 'downLoadNotice',
    computed: {
      ...mapState({
      'progressList': state => state.caseInformation.progressList
    })
    },
    data() {
      return {
        notify: {} // 用来维护下载文件进度弹框对象
      }
    },
    watch: { // 监听进度列表
      progressList: {
        handler(n) {
          let data = JSON.parse(JSON.stringify(n))
          data.forEach(item => {
            const domList = [...document.getElementsByClassName(item.path)]
            if (domList.find(i => i.className == item.path)) { // 如果页面已经有该进度对象的弹框,则更新它的进度progress
              domList.find(i => i.className == item.path).innerHTML = item.progress + '%'
            } else {
              if (item.progress === null) { // 此处容错处理,如果后端传输文件流报错,删除当前进度对象
                this.$store.commit('caseInformation/DEL_PROGRESS', item.path)
                return
              }// 如果页面中没有该进度对象所对应的弹框,页面新建弹框,并在notify中加入该弹框对象,属性名为该进度对象的path(上文可知path是唯一的),属性值为$notify(element ui中的通知组件)弹框对象
              this.notify[item.path] = this.$notify.success({
                // title: 'Info',
                dangerouslyUseHTMLString: true,
                message: `<p style="width: 100px;">正在下载<span class="${item.path}" style="float: right">${item.progress}%</span></p>`, // 显示下载百分比,类名为进度对象的path(便于后面更新进度百分比)
                showClose: false,
                duration: 0
              })
            }
            console.log(item.progress + '%', '-------------------------->')

            if (item.progress == 100) { // 如果下载进度到了100%,关闭该弹框,并删除notify中维护的弹框对象
              this.notify[item.path].close()
              // delete this.notify[item.path] 上面的close()事件是异步的,这里直接删除会报错,利用setTimeout,将该操作加入异步队列
              setTimeout(() => {
                delete this.notify[item.path]
              }, 1000)
              this.$store.commit('caseInformation/DEL_PROGRESS', item.path)// 删除caseInformation中state的progressList中的进度对象
            }
          })
        },
        deep: true
      }
    }
  }
</script>

<style scoped>

</style>

以上代码我们可以封装到mixins文件夹中,在页面内用mixins混入:

页面中触发下载操作:

downLoad(item){
   let downData = {
      url: `ipdoc${item.url}`,
      downLoad: item.fileName
   }
   this.$commonUtils.downLoadAll(downData) // 下载
},

最终页面的效果:

最后注意一点,以上的下载进度并不是真正下载的文件,而是文件流,文件流下载完成后,再通过上文的js-file-download插件下载真正的文件!

脚踏实地行,海阔天空飞

### Vue 实现文件下载显示浏览器进度条 为了实现Vue项目中进行大文件下载显示浏览器进度条的功能,可以借鉴NProgress库用于页面跳转时的进度条逻辑[^2]。不过针对文件下载场景,则需进一步调整以适应API请求的特点。 #### 安装依赖 首先,在项目中安装`axios`作为HTTP客户端,因为其能够处理上传/下载操作,并且支持事件监听以便于获取下载进度: ```bash npm install axios ``` 接着按照之前的说明引入`nprogress`来创建视觉上的进度反馈[^4]。 #### 创建服务函数 定义一个专门的服务函数用来发起带有进度监控的大文件下载请求。此部分代码应放置在一个独立的服务模块内或是Vuex store里视具体架构而定。 ```javascript // services/downloadService.js import axios from &#39;axios&#39;; import NProgress from &#39;nprogress&#39;; export const downloadLargeFile = async (url, fileName) => { try { NProgress.start(); await axios({ url, method: &#39;GET&#39;, responseType: &#39;blob&#39;, // important onDownloadProgress: progressEvent => { let percentCompleted = Math.floor((progressEvent.loaded * 100) / progressEvent.total); NProgress.set(percentCompleted / 100); } }).then(response => { // Create a Blob object using the response data. var blob = new Blob([response.data], { type: &#39;application/octet-stream&#39; }); // Create an invisible A element to trigger file save dialog. const link = document.createElement(&#39;a&#39;); link.href = URL.createObjectURL(blob); link.download = fileName; link.click(); // Clean up and complete progress bar after saving file locally. setTimeout(() => { URL.revokeObjectURL(link.href); NProgress.done(); }, 1000); }); } catch (error) { console.error(error); NProgress.done(false); } }; ``` 这段代码通过配置`onDownloadProgress`回调函数实时更新进度条状态;当整个过程完成后调用`NProgress.done()`关闭进度指示器。 #### 调用服务函数 最后一步是在适当的地方触发这个下载动作,比如按钮点击事件处理器中: ```html <template> <!-- ... --> <button @click="handleClick">下载</button> <!-- ... --> </template> <script setup> import { ref } from "vue"; import { downloadLargeFile } from "@/services/downloadService"; const handleClick = () => { const url = "/api/large-file"; // Replace with your actual API endpoint const filename = "largefile.zip"; downloadLargeFile(url, filename); } </script> ``` 这样就可以在用户点击按钮后启动带进度提示的大文件下载流程了。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值