Uncle_MrLee

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理
/**
 * 通用Excel导出工具
 * 支持根据配置动态生成Excel文件,可自定义样式、格式和内容
 */


/*
使用方法
import { UniversalExcelExporter, exportExcel } from './universalExcelExport.js';

// 示例数据
const data = {
  sheet1: {
    headers: ['headers1', 'headers2', 'headers3', 'headers4'],
    data: [
      ['data1', 'data2', data3, data4],
    ]
  },
};

// 方法一:使用类方式
async function exportWithClass() {
  try {
    const exporter = new UniversalExcelExporter();
    exporter.addSheet({
      name: '销售数据', // 工作表名称
      headers: data.sheet1.headers,
      data: data.sheet1.data,
      options: {
        addTotalRow: true,
        totalColumns: [2, 3], // 合计销售额和利润列
        columnWidths: [15, 15, 15, 15],
        style: {
          header: { fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFCCCCCC' } } },
          totalRow: { fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFEEEEEE' } } }
        }
      }
    });


    await exporter.export('导出文件名.xlsx');
    console.log('导出成功');
  } catch (error) {
    console.error('导出失败:', error);
  }
}

// 方法二:使用简化函数
async function exportWithFunction() {
  try {
    await exportExcel({
      fileName: '导出文件名.xlsx',
      sheets: [
        {
          headers: data.sheet1.headers,
          data: data.sheet1.data,
          data: data.sales.data,
          options: {
            addTotalRow: true,
            totalColumns: [2, 3],
            columnWidths: [15, 15, 15, 15],
            style: {
              header: { font: { bold: true, color: { argb: 'FFFFFFFF' } }, fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF5B9BD5' } } },
              cells: (value, rowIndex, colIndex) => {
                // 根据条件设置单元格样式
                if (colIndex === 2 && value > 10000) {
                  return { font: { color: { argb: 'FF008000' } } };
                }
                return {};
              }
            }
          }
        }
      ]
    });
    console.log('导出成功');
  } catch (error) {
    console.error('导出失败:', error);
  }
}



*/
import Excel from 'exceljs';

export class UniversalExcelExporter {
   constructor() {
      if(!Excel) {
         throw new Error('ExcelJS库未加载,请先引入ExcelJS库');
      }

      this.workbook = new Excel.Workbook();
   }

   /**
   * 添加工作表
   * @param {Object} sheetConfig - 工作表配置
   * @param {string} sheetConfig.name - 工作表名称
   * @param {Array} sheetConfig.headers - 表头配置
   * @param {Array} sheetConfig.data - 表格数据
   * @param {Object} [sheetConfig.options] - 可选配置
   * @param {boolean} [sheetConfig.options.addTotalRow] - 是否添加合计行
   * @param {Array<string>} [sheetConfig.options.totalRowLabels] - 合计行标签
   * @param {Array<number>} [sheetConfig.options.totalColumns] - 需要合计的列索引
   * @param {Array<number>} [sheetConfig.options.columnWidths] - 列宽配置
   * @param {Object} [sheetConfig.options.style] - 样式配置
   * @param {Object} [sheetConfig.options.style.mergeCells] - 合并单元格配置
   * @returns {UniversalExcelExporter} - 支持链式调用
   */
   addSheet(sheetConfig) {
      const { name, headers, data, options = {} } = sheetConfig;

      if(!name || !headers || !Array.isArray(headers) || headers.length === 0) {
         console.warn('工作表配置不完整,跳过此工作表');
         return this;
      }

      const sheet = this.workbook.addWorksheet(name);

      // 添加表头
      const headerRow = sheet.addRow(headers);
      this._applyCellStyle(headerRow, {
         font: { bold: true },
         alignment: { horizontal: 'center' },
         ...options.style?.header,
      });

      // 添加数据
      if(data && Array.isArray(data) && data.length > 0) {
         data.forEach((rowData, rowIndex) => {
            const row = sheet.addRow(rowData);

            // 应用行样式(如果有)
            if(options.style?.rows) {
               const rowStyle = typeof options.style.rows === 'function'
                  ? options.style.rows(rowData, rowIndex)
                  : options.style.rows;
               this._applyCellStyle(row, rowStyle);
            }

            // 应用单元格样式(如果有)
            if(options.style?.cells) {
               row.eachCell((cell, colIndex) => {
                  const cellStyle = typeof options.style.cells === 'function'
                     ? options.style.cells(cell.value, rowIndex, colIndex)
                     : options.style.cells;
                  this._applyCellStyle(cell, cellStyle);
               });
            }
         });
      }

      // 添加合计行
      if(options.addTotalRow && options.totalColumns && options.totalColumns.length > 0) {
         const totalRowData = new Array(headers.length).fill('');

         // 设置合计行标签
         if(options.totalRowLabels && options.totalRowLabels.length > 0) {
            options.totalRowLabels.forEach((label, index) => {
               if(index < totalRowData.length) {
                  totalRowData[index] = label;
               }
            });
         }
         else {
            totalRowData[0] = '合计';
         }

         // 计算合计值
         options.totalColumns.forEach(colIndex => {
            if(colIndex < headers.length) {
               const columnLetter = this._getColumnLetter(colIndex + 1); // 转为Excel列字母
               const lastDataRow = data ? data.length + 1 : 1;
               totalRowData[colIndex] = {
                  formula: `SUM(${columnLetter}2:${columnLetter}${lastDataRow})`,
                  result: this._calculateColumnSum(sheet, colIndex + 1),
               };
            }
         });

         // 添加合计行并应用样式
         const totalRow = sheet.addRow(totalRowData);
         this._applyCellStyle(totalRow, {
            font: { bold: true },
            ...options.style?.totalRow,
         });
      }

      // 设置列宽
      if(options.columnWidths && options.columnWidths.length > 0) {
         options.columnWidths.forEach((width, index) => {
            if(index < sheet.columns.length) {
               sheet.columns[index].width = width;
            }
         });
      }

      // 合并单元格
      if(options.mergeCells && Array.isArray(options.mergeCells)) {
         options.mergeCells.forEach(mergeRange => {
            try {
               sheet.mergeCells(mergeRange);
            }
            catch(error) {
               console.warn(`合并单元格失败: ${mergeRange}`, error);
            }
         });
      }

      return this;
   }

   /**
   * 导出Excel文件
   * @param {string} fileName - 导出的文件名
   * @returns {Promise} - 导出操作的Promise
   */
   async export(fileName = 'ExportedExcel.xlsx') {
      if(this.workbook.worksheets.length === 0) {
         throw new Error('Excel文件至少需要一个工作表');
      }

      try {
         const buffer = await this.workbook.xlsx.writeBuffer();
         this._downloadFile(buffer, fileName);
         return true;
      }
      catch(error) {
         console.error('导出Excel失败:', error);
         throw error;
      }
   }

   /**
   * 应用单元格样式
   * @private
   */
   _applyCellStyle(target, style) {
      if(!style || typeof style !== 'object') return;

      if(target.eachCell) {
         // 针对整行应用样式
         target.eachCell(cell => {
            Object.assign(cell, style);
         });
      }
      else {
         // 针对单个单元格应用样式
         Object.assign(target, style);
      }
   }

   /**
   * 计算列的合计值
   * @private
   */
   _calculateColumnSum(sheet, columnIndex) {
      let sum = 0;
      sheet.eachRow((row, rowIndex) => {
         if(rowIndex > 1) { // 跳过表头
            const cell = row.getCell(columnIndex);
            const value = parseFloat(cell.value);

            if(!isNaN(value)) {
               sum += value;
            }
         }
      });
      return sum;
   }

   /**
   * 将列索引转换为Excel列字母
   * @private
   */
   _getColumnLetter(columnIndex) {
      let letter = '';
      let tempIndex = columnIndex;

      while(tempIndex > 0) {
         const remainder = (tempIndex - 1) % 26;
         letter = String.fromCharCode(65 + remainder) + letter;
         tempIndex = Math.floor((tempIndex - 1) / 26);
      }

      return letter;
   }

   /**
   * 下载文件
   * @private
   */
   _downloadFile(buffer, fileName) {
      const blob = new Blob([buffer], {
         type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
      });
      const link = document.createElement('a');
      link.href = URL.createObjectURL(blob);
      link.download = fileName;
      link.click();

      // 清理资源
      setTimeout(() => {
         URL.revokeObjectURL(link.href);
      }, 100);
   }
}

/**
 * 简化的导出函数
 * @param {Object} options - 导出选项
 * @param {Array} options.sheets - 工作表配置数组
 * @param {string} options.fileName - 文件名
 * @returns {Promise} - 导出操作的Promise
 */
export async function exportExcel(options = {}) {
   try {
      const exporter = new UniversalExcelExporter();

      if(!options.sheets || !Array.isArray(options.sheets) || options.sheets.length === 0) {
         throw new Error('至少需要配置一个工作表');
      }

      options.sheets.forEach(sheet => {
         exporter.addSheet(sheet);
      });

      return await exporter.export(options.fileName || 'ExportedExcel.xlsx');
   }
   catch(error) {
      console.error('导出Excel失败:', error);
      throw error;
   }
}
posted on 2025-06-26 10:12  Uncle_MrLee  阅读(34)  评论(0)    收藏  举报