目录
一、store
二、mixins
三、utils
四、js-file-download插件
五、使用
一、store
caseInfomation.js
// initial state
const state = {
progressList: [], // 文件下载进度列表
}
// mutations
const mutations = {
SET_PROGRESS: (state, progressObj)=>{ // 修改进度列表
if(state.progressList.length && 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) // 删除进度列表中的进度对象
}
}
// getters
const getters = {}
// actions
const actions = {}
export default {
state,
getters,
actions,
mutations
}
二、mixins
downLoadNotice.vue
<template>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'downLoadNotice',
computed: {
...mapState({
'progressList': state => state.caseInformation.progressList
})
},
data() {
return {
notify: {}, // 用来维护下载文件进度弹框对象
progress: 0
}
},
watch: { // 监听进度列表
'$store.state.caseInfomation.progressList': {
handler(n) {
let data = JSON.parse(JSON.stringify(n))
data.forEach(item => {
const domList = [...document.getElementsByClassName(item.path)]
const progressList = [...document.getElementsByClassName('progress' + item.path)]
if (domList.find(i => i.className == item.path)) { // 如果页面已经有该进度对象的弹框,则更新它的进度progress
domList.find(i => i.className == item.path).innerHTML = item.progress + '%'
progressList.find(i => i.className == ('progress' + item.path)).value = item.progress
} else {
if (item.progress === null) { // 此处容错处理,如果后端传输文件流报错,删除当前进度对象
this.$store.commit('DEL_PROGRESS', item.path)
return
}// 如果页面中没有该进度对象所对应的弹框,页面新建弹框,并在notify中加入该弹框对象,属性名为该进度对象的path(上文可知path是唯一的),属性值为$notify(element ui中的通知组件)弹框对象
this.notify[item.path] = this.$notify({
// title: 'Info',
dangerouslyUseHTMLString: true,
message: `<p style="margin-top:-4px;padding-left: 3px;">
<span style="font-size: 10px;">${item.name}</span><br/>
<div style="display: flex;">
<p style="padding: 3px 0 0 6px;"><progress class="${ 'progress' + item.path}" style="width: 228px;" max="100" value="${item.progress}"></progress></p>
<span class="${item.path}" style="margin-left: 10px;">${item.progress}%</span>
</div>
</p>
`, // 显示下载百分比,类名为进度对象的path(便于后面更新进度百分比) 这里无法实现响应式,通过获取Dom去更新值的改变
showClose: true,
duration: 0,
onClose: (e)=>{
if(this.progress !== 100) {
if(e.$cancelList.length){
e.$cancelList.find(n=>n.url == item.url).cancel.cancel('当前下载已被手动取消') // 取消当前接口请求
e.$cancelList.splice(e.$cancelList.findIndex(i=>i.url == item.url), 1) // 删除全局list当前接口token值
}
this.$store.commit('DEL_PROGRESS', item.path)
console.log('当前下载已被手动取消')
}
}
})
}
// console.log(item.progress + '%', '-------------------------->')
this.progress = 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('DEL_PROGRESS', item.path)// 删除caseInformation中state的progressList中的进度对象
this.$cancelList.splice(this.$cancelList.findIndex(i=>i.url == item.url), 1) // 删除全局list当前接口token值
}
})
},
deep: true
}
}
}
</script>
<style>
.el-notification__closeBtn{
top: 28px;
}
.el-notification__group{
margin-left: 2px !important;
}
</style>
三、utils
import axios from 'axios';
import store from "@/store";
import jsFileDownload from 'js-file-download'
import { Message,Loading } from 'element-ui';
import Vue from 'vue'
import _ from 'lodash'
Vue.prototype.$cancelList = []
const fn = _.throttle(function(data) {
let downProgress = {};
let uniSign = new Date().getTime() + ''; // 可能会连续点击下载多个文件,这里用时间戳来区分每一次下载的文件,也可以用生成器和迭代器实现文件的唯一标识(见下文)
let progressList = store.state.caseInfomation.progressList
if(progressList.length && progressList.find(item=>item.url == data.url)){
Message({
message: '该文件正在下载!',
type: 'warning'
});
return
}else{
const CancelToken = axios.CancelToken;
Vue.prototype.$cancelList.push({...data, cancel:CancelToken.source() })
const loading = Loading.service({
lock: true,
text: '',
background: 'rgba(6, 6, 6, 0.1) !important'
});
axios.get(
data.url,
{
responseType: 'blob',
headers: {"Content-Type": "application/json; charset=utf-8"},
cancelToken: Vue.prototype.$cancelList.find(item=>item.url == data.url).cancel.token,
onDownloadProgress (progress) {
loading.close();
downProgress = Math.round(100 * progress.loaded / progress.total) // progress对象中的loaded表示已经下载的数量,total表示总数量,这里计算出百分比
store.commit('SET_PROGRESS', {path: uniSign, 'progress': downProgress, url: data.url, name:data.downLoad }) // 将此次下载的文件名和下载进度组成对象再用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'
jsFileDownload(res.data,data.downLoad)
} else {
jsFileDownload(res.data, data.url.split('/')[data.url.split('/').length-1]);
}
}).catch((e)=>{
this.$message.error('该文件无法下载')
})
}
}, 3000)
/**
* @param {Object} data: {url: 文件地址, downLoad: 文件名称}
*/
export function downLoadAll(data) {
fn(data)
}
四、js-file-download插件
jsFileDownLoad是用来下载文件流的
下载插件:npm i js-file-download
import引入:import jsFileDownLoad from 'js-file-download'
五、使用
import downLoadNotice from '@/mixins/downLoadNotice.vue';
mixins:[downLoadNotice], // 推荐在app.vue内进行全局混入
let downData = {
url: `下载地址`,
downLoad: 'aaaa.yml' // 文件名称
}
this.$utils.downLoadAll(downData) // 下载