axios实现文件下载

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)) {
// 改变当前进度对象的progress
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;
//调用Vuex中的模拟生成器获取id
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) {
// progress对象中的loaded表示已经下载的数量,total表示总数量,这里计算出百分比
downProgress = Math.round(100 * progress.loaded / progress.total);
// 将此次下载的文件名和下载进度组成对象再用vuex状态管理
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],
}

之后在页面中触发下载操作的时候,就能够出现相应的下载进度提示效果。

参考文章

  1. vue项目实现文件下载进度条_coder__wang的博客-CSDN博客_vue 文件下载进度条

axios实现文件下载
http://example.com/2022/02/11/axios实现文件下载/
作者
EverNorif
发布于
2022年2月11日
许可协议