React + Ant Design 导出按钮组件

该笔记记录了如何创建一个 通用的导出按钮组件,它能够调用后台 API 导出数据并提供下载。此组件在点击时会弹出确认框,确认后调用 API 获取导出数据并下载文件。

功能概述

  • 按钮展示:展示一个 Ant Design Button 组件,配合下载图标(DownloadOutlined)。
  • 二次确认:点击按钮时会弹出一个确认框,用户确认后才开始导出操作。
  • API 调用:通过传入 apiparams,动态获取数据,api 是导出数据的 API 方法,params 是获取导出 API 所需参数的函数。
  • 文件导出:成功获取数据后,会以指定格式导出文件,默认导出为 .xlsx 格式。

组件代码

import React, { useState } from 'react';
import { Button, Modal } from 'antd'; // 使用 Ant Design 的 Button 组件
import { DownloadOutlined } from '@ant-design/icons'; // 下载图标
import { useTranslation } from "react-i18next";
import moment from 'moment';
 
import PermissionButton from '@/components/Basic/PermissionButton';
/**
 * 通用的导出按钮组件
 * @param {Object} props - 组件的传递参数
 * @param {Function} props.api - 导出数据的 API 方法,必须返回 Promise
 * @param {Function} props.params - 获取导出 API 所需的参数的函数
 * @param {React.ReactNode} [props.children='导出'] - 按钮文本,可以自定义
 * @param {string} [props.fileName='导出文件名'] - 导出的文件名(不含后缀)
 * @param {string} [props.suffixName='.xlsx'] - 导出文件的后缀名,默认为 `.xlsx`
 * @param {Boolean} [props.isConfirm=true] - 是否需要二次确认,默认为 true
 * @param {Boolean} [props.isTimeStamp=false] - 是否需要添加时间戳,默认为 false
 * @param {string} [props.fileType='application/vnd.ms-excel'] - 文件的 MIME 类型,默认是 Excel 格式
 * @returns {JSX.Element} 返回渲染的导出按钮
 */
const ExportButton = ({
  api,
  params,
  children = '',
  fileName = '',
  suffixName = '.xlsx',
  fileType = 'application/vnd.ms-excel',
  isConfirm = true, // 是否需要二次确认
  isTimeStamp = false, // 是否需要添加时间戳
  ...props
}) => {
 
  const [loading, setLoading] = useState(false);
 
  const { t } = useTranslation();
  /**
   * 显示确认导出数据的二次确认框
   * @returns {void}
   */
  const handleConfirm = async () => {
    if (isConfirm) {
      Modal.confirm({
        title: t('global.export.exprotTis'),
        onOk: handleExport,
      });
    } else {
      await handleExport()
    }
 
  };
 
  const errConfirm = (text = '') => {
    Modal.error({
      title: text,
    });
  }
  /**
   * 处理文件导出逻辑,调用 API 并导出文件
   * @returns {Promise<void>}
   */
  const handleExport = async () => {
    setLoading(true);
    try {
 
      const res = await api(params()); // 调用 API 获取数据
 
      // 处理 Blob 对象
      const blobRes = new Blob([res], { type: fileType });
 
      // 如果 Blob 里面包含 JSON 格式数据,可以这样处理:
      const text = await blobRes.text(); // 获取文本内容
      try {
        const json = JSON.parse(text); // 尝试解析成 JSON 对象
        console.log('Parsed JSON: ', json); // 打印解析后的 JSON 对象
        return errConfirm(json.message);
      } catch (error) {
        console.error('无法解析 Blob 为 JSON:', error);
      }
 
      if (res instanceof Blob) {
        exportFile(res, fileName, suffixName, fileType);
      } else {
        const contentDisposition = res.headers['content-disposition']
        let apiFilename
        if (contentDisposition) {
          const match = contentDisposition.match(/filename="?([^";]*)"?/);
          if (match && match[1]) {
            apiFilename = decodeURIComponent(match[1]);
          }
        }
        exportFile(res.data, fileName || apiFilename, fileName ? suffixName : '', fileType); // 导出文件
      }
    } catch (error) {
      console.error(t('global.export.exprotFail'), error);
    } finally {
      setLoading(false);
    }
  };
 
  /**
   * 导出文件到本地
   * @param {BlobPart} blobData - 要导出的二进制数据
   * @param {string} fileName - 文件名(不含后缀)
   * @param {string} suffixName - 文件的后缀名
   * @param {string} fileType - 文件类型(MIME)
   * @returns {void}
   */
  const exportFile = (blobData, fileName, suffixName, fileType) => {
    const time = moment().format('YYYYMMDDHHmmss')
    const blob = new Blob([blobData], { type: fileType }); // 创建 Blob 对象
    const downloadElement = document.createElement('a'); // 创建下载链接
    const href = window.URL.createObjectURL(blob); // 创建下载链接的 URL
    downloadElement.href = href;
    downloadElement.download = isTimeStamp ? `${fileName}${time}${suffixName}` : `${fileName}${suffixName}`; // 设置下载文件的名称和后缀
    document.body.appendChild(downloadElement); // 将链接添加到 DOM
    downloadElement.click(); // 模拟点击触发下载
    document.body.removeChild(downloadElement); // 下载完成后移除链接
  };
 
  return (
    <PermissionButton
      type="primary"
      loading={loading}
      icon={<DownloadOutlined />} // 使用下载图标
      onClick={handleConfirm} // 点击触发二次确认弹框
      {...props}
    >
      {children} {/* 按钮文本 */}
    </PermissionButton>
  );
};
 
export default ExportButton;

PermissionButton 参考 权限按钮

使用说明

  1. 传入 Props 参数

    • api: 必须传入一个返回 Promise 的 API 方法,该方法会调用并获取要导出的数据。
    • params: 一个函数,返回传递给 api 的参数。
    • children: 按钮文本(可选,默认为 '导出')。
    • fileName: 导出的文件名称(不含后缀,默认为空字符串)。
    • suffixName: 导出的文件后缀名(默认为 .xlsx)。
    • fileType: 文件 MIME 类型(默认为 application/vnd.ms-excel)。
  2. 使用示例

import React from 'react';
import { message } from 'antd';
import ExportButton from './ExportButton';
 
// 假设有一个 API 获取数据
const fetchData = async (params) => {
  // 模拟 API 请求,返回 Blob 数据
  return new Promise((resolve) => {
    setTimeout(() => {
      const blobData = new Blob([JSON.stringify({ data: 'example data' })], {
        type: 'application/json',
      });
      resolve(blobData);
    }, 1000);
  });
};
 
// 获取参数的函数
const getExportParams = () => {
  return { page: 1, size: 20 };  // 示例参数
};
 
const ExampleComponent = () => {
  return (
    <div>
      <ExportButton
        api={fetchData}           // 传入 API 方法
        params={getExportParams}  // 传入获取参数的函数
        fileName="example"        // 设置导出文件名
        suffixName=".json"        // 设置文件后缀
        fileType="application/json" // 设置文件类型
      >
        导出示例数据
      </ExportButton>
    </div>
  );
};
 
export default ExampleComponent;

组件解析

  • handleConfirm: 弹出确认框,当用户点击“确定”按钮时,会调用 handleExport 方法。
  • handleExport: 执行数据导出操作,首先打印导出的参数,接着调用 api 方法获取数据,并通过 exportFile 导出文件。
  • exportFile: 使用 Blob 创建文件,并通过动态生成的 <a> 标签进行文件下载。

总结

该组件适用于各种后台管理系统中的数据导出需求,用户只需提供对应的 API 和参数获取函数,组件会处理导出逻辑并支持弹框确认。通过自定义文件名、文件类型等参数,能够适应不同的导出需求,且具有较强的复用性和扩展性。