目录

一、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) // 下载

VUEJS 浏览器下载进度条功能 java vue下载文件显示进度_下载