一个简单有意思的download

195 阅读2分钟

封装下载函数:

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})
}

效果图:

image.png

image.png

image.png

image.png