实现下载进度条

平时业务中下载文件方式常见的有俩种:

第一种,直接访问服务器的文件地址,自动下载文件;

第二种 ,服务器返回blob文件流,再对文件流进行处理和下载。

第一种自行百度

第二种方式有弊端,在文件流传输过程中,用户无法感知文件流的传输状态(进度),会造成一些困扰(无法确定当前下载操作是否已经生效)。针对这种情况,我们可以在页面显示文件流的状态和传输进度,提高页面交互性和友好性。

封装JS方法

/**
 * @param {Object} data: {url: 文件地址, download: 文件名称}
 */
import axios from "axios";
import jsFileDownLoad from 'js-file-download'
import store from "@/store/index"
import {ElMessage} from "element-plus";

export function downLoadAll(data) {
    let downProgress = {};
    let uniSign = Date.now(); //可能会连续点击下载多个文件,这里用时间戳来区分每一次下载的文件
    //通过传递一个 executor 函数到 CancelToken 的构造函数来创建 cancel token:
    const CancelToken = axios.CancelToken;
    let cancel;
    axios.get(
        data.url,
        {
            responseType: 'blob', headers: {"Content-Type": "application/json; charset=utf-8"},
            //取消axios的函数
            cancelToken: new CancelToken(function executor(c) {
                // executor 函数接收一个 cancel 函数作为参数
                cancel = c;
            }),
            onDownloadProgress(progress) {
                downProgress = Math.round(100 * progress.loaded / progress.total) // progress对象中的loaded表示已经下载的数量,total表示总数量,这里计算出百分比
                store.commit('SET_PROGRESS', {
                    path: uniSign,
                    name: data.downLoad,
                    progress: downProgress,
                    status: downProgress == 100 ? 'success' : 'downloading',
                    cancel: cancel
                }) // 将此次下载的文件名和下载进度组成对象再用vuex状态管理
            }
        }).then((res) => { // 文件流传输完成后,开启文件下载
        if (data.downLoad) {
            jsFileDownLoad(res.data, data.downLoad); // jsFileDownLoad是用来下载文件流的,下载插件:npm i js-file-download,import引入:import jsFileDownLoad from 'js-file-download'
            //文件下载成功提示一下。此处我是用的Element-plus提示框
            if (downProgress == 100) {
                ElMessage.success("Download Complete:" + data.downLoad)
            }
        } else {
            jsFileDownLoad(res.data, data.url.split('/')[data.url.split('/').length - 1]);
        }
    }).catch((e) => {
        if (JSON.stringify(e) == "{}") {
            ElMessage.error("Download Cancelled:" + data.downLoad)
        } else {
            ElMessage.error("Download Failed:" + data.downLoad)
        }
        store.commit('SET_PROGRESS', {
            path: uniSign,
            name: data.downLoad,
            progress: downProgress,
            status: 'error',
            cancel: cancel
        })
    })
}


使用store存储文件下载进度列表

/store/download.js

const downloadStore={
    state: {
        progressList:[]
    },
    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
                    state.progressList.find(item=>item.path == progressObj.path).status = progressObj.status // 改变当前进度对象的status
                }else{
                    state.progressList.push(progressObj) // 当前进度列表为空,没有下载任务,直接将该进度对象添加到进度数组内
                }
            }else{
                state.progressList.push(progressObj) // 当前进度列表为空,没有下载任务,直接将该进度对象添加到进度数组内
            }
        },
        DEL_PROGRESS: (state, props) => {
            state.progressList.splice(state.progressList.findIndex(item=>item.path == props), 1) // 删除进度列表中的进度对象
        },
    },
    actions:{
    },
    getters:{
        progressList:state=>state.progressList,
    }
}

export default downloadStore;

创建一个列表组件

/components/DownloadList/index.vue

<template>
  <el-table :data="progressList">
    <el-table-column prop="name" label="Name"></el-table-column>
    <el-table-column prop="status" label="Status">
      <template #default="scope">
          <!-- 显示文件状态  -->
        <el-tag v-if="scope.row.status=='downloading'">Downloading</el-tag>
        <el-tag v-if="scope.row.status=='success'" type="success">Success</el-tag>
        <el-tag v-if="scope.row.status=='error'" type="danger">Download failed</el-tag>
      </template>
    </el-table-column>
    <el-table-column label="Progress">
    <!-- 根据状态判断使用不同的进度条  -->
      <template #default="scope">
        <el-progress :percentage="scope.row.progress" v-if="scope.row.status=='downloading'"/>
        <el-progress :percentage="scope.row.progress" v-if="scope.row.status=='success'" status="success"/>
        <el-progress :percentage="scope.row.progress" v-if="scope.row.status=='error'" status="exception"/>
      </template>
    </el-table-column>
    <el-table-column width="80px">
      <template #default="scope">
      <!--点击按钮取消下载 -->
        <el-icon v-if="scope.row.status=='downloading'" style="cursor: pointer" color="#F56C6C"
                 @click="handleDownloadClose(scope.row)">
          <close-bold/>
        </el-icon>
      </template>
    </el-table-column>
  </el-table>
</template>

<script>
export default {
  name: "index",
  props: {
    progressList: {
      type: Object,
      default: []
    }
  },
  methods: {
    handleDownloadClose(row) {
      if (row.status == 'downloading') {
        row.cancel()
      }
    }
  }
}
</script>

<style scoped>

</style>

在页面中使用

假设现在有一个文件列表fileList

<template>
  //点击按钮显示文件下载中心
  <el-button icon="download" type="primary" @click="handleDownloadTable">Download</el-button>
  //文件列表
  <el-table :data="progressList">
    <el-table-column prop="name" label="Name"></el-table-column>
 	        <el-table-column width="80">
          <template #default="scope">
              <div @click="handleDownload(scope.row)">Download</div>
          </template>
        </el-table-column>
  </el-table>
    <el-drawer v-model="drawerDownload" title="Download">
      <DownloadList  :progressList="progressList"></DownloadList>
    </el-drawer>
</template>

<script>
import DownloadList from "@/components/DownloadList/index.vue"
export default {
  name: "index",
  components:{DownloadList},
  data(){
  	return{
		drawerDownload:false,//下载中心以抽屉的形式打开
		progressList:[//文件列表
			{
				name:1,
				file_url:'www.baidu.com'
			}
		]
	}
  }
  methods:{
	 handleDownload(row){
	  ElMessage.success("Downloading.  It can be viewed at the download center")
	     let downData = {
	       url: row.file_url,
	       downLoad: row.name
	     }
	     downLoadAll(downData) // 下载
	 },
	 handleDownloadTable(){
      this.drawerDownload=true
    },
  }
}
</script>

<style scoped>

</style>

效果展示

在这里插入图片描述
本文参考链接:https://www.cnblogs.com/coder–wang/p/15320511.html
axios文档地址:http://www.axios-js.com/zh-cn/docs/#%E5%8F%96%E6%B6%88

Logo

前往低代码交流专区

更多推荐