antd下载(导出)组件的封装

我们项目中经常会用到下载文件的操作,所以这里进行封装了一下,方便使用

实现的功能

  • 点击按钮直接下载文件到本地
  • 下载时会有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="下载模板" />
复制代码

Guess you like

Origin juejin.im/post/7068154846997643272