React 项目中封装 Excel 导入导出组件:技术分享与实践

发布于:2025-06-01 ⋅ 阅读:(30) ⋅ 点赞:(0)


前言

在 React 项目中,处理 Excel 文件的导入和导出是常见的业务需求。无论是导出报表数据供用户下载,还是让用户上传 Excel 文件进行数据解析,一个高效、易用的组件都能极大提升开发效率和用户体验。本文将分享如何在 React 项目中封装一个通用的 Excel 导入导出组件,涵盖核心实现思路、代码示例以及最佳实践。


一、为什么需要封装 Excel 组件?

  1. 统一处理逻辑:避免在多个页面重复编写 Excel 解析或生成代码。
  2. 提升用户体验:通过统一的 UI 和交互,降低用户学习成本。
  3. 减少错误:集中处理文件格式校验、数据转换等易错环节。
  4. 可扩展性:支持自定义配置(如列映射、样式调整等)。

二、技术选型

  • 导出 Excel:使用 xlsx 库 & file-saver
  • 导入 Excel:使用 xlsx 库解析文件内容。
  • UI 框架:基于 Ant Design 的 Upload 组件或自定义按钮。

三、核心实现

1. 安装依赖

	npm install xlsx file-saver @ant-design/icons
	# 或使用 yarn
	yarn add xlsx file-saver @ant-design/icons

2. 封装Excel导出

根据泛型传入不同的data数据类型和动态传递header表头

import * as XLSX from "xlsx";
import { saveAs } from "file-saver";

/**
 *  导出Excel
 * @param data
 * @param header
 */
export function exportExcel<T>(data: T[], header: string[]) {
  const worksheet = XLSX.utils.json_to_sheet(data, { header });
  // 创建工作簿
  const workbook = XLSX.utils.book_new();
  // 将工作表添加到工作簿
  XLSX.utils.book_append_sheet(workbook, worksheet, "Sheet1");
  // 将工作簿转换为二进制数据
  const excelBuffer = XLSX.write(workbook, { bookType: "xlsx", type: "array" });
  // 创建 Blob 对象
  const blob = new Blob([excelBuffer], { type: "application/octet-stream" });
  // 使用 file-saver 库保存文件
  saveAs(blob, "exported_data.xlsx");
}

使用示例

import {useState} from 'react'
import { exportExcel } from "@/utils/exprotExcel";
// 这个是antd的table组件的表格多选框 selectedRowKeys, setSelectedRowKeys要绑定的状态值
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]); 
const [selectRows, setSelectRows] = useState<IBill[]>([]); 
const header = [
  "accountNo",
  "status",
  "roomNo",
  "carNo",
  "tel",
  "costName1",
  "costName2",
  "costName3",
  "startDate",
  "endDate",
  "preferential",
  "money",
  "pay",
];
  // 多选
  const onSelectChange = (
    selectedRowKeys: React.Key[],
    selectedRows: IBill[]
  ) => {
    setSelectedRowKeys(selectedRowKeys);
    setSelectRows(selectedRows);
  };
  // 多选框配置项
  const rowSelection = {
    selectedRowKeys,
    onChange: onSelectChange,
    preserveSelectedRowKeys: true,
  };


// 这下面是结构
<Table
          loading={loading}
          dataSource={billList}
          columns={columns}
          pagination={false}
          rowKey={(record) => record.accountNo}
          scroll={{ x: 1200 }}
          rowSelection={rowSelection}
        />
 <Button
 		// 使用封装好的Excel导出组件
          type="primary"
          onClick={() => exportExcel(selectRows, header)}
          icon={<DownloadOutlined />}
          disabled={disabled}
        >
          导出为Excel
</Button>

在这里插入图片描述
在这里插入图片描述


3. 封装导入组件 (UploadExcel)

import { Upload, message } from "antd";
import type { UploadProps } from "antd";
import { InboxOutlined } from "@ant-design/icons";
const { Dragger } = Upload;

import * as XLSX from "xlsx";



interface ExcelRowItem {
  [key: number]: string; // 索引签名,允许任意数字键
}

interface IParms<U> {
  setStaffList: (staffList: (prev: U[]) => U[]) => void;
  headers: (keyof U)[];
}
// 处理导入的Excel数据
function handleImpotedJson<T>(
  jsonArr: ExcelRowItem[],
  headers: (keyof T)[] // 将 headers 明确为 T 的键数组
) {
  // 去掉表头
  jsonArr.splice(0, 1);
  const jsonArrData: T[] = jsonArr.map((item: ExcelRowItem) => {
    console.log(item, "item");
    // 这样做可以避免不用初始化对象,不用硬编码Partial
    const jsonObj: Partial<T> = {};
    // const jsonObj: T = {
    //   key: Math.floor(Math.random() * 10000000),
    //   name: item[0],
    //   region: item[1],
    //   role: item[2],
    //   phone: item[3],
    // };

    // 动态生成表头
    headers.forEach((header, index) => {
      const value = item[index];
      if (value !== undefined) {
        jsonObj[header] = value as T[keyof T];
      }
    });

    return jsonObj as T;
  });
  console.log(jsonArrData, "jsonArrData");
  return jsonArrData;
}

/**
 *  Excel上传组件
 * @param param0  传参需要:1.useState表格数据列表 2.表头信息 3.传表头的泛型
 * @returns
 */
function UploadExcel<U>({ setStaffList, headers }: IParms<U>) {
  const props: UploadProps = {
    name: "file",
    multiple: true,
    accept: ".xls,.xlsx", // 只允许上传 Excel 文件
    action: "https://www.demo.com/import",
    onChange(info) {
      console.log("我同时触发了onChange和onDrop");
      const { status } = info.file;
      if (status !== "uploading") {
        const file = info.file.originFileObj; //
        if (file) {
          const reader = new FileReader();
          reader.onload = (e) => {
            const target = e.target?.result as ArrayBuffer;
            if (target) {
              const data = target; // 安全访问
              const workbook = XLSX.read(data, { type: "binary" });
              const first_worksheet = workbook.Sheets[workbook.SheetNames[0]];

              const jsonArr = XLSX.utils.sheet_to_json(first_worksheet, {
                header: 1,
              });

              const res = handleImpotedJson<U>(
                jsonArr as ExcelRowItem[],
                headers
              );
              console.log(res, "我是最后的结果");

              setStaffList((prevStaffList) => {
                return [...prevStaffList, ...res];
              });
          // 添加这一行,开始读取文件
          reader.readAsArrayBuffer(file);
        }
      }
      if (status === "done") {
        message.success(`${info.file.name} 文件已成功上传.`);
      } else if (status === "error") {
        message.error(`${info.file.name} 文件上传失败.`);
      }
    },
    onDrop(e) {
      console.log("我开始拖拽了", e.dataTransfer.files);
    },
  };

  return (
    //  这个是antd的上传组件
    <>
      <Dragger {...props}>
        <p className="ant-upload-drag-icon">
          <InboxOutlined />
        </p>
        <p className="ant-upload-text">点击或拖拽上传文件哦!!!</p>
        <p className="ant-upload-hint">
          支持单个文件或批量上传。严禁上传公司数据或其他受禁文件。
        </p>
      </Dragger>
    </>
  );
}

export default UploadExcel;

使用示例:
也是引入组件,传表头,和你的修改状态的setState

import UploadExcel from "@/components/UploadExcel";
import {useState} from 'react'
 const [staffList, setStaffList] = useState<IStaff[]>([]);
 const header: StaffKeys[] = ["name", "region", "role", "phone"];
  <UploadExcel<StaffMemberData>
          headers={header}
          setStaffList={setStaffList}
   />

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

总结

通过封装 Excel 导入导出组件,我们可以将重复的逻辑抽象为可复用的模块,提升开发效率和代码质量。关键点包括:

  • 使用 xlsx 库处理核心逻辑。
  • 结合 Ant Design 等 UI 框架提升交互体验。
  • 通过配置化(如列映射、泛型约束)满足不同场景需求。

网站公告

今日签到

点亮在社区的每一天
去签到