问题

上传下载是前端经常面临的两大需求,当文件比较大时,下载进度显示能提升用户体验。本文结合vue3介绍下载对话框的实现。当点击页面中下载按钮后,会呈现类似下面效果的对话框:
在这里插入图片描述
下载进度达到100%时,点击保存按钮即可保存文件。

主要原理:axios下载文件时,文件数据作为blob对象先放到内存中,然后可以对这个blob对象做各种操作。

实现

采用axios实现下载请求

进度显示主要利用了axios的onProgress()重载方法。

import Axios, { AxiosInstance, AxiosRequestConfig, ResponseType } from "axios";

const downloadConfig: AxiosRequestConfig = {
  baseURL,
  timeout: 100000,
  responseType: "blob",
  headers: {
    'Content-Type': 'application/octet-stream'
  }
}

private static axiosInstance: AxiosInstance = Axios.create(defaultConfig);
private static axiosDownloadInstance: AxiosInstance = Axios.create(downloadConfig);

public download<T, P>(url: string, filename: string, onProgress: DownloadProgress, onCompleted: DownloadCompleted): Promise<P> {    
    const config = {
      method: 'get',
      onDownloadProgress: (evt)=>onProgress(evt.loaded, evt.total),
      url
    } as PureHttpRequestConfig;
    return new Promise((resolve, reject) => {
      PureHttp.axiosDownloadInstance
        .request(config)
        .then((response: any) => {
          onCompleted(response.data);          
          resolve(response);
        })
        .catch(error => {
          reject(error);
        });
    });
  }

写一个进度下载对话框

download_dialog.vue的实现:

<template>
  <el-dialog v-model="visible" :title="title" :append-to-body=true width="50%">
    <el-progress :text-inside="true" :stroke-width="26" :percentage="percent" />
    <template #footer>
        <span>
          <el-button type="primary" @click="cancel()">取消</el-button>
          <el-button :type="type" @click="doSave()" :disabled="disabled">保存</el-button>
        </span>
    </template>
  </el-dialog>
</template>
<script lang="ts" setup>
import {ref, shallowRef, watch, onMounted, getCurrentInstance} from "vue"
import {ElDialog, ElForm, ElFormItem, ElInput, ElButton, ElProgress} from "element-plus";
import {closeDialog} from "/@/components/dialog"
import { http } from "/@/utils/http";

const props = defineProps<{downloadUrl:string, filename: string}>()
const visible = ref<boolean>(true)
const type = ref('info')
const disabled = ref<boolean>(true)
const title = ref(`${props.filename}》下载`)

const percent = ref<number>(0)
const blobData = ref()

onMounted(() => {
  http.download(props.downloadUrl, props.filename, onProgress, onCompleted)
})

const onProgress = (loaded, total) => {      
  percent.value = parseInt(loaded * 100 / total)
  if (percent.value == 100) {    
    disabled.value = false
    type.value = 'success'
  }
}

const onCompleted = (blob) => {  
  blobData.value = blob
}

const cancel = () => {  
  visible.value = false
}

const doSave = () => {         
  var urlObject = window.URL || window.webkitURL || window
  const url = urlObject.createObjectURL(
    new Blob([blobData.value], {
      type: 'application/octet-stream',
    })
  );    

  let saveLink = document.createElement('a')
  saveLink.href = url
  saveLink.download = props.filename + '.pdf'
  saveLink.click()

  URL.revokeObjectURL(url);
  visible.value = false
}



</script>


调用对话框

利用vue3的h()和render()函数绘制对话框:

import {Component, h, render, shallowRef} from "vue";
import DownloadDialog from "./download_dialog.vue"

/**
 * 开启一个下载对话框
 * @param downloadUrl :下载文件的链接
 * @param filename :保存文件的名称
 * @returns 
 */
export function openDownloadDialog(downloadUrl, filename) {
    const vnode = h(DownloadDialog, {downloadUrl, filename})
    vnode.appContext = null
    const container = document.createElement('div')
    render(vnode, container)
    const instance = vnode.component
    const vm = instance.proxy
    return vm
}

测试:

const downloadPDF = async (bookId, bookName) => {
  openDownloadDialog("/api/ebook/download/pdf/" + bookId, bookName)  
}

参考链接

  • https://muhimasri.com/blogs/how-to-save-files-in-javascript/
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐