[特殊字符] 使用 Handsontable 构建一个支持 Excel 公式计算的动态表格

在 Web 应用中,处理表格数据并提供 Excel 级的功能(如公式计算、数据导入导出)一直是个挑战。今天,我将带你使用 React + Handsontable 搭建一个强大的 Excel 风格表格,支持 公式计算Excel 文件导入导出 ,并实现 动态单元格样式

🎯 项目目标

  • ✅ 创建一个可编辑的 Excel 风格表格

  • ✅ 支持 Excel 公式解析 (如 =B1+10

  • ✅ 支持 Excel 文件导入/导出(.xlsx/.xls)

  • ✅ 实现 单元格动态渲染(如公式高亮)

📦 依赖安装

首先,确保你的 React 项目已创建(若没有,可用 npx create-react-app my-app 创建)。然后,安装必要的依赖项:

javascript 复制代码
npm install @handsontable/react handsontable mathjs xlsx react-i18next

📌 核心代码解析

1️⃣ 创建 Excel 风格的 Handsontable 表格

javascript 复制代码
import React, { useState } from 'react';
import { HotTable } from '@handsontable/react';
import { useTranslation } from 'react-i18next';
import * as math from 'mathjs';
import * as XLSX from 'xlsx';
import 'handsontable/dist/handsontable.full.min.css';

const ExcelTable = () => {
  const { t } = useTranslation();
  const [data, setData] = useState([
    [t('name'), t('age'), t('city'), 'Total'],
    ['John', 30, 'New York', '=B1+10'],
    ['Alice', 25, 'London', '=B2*2'],
  ]);
  • useState 初始化数据,支持 公式输入 (如 =B1+10

  • Handsontable 是一个轻量级但功能强大的表格库,支持 Excel 风格的操作


2️⃣ 实现公式计算功能

javascript 复制代码
const calculateFormula = (value, row, col, dataArray) => {
  if (typeof value === 'string' && value.startsWith('=')) {
    try {
      const formula = value.slice(1).replace(/[A-Z]\d+/g, (cell) => {
        const colLetter = cell.match(/[A-Z]/)[0];
        const rowNum = parseInt(cell.match(/\d+/)[0], 10) - 1;
        const colNum = colLetter.charCodeAt(0) - 65;
        return dataArray[rowNum][colNum];
      });
      return math.evaluate(formula);
    } catch (e) {
      return '#ERROR';
    }
  }
  return value;
};
  • 解析 Excel 格式的公式=B1+10

  • 将公式转换为 数学计算表达式 并使用 math.evaluate() 计算结果

  • 错误处理 :如果解析失败,返回 #ERROR


3️⃣ 实现 Excel 文件导入功能

javascript 复制代码
const handleImport = (event) => {
  const file = event.target.files[0];
  const reader = new FileReader();
  reader.onload = (e) => {
    const binaryStr = e.target.result;
    const workbook = XLSX.read(binaryStr, { type: 'binary' });
    const sheetName = workbook.SheetNames[0];
    const sheet = workbook.Sheets[sheetName];
    const importedData = XLSX.utils.sheet_to_json(sheet, { header: 1 });
    setData(importedData);
  };
  reader.readAsBinaryString(file);
};
  • 用户选择 Excel 文件

  • 解析 Excel 数据 并转换为 JavaScript 数组

  • 更新 Handsontable 的 data 以渲染新数据


4️⃣ 实现 Excel 文件导出功能

javascript 复制代码
const handleExport = () => {
  const ws = XLSX.utils.aoa_to_sheet(data);
  const wb = XLSX.utils.book_new();
  XLSX.utils.book_append_sheet(wb, ws, 'Sheet1');
  XLSX.writeFile(wb, 'exported_excel.xlsx');
};
  • 将 Handsontable 数据 转换为 Excel sheet

  • 创建新的 Excel 工作簿

  • 下载 Excel 文件 ,实现 导出功能


5️⃣ 动态单元格渲染(公式高亮)

javascript 复制代码
<HotTable
  data={data}
  rowHeaders={true}
  colHeaders={true}
  contextMenu={true}
  stretchH="all"
  beforeChange={(changes) => {
    changes.forEach(([row, col, , newValue]) => {
      data[row][col] = newValue;
    });
    setData([...data]);
  }}
  afterGetCellMeta={(row, col, cellProperties) => {
    const value = data[row][col];
    if (typeof value === 'string' && value.startsWith('=')) {
      cellProperties.readOnly = false;
      cellProperties.renderer = (instance, td, r, c, prop, val) => {
        const result = calculateFormula(val, r, c, data);
        td.innerHTML = result;
        td.style.backgroundColor = '#e0f7fa'; // 公式单元格高亮
      };
    } else {
      cellProperties.renderer = (instance, td, r, c, prop, val) => {
        td.innerHTML = val;
        td.style.backgroundColor = '#ffffff'; // 普通单元格白色背景
      };
    }
  }}
  cells={(row, col) => {
    const cellProperties = {};
    if (row === 0) {
      cellProperties.className = 'header-cell'; // 表头样式
    }
    return cellProperties;
  }}
  licenseKey="non-commercial-and-evaluation"
/>
  • 检测单元格是否包含公式

  • 动态渲染公式计算结果

  • 高亮公式单元格 以增强用户体验


完整代码

javascript 复制代码
// src/components/ExcelTable.jsx
import React, { useState } from 'react';
import { HotTable } from '@handsontable/react';
import { useTranslation } from 'react-i18next';
import * as math from 'mathjs';
import * as XLSX from 'xlsx';
import 'handsontable/dist/handsontable.full.min.css';

const ExcelTable = () => {
  const { t } = useTranslation();
  const [data, setData] = useState([
    [t('name'), t('age'), t('city'), 'Total'],
    ['John', 30, 'New York', '=B1+10'],
    ['Alice', 25, 'London', '=B2*2'],
  ]);

  // 计算公式
  const calculateFormula = (value, row, col, dataArray) => {
    if (typeof value === 'string' && value.startsWith('=')) {
      try {
        const formula = value.slice(1).replace(/[A-Z]\d+/g, (cell) => {
          const colLetter = cell.match(/[A-Z]/)[0];
          const rowNum = parseInt(cell.match(/\d+/)[0], 10) - 1;
          const colNum = colLetter.charCodeAt(0) - 65;
          return dataArray[rowNum][colNum];
        });
        return math.evaluate(formula);
      } catch (e) {
        return '#ERROR';
      }
    }
    return value;
  };

  // 导入 Excel 文件
  const handleImport = (event) => {
    const file = event.target.files[0];
    const reader = new FileReader();
    reader.onload = (e) => {
      const binaryStr = e.target.result;
      const workbook = XLSX.read(binaryStr, { type: 'binary' });
      const sheetName = workbook.SheetNames[0];
      const sheet = workbook.Sheets[sheetName];
      const importedData = XLSX.utils.sheet_to_json(sheet, { header: 1 });
      setData(importedData);
    };
    reader.readAsBinaryString(file);
  };

  // 导出 Excel 文件
  const handleExport = () => {
    const ws = XLSX.utils.aoa_to_sheet(data);
    const wb = XLSX.utils.book_new();
    XLSX.utils.book_append_sheet(wb, ws, 'Sheet1');
    XLSX.writeFile(wb, 'exported_excel.xlsx');
  };

  return (
    <div>
      <div style={{ marginBottom: '10px' }}>
        <input type="file" accept=".xlsx, .xls" onChange={handleImport} />
        <button onClick={handleExport}>Export to Excel</button>
      </div>
      <HotTable
        data={data}
        rowHeaders={true}
        colHeaders={true}
        contextMenu={true}
        stretchH="all"
        beforeChange={(changes) => {
          changes.forEach(([row, col, , newValue]) => {
            data[row][col] = newValue;
          });
          setData([...data]);
        }}
        afterGetCellMeta={(row, col, cellProperties) => {
          const value = data[row][col];
          if (typeof value === 'string' && value.startsWith('=')) {
            cellProperties.readOnly = false;
            cellProperties.renderer = (instance, td, r, c, prop, val) => {
              const result = calculateFormula(val, r, c, data);
              td.innerHTML = result;
              td.style.backgroundColor = '#e0f7fa'; // 公式单元格高亮
            };
          } else {
            cellProperties.renderer = (instance, td, r, c, prop, val) => {
              td.innerHTML = val;
              td.style.backgroundColor = '#ffffff'; // 普通单元格白色背景
            };
          }
        }}
        cells={(row, col) => {
          const cellProperties = {};
          if (row === 0) {
            cellProperties.className = 'header-cell'; // 表头样式
          }
          return cellProperties;
        }}
        licenseKey="non-commercial-and-evaluation"
      />
      <style jsx>{
        .header-cell {
          background-color: #f0f0f0;
          font-weight: bold;
        }
      }</style>
    </div>
  );
};

export default ExcelTable; 

🎉 运行效果

🚀 你现在拥有了一个 功能强大的 Excel 风格表格 ,支持:

公式计算 (自动计算 =B1+10

Excel 文件导入/导出

动态高亮公式单元格

行列可编辑 & 右键菜单操作

💡 总结

通过 Handsontable + mathjs + xlsx ,我们轻松构建了一个 Excel 风格的动态表格 。这一方案适用于 财务管理、数据分析、在线表单应用 等场景,提升了数据处理的灵活性!

🔹 完整代码已上传 GitHub (可在评论区留言获取)

🔹 你对这个 Excel 组件有什么优化建议?欢迎评论交流!

🔥 如果觉得有帮助,别忘了点赞 + 收藏! 🎯

相关推荐
范文杰3 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪3 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪3 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy4 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom4 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom4 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom4 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom4 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom5 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试
LaoZhangAI6 小时前
2025最全GPT-4o图像生成API指南:官方接口配置+15个实用提示词【保姆级教程】
前端