ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

前端Excel处理实战:js-xlsx解析与导出的完整指南

前端Excel处理实战:js-xlsx解析与导出的完整指南 简介面向Web前端开发者的JS-XLSX库实战Demo演示用JavaScript将HTML表格数据导出为Excel文件完整覆盖从环境安装、库引入、HTML表格读取、工作簿对象生成、二进制字符串转换到文件下载触发的关键链路并适配XLSX、XLSM、XLSB等多种Excel格式能帮助开发者快速解决浏览器端Excel导入导出需求。压缩包共6个文件以5个JavaScript脚本为主内含Excel解析核心库、单元格样式处理模块与JSZip压缩工具等依赖另有一个HTML页面用于直接演示整体仅903KB结构清晰便于拆解学习。该Demo在CSDN已有1541人学习下载是从入门到实际调用JS-XLSX的实用参考。源码包还附带教程式描述深入解析table_to_book、XLSX.write等API的用法并针对单元格样式设置、合并单元格、异步操作和服务器端导出等常见难点给出优化思路读者可快速掌握前端Excel导入导出技巧并据此扩展出符合自身业务需求的工具。 在项目里跟 Excel 打交道多了你会发现前端解析和导出表格其实是个高频需求。我最早接触js-xlsx是在做一个后台管理系统的导入功能用户上传 Excel 文件前端需要直接解析并回填到业务表单里。当时第一反应是找现成的库搜了一圈下来js-xlsxSheetJS 社区版几乎是绕不开的选择。这个库的核心能力就一句话让浏览器端拥有读写 Excel 文件的能力而且不依赖任何后端服务。它能做的事情很直接读取.xlsx/.xls/.csv文件并提取数据、把前端数据导出成表格文件、处理单元格合并和基本格式。适合的场景包括中后台系统的批量导入、报表导出、数据采集页面的模板下载、甚至是一些离线工具类页面。如果你正在做类似的功能又不想为了一个表格操作引一个笨重的后端组件那这篇文章应该能帮你省下不少摸索时间。1. 项目概述与方案选型1.1 为什么选 js-xlsx 而不是其他表格库做技术选型的时候我对比过几个常用方案包括js-xlsx、xlsx-js-style、exceljs这三类。exceljs功能确实强支持样式、图片、图表但浏览器端打包体积偏大而且 API 风格更偏 Node.js浏览器里用起来总觉得有点绕。xlsx-js-style是社区对js-xlsx的一个扩展分支主要补上了写样式的能力但它跟进上游版本的速度不稳定兼容性需要自己验证。js-xlsx社区版最大的优势是轻量和简单。它的核心 API 就几个XLSX.read()负责解析文件XLSX.utils.sheet_to_json()把工作表转成 JSONXLSX.utils.json_to_sheet()把 JSON 转成工作表XLSX.writeFile()负责触发浏览器下载。这个组合基本覆盖了 90% 的前端表格处理需求而且社区版虽然在样式上能力有限但数据层面的功能已经非常成熟在上游版本冻结之前经历过大量线上场景验证稳定性是有保障的。1.2 引入方式与版本坑这里要先说一个容易踩的坑npm 上搜js-xlsx这个包名老版本和官方最新版本是有区别的。SheetJS 官方现在推荐的 npm 包名是xlsxCDN 上则提供了xlsx.full.min.js这个文件。直接用 CDN 引入是最省事的方式script srchttps://cdn.sheetjs.com/xlsx-0.18.5/package/dist/xlsx.full.min.js/script注意版本号。0.18.5 是社区版最后更新的版本之后官方把主要精力转移到需要付费的 SheetJS Pro 上所以社区版能稳定使用的版本基本就停留在这里。如果你用npm install xlsx装到的也是 0.18.5这个版本在 npm 上目前是安全的可以放心用。如果你在公司内网环境没法访问外网 CDN那就把xlsx.full.min.js下载下来放到自己的静态资源目录里。这里有个关键点下载的 JS 文件名建议保留原文件名不要乱改因为库内部没有做模块名强绑定但改成别的名字容易在调试时候产生困惑。用 npm webpack / vite 的同学直接import * as XLSX from xlsx就行打包工具会自动处理。2. 核心接口与数据处理细节2.1 读取 Excel 文件的完整链路读取 Excel 的逻辑可以拆成三步拿文件 → 解析工作簿 → 提取数据。用代码表示就是这样// 假设你从 input[typefile] 拿到了 File 对象 const file document.getElementById(excelFile).files[0]; const data await file.arrayBuffer(); const workbook XLSX.read(data, { type: array }); // workbook.SheetNames 是工作表名数组 // workbook.Sheets[名字] 是工作表对象 const firstSheetName workbook.SheetNames[0]; const firstSheet workbook.Sheets[firstSheetName]; // 转成 JSON 数组 const jsonData XLSX.utils.sheet_to_json(firstSheet, { header: 1 }); console.log(jsonData);这里有个我最初没意识到的重要细节sheet_to_json默认的行为是把第一行当作表头返回一个对象数组每个对象的 key 是表头文字value 是对应的单元格值。但如果你需要拿到原始的行列结构比如做数据清洗那就要指定{ header: 1 }这样返回的是二维数组每一行就是一个数组单元格顺序一一对应。实际项目里我通常两种模式都会用到。模板导入类场景用默认的表头模式因为用户上传的 Excel 表头是固定的直接用对象的 key 访问数据代码可读性高。而遇到表头不固定的场景或者需要拿原始单元格坐标继续处理的场景用header: 1更稳。2.2 单元格对象到底长什么样如果只用sheet_to_json你其实接触不到底层的数据结构。但遇到复杂场景比如你要判断一个单元格是不是空的、要读取单元格的显示格式那就得直接操作工作表对象了。工作表里每个单元格都对应一个 key格式是列字母加行数字比如A1、B2。每个单元格对象包含几个核心字段字段含义典型值v原始值字符串、数字、布尔值、日期对象t数据类型s字符串、n数字、b布尔、d日期、e错误w格式化文本2024/01/15、1,234.56r富文本很少用HTML 内容对象举个实际例子Excel 单元格A1显示的是2024/01/15在 js-xlsx 里解析出来t字段可能是n数字v字段是 45241Excel 日期序列号w字段才是2024/01/15。这就是后面要说的日期序列化问题的根源也是新手最容易懵的地方。2.3 写数据从 JSON 到 Excel写数据要比读数据简单很多。核心就两个函数json_to_sheet和aoa_to_sheet。前者接受对象数组对象的 key 自动变成表头后者接受二维数组第一行自动变成表头。const jsonData [ { 姓名: 张三, 部门: 技术部, 薪资: 12000 }, { 姓名: 李四, 部门: 市场部, 薪资: 10000 }, ]; const worksheet XLSX.utils.json_to_sheet(jsonData); const workbook XLSX.utils.book_new(); XLSX.utils.book_append_sheet(workbook, worksheet, 员工信息); XLSX.writeFile(workbook, 员工信息.xlsx);这五个方法串起来就是一次完整的导出。book_new创建一个空工作簿book_append_sheet把工作表挂到工作簿上并指定名字writeFile同时做了 JSON → Excel 格式转换和触发浏览器下载。中间不需要你手动处理 Blob 和 URL.createObjectURL库已经帮你封装好了。这里值得提醒的是工作表名称。Excel 要求工作表名不能为空、不能包含: \ / ? * [ ]这些字符长度不能超过 31 个字符。在代码里写死的工作表名字一般没什么问题但如果工作表名来自用户输入就一定要做字符校验否则writeFile会直接抛异常。3. 实操 Demo读取、处理、导出一把梭3.1 常见场景解析上传文件并按条件过滤我从实际项目里抽了一个典型的 demo 场景用户在页面上传一个员工数据 Excel前端解析后需要过滤掉薪资低于某个阈值的记录然后把结果渲染到表格里。这个功能要分两步做第一步是从 File 对象解析数据第二步就是普通的数组过滤逻辑。async function handleFile(file, minSalary 8000) { const data await file.arrayBuffer(); const workbook XLSX.read(data, { type: array }); const sheetName workbook.SheetNames[0]; const sheet workbook.Sheets[sheetName]; // 转成二维数组方便手动控制表头 const rows XLSX.utils.sheet_to_json(sheet, { header: 1 }); const header rows[0]; const nameIndex header.indexOf(姓名); const salaryIndex header.indexOf(薪资); if (nameIndex -1 || salaryIndex -1) { throw new Error(文件缺少必要的列姓名或薪资); } const filtered rows.slice(1) .filter(row row[salaryIndex] ! undefined Number(row[salaryIndex]) minSalary) .map(row ({ 姓名: row[nameIndex], 薪资: row[salaryIndex], })); return filtered; }这段代码有两个值得注意的细节一是先找到表头里需要的列索引然后用索引取数据而不是写死第几列这样用户调整列顺序之后程序不会崩二是过滤时做了row[salaryIndex] ! undefined的判断因为 Excel 里的空单元格在header: 1模式下不会为null而是直接缺省这个判断能有效避免Number(undefined)变成NaN的情况。3.2 实时生成带列宽和合并单元格的导出导出场景里如果只是简单地把数据写进文件那生产环境基本没法用。真实的报表导出往往需要设置列宽、合并表头、冻结首行这些操作。js-xlsx 社区版虽然不支持单元格样式但列宽和合并是支持的它们存放在工作表的特殊属性里列宽存在!cols合并信息存在!merges。function createReportSheet(data) { const ws XLSX.utils.json_to_sheet(data, { header: [姓名, 部门, 薪资] }); // 设置列宽以字符宽度为单位 ws[!cols] [ { wch: 10 }, // 姓名列宽 10 { wch: 16 }, // 部门列宽 16 { wch: 12 }, // 薪资列宽 12 ]; // 合并 A1:C1通常用于做表头标题 ws[!merges] [ { s: { r: 0, c: 0 }, e: { r: 0, c: 2 } }, ]; // 冻结首行滚动时表头固定 ws[!freeze] { x: 0, y: 1 }; return ws; }这里!cols是最常用的。wch表示字符宽度可以简单理解为能放下多少个英文字符中文字符大概占两个位置所以要给中文字段留足宽度。比如姓名列设置 10实际显示效果大概是 5 个汉字加一点余量。如果你不设置列宽Excel 打开时所有列宽都默认是 8.43 字符中文表头大概率会被截断用户每次都要手动拉宽体验很差。!merges合并单元格的坐标是行优先的。{ s: { r: 0, c: 0 }, e: { r: 0, c: 2 } }表示从A1合并到C1也就是把第一行的前三列合成一格。坐标都是从 0 开始的这个容易搞混我一开始就犯过这个错误把s和e写反导致合并范围完全不对。!freeze是一个比较隐蔽的配置官方文档在社区版里没有重点提但它确实生效。x: 0, y: 1表示冻结第一行x: 1, y: 0表示冻结第一列适合做大量数据的时候保持表头可见。3.3 导出 CSV 格式的坑与解决方案有的业务场景要求的不是.xlsx而是.csv。csv 的优势是兼容性极好Excel、WPS、记事本都能打开劣势是字符编码问题很折磨人。最典型的问题就是用writeFile导出 csv 后用 Excel 打开中文乱码。原因很直接js-xlsx 默认输出的 csv 编码是 UTF-8而 Windows 版 Excel 在没有 BOM 头的情况下会默认用 ANSI 编码解析导致中文显示成乱码。解决办法是在生成 csv 内容后手动加上 UTF-8 BOM 头再创建一个 Blob 对象来下载function exportCsv(workbook, fileName data.csv) { const csvContent XLSX.write(workbook, { bookType: csv, type: string }); const blob new Blob([\uFEFF csvContent], { type: text/csv;charsetutf-8; }); const url URL.createObjectURL(blob); const a document.createElement(a); a.href url; a.download fileName; a.click(); URL.revokeObjectURL(url); }这里\uFEFF就是 BOM 头。加了这个字符之后Excel 打开 csv 时会识别出这是 UTF-8 编码中文就能正常显示。iPhone 自带的 Numbers 表格也有同样的坑加 BOM 后一并解决。4. 常见问题与排查技巧4.1 引入后XLSX is not defined这个问题出现概率极高而且十有八九是 CDN 引入方式不对。xlsx.full.min.js这个文件是老式浏览器脚本的方式写的它会往全局window对象上挂一个XLSX变量。但如果你在模块化项目里用import方式引入这个脚本文件或者把script标签放在了页面底部而代码又在那之前执行了就会出现XLSX is not defined。解决办法有两个确认script标签在业务代码之前加载或者直接用 npm 包在代码里import * as XLSX from xlsx。如果你是 vite 项目更推荐用 npm 包因为 vite 对 globals 的处理比较严格CDN 文件在 vite 里很容易踩雷。4.2 日期变成了数字这是 Excel 处理的经典问题老生常谈但依然频繁出现。在 js-xlsx 里默认情况下日期单元格读出来是数字这个数字代表从 1900 年 1 月 1 日到当天的天数。比如2024/01/15读出来是45241看起来跟 Excel 里的原始显示完全不同。处理方式是在读取的时候加上cellDates: true配置const workbook XLSX.read(data, { type: array, cellDates: true });加了cellDates: true之后日期单元格会转成 JavaScript 的Date对象后续处理就直观多了。还有个辅助配置dateNF: yyyy-mm-dd它用来控制日期的输出格式但这个格式只在部分场景下生效最稳妥的做法还是拿到Date对象后自己用工具库格式化。4.3 大文件导出导致页面卡死当数据量到了一定程度比如几万行甚至十几万行前端直接调json_to_sheet和writeFile会非常卡页面甚至会失去响应。原因是 js-xlsx 默认会把所有数据一次性加载进内存生成大量中间对象几万行的数据量产生的内存压力就不小了。我的经验是分两个方向优化。如果数据本身是结构化的表格数据优先考虑导出 csvcsv 不涉及太多对象转换性能比 xlsx 好很多。如果业务要求必须是 xlsx那就做分批写入比如每次只处理 5000 行用一个Web Worker在后台线程里完成转换和数据组装页面主线程就不会卡了。Web Worker 方案在大型报表导出场景里非常实用不过它的调试成本会高一些要提前设计好 worker 和主线程之间的数据传递格式。4.4 空单元格导致数据错位这个坑很隐蔽。用sheet_to_json把 Excel 转成 JSON 后如果你的 Excel 里某一行中间有几个空单元格你会发现 JSON 对象里对应那些列的 key 直接不存在而不是值为null或空字符串。导致后续拿数据时undefined满天飞。建议养成习惯解析完成后统一做一次数据清洗把缺失的 key 补上默认值const rawData XLSX.utils.sheet_to_json(sheet); const cleaned rawData.map(row ({ 姓名: row[姓名] || , 部门: row[部门] || , 薪资: row[薪资] || 0, }));这种做法虽然有点笨但在生产环境里反而是最稳的。尤其是让业务方直接填的 Excel 模板你根本不知道用户会不会留几个空单元格不填。5. 进阶扩展样式支持与周边生态5.1 社区版样式方案xlsx-js-style如果你需要在导出的文件里设置背景色、字体加粗、边框这些样式社区版js-xlsx是做不到的这时候可以用xlsx-js-style这个分支库。它的用法和js-xlsx基本一致只是多了一个s属性来指定样式import XLSX from xlsx-js-style; const ws XLSX.utils.aoa_to_sheet([ [{ v: 部门报表, s: { font: { bold: true, sz: 14 }, alignment: { horizontal: center } } }], [姓名, 部门, 薪资], ]); ws[!cols] [{ wch: 10 }, { wch: 16 }, { wch: 12 }]; const workbook XLSX.utils.book_new(); XLSX.utils.book_append_sheet(workbook, ws, 报表); XLSX.writeFile(workbook, 报表.xlsx);注意aoa_to_sheet里{ v: 部门报表, s: { ... } }这种写法v对应单元格的值s对应样式对象。字体的bold、sz对齐方式horizontal这些都是原生 Excel 样式模型的简化版本上手成本不高。用这个分支库的时候要多测几个版本因为它是社区维护的偶尔会有跟不上上游 bug 修复的情况。5.2 结合 Vue / React 组件化实际项目里读文件和导出文件通常会被封装成可复用的组合式函数或者自定义 Hook。比如在 Vue 3 里可以这样封装一个useExcelExportimport * as XLSX from xlsx; export function useExcelExport() { function exportJsonToExcel(jsonData, sheetName Sheet1, fileName export.xlsx) { const ws XLSX.utils.json_to_sheet(jsonData); ws[!cols] Object.keys(jsonData[0] || {}).map(() ({ wch: 16 })); const wb XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, sheetName); XLSX.writeFile(wb, fileName); } return { exportJsonToExcel }; }这里的ws[!cols]用了动态生成的方式根据 JSON 对象的 key 数量生成对应的列宽避免了手动维护每个字段列宽的麻烦。虽然 16 个字符宽度对长文本可能不够但对于常规的字段名来说足够用了。我在实际项目里做表格导入导出功能的时候最大的体会是不要一开始就追求把库的所有能力用上先把read、sheet_to_json、json_to_sheet、writeFile这四板斧练熟能覆盖业务开发里 80% 以上的场景。等真遇到样式和复杂格式需求再去看xlsx-js-style或者考虑后端生成方案这是性价比最高的学习路径。回到开头说的那个后台管理系统导入功能上线之后用户反馈最多的问题不是解析慢而是上传的模板格式五花八门有的改了表头文字有的加了备注列有的日期格式不统一。前端解析代码再完美也扛不住业务方不按模板填数据。所以后来我总结了一个经验接口层一定要做数据校验解析完之后立即检查关键字段是否存在格式不对就直接给用户明确的中文提示而不是让错误数据流到下一环节。这个习惯一直保留到现在也推荐给正在做类似功能的你。本文还有配套的精品资源点击获取
返回列表