我们项目中经常会用到下载文件的操作,所以这里进行封装了一下,方便使用
实现的功能
- 点击按钮直接下载文件到本地
- 下载时会有loading提示,下载完成loading消失
- 下载接口报错时,能提示接口返回的错误提示(正常返回的是二进制(文件流))
- 点击下载时可以传入外部的参数,比如:表格查询的参数
实现的思路
1、利用div和antd的Button组件封装了一个下载按钮,传入定义的loading
2、点击时利用umi-request请求后端接口,后端返回文件流,注意请求时的responseType是blob,
前端把拿到文件流转成Blob对象,然后进行下载,具体下载代码如下downFile方法。
3、由于我们的responseType设置成了blob,所以接口返回的数据是个blob的对象,接口报错时返回的json数据,我们不能直接获取到,这里我使用了FileReader来读取blob对象的内容,调用FileReader.readAsText(data),传入返回的data,读取data中的内容。一旦完成,result属性中将包含一个字符串以表示所读取的文件内容。读取完成后调用FileReader.onload,这时候就可以使用data里面的type来判断返回的数据格式,如果是application/json,说明后端返回的是json数据,可以直接读取提示,反之就是文件流数据,直接下载。
实现的代码
import React, { useState } from 'react';
import type { ReactNode } from 'react';
import { Button, notification } from 'antd';
import type { ButtonProps } from 'antd';
import Cookies from 'js-cookie';
import { extend } from 'umi-request';
interface OptionsProps {
loading: boolean;
}
interface FileProps {
params?: object;
children?: (options: OptionsProps) => ReactNode;
style?: any;
action: string;
accept?: string;
method?: string;
callback?: () => void;
title?: string;
header?: object;
onClick?: () => object;
ButtonProps?: ButtonProps;
}
const DownloadFile = (props: FileProps) => {
const {
params = {},
onClick = () => ({}),
children,
style = {},
action = '',
accept = '*/*',
method = 'GET',
callback = () => {},
title = '导出数据',
ButtonProps = {},
header = {},
} = props;
const [loading, setLoading] = useState<boolean>(false);
const downFile = (blob: any, fileName: any) => {
if (window.navigator.msSaveOrOpenBlob) {
navigator.msSaveBlob(blob, fileName);
} else {
let link: any = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = fileName;
link.click();
window.URL.revokeObjectURL(link.href);
link = null;
}
};
const downloadTmpl = () => {
// 点击下载时事件获取额外的参数
const values = onClick() || {};
const headers: any = {
...(Cookies.get('token') ? { Authorization: Cookies.get('token') } : null),
Accept: accept,
...header,
};
const request = extend({
credentials: 'include', // 默认请求是否带上cookie
});
if (loading) return;
setLoading(true);
request(action, {
method,
headers,
// responseType: 'arrayBuffer',
responseType: 'blob',
getResponse: true,
params: { ...values, ...params },
data: { ...values, ...params },
})
.then(({ data, response }) => {
const fileReader = new FileReader(); // 读取blob对象
fileReader.onload = () => {
if (data.type === 'application/json') {
notification.error({
message: '错误',
description: fileReader.result,
});
} else {
//获取文件名称,返回的是encodedURI编码,需要decodeURI解码一下
const contentDisposition = response.headers.get('content-disposition');
let [fileName = ''] = contentDisposition?.split('=').slice(-1) || '';
fileName = fileName.replace(`utf-8''`, '');
const blob = new Blob([data]);
downFile(blob, decodeURI(fileName));
}
};
fileReader.readAsText(data);
})
.then(() => callback())
.finally(() => {
setLoading(false);
});
};
return (
<div onClick={downloadTmpl} style={{ display: 'inline-block', ...style }}>
{children ? (
children({ loading })
) : (
<Button loading={loading} {...ButtonProps}>
{title}
</Button>
)}
</div>
);
};
export default DownloadFile;
复制代码
使用
import DownloadFile from './DownloadFile';
<DownloadFile action="接口地址" title="下载模板" />
复制代码