本文最后更新于:2022-02-12T15:25:54+08:00
axios完成文件下载
常见的下载方式有两种,第一种是通过直接访问服务器的文件地址进行自动下载;第二种是服务器返回blob文件流,然后前端再对文件流进行处理和下载。(一般小文件适用于第一种下载方案,不占用过多服务器资源,而对于体积庞大的文件,常常使用文件流的方式进行传输)
下面是通过axios完成文件下载的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import axios from 'axios' fileDownload (path, name ) { console .log ("开始下载" + path); axios.get (path, { responseType : "arraybuffer" }) .then ((response ) => { const blob = new Blob ([response.data ]); const blobUrl = window .URL .createObjectURL (blob); const tmpLink = document .createElement ('a' ); tmpLink.style .display = 'none' ; tmpLink.href = blobUrl; tmpLink.setAttribute ('download' , name); document .body .appendChild (tmpLink); tmpLink.click (); document .body .removeChild (tmpLink); window .URL .revokeObjectURL (blobUrl); }) }
但是这种方式实现的下载功能有一个缺点,就是用户无法感知下载的进度,只有在下载成功之后才会显示成功,而不会显示下载的过程。这点对于用户体验是很不友好的,于是考虑增加文件下载进度显示功能。
实现文件下载进度显示功能
具体实现过程如下,以下过程实现是在Nuxt工程中,其他工程可以进行参考。
首先创建store/download.js
文件,通过Vuex模块来保存有关下载的全局变量,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 export const state = ( ) => ({ progressList : [], id : 0 , })export const mutations = { setProgress (state, progressObj ) { if (state.progressList .length ) { if (state.progressList .find (item => item.path === progressObj.path )) { state.progressList .find (item => item.path === progressObj.path ).progress = progressObj.progress ; } } else { state.progressList .push (progressObj); } }, delProgress (state, props ) { state.progressList .splice (state.progressList .findIndex (item => item.path === props), 1 ) }, idGenerator (state ) { state.id ++; } }
其中progressList中存放当前正在下载的进度对象,通过id进行表示。id为一个全局的变量,通过生成器方法保证递增不重复。在每次使用id之前,都需要调用一次idGenerator方法(这点应该有更好的解决方案,目的就是为了保证每个进度对象对应的id是不重复的)。同时方法中提供新增和删除进度对象的方法。
之后,创建全局组件,用于展示进度。创建components/DownloadNotice.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 <template> </template> <script> import {mapState} from 'vuex' export default { // 用于提示下载进度 name: 'DownloadNotice', computed: { ...mapState('download', ['progressList']) // 转化成计算属性 }, data() { return { notify: {} // 用来维护下载文件进度弹框对象 } }, watch: { // 监听进度列表的变化 progressList: { handler(n) { let data = JSON.parse(JSON.stringify(n)); data.forEach(item => { const domList = [...document.getElementsByClassName(item.path)]; // 如果页面已经有该进度对象的弹框,则更新它的进度progress // 这里className和path的类型不同但是值相同,如果使用===会出现多个消息框 if (domList.find(i => i.className == item.path)) { domList.find(i => i.className == item.path).innerHTML = item.progress + '%'; } else { // 此处容错处理,如果后端传输文件流报错,删除当前进度对象 if (item.progress == null) { this.$store.commit('download/delProgress', item.path); return; }// 如果页面中没有该进度对象所对应的弹框,页面新建弹框,并在notify中加入该弹框对象,属性名为该进度对象的path(上文可知path是唯一的),属性值为$notify(element ui中的通知组件)弹框对象 this.notify[item.path] = this.$notify.success({ title: '', 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中维护的弹框对象 //异步函数,等待1s之后将其close setTimeout(() => { this.notify[item.path].close() }, 1000); // delete this.notify[item.path] 上面的close()事件是异步的,这里直接删除会报错,利用setTimeout,将该操作加入异步队列 setTimeout(() => { delete this.notify[item.path] }, 1000) // 删除caseInformation中state的progressList中的进度对象 this.$store.commit('download/delProgress', item.path) } }) }, deep: true } } } </script> <style scoped> </style>
组件中会监听全局的进度对象的变化,然后做出相应的显示变化。
之后在下载相关的方法中进行相应的修改:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 fileDownload (name, com_name ) { let downProgress = {}; let that = this ; this .$store .commit ('download/idGenerator' ); let id = this .$store .state .download .id ; console .log ("开始下载" + name); this .$axios .get (this .$store .state .backend_url + 'data/download/' + com_name + name, { responseType : "arraybuffer" , onDownloadProgress (progress ) { downProgress = Math .round (100 * progress.loaded / progress.total ); that.$store .commit ('download/setProgress' , {path : id, 'progress' : downProgress}); } }) .then ((response ) => { const blob = new Blob ([response.data ]); const blobUrl = window .URL .createObjectURL (blob); const tmpLink = document .createElement ('a' ); tmpLink.style .display = 'none' ; tmpLink.href = blobUrl; tmpLink.setAttribute ('download' , name); document .body .appendChild (tmpLink); tmpLink.click (); document .body .removeChild (tmpLink); window .URL .revokeObjectURL (blobUrl); }) .catch (error => { console .log (error); that.$notify .error ({ title : '' , message : '下载失败' , duration : 2000 , }) }) },
同时需要在页面中用mixins混入downloadNotice组件(与上面的下载方法在同一个.vue文件当中):
1 2 3 4 5 import DownloadNotice from "./DownloadNotice" ;export default { mixins : [DownloadNotice ], }
之后在页面中触发下载操作的时候,就能够出现相应的下载进度提示效果。
参考文章
vue项目实现文件下载进度条_coder__wang的博客-CSDN博客_vue
文件下载进度条