news 2026/9/9 3:59:37

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
前端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-xlsx(SheetJS 社区版)几乎是绕不开的选择。这个库的核心能力就一句话:让浏览器端拥有读写 Excel 文件的能力,而且不依赖任何后端服务。

它能做的事情很直接:读取.xlsx/.xls/.csv文件并提取数据、把前端数据导出成表格文件、处理单元格合并和基本格式。适合的场景包括中后台系统的批量导入、报表导出、数据采集页面的模板下载、甚至是一些离线工具类页面。如果你正在做类似的功能,又不想为了一个表格操作引一个笨重的后端组件,那这篇文章应该能帮你省下不少摸索时间。

1. 项目概述与方案选型

1.1 为什么选 js-xlsx 而不是其他表格库

做技术选型的时候,我对比过几个常用方案,包括js-xlsxxlsx-js-styleexceljs这三类。exceljs功能确实强,支持样式、图片、图表,但浏览器端打包体积偏大,而且 API 风格更偏 Node.js,浏览器里用起来总觉得有点绕。xlsx-js-style是社区对js-xlsx的一个扩展分支,主要补上了写样式的能力,但它跟进上游版本的速度不稳定,兼容性需要自己验证。

js-xlsx社区版最大的优势是轻量和简单。它的核心 API 就几个:XLSX.read()负责解析文件,XLSX.utils.sheet_to_json()把工作表转成 JSON,XLSX.utils.json_to_sheet()把 JSON 转成工作表,XLSX.writeFile()负责触发浏览器下载。这个组合基本覆盖了 90% 的前端表格处理需求,而且社区版虽然在样式上能力有限,但数据层面的功能已经非常成熟,在上游版本冻结之前经历过大量线上场景验证,稳定性是有保障的。

1.2 引入方式与版本坑

这里要先说一个容易踩的坑:npm 上搜js-xlsx这个包名,老版本和官方最新版本是有区别的。SheetJS 官方现在推荐的 npm 包名是xlsx,CDN 上则提供了xlsx.full.min.js这个文件。直接用 CDN 引入是最省事的方式:

<script src="https://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[type=file] 拿到了 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,格式是列字母加行数字,比如A1B2。每个单元格对象包含几个核心字段:

字段含义典型值
v原始值字符串、数字、布尔值、日期对象
t数据类型s字符串、n数字、b布尔、d日期、e错误
w格式化文本2024/01/151,234.56
r富文本(很少用)HTML 内容对象

举个实际例子,Excel 单元格A1显示的是2024/01/15,在 js-xlsx 里解析出来,t字段可能是n(数字),v字段是 45241(Excel 日期序列号),w字段才是2024/01/15。这就是后面要说的日期序列化问题的根源,也是新手最容易懵的地方。

2.3 写数据:从 JSON 到 Excel

写数据要比读数据简单很多。核心就两个函数:json_to_sheetaoa_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 开始的,这个容易搞混,我一开始就犯过这个错误,把se写反导致合并范围完全不对。

!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;charset=utf-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_sheetwriteFile会非常卡,页面甚至会失去响应。原因是 js-xlsx 默认会把所有数据一次性加载进内存,生成大量中间对象,几万行的数据量产生的内存压力就不小了。

我的经验是分两个方向优化。如果数据本身是结构化的表格数据,优先考虑导出 csv,csv 不涉及太多对象转换,性能比 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对应样式对象。字体的boldsz,对齐方式horizontal,这些都是原生 Excel 样式模型的简化版本,上手成本不高。用这个分支库的时候要多测几个版本,因为它是社区维护的,偶尔会有跟不上上游 bug 修复的情况。

5.2 结合 Vue / React 组件化

实际项目里,读文件和导出文件通常会被封装成可复用的组合式函数或者自定义 Hook。比如在 Vue 3 里可以这样封装一个useExcelExport

import * 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 个字符宽度对长文本可能不够,但对于常规的字段名来说足够用了。

我在实际项目里做表格导入导出功能的时候,最大的体会是:不要一开始就追求把库的所有能力用上,先把readsheet_to_jsonjson_to_sheetwriteFile这四板斧练熟,能覆盖业务开发里 80% 以上的场景。等真遇到样式和复杂格式需求,再去看xlsx-js-style或者考虑后端生成方案,这是性价比最高的学习路径。

回到开头说的那个后台管理系统,导入功能上线之后,用户反馈最多的问题不是解析慢,而是上传的模板格式五花八门:有的改了表头文字,有的加了备注列,有的日期格式不统一。前端解析代码再完美,也扛不住业务方不按模板填数据。所以后来我总结了一个经验:接口层一定要做数据校验,解析完之后立即检查关键字段是否存在,格式不对就直接给用户明确的中文提示,而不是让错误数据流到下一环节。这个习惯一直保留到现在,也推荐给正在做类似功能的你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 3:58:44

企业级AI部署平台从0到1搭建实战指南

做AI部署平台这一年多&#xff0c;我最大的感受是&#xff1a;真正拉开技术团队差距的&#xff0c;往往不是算法有多先进&#xff0c;而是模型能不能稳定、高效地跑在生产环境里。很多转型AI的程序员一上来就啃Transformer、调Prompt&#xff0c;结果真到了上线环节&#xff0c…

作者头像 李华
网站建设 2026/9/9 3:58:01

用 Telegram Bot 远程操控 OpenCode:本地 AI 编程代理的移动控制方案

你知道吗&#xff0c;OpenCode 这类本地 AI 编程代理最大的痛点不是不好用&#xff0c;而是被绑死在工位上。跑一个长时间的重构任务&#xff0c;你人出门了&#xff0c;任务跑挂了或者需要确认下一步&#xff0c;你根本不知道。我实际用下来的方案是搭一个 Telegram Bot 来远程…

作者头像 李华
网站建设 2026/9/9 3:56:23

从AI教程到游戏开发:参数化思维构建可感知世界

1. 从AI教程博主到独立游戏开发者&#xff1a;一场认知框架的彻底迁移三年时间&#xff0c;我录过217期AI工具实操视频&#xff0c;写过43篇Prompt工程拆解长文&#xff0c;帮上万用户把ChatGPT用成Excel替代品。但去年冬天&#xff0c;在剪辑第189期“如何用Stable Diffusion批…

作者头像 李华
网站建设 2026/9/9 3:54:11

轻量级Node.js流程编排框架ruflo设计与实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/9 3:54:09

风储虚拟惯量调频仿真:四机两区系统频域模型法稳定性分析

前阵子帮一位同行调试风储虚拟惯量调频仿真模型&#xff0c;他把风电渗透率做到 25%&#xff0c;系统设定在四机两区上&#xff0c;结果一跑时域仿真不是发散就是振荡&#xff0c;折腾到后面大家都有点怀疑模型参数了。后来我们把主线改成频域模型法&#xff0c;先在小信号模型…

作者头像 李华
网站建设 2026/9/9 3:53:36

干掉PS?用InstructPix2Pix和扩散模型打造一句话AI修图工具

先问大家一个问题&#xff1a;你平时修一张图需要多久&#xff1f;如果是一张复杂的风景照&#xff0c;要抠掉路人、换掉天空、再把画面改成"落日熔金"的氛围&#xff0c;熟练的设计师可能也要十几分钟&#xff0c;新手更是无从下手。而 AI 时代的图像编辑工具&#…

作者头像 李华