阅读视图

发现新文章,点击刷新页面。

服务端返回的二进制流excel文件,前端实现下载

近期有个excel的下载功能,服务端返回的是二进制的文件流,前端实现excel文件下载。

简易axios:

// utils/request.ts
import axios, { AxiosRequestConfig, AxiosRequestHeaders } from 'axios'
axios.interceptors.request.use(config => {
    ……
    return config
})
axios.interceptors.response.use(
    response => {
        return new Promise((resolve, reject) => {
            ……
        })
    }
)
    
 export const getBlobFile = (url: string, params = {}) =>
    axios({
        data: params,
        responseType: 'blob',
        method: 'POST',
        url
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
    }) as Promise<any>

下面是工具函数文件的方法:

// utils/index.ts
export function useState<T>(initData: T): [Ref<T>, (val?: T) => void] {
    const data = ref(initData) as Ref<T>
    function setData(newVal?: T) {
        data.value = newVal || initData
    }
    return [data, setData]
}

/**
 * 下载二进制文件
 * @param file
 * @param fn
 */
import { ref, Ref } from 'vue'

export function downloadFile(file: File, fn?: () => void) {
    if ('msSaveOrOpenBlob' in navigator) {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const nav = navigator as any
        nav.msSaveOrOpenBlob(file, file.name)
    } else {
        const url = URL.createObjectURL(file)
        const event = new MouseEvent('click')
        const link = document.createElement('a')
        link.href = url
        link.download = file.name
        file.type && (link.type = file.type)
        link.dispatchEvent(event)
        URL.revokeObjectURL(url)
        fn && fn()
    }
}

实现下载相关逻辑的hooks如下:

// hooks.ts
import { ref } from 'vue'
import { ElLoading } from 'element-plus'
import { EXCEL_EXPORT_URL } from '@/const/url'
import { useState, downloadFile } from '@/utils'
import { getBlobFile } from '@/utils/request'

export const OK_STATUS_CODE = '200'

export function useExportExcelFile() {
    const [exportData, handleExport] = useState([] as Array<ApiRes.ExcelListItem>)

    const exportLoading = ref(false)

    async function exportExcelFile(params = {}) {
        const text = '正在导出当前数据,请稍等~~ '
        const loading = ElLoading.service({
            lock: true,
            text,
            background: 'rgba(0, 0, 0, 0.7)',
            customClass: 'export-loading-class'
        })
        try {
            queryBlobExcelFile(params).then((res) => {
                exportExcelFileByPost(res)
            }).finally(() => {
                loading.close()
                exportLoading.value = false
            })
        } catch (error) {
            console.error('导出失败:', error)
        }
    }

    async function queryBlobExcelFile (params = {}) {
        return new Promise((resolve, reject) => {
            getBlobFile(EXCEL_EXPORT_URL, params)
                .then(res => {
                    if (res && res?.status === OK_STATUS_CODE) {
                        resolve(res)
                    }
                })
                .catch(err => reject(err))
        })
    }

    async function exportExcelFileByPost(res: {
        type: string
        data: Blob
    }) {
        const fileName =  `Excel文件-${+new Date()}.xlsx`
        downloadFile(new File([res.data], fileName))
    }

    return {
        exportData,
        handleExport,
        exportLoading,
        exportExcelFile,
    }
}

在页面中的使用

</template>
    <el-button
        type="primary"
        :disabled="false"
        :export-loading="exportLoading"
        @click="doExport"
    >
        导出
    </el-button>
</template>

import { ElMessageBox } from 'element-plus'
import { useExportExcelFile } from './hooks'

// 导出
const { exportLoading, exportExcelFile } = useExportExcelFile()
function doExport() {
    ElMessageBox.confirm('确定导出excel数据吗?', '导出', {
        cancelButtonText: '取消',
        confirmButtonText: '确认',
        showClose: true
    }).then(() => {
        exportLoading.value = true
        exportExcelFile(formData)
    })
}
❌