封装下载函数:
downloadAxios.ts
import axios from 'axios'
import { useUserInfoStore, useLangsStore, useAppStore } from '@/stores'
import { ElMessage } from 'element-plus'
const service = axios.create({
timeout: 6000000, // request timeout
// 下载进度
onDownloadProgress: (e) => {
// 后端在响应头中要返回Content-Length: xxx;才能拿到e.total 否则是undefined
const percentComplete: number = e.loaded / (e.total ?? 0)
const progress: number = parseInt((percentComplete * 100).toString())
useAppStore().Progress(progress)
}
})
const request = (url: string, params: any = {}, method: string = 'post') => {
// 所有请求添加
const { locale } = useLangsStore()
const { userTocken } = useUserInfoStore()
const lang = locale ? {
zh: 'zh-cn',
en: 'en',
ja: 'ja',
ko: 'ko',
}[locale] : 'zh-cn'
// params中有name时,前端固定命名,不需要headers
// 如果走接口走代理需要headers,走url CDN不需要
const headers = params.name ? {} : {
lang,
app_id: 'all',
'admin-gateway-token': userTocken,
}
let data = {}
if (method === 'get') data = { params }
if (method === 'post') data = { data: params }
service({
url,
method,
...data,
headers,
responseType: 'arraybuffer',
}).then((res) => {
const blob = new Blob([res.data], {
type: res.headers['content-type']
})
if (res.headers['content-type'].includes('application/json')) {
// 接口错误 返回 json, 解析后提示 msg
const reader = new FileReader()
reader.readAsText(blob, 'utf-8')
reader.onload = () => {
const data: { msg: string } = JSON.parse(reader.result as string)
ElMessage.error(data.msg)
}
} else {
// 接口正确,返回
const link = document.createElement('a') // 生成一个a标签
link.href = window.URL.createObjectURL(blob) // href属性指定下载链接
if (params.name) {
link.download = params.name // 前端固定命名
} else {
link.download = decodeURIComponent(res.headers['filename']) // 从响应头中获取后端返回的动态文件名
}
link.click() // click()事件触发下载
}
}).catch((err) => {
console.log(err)
useAppStore().Progress(-1)
ElMessage.error('下载失败!请重试')
})
}
export default request
使用:
前端固定命名,使用方法:
import downloadAxios from '@/server/downloadAxios'
// this.download_links['terms']为下载URL cdn
download () {
downloadAxios(this.download_links['terms'], { name: 'xx模版.xlsx' }, 'get')
},
接口下载使用方法
export const testDownload: ApiT = (params) =>
downloadAxios(`/url/download`, params)
const donwLoad = () => { testDownload({id: id}) }
封装下载进度组件:
Progress.vue
<template>
<div class="loding" v-drag v-if="process > -1">
<el-progress :text-inside="true" :stroke-width="24" :format="proformat" :percentage="process" status="success"></el-progress>
</div>
<div class="fpshade"></div>
</template>
<script setup lang="ts">
import { computed, watch, type Directive } from 'vue'
import { useAppStore } from '@/stores'
const process = computed(() => useAppStore().progressNum)
watch(process, (n) => {
// 下载完成后进度条消失
if (n === 100) {
setTimeout(() => {
useAppStore().Progress(-1)
}, 2000)
}
})
const proformat = (percentage: number) => (percentage === 100 ? `${percentage}% 下载完成` : `${percentage}%`)
// 自定义拖拽指令
const vDrag: Directive<any, void> = (el: HTMLElement) => {
el.onmousedown = e => {
// 打开遮罩层
(document.querySelector('.fpshade') as HTMLElement).style.display = 'block'
// 算出鼠标相对元素的位置
let disX = e.clientX - el.offsetLeft
let disY = e.clientY - el.offsetTop
const maxY = document.body.clientWidth - el.offsetWidth
const maxX = document.body.clientHeight - el.offsetHeight
document.onmousemove = e => {
// 用鼠标的位置减去鼠标相对元素的位置,得到元素的位置
let left = e.clientX - disX
let top = e.clientY - disY
// 设置边缘
left = left > maxY ? maxY : left
left = left < 0 ? 0 : left
top = top > maxX ? maxX : top
top = top < 0 ? 0 : top
// 移动当前元素
el.style.left = left + 'px'
el.style.top = top + 'px'
}
document.onmouseup = () => {
document.onmousemove = () => {}
document.onmouseup = () => {}
// 关闭遮罩
(document.querySelector('.fpshade') as HTMLElement).style.display = 'none'
}
}
}
</script>
<style lang="scss" scoped>
.loding {
position: fixed;
bottom: 10px;
right: 20px;
height: 24px;
width: 130px;
z-index: 6001;
opacity: 0.8;
cursor: move;
border-radius: 24px;
box-shadow: 0px 1px 7px 1px rgba(0,0,0,0.8);
user-select: none;
.el-progress-bar__outer {
background-color: rgba(0,0,0,0.8);
}
}
.fpshade {
position: absolute;
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
z-index: 6000;
height: 100%;
width: 100%;
display: none;
}
</style>
在pinia中记录进度:
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useAppStore = defineStore('app', () => {
// 下载进度
const progressNum = ref<number>(-1)
const Progress = (progress: number) => {
progressNum.value = progress
}
return { Progress, progressNum }
})
根组件APP.vue中使用:
<template>
<div id="app">
<router-view v-if="isRouterAlive"/>
<Progress/>
</div>
</template>
<script setup lang="ts">
import Progress from '@/components/Progress.vue'
const isRouterAlive = ref(true)
</script>
<style lang="scss" scoped>
</style>
单页面下载按钮:
<el-form-item>
<el-button type="primary" icon="Download" @click="donwLoad"
:loading="progState"
:disabled="progState">{{ $t('btn_download') }}
<span v-if="progState">{{ progressNum + '%' }}</span>
</el-button>
</el-form-item>
import { useAppStore } from '@/stores'
import { testDownload } from '@/api/test'
const progressNum = computed(() => useAppStore().progressNum)
const progState = ref<boolean>(false)
watch(progressNum, (n) => {
progState.value = (n < 100 && n > -1) ? true : false
})
const donwLoad = () => {
testDownload({id: id})
}
效果图: