You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular导出带CSS样式的HTML表格到Excel多工作表问题

Angular导出多工作表Excel(含样式与图片)解决方案

核心问题分析

  • 工作表内容重叠:未正确为每个表格创建独立的工作表实例,导致重复写入同一张表
  • 样式丢失:原生xlsx库不支持解析外部CSS,仅能识别内联样式;或未使用支持样式的扩展库
  • base64图片损坏:HTML中的data URI格式图片无法直接被Excel识别,需转换为二进制数据插入工作表

步骤1:安装依赖

优先选择支持样式和图片操作的库组合:

npm install xlsx xlsx-style dom-to-image --save
  • xlsx:基础Excel读写能力
  • xlsx-style:扩展单元格样式支持
  • dom-to-image:辅助处理HTML中base64图片的格式转换

步骤2:正确分离工作表(解决内容重叠问题)

确保每个表格单独转换为工作表,再分别添加到工作簿:

import * as XLSX from 'xlsx';
import * as XLSXStyle from 'xlsx-style';

exportToExcel() {
  // 获取两个目标表格的DOM元素
  const table1 = document.getElementById('table1') as HTMLElement;
  const table2 = document.getElementById('table2') as HTMLElement;

  // 单独转换每个表格为工作表
  const ws1 = XLSX.utils.table_to_sheet(table1);
  const ws2 = XLSX.utils.table_to_sheet(table2);

  // 创建工作簿并添加工作表
  const wb = XLSX.utils.book_new();
  XLSX.utils.book_append_sheet(wb, ws1, '工作表1');
  XLSX.utils.book_append_sheet(wb, ws2, '工作表2');

  // 后续执行样式与图片处理逻辑
}

步骤3:保留CSS样式(解决样式丢失问题)

方法1:外部CSS转内联样式(兼容性最优)

导出前遍历表格元素,把外部样式转为内联样式,确保xlsx能识别:

function convertCssToInline(element: HTMLElement) {
  const computedStyle = window.getComputedStyle(element);
  for (let i = 0; i < computedStyle.length; i++) {
    const prop = computedStyle[i];
    element.style.setProperty(prop, computedStyle.getPropertyValue(prop));
  }
  // 递归处理所有子元素
  Array.from(element.children).forEach(child => convertCssToInline(child as HTMLElement));
}

// 导出前调用该方法处理表格
convertCssToInline(table1);
convertCssToInline(table2);

方法2:手动设置单元格样式(精细控制)

使用xlsx-style为指定单元格添加自定义样式:

// 示例:为表头设置灰色背景与加粗字体
function applyHeaderStyle(ws: XLSX.WorkSheet) {
  const range = XLSX.utils.decode_range(ws['!ref'] || 'A1:A1');
  for (let col = range.s.c; col <= range.e.c; col++) {
    const cellAddr = XLSX.utils.encode_cell({r: 0, c: col});
    if (ws[cellAddr]) {
      ws[cellAddr].s = {
        fill: {fgColor: {rgb: 'E0E0E0'}},
        font: {bold: true, sz: 12},
        alignment: {horizontal: 'center', vertical: 'center'}
      };
    }
  }
}

// 为两个工作表添加表头样式
applyHeaderStyle(ws1);
applyHeaderStyle(ws2);

步骤4:修复base64图片导出问题

将HTML中的data URI图片转换为二进制数据,插入到Excel工作表的对应位置:

async function handleTableImages(table: HTMLElement, ws: XLSX.WorkSheet, wb: XLSX.WorkBook) {
  const images = table.querySelectorAll('img');
  for (const img of images) {
    try {
      // 将base64图片转为二进制数组
      const blob = await fetch(img.src).then(res => res.blob());
      const arrayBuffer = await blob.arrayBuffer();
      const uint8Array = new Uint8Array(arrayBuffer);

      // 获取图片所在的单元格位置
      const cell = img.parentElement as HTMLElement;
      const cellCol = Array.from(cell.parentElement!.children).indexOf(cell);
      const cellRow = Array.from(cell.parentElement!.parentElement!.children).indexOf(cell.parentElement!);

      // 将图片插入工作簿
      const imgId = (wb['Drawings']?.length || 0) + 1;
      wb['Drawings'] = wb['Drawings'] || [];
      wb['Drawings'].push({
        type: 'image',
        image: uint8Array,
        name: `ExportedImage${imgId}`,
        position: {
          type: 'twoCellAnchor',
          from: {r: cellRow, c: cellCol},
          to: {r: cellRow + 1, c: cellCol + 1}
        }
      });
    } catch (err) {
      console.error('图片处理失败:', err);
    }
  }
}

// 在转换表格为工作表后调用
await handleTableImages(table1, ws1, wb);
await handleTableImages(table2, ws2, wb);

完整整合代码

import * as XLSX from 'xlsx';
import * as XLSXStyle from 'xlsx-style';

async function exportTablesToExcel() {
  // 获取表格DOM
  const table1 = document.getElementById('table1') as HTMLElement;
  const table2 = document.getElementById('table2') as HTMLElement;

  // 转换外部CSS为内联样式
  convertCssToInline(table1);
  convertCssToInline(table2);

  // 转换表格为工作表
  const ws1 = XLSX.utils.table_to_sheet(table1);
  const ws2 = XLSX.utils.table_to_sheet(table2);

  // 创建工作簿并添加工作表
  const wb = XLSX.utils.book_new();
  XLSX.utils.book_append_sheet(wb, ws1, '工作表1');
  XLSX.utils.book_append_sheet(wb, ws2, '工作表2');

  // 处理表格中的图片
  await handleTableImages(table1, ws1, wb);
  await handleTableImages(table2, ws2, wb);

  // 添加表头样式
  applyHeaderStyle(ws1);
  applyHeaderStyle(ws2);

  // 导出带样式的Excel文件
  XLSXStyle.writeFile(wb, '双表格导出.xlsx');
}

function convertCssToInline(element: HTMLElement) {
  const computedStyle = window.getComputedStyle(element);
  for (let i = 0; i < computedStyle.length; i++) {
    const prop = computedStyle[i];
    element.style.setProperty(prop, computedStyle.getPropertyValue(prop));
  }
  Array.from(element.children).forEach(child => convertCssToInline(child as HTMLElement));
}

async function handleTableImages(table: HTMLElement, ws: XLSX.WorkSheet, wb: XLSX.WorkBook) {
  const images = table.querySelectorAll('img');
  for (const img of images) {
    try {
      const blob = await fetch(img.src).then(res => res.blob());
      const arrayBuffer = await blob.arrayBuffer();
      const uint8Array = new Uint8Array(arrayBuffer);

      const cell = img.parentElement as HTMLElement;
      const cellCol = Array.from(cell.parentElement!.children).indexOf(cell);
      const cellRow = Array.from(cell.parentElement!.parentElement!.children).indexOf(cell.parentElement!);

      const imgId = (wb['Drawings']?.length || 0) + 1;
      wb['Drawings'] = wb['Drawings'] || [];
      wb['Drawings'].push({
        type: 'image',
        image: uint8Array,
        name: `ExportedImage${imgId}`,
        position: {
          type: 'twoCellAnchor',
          from: {r: cellRow, c: cellCol},
          to: {r: cellRow + 1, c: cellCol + 1}
        }
      });
    } catch (err) {
      console.error('图片处理失败:', err);
    }
  }
}

function applyHeaderStyle(ws: XLSX.WorkSheet) {
  const range = XLSX.utils.decode_range(ws['!ref'] || 'A1:A1');
  for (let col = range.s.c; col <= range.e.c; col++) {
    const cellAddr = XLSX.utils.encode_cell({r: 0, c: col});
    if (ws[cellAddr]) {
      ws[cellAddr].s = {
        fill: {fgColor: {rgb: 'E0E0E0'}},
        font: {bold: true, sz: 12},
        alignment: {horizontal: 'center', vertical: 'center'}
      };
    }
  }
}

关键注意事项

  • Angular 12+版本需在tsconfig.json中添加"skipLibCheck": true,避免xlsx-style的类型检查报错
  • 合并单元格需手动调整工作表的ws['!merges']属性,可参考XLSX.utils.decode_range与XLSX.utils.encode_range方法
  • 大体积图片建议提前压缩,避免导出的Excel文件过大

内容的提问来源于stack exchange,提问作者user2171002

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 18:58:12