上传下载是前端经常面临的两大需求,当文件比较大时,下载进度显示能提升用户体验。本文结合vue3介绍下载对话框的实现。当点击页面中下载按钮后,会呈现类似下面效果的对话框:
下载进度达到100%时,点击保存按钮即可保存文件。
主要原理:axios下载文件时,文件数据作为blob对象先放到内存中,然后可以对这个blob对象做各种操作。
进度显示主要利用了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)
}