尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Vue前端Excel样式定制:xlsx与xlsx-style组合实现复杂表格导出

Vue前端Excel样式定制:xlsx与xlsx-style组合实现复杂表格导出 1. 项目概述为什么前端需要精细化的Excel处理能力在日常的业务开发中尤其是后台管理系统Excel的导入导出功能几乎是标配。但很多时候我们拿到的需求不仅仅是“把数据导出来”这么简单。产品经理或业务方可能会指着导出的Excel文件说“这一列数据是异常状态能不能标红高亮一下”、“这个表头需要合并看起来更清晰”、“金额列能不能固定宽度防止数字显示不全”。这些需求已经超出了简单数据搬运的范畴进入了“表格样式定制”的领域。Vue生态中处理Excelxlsx库是当之无愧的王者它能完美地处理数据的读写。然而一个常见的误区是认为xlsx库也能直接设置样式。实际上原生的xlsx库核心专注于数据的解析和生成对单元格样式如背景色、字体、边框的支持非常有限。这就引出了我们今天要讨论的核心组合xlsxxlsx-style。xlsx负责数据的骨架xlsx-style则为这副骨架披上华丽的外衣负责所有样式的渲染。这个技术组合解决的痛点非常明确在纯前端环境下不依赖后端实现高度定制化的Excel文件生成与解析。无论是生成带有复杂样式的工作报告还是解析用户上传的、带有颜色标记的模板文件这套方案都能胜任。它特别适合前端开发者、全栈工程师以及需要开发数据密集型后台应用的朋友。接下来我将从一个完整的项目实操角度带你一步步实现从基础导出到复杂样式定制的全过程并分享我趟过的坑和总结的技巧。2. 核心工具链解析与项目初始化2.1 xlsx与xlsx-style的角色分工首先我们必须理清这两个库的职责边界这是后续一切操作的基础。xlsx通常指SheetJS社区版是一个功能强大、纯JavaScript编写的电子表格处理库。它的核心能力在于读取解析将各种格式XLSX, XLS, CSV等的Excel文件解析成JavaScript可操作的对象workbook。写入生成将JavaScript对象构建成的workbook写入并生成Excel文件二进制数据。数据处理它提供了完整的API来操作工作表sheet、单元格范围range、单元格数据cell等。但是社区版的xlsx在样式处理上是有意被阉割的。它只能处理非常基础的样式比如单元格数据类型字符串、数字、日期对于背景色、字体、边框等丰富样式其官方做法是将其忽略或仅作保留不解析/不生成。xlsx-style库的出现正是为了填补这个空白。它是在xlsx源代码基础上修改而来的一个分支版本核心修改在于增强了样式对象的处理能力。它完整实现了对Excel样式属性在Office Open XML标准中对应cellXfs等节点的读取和写入。重要提示由于xlsx-style是修改版它与原版xlsx的API在数据读写层面完全兼容但在导入和使用时你必须用xlsx-style完全替代xlsx。你不能同时安装两个库并期望它们协同工作这会导致冲突。2.2 项目环境搭建与依赖安装我们基于一个Vue 3项目使用Vite进行演示。Vue 2的项目在核心逻辑上完全一致主要区别在于组合式API和选项式API的写法。第一步安装正确的依赖。这里有一个关键点xlsx-style的官方npm包在某些构建工具下可能存在兼容性问题。更稳定、更常见的做法是使用其GitHub仓库的发布版本。# 进入你的Vue项目目录 cd your-vue-project # 安装核心库。我们使用一个广泛认可的xlsx-style分支版本 npm install xlsx npm install https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz # 注意上面这个tgz链接是SheetJS官方提供的包含样式的版本它等价于xlsx-style。 # 你也可以使用其他社区维护的xlsx-style包但务必测试其功能。 # 安装文件保存辅助库用于触发浏览器下载 npm install file-saver安装完成后在你的Vue组件中你需要这样引入import * as XLSX from xlsx; // 实际上你安装的已经是支持样式的版本了 import FileSaver from file-saver;验证安装是否成功可以检查XLSX对象是否有write和read方法并且尝试访问一个样式属性比如在浏览器控制台查看XLSX.SSF是否存在这是一个样式相关的内部对象但这并非绝对。最直接的验证是在后续编写样式代码时不报错。2.3 理解核心数据结构Workbook、Sheet与Cell在操作Excel前必须理解xlsx库是如何在内存中表示一个Excel文件的。它遵循一个层次化的结构工作簿Workbook对应一个.xlsx文件。它是一个JavaScript对象其Sheets属性包含了所有工作表。const workbook { SheetNames: [Sheet1, Sheet2], // 工作表名称数组 Sheets: { Sheet1: sheet1Object, // 工作表对象 Sheet2: sheet2Object } };工作表Sheet对应Excel中的一个标签页如Sheet1。它是一个以单元格地址为键key的普通对象。const sheet { !ref: A1:D5, // 定义工作表数据范围非常重要 A1: { v: 姓名, t: s, s: { /* 样式对象 */ } }, B1: { v: 年龄, t: s }, C1: { v: 得分, t: n }, A2: { v: 张三, t: s }, B2: { v: 25, t: n }, C2: { v: 89.5, t: n }, // ... 其他单元格 !merges: [ { s: {r:0, c:0}, e: {r:0, c:2} } ], // 合并单元格信息 !cols: [ {wpx: 100}, {wpx: 50}, {wpx: 80} ] // 列宽信息 };单元格Cell是工作表对象中的每一个属性值。它是一个包含以下核心属性的对象v: 单元格的原始值value。t: 值的类型type。s代表字符串Stringn代表数字Numberb代表布尔值Booleand代表日期Date。s:样式对象Style这是xlsx-style发挥作用的关键。它是一个定义了字体、填充、对齐、边框等样式的对象。!ref、!merges、!cols、!rows等是工作表的特殊键以!开头用于存储元数据。理解这个结构就像理解了DOM树一样是进行一切精细化操作的前提。3. 基础数据导出与导入功能实现在添加华丽样式之前我们必须先把数据读写的基础打牢。这一部分将实现一个干净、健壮的导出导入功能。3.1 构建数据并生成基础工作簿假设我们有一个员工列表数据需要导出const employeeList [ { name: 张三, age: 28, department: 技术部, score: 85 }, { name: 李四, age: 32, department: 市场部, score: 92 }, { name: 王五, age: 24, department: 设计部, score: 78 }, ];第一步是将这个JSON数据转换为工作表对象。XLSX.utils提供了一系列工具函数最常用的是json_to_sheet。// 在Vue组件的方法或setup函数中 function exportBasicExcel() { // 1. 将JSON数据转换为工作表 const worksheet XLSX.utils.json_to_sheet(employeeList); // 2. 创建一个新的工作簿 const workbook XLSX.utils.book_new(); // 3. 将工作表添加到工作簿并命名为“员工列表” XLSX.utils.book_append_sheet(workbook, worksheet, 员工列表); // 4. 生成Excel文件二进制数据 const excelBuffer XLSX.write(workbook, { bookType: xlsx, type: array // 输出为ArrayBuffer兼容性好 }); // 5. 创建Blob并触发下载 const dataBlob new Blob([excelBuffer], { type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charsetUTF-8 }); FileSaver.saveAs(dataBlob, 员工列表_${new Date().getTime()}.xlsx); }点击触发这个函数你就能下载一个包含数据的Excel文件了。但是你会发现表头是name、age而不是中文。json_to_sheet默认使用对象的键作为表头。3.2 自定义表头与复杂数据转换为了使用中文表头我们需要多一步处理。有两种常见方法方法一转换数据源在调用json_to_sheet前先将数据映射为具有中文键的对象。const dataWithChineseHeader employeeList.map(item ({ 姓名: item.name, 年龄: item.age, 部门: item.department, 绩效得分: item.score })); const worksheet XLSX.utils.json_to_sheet(dataWithChineseHeader);方法二直接操作工作表对象更灵活先生成工作表然后直接修改A1、B1等表头单元格的值。const worksheet XLSX.utils.json_to_sheet(employeeList); // 覆盖第一行的值 worksheet[A1].v 姓名; worksheet[B1].v 年龄; worksheet[C1].v 部门; worksheet[D1].v 绩效得分; // 注意这种方法要求你知道原始键的顺序。如果数据顺序可能变化方法一更安全。对于更复杂的数据结构比如嵌套对象你可能需要手动构建工作表。这时可以循环数据使用XLSX.utils.encode_cell来定位单元格。function buildComplexSheet(data) { const worksheet {}; const range { s: { c: 0, r: 0 }, e: { c: 0, r: 0 } }; // 初始范围 // 设置表头 const headers [姓名, 年龄, 联系信息, 得分]; headers.forEach((header, colIndex) { const cellAddress XLSX.utils.encode_cell({ c: colIndex, r: 0 }); worksheet[cellAddress] { v: header, t: s }; range.e.c Math.max(range.e.c, colIndex); }); // 填充数据 data.forEach((item, rowIndex) { const dataRow rowIndex 1; // 数据从第2行开始 worksheet[XLSX.utils.encode_cell({ c: 0, r: dataRow })] { v: item.name, t: s }; worksheet[XLSX.utils.encode_cell({ c: 1, r: dataRow })] { v: item.age, t: n }; // 假设联系信息是一个对象我们只取邮箱 worksheet[XLSX.utils.encode_cell({ c: 2, r: dataRow })] { v: item.contact?.email || , t: s }; worksheet[XLSX.utils.encode_cell({ c: 3, r: dataRow })] { v: item.score, t: n }; range.e.r Math.max(range.e.r, dataRow); }); // 设置工作表的有效范围 worksheet[!ref] XLSX.utils.encode_range(range); return worksheet; }3.3 实现文件导入与数据解析导入功能允许用户上传Excel文件前端将其解析为JSON数据。template div input typefile accept.xlsx, .xls, .csv changehandleFileImport / /div /template script setup import * as XLSX from xlsx; function handleFileImport(event) { const file event.target.files[0]; if (!file) return; const reader new FileReader(); reader.onload (e) { const data e.target.result; // 读取工作簿 const workbook XLSX.read(data, { type: binary }); // 获取第一个工作表的名字 const firstSheetName workbook.SheetNames[0]; // 将工作表转换为JSON数据 const worksheet workbook.Sheets[firstSheetName]; const jsonData XLSX.utils.sheet_to_json(worksheet, { header: 1, // 以数组形式返回包含表头行。如果设为‘A’则使用第一行作为键。 defval: , // 空单元格的默认值 raw: false // 为true时返回原始值为false时会尝试转换日期和数字 }); console.log(解析到的数据, jsonData); // 通常你需要在这里处理jsonData比如映射为你的业务对象提交给后端或更新状态。 processImportedData(jsonData); }; reader.readAsBinaryString(file); // 也可以用 readAsArrayBuffer但 type 要改为 array } function processImportedData(dataArray) { // 假设第一行是表头 const [headerRow, ...dataRows] dataArray; const result dataRows.map(row { const obj {}; headerRow.forEach((header, index) { obj[header] row[index]; }); return obj; }); console.log(处理后的对象数组, result); return result; } /script实操心得在导入时sheet_to_json的header参数非常关键。如果你的Excel第一行是明确的表头使用header: 1或header: ‘A’可以方便地将其转换为对象的键。如果文件没有表头或者表头不规范你可能需要手动指定键名。另外注意处理空行和合并单元格sheet_to_json默认会跳过完全空的行但合并单元格只有左上角单元格有值。4. 高级样式定制详解现在进入核心部分如何为单元格添加样式。所有样式都通过单元格对象的s属性来定义。4.1 样式对象s的结构解析一个完整的样式对象可以包含非常多的属性以下是最常用的部分const style { // 字体 font: { name: 宋体, // 字体名称如 ‘Microsoft YaHei’, ‘Arial’ sz: 12, // 字体大小磅 color: { rgb: FF0000 }, // 字体颜色RGB格式不带#号 bold: true, // 加粗 italic: false, // 斜体 underline: false, // 下划线 strike: false // 删除线 }, // 填充背景色 fill: { fgColor: { rgb: FFFF00 }, // 前景色即填充色 bgColor: { rgb: 000000 }, // 背景色通常不用 patternType: solid // 填充模式solid为纯色填充 }, // 对齐 alignment: { horizontal: center, // 水平对齐left, center, right, fill, justify, centerContinuous, distributed vertical: center, // 垂直对齐top, center, bottom, justify, distributed wrapText: true, // 是否自动换行 indent: 0, // 缩进 readingOrder: 0, // 阅读顺序 textRotation: 0 // 文字旋转角度0到180或-90到90 }, // 边框 border: { top: { style: thin, color: { rgb: 000000 } }, bottom: { style: thin, color: { rgb: 000000 } }, left: { style: thin, color: { rgb: 000000 } }, right: { style: thin, color: { rgb: 000000 } }, // diagonal: { style: thin, color: { rgb: 000000 } } // 斜线边框 }, // 数字格式如日期、货币、百分比 numFmt: 0.00% // 例如yyyy-mm-dd, #,##0.00, 0% };4.2 设置单元格背景色与文字居中让我们为之前导出的员工表添加样式表头行背景设为浅蓝色、加粗居中绩效得分高于90的单元格背景设为浅绿色。function exportStyledExcel() { const worksheet XLSX.utils.json_to_sheet(employeeList); // 修改表头为中文 worksheet[A1].v 姓名; worksheet[B1].v 年龄; worksheet[C1].v 部门; worksheet[D1].v 绩效得分; // 1. 定义样式对象 const headerStyle { font: { bold: true, sz: 14, color: { rgb: FFFFFF } }, fill: { fgColor: { rgb: 4472C4 }, patternType: solid }, // 蓝色背景 alignment: { horizontal: center, vertical: center } }; const highScoreStyle { fill: { fgColor: { rgb: C6EFCE }, patternType: solid }, // 浅绿色背景 font: { bold: true, color: { rgb: 006100 } } }; const centerAlignStyle { alignment: { horizontal: center, vertical: center } }; // 2. 应用表头样式 (第1行A到D列) for (let col 0; col 4; col) { const cellAddress XLSX.utils.encode_cell({ c: col, r: 0 }); if (worksheet[cellAddress]) { worksheet[cellAddress].s headerStyle; } } // 3. 应用数据行样式 const dataStartRow 1; // 数据从第2行开始 employeeList.forEach((row, rowIndex) { const absoluteRow dataStartRow rowIndex; // 所有数据行水平居中 for (let col 0; col 4; col) { const cellAddress XLSX.utils.encode_cell({ c: col, r: absoluteRow }); if (worksheet[cellAddress]) { // 注意不能直接赋值会覆盖已有样式。应先合并或创建新对象。 worksheet[cellAddress].s { ...(worksheet[cellAddress].s || {}), ...centerAlignStyle }; } } // 为高分单元格应用特殊背景色假设得分在D列索引为3 const scoreCellAddress XLSX.utils.encode_cell({ c: 3, r: absoluteRow }); if (worksheet[scoreCellAddress] worksheet[scoreCellAddress].v 90) { worksheet[scoreCellAddress].s { ...(worksheet[scoreCellAddress].s || {}), ...highScoreStyle }; } }); // 生成工作簿和文件同上 const workbook XLSX.utils.book_new(); XLSX.utils.book_append_sheet(workbook, worksheet, 员工列表带样式); const excelBuffer XLSX.write(workbook, { bookType: xlsx, type: array }); const dataBlob new Blob([excelBuffer], { type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet }); FileSaver.saveAs(dataBlob, 员工列表_带样式_${new Date().getTime()}.xlsx); }注意事项样式对象的合并需要小心。直接赋值s newStyle会完全覆盖旧样式。通常的做法是使用扩展运算符...进行浅合并如s: { ...oldStyle, ...newStylePart }。对于字体、填充等复杂对象浅合并是有效的但如果要修改嵌套很深的属性可能需要更精细的操作。4.3 合并单元格与设置列宽行高合并单元格和设置列宽/行高不是通过单元格样式s实现的而是通过工作表的特殊属性!merges、!cols、!rows来设置。合并单元格!merges是一个数组每个元素描述一个合并区域。// 假设我们要合并A1到D1作为大标题 if (!worksheet[!merges]) worksheet[!merges] []; worksheet[!merges].push({ s: { r: 0, c: 0 }, e: { r: 0, c: 3 } }); // s: start (起始单元格), e: end (结束单元格)。r: row (行索引从0开始), c: column (列索引从0开始) // 合并A2和A3单元格 worksheet[!merges].push({ s: { r: 1, c: 0 }, e: { r: 2, c: 0 } });合并后只有左上角s单元格的内容和样式会被保留其他单元格即使有值也会在Excel中被忽略。在xlsx内部这些单元格可能仍然存在但!ref范围外或合并区域内的非主单元格在写入文件时会被处理。设置列宽!cols是一个数组每个元素对应一列的宽度配置。如果没有定义Excel会使用默认宽度。if (!worksheet[!cols]) worksheet[!cols] []; // 设置A列宽度为100像素wpx B列宽度为20字符wchC列自动不设置 worksheet[!cols][0] { wpx: 100 }; // A列 worksheet[!cols][1] { wch: 20 }; // B列 // wpx: 像素宽度 wch: 字符宽度基于默认字体。通常使用wch更符合Excel习惯。注意数组索引对应列索引0代表A列。如果只设置了部分列的宽度其他列仍为默认。设置行高!rows是一个数组每个元素对应一行的高度配置。if (!worksheet[!rows]) worksheet[!rows] []; // 设置第1行索引0高度为40像素 worksheet[!rows][0] { hpx: 40 }; // 设置第2行索引1高度为80像素 worksheet[!rows][1] { hpx: 80 }; // hpx: 像素高度 hpt: 磅值高度。一个综合示例创建一个带有标题行、合并单元格和自定义列宽的报表。function exportReport() { const worksheet {}; // 1. 设置大标题合并A1-E1 worksheet[A1] { v: 2024年度部门绩效报告, t: s, s: { font: { bold: true, sz: 16 }, alignment: { horizontal: center } } }; worksheet[!merges] [{ s: { r: 0, c: 0 }, e: { r: 0, c: 4 } }]; // 2. 设置表头第3行 const headers [部门, Q1, Q2, Q3, Q4, 全年总计]; headers.forEach((header, idx) { const cellAddr XLSX.utils.encode_cell({ c: idx, r: 2 }); // 第3行索引2 worksheet[cellAddr] { v: header, t: s, s: { font: { bold: true }, fill: { fgColor: { rgb: D9E1F2 }, patternType: solid }, alignment: { horizontal: center } } }; }); // 3. 填充一些示例数据第4-6行 const data [ [技术部, 120, 135, 140, 155], [市场部, 95, 110, 125, 118], [销售部, 200, 180, 220, 240] ]; data.forEach((row, rowIdx) { row.forEach((val, colIdx) { const cellAddr XLSX.utils.encode_cell({ c: colIdx, r: rowIdx 3 }); // 从第4行开始 worksheet[cellAddr] { v: val, t: n, s: { alignment: { horizontal: center } } }; }); // 计算总计假设在最后一列索引5 const total row.reduce((sum, num) sum num, 0); const totalCellAddr XLSX.utils.encode_cell({ c: 5, r: rowIdx 3 }); worksheet[totalCellAddr] { v: total, t: n, s: { font: { bold: true }, alignment: { horizontal: center } } }; }); // 4. 设置列宽 worksheet[!cols] [ { wch: 15 }, // A列部门15字符宽 { wch: 10 }, // B列Q1 { wch: 10 }, // C列Q2 { wch: 10 }, // D列Q3 { wch: 10 }, // E列Q4 { wch: 12 }, // F列全年总计 ]; // 5. 设置行高标题行高一些 worksheet[!rows] [{ hpx: 30 }]; // 只设置第一行其他行默认 // 6. 定义数据范围非常重要 worksheet[!ref] A1:F6; // 根据实际数据范围设置 // 生成工作簿和文件... const workbook XLSX.utils.book_new(); XLSX.utils.book_append_sheet(workbook, worksheet, 年度报告); const excelBuffer XLSX.write(workbook, { bookType: xlsx, type: array }); FileSaver.saveAs(new Blob([excelBuffer], { type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet }), 年度绩效报告.xlsx); }5. 实战技巧、常见问题与性能优化经过前面的步骤你已经掌握了核心操作。但在实际项目中还会遇到一些边界情况和性能问题。5.1 样式复用与工具函数封装当样式复杂时为每个单元格单独写样式对象很低效。最佳实践是封装样式生成函数和样式缓存。// 样式定义中心 const cellStyles { header: { font: { bold: true, sz: 14, color: { rgb: FFFFFF } }, fill: { fgColor: { rgb: 4472C4 }, patternType: solid }, alignment: { horizontal: center, vertical: center } }, center: { alignment: { horizontal: center, vertical: center } }, greenBg: { fill: { fgColor: { rgb: C6EFCE }, patternType: solid } }, redText: { font: { color: { rgb: FF0000 } } }, currency: { numFmt: ¥#,##0.00 // 人民币格式 } }; // 工具函数安全地合并样式到单元格 function applyStyle(cell, styleNameOrObj) { if (!cell) return; const styleToApply typeof styleNameOrObj string ? cellStyles[styleNameOrObj] : styleNameOrObj; if (styleToApply) { cell.s { ...(cell.s || {}), ...styleToApply }; } } // 使用示例 worksheet[A1] { v: 金额, t: s }; applyStyle(worksheet[A1], header); applyStyle(worksheet[B2], { font: { bold: true }, alignment: { horizontal: right } });5.2 处理大量数据时的性能问题当需要导出的数据行数成千上万时直接循环操作每个单元格会非常慢甚至导致浏览器卡死。优化策略1批量构建单元格对象避免在循环中频繁调用XLSX.utils.encode_cell和直接赋值。可以先构建一个纯数据数组然后使用XLSX.utils.aoa_to_sheetArray of Arrays或XLSX.utils.json_to_sheet一次生成工作表再批量应用样式。function exportLargeData(dataArray) { // 假设dataArray是一个二维数组第一行是表头 const ws XLSX.utils.aoa_to_sheet(dataArray); // 批量设置第一行表头样式 const headerRowRange XLSX.utils.decode_range(ws[!ref]); for (let C headerRowRange.s.c; C headerRowRange.e.c; C) { const cellAddress XLSX.utils.encode_cell({ r: headerRowRange.s.r, c: C }); if (ws[cellAddress]) applyStyle(ws[cellAddress], header); } // ... 其他批量样式操作 }优化策略2使用流式生成或分页对于极端大量的数据如10万行前端导出可能不是最佳选择应考虑后端生成。如果必须前端处理可以尝试分Sheet生成或者使用Web Worker避免阻塞UI。优化策略3样式与数据分离先构建一个不包含样式的“数据工作表”然后创建一个“样式映射表”记录哪些单元格需要什么样式。最后再遍历这个映射表来应用样式这样逻辑更清晰也便于调试。5.3 常见问题排查实录文件损坏或无法打开原因最常见的原因是XLSX.write时没有指定type: array或type: binary导致生成的二进制数据格式不对。或者Blob的MIME类型错误。解决确保write选项正确并使用FileSaver.saveAs或URL.createObjectURL触发下载。// 正确写法 const wbout XLSX.write(workbook, { bookType: xlsx, type: array }); const blob new Blob([wbout], { type: application/octet-stream }); FileSaver.saveAs(blob, file.xlsx);样式不生效原因A使用了不支持样式的xlsx库。确保你安装并导入的是支持样式的版本如我们安装的xlsx-0.20.2.tgz。原因B样式对象格式错误。比如颜色值带了#号应该是FF0000而不是#FF0000或者属性名拼写错误alignment不是align。原因C样式被覆盖。后续的代码覆盖了单元格的s属性。排查在应用样式后用console.log(worksheet[‘A1’])检查单元格对象确认s属性是否存在且格式正确。合并单元格后内容消失原因合并区域内的非左上角单元格如果有内容在Excel中不会被显示。xlsx库在写入时可能会根据!merges信息处理这些单元格。解决只在合并区域的起始单元格s设置内容和样式。清除或不要设置区域内其他单元格的内容。列宽/行高设置无效原因!cols或!rows数组的索引设置错误或者单位不对。wch字符宽度比wpx更通用。解决确认数组索引从0开始对应A列/第1行。使用wch和hpx作为首选单位。导入时数字变成文本或日期解析错误原因Excel单元格的数据类型可能被错误识别。sheet_to_json的raw参数控制是否进行转换。解决尝试设置raw: false让库自动转换日期和数字。对于特定格式的数字如身份证号可能需要先将其在Excel中设置为“文本”格式或者导入后手动处理。5.4 进阶技巧读取带样式的Excelxlsx-style同样增强了读取样式的能力。你可以解析用户上传的模板文件获取其中的样式信息。function readExcelWithStyle(file) { const reader new FileReader(); reader.onload function(e) { const workbook XLSX.read(e.target.result, { type: binary }); const worksheet workbook.Sheets[workbook.SheetNames[0]]; // 获取A1单元格的样式 const cellA1 worksheet[A1]; if (cellA1 cellA1.s) { console.log(A1单元格样式, cellA1.s); if (cellA1.s.fill) { console.log(背景色, cellA1.s.fill.fgColor.rgb); } if (cellA1.s.font) { console.log(字体颜色, cellA1.s.font.color?.rgb); } } // 获取合并单元格信息 console.log(合并区域, worksheet[!merges]); // 获取列宽 console.log(列宽设置, worksheet[!cols]); }; reader.readAsBinaryString(file); }这个功能非常有用比如你可以让业务人员在Excel模板中设置好样式前端读取这个模板然后只填充数据再导出就能生成样式统一的报表。最后分享一个我个人的体会前端Excel处理尤其是样式处理是一个细节决定成败的领域。在开始编码前最好先用Excel手动制作一个你期望效果的样板文件然后用xlsx-style读出来看看它的数据结构是什么样的这能帮你快速理解该如何用代码去构建它。另外对于非常复杂的报表比如包含大量公式、图表、条件格式纯前端生成可能会力不从心这时就需要评估是否采用服务端生成如Node.js ExcelJS或后端语言库的方案了。但对于大多数后台管理系统的导出需求xlsxxlsx-style的组合已经足够强大和灵活。
返回列表