需求场景
大文件下载,花费的时间比较长,没有任何提示,用户体验很差。
需要优化,提示文件在下载中,并且显示进度百分比。
实现步骤
1.下载文件的方法,需要拿到当前进度。
2.每一次下载进度更新,需要监听变化,并且刷新页面显示的数据。
3.封装一个文件下载进度的组件。
下面一步步来实现:
1.获取文件下载进度
axios作为一个易用、简洁且高效的http库,有没有获取下载进度的方法呢?
打开axios官网查看一下,文档中有一个对原生进度事件的处理的方法
onDownloadProgress
允许为下载处理进度事件。
在项目中,我已经对axios进行了封装,并且增加了请求拦截器和响应拦截器。
详情请见我的另一篇文章,传送门在此:
在封装接口的文件interface.js中,新增一个下载文件,并且获取下载进度的方法。
export const downFileProgress =(url,parameter,callback,totalSize,uniSign) =>{
return axios({
url: url,
params: parameter,
method:'get' ,
responseType: 'blob',
onDownloadProgress (progress) {
callback(progress, totalSize, uniSign)
}
})
}
2.通过vuex状态管理下载进度
下载进度对象,需要通过vuex状态管理。
关于vuex进行状态管理,详情请见我的另一篇文章,传送门在此:
在store文件下面的modules文件夹中,新建 downLoadProgress.js文件。
用来存放文件下载进度的数组 progressList 和修改进度列表方法,都在这里面。
const state = {
// 文件下载进度
progressList: [],
progressError: '',
}
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) // 删除进度列表中的进度对象
},
CHANGE_SETTING: (state, { key, value }) => {
// eslint-disable-next-line no-prototype-builtins
if (state.hasOwnProperty(key)) {
state[key] = value
}
}
}
const actions = {
changeSetting({ commit }, data) {
commit('CHANGE_SETTING', data)
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
3.新增一个显示进度弹框的组件
<template>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'downLoadNotice',
computed: {
...mapState({
'progressList': state => state.downLoadProgress.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
if(item.progress)domList.find(i => i.className == item.path).innerHTML = item.progress + '%'
if (item.progress === null) { // 此处容错处理,如果后端传输文件流报错,删除当前进度对象
this.$store.commit('downLoadProgress/DEL_PROGRESS', item.path)
this.$notify.error({
title: '错误',
message: '文件下载失败!'
});
}
} else {
// 如果页面中没有该进度对象所对应的弹框,页面新建弹框,并在notify中加入该弹框对象,属性名为该进度对象的path(上文可知path是唯一的),属性值为$notify(element ui中的通知组件)弹框对象
this.notify[item.path] = this.$notify.success({
// title: 'info',
dangerouslyUseHTMLString: true,
customClass: 'progress-notify',
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('downLoadProgress/DEL_PROGRESS', item.path)// 删除caseInformation中state的progressList中的进度对象
}
})
},
deep: true
}
}
}
</script>
<style lang="scss" scoped>
</style>
axios请求的接口方法,vuex状态管理,显示进度弹框的组件,都已经完成。
接下来,在需要显示下载进度的页面,引入进度弹框的组件。
修改一些之前下载文件的方法。
使用之前封装的 downFileProgress 接口下载文件,在回调函数callBackProgress中出来下载文件的进度。
dowOrgFile(id, fileFprimalnam) {
const params = {
id: id,
templateId: this.templateId,
};
let totalSize = 0;
let obj = this.fileList.find(element => element.id == id);
if(obj)totalSize = obj.fileSize;
let uniSign = new Date().getTime() + ''; // 可能会连续点击下载多个文件,这里用时间戳来区分每一次下载的文件
this.$api.downFileProgress(this.url.dowOrgFile, params, this.callBackProgress, totalSize, uniSign).then((data) => {
if (!data) {
this.$sweetAlert.errorWithTimer("文件下载失败!");
return;
}
if (typeof window.navigator.msSaveBlob !== "undefined") {
window.navigator.msSaveBlob(new Blob([data]), fileFprimalnam);
} else {
const url = window.URL.createObjectURL(new Blob([data]));
const link = document.createElement("a");
link.style.display = "none";
link.href = url;
link.setAttribute("download", fileFprimalnam);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
}
});
},
callBackProgress(progress, totalSize, uniSign){
let total = progress.total || totalSize;
let loaded = progress.loaded;
this.progressSign = uniSign;
// progress对象中的loaded表示已经下载的数量,total表示总数量,这里计算出百分比
let downProgress = Math.round(100 * loaded / total)
// 将此次下载的文件名和下载进度组成对象再用vuex状态管理
this.$store.commit('downLoadProgress/SET_PROGRESS', {path: uniSign, 'progress': downProgress})
},
至此,下载文件显示进度条的功能,已实现。