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
相关产品推荐
相关产品推荐

