合作联系微信: w6668263      合作联系电话:177-9238-7426     

前端数据导出excel工具函数

npm install exceljs
npm install file-saver

 

 

import ExcelJS from 'exceljs';
import { saveAs } from 'file-saver';

/**
 * 树形多级表头 Excel 导出(默认行高26 + 全自定义配置)
 * @param {Array} columns  [{ prop, label, children, width, height, fontSize, isBold }]
 * @param {Array} rows     数据
 * @param {String} fileName 文件名
 */
export async function dataToExcel(columns = [], rows = [], fileName = '表格导出') {
  const workbook = new ExcelJS.Workbook();
  const worksheet = workbook.addWorksheet('Sheet1');

  // ===================== 构建表头结构 =====================
  let headerRows = [];
  let headerFontConfigs = [];
  let headerRowHeights = [];
  let merges = [];
  let maxLevel = 0;

  // 获取最大层级
  function getMaxLevel(cols, level = 0) {
    maxLevel = Math.max(maxLevel, level);
    cols.forEach(c => c.children?.length && getMaxLevel(c.children, level + 1));
  }
  getMaxLevel(columns);

  // 计算叶子列数
  function getLeafCount(cols) {
    return cols.reduce((sum, c) => sum + (c.children?.length ? getLeafCount(c.children) : 1), 0);
  }
  const totalCols = getLeafCount(columns);

  // 递归构建表头 + 收集配置
  function buildHeader(cols, level = 0, startCol = 0) {
    if (!headerRows[level]) headerRows[level] = [];
    if (!headerFontConfigs[level]) headerFontConfigs[level] = [];
    if (!headerRowHeights[level]) headerRowHeights[level] = 26; // 默认行高改为 26
    let col = startCol;

    cols.forEach(item => {
      const leafCount = item.children?.length ? getLeafCount(item.children) : 1;
      
      headerRows[level][col] = item.label;
      headerFontConfigs[level][col] = {
        fontSize: item.fontSize ?? 12,
        isBold: item.isBold ?? true,
      };

      // 行高取最大值
      if (item.height) {
        headerRowHeights[level] = Math.max(headerRowHeights[level], item.height);
      }

      if (item.children?.length) {
        merges.push({
          start: { row: level + 1, col: col + 1 },
          end: { row: level + 1, col: col + leafCount }
        });
        buildHeader(item.children, level + 1, col);
      } else {
        merges.push({
          start: { row: level + 1, col: col + 1 },
          end: { row: maxLevel + 1, col: col + 1 }
        });
      }
      col += leafCount;
    });
  }

  buildHeader(columns);

  // 补全空列
  headerRows = headerRows.map(row => {
    const newRow = [];
    for (let i = 0; i < totalCols; i++) newRow[i] = row[i] ?? '';
    return newRow;
  });
  headerFontConfigs = headerFontConfigs.map(row => {
    const newRow = [];
    for (let i = 0; i < totalCols; i++) newRow[i] = row[i] ?? { fontSize: 12, isBold: true };
    return newRow;
  });

  // ===================== 写入表头 =====================
  headerRows.forEach((row, levelIndex) => {
    const excelRow = worksheet.addRow(row);
    excelRow.height = headerRowHeights[levelIndex];
    excelRow.alignment = { horizontal: 'center', vertical: 'middle' };
    excelRow.fill = {
      type: 'pattern',
      pattern: 'solid',
      fgColor: { argb: 'FFEFEFEF' }
    };

    row.forEach((_, colIndex) => {
      const cell = excelRow.getCell(colIndex + 1);
      const { fontSize, isBold } = headerFontConfigs[levelIndex][colIndex];
      cell.font = { bold: isBold, size: fontSize, color: { argb: 'FF000000' } };
    });
  });

  // 合并单元格
  merges.forEach(({ start, end }) => {
    worksheet.mergeCells(start.row, start.col, end.row, end.col);
  });

  // ===================== 写入数据 =====================
  function getLeafPropsAndWidths(cols) {
    let props = [], widths = [];
    cols.forEach(c => {
      if (c.children?.length) {
        const child = getLeafPropsAndWidths(c.children);
        props = props.concat(child.props);
        widths = widths.concat(child.widths);
      } else {
        props.push(c.prop);
        widths.push(c.width ?? 16);
      }
    });
    return { props, widths };
  }
  const { props, widths: columnWidths } = getLeafPropsAndWidths(columns);

  rows.forEach(item => {
    const excelRow = worksheet.addRow(props.map(p => item[p] ?? ''));
    excelRow.height = 26; // 数据行默认高度也改为 26
  });

  // 设置列宽
  columnWidths.forEach((width, index) => {
    if (worksheet.columns[index]) worksheet.columns[index].width = width;
  });

  // ===================== 全黑色边框 + 居中 =====================
  const totalRows = worksheet.rowCount;
  for (let r = 1; r <= totalRows; r++) {
    for (let c = 1; c <= totalCols; c++) {
      const cell = worksheet.getCell(r, c);
      cell.border = {
        top: { style: 'thin', color: { argb: 'FF000000' } },
        left: { style: 'thin', color: { argb: 'FF000000' } },
        bottom: { style: 'thin', color: { argb: 'FF000000' } },
        right: { style: 'thin', color: { argb: 'FF000000' } }
      };
      cell.alignment = { horizontal: 'center', vertical: 'middle' };
    }
  }

  // ===================== 保存文件 =====================
  const buffer = await workbook.xlsx.writeBuffer();
  const blob = new Blob([buffer], {
    type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
  });
  saveAs(blob, `${fileName}.xlsx`);
}

 

 

用法示例:

 

const columns = [
  { 
    prop: 'name', 
    label: '姓名', 
    width: 12, 
    height: 30,    // 行高30
    fontSize: 14, 
    isBold: true 
  },
  {
    label: '基础信息',
    height: 25,    // 行高25
    fontSize: 13,
    isBold: true,
    children: [
      { prop: 'age', label: '年龄', width: 10, fontSize: 12, isBold: false },
      { prop: 'gender', label: '性别', width: 10, fontSize: 12, isBold: false },
    ]
  },
  {
    label: '成绩',
    fontSize: 13,
    isBold: true,
    children: [
      { prop: 'chinese', label: '语文', width: 14, fontSize: 12 },
      { prop: 'math', label: '数学', width: 14, fontSize: 12 },
    ]
  }
];

const rows = [
  { name: '张三', age: 20, gender: '男', chinese: 90, math: 95 },
  { name: '李四', age: 21, gender: '女', chinese: 85, math: 92 }
];

dataToExcel(columns, rows, '学生成绩表');

 

posted on 2026-04-01 23:28  草率的龙果果  阅读(8)  评论(0)    收藏  举报

导航