首页 / 资讯中心 / 文章详情

Vue2+ElementUI表格导出Excel实战:vue-json-excel从入门到填坑

Vue2+ElementUI表格导出Excel实战:vue-json-excel从入门到填坑 ★ FEATURED ARTICLE
在VUE2 ElementUI的中后台项目里表格数据一键导出Excel几乎成了标配需求vue-json-excel是我用下来最顺手的前端导出方案。这篇文章从方案选型到完整接入再到动态列、大数据量、各种乱码和格式坑全部按实战顺序梳理直接照着抄就能用。1. 为什么要在前端做Excel导出1.1 常见导出需求场景做管理后台的都知道表格导出不是“能不能导”的问题而是“怎么导才不折腾”的问题。最典型的场景就是列表页的导出按钮用户勾选几行数据、或者把当前筛选条件下的所有结果导成Excel拿去做线下汇总、报销审批、数据留档。这种需求和普通的数据展示不一样它对表头文字、字段顺序、文件命名都有明确要求。比如财务要看“金额”列带两位小数人事要“入职日期”是YYYY-MM-DD格式销售要“客户名称”别出现科学计数法。你费劲把数据弄对了结果导出文件打开后格式还是乱的那体验就很糟糕。我在实际项目里遇到过特别多类似的细节要求比如表头是中文但Excel打开后中文乱码数字超过12位被Excel自动转换成科学计数法导出的文件名带时间戳方便区分版本只导出用户勾选的行而不是全表数据导出的列顺序必须和表格展示顺序一致不能乱跳。这些问题如果都靠后端来处理沟通成本极其高。改一个列名、加一列数据都要找后端同事重新发版。而前端直接搞定的话所有表头映射、字段筛选、数据格式化都在同一个组件里完成体验和效率都会好很多。1.2 为什么选择前端方案而不是后端导出很多人第一反应是让后端导后端查数据库生成Excel文件前端用window.open下载或者a标签点击下载。这个方案确实可行特别适合那种数据量几十万行、需要服务端分页查询的场景。但如果你只是想把当前页表格里的数据导出来或者把用户选中的几十行导出去让后端绕一圈反而很别扭。后端导出天然有几个痛点前后端要约定导出接口的参数格式比如筛选条件、排序字段、勾选ID列表后端要引额外的POI或者EasyExcel依赖增加部署体积临时调整导出列要等后端改代码、重新打包如果导出请求发出去后接口超时前端很难做提示和重试。前端方案的好处在于数据本身就在前端表格里或者可以通过前端已存在的list一次性拿到做数据映射、列过滤、格式化都是纯前端操作所见即所得。你看到表格长什么样导出的Excel就是什么样不用猜后端有没有把字段传对。当然前端导出也不是万能的。数据量如果到了几十万行甚至百万行级别一次性把数据挂在页面上本身就不合理更别说导出了。那种场景我会建议还是走后端或者让后端先生成导出任务用户稍后下载。但日常后台管理里90%的表格导出需求都是几百到几千行的量级前端方案完全够用。2. 方案选型vue-json-excel 和其他方式对比2.1 主流导出方案一览VUE2项目里常见的Excel导出方式我简单归纳了一下方案核心依赖优点缺点vue-json-excelvue-json-excel配置简单JSON转Excel一步到位样式自定义能力弱适合普通数据导出SheetJSxlsxxlsx file-saver功能强支持读取、修改、合并单元格要自己写不少代码学习成本稍高exceljs file-saverexceljs样式控制最细支持公式、图片、批注体积偏大API偏复杂后端导出POI/EasyExcel适合海量数据能力不受前端限制前后端配合成本高迭代慢如果只看“把JSON数据变成Excel文件”这个动作vue-json-excel几乎是最省脑子的一种。它直接把JSON数组和字段映射关系传进去组件内部负责生成Blob并触发下载不用自己拼table转HTML也不用关心Blob怎么封装。SheetJS那边我也用过功能确实强能读取已有Excel模板、做到单元格级别的修改。但回到日常业务中后台导出最根本的需求就是“当前表格数据导出来别乱码别变形”为了这些功能去手写一堆转换逻辑性价比不高。如果你的需求是“导出指定样式的复杂报表”那可以考虑SheetJS或exceljs否则不用太折腾。2.2 vue-json-excel 的核心优势vue-json-excel这个名字起得很直白就是把JSON数据转换成Excel的组件。它内部实际是把数据渲染成一个HTML Table然后以.xls格式导出。这听着不像“真”Excel但Excel完全能正常打开而且对大多数业务场景来说完全够用。它的使用成本低到让人舒服安装后全局注册一个组件模板里就能直接用数据源直接传前端已经拿到的list数组不用二次请求字段映射用一个对象就搞定中文表头对应英文字段支持fields自定义顺序表格列随意调整不影响导出支持name属性动态生成文件名比如带上当前时间。这些能力组合起来意味着你可以在不改动后端接口的前提下把“当前用户看到的数据原样导出”而且列名、列顺序、文件命名都完全可控。还有一个细节我很喜欢这个组件支持点击下载和手动触发下载两种方式。默认是在页面上渲染一个按钮但你也可以把真正的按钮藏起来用ref拿到组件实例在其他地方调用它的下载方法。这在项目里特别实用因为很多设计稿里的导出按钮都长得和默认按钮不一样。2.3 什么情况下建议用其他方案虽然vue-json-excel好用但也不是万能的我踩过几次坑之后总结了几类不适合用它的情况第一类是数据量特别大的场景。组件内部要把所有数据渲染成表格再导出数据量到几万行的时候页面会明显卡顿甚至浏览器崩溃。我测试过在普通笔记本上两万行左右基本是流畅上限再往上就开始掉帧。这时候后端导出是更好的选择。第二类是需要复杂样式的场景。比如合并单元格、单元格背景色、Logo图片、自定义页眉页脚这类需求用vue-json-excel基本搞不定。它生成的表格是纯数据格式样式控制能力非常有限。真要弄这种报表用exceljs或者直接后端处理更靠谱。第三类是前端拿不到全量数据的场景。如果列表接口是分页查询一次只能拿到一页而用户想导出的数据跨越好几页你要么让后端加一个导出接口要么前端把所有页都请求一遍再导。后者在数据量大的时候并不合适。所以我的建议是先判断需求复杂度简单导出用vue-json-excel复杂报表选exceljs海量数据直接找后端。工具不是越强越好够用才是最优解。3. 完整接入实录从安装到首次导出3.1 安装与全局注册先装依赖。项目如果是npm管理直接执行npm install vue-json-excel装完之后在main.js里注册为全局组件。这里有一点要注意不同版本的默认导出名不一样老版本可能直接导出组件对象新版本则挂在default属性下如果导入后页面报错先检查一下这步。import Vue from vue; import vueJsonExcel from vue-json-excel; Vue.component(downloadExcel, vueJsonExcel);注册完成后你可以直接在任意VUE2组件模板里使用download-excel标签。我这里把组件名注册成downloadExcel模板里对应download-excel语义比较清晰。有些项目会按需引入而不是全局注册那可以在单文件组件里这样写import downloadExcel from vue-json-excel; export default { components: { downloadExcel } };两种方式效果都一样看项目规范了。全局注册的好处是一次配好到处用局部引入的好处是首屏加载能少一点体积。我自己的习惯是后台管理系统里全局注册因为导出功能太常用了几乎每个列表页都要用。3.2 模板中的基础用法先看一个最简单的例子把当前组件里已有的tableData导出来template div el-table :datatableData border el-table-column propdate label日期/el-table-column el-table-column propname label姓名/el-table-column el-table-column propamount label金额/el-table-column /el-table download-excel :datatableData :fieldsexportFields name用户列表.xls classexport-btn 导出Excel /download-excel /div /template script export default { data() { return { tableData: [ { date: 2024-01-01, name: 张三, amount: 100 }, { date: 2024-01-02, name: 李四, amount: 200 } ], exportFields: { 日期: date, 姓名: name, 金额: amount } }; } }; /script这段代码里核心就是两个属性data要导出的数据源数组类型里面是对象fields字段映射关系键是导出的中文表头值是对应的数据字段名。:fieldsexportFields里定义的对象顺序就是Excel里列的顺序。比如上面写日期、姓名、金额导出的Excel就是三列按这个顺序排列。这里有个使用习惯分享如果表格里展示的字段和导出的字段不完全一样比如表格里有个操作列、勾选列或者是格式化之后展示的字段那导出的时候要用fields把不需要的列过滤掉只留下真正要导出的业务字段。3.3 字段映射与表头定制字段映射是vue-json-excel的核心玩法除了最基础的对象映射它还能支持更复杂的映射结构比如字段嵌套、默认值、格式化。我在项目里经常遇到一种情况表格里展示的是格式化后的文本比如状态字段在数据里是0和1表格里通过formatter显示成“启用”和“禁用”。但导出的时候直接用原始字段导出用户拿到的就是一堆数字这显然不行。这种场景可以在fields里配合formatter来处理。vue-json-excel支持fields的子项是一个对象里面可以指定field、callback等配置。我举一个实际项目里的写法template download-excel :datatableData :fieldsexportFields name账号列表.xls 导出Excel /download-excel /template script export default { data() { return { tableData: [ { userName: zhangsan, status: 1, createdAt: 2024-05-01 10:30:00 }, { userName: lisi, status: 0, createdAt: 2024-06-11 09:00:00 } ] }; }, computed: { exportFields() { return { 用户名: userName, 状态: { field: status, callback: (value) { return value 1 ? 启用 : 禁用; } }, 创建时间: { field: createdAt, callback: (value) { return value ? value.slice(0, 10) : ; } } }; } } }; /script这里有个关键点callback函数接收当前字段的值返回处理后的字符串。这样导出的Excel里状态列就是“启用/禁用”创建时间列只保留日期部分用户拿到的文件更友好。我还用过一种场景字段值不是一个简单字符串而是数组或者对象。这种情况不能在fields里直接映射需要先在computed里把数据处理成一维对象结构再传给组件。比如后端返回了tags: [前端, VUE2]想导成“前端、VUE2”这种格式就得先用map处理数组或者直接在callback里调用join方法。3.4 事件与类名配置除了基础的数据绑定vue-json-excel还提供了一些额外配置虽然文档里写得不那么多但实践中很常用。class和style可以直接作用到组件渲染出来的按钮上。默认情况下download-excel渲染出来的就是一个button元素你可以通过传类名控制它的样式。如果想把它伪装成ElementUI的按钮样式可以直接在类名里配合全局样式覆盖或者包一层自定义按钮。我实际用过一种写法把默认按钮隐藏用ElementUI的el-button来触发导出。因为组件默认的内容就是按钮文字渲染出来的样式比较朴素和ElementUI主题不一定搭配。这时候可以在组件外面套一个元素把组件本身的按钮透明度设成0再用绝对定位盖上去或者更简单的方式是用ref手动触发下载。我上面的需求用的是手动触发方式模板里不显示导出按钮而是在工具栏里放一个el-button点击时调用组件内部下载方法template div el-button typeprimary clickhandleExport导出当前数据/el-button download-excel refexportExcel :dataexportData :fieldsexportFields name导出数据.xls styledisplay: none /download-excel /div /template script export default { methods: { handleExport() { this.$refs.exportExcel.$emit(export_data); } } }; /script这个写法的原理是vue-json-excel内部监听export_data事件来触发下载。把组件隐藏起来再通过$emit触发按钮样式就完全由自己控制了。这种方式我比较推荐因为页面里各处导出按钮的风格都能统一不用在模板里写一堆行内样式。4. 进阶实战把ElementUI表格数据完整导出去4.1 表格数据和导出数据的联动ElementUI表格用的数据源往往不是直接就能导的。最常见的问题是表格里有操作列、勾选列这些列并不需要导出。如果你直接把tableData传给download-excel导出的Excel里会多出一列无意义的数据甚至因为某些字段是undefined导致列错位。所以我的习惯是给导出单独维护一个exportData用computed或methods从tableData里派生出来。比如computed: { exportData() { return this.tableData.map((item) ({ user: item.name, mobile: item.phone, status: item.status 1 ? 正常 : 停用, createTime: item.createTime })); } }这样做的另一个好处是可以把格式化逻辑集中到一个地方而不是分散在模板的callback里。特别是当同一个数据源有多个导出按钮、但导出字段不一样的时候维护起来会很清晰。我另一个经验是导出数据最好不要直接引用tableData里那些可能被表格组件修改过的字段。比如ElementUI的表格如果用sortable排序表格展示顺序变了但tableData本身顺序不一定同步。此时导出顺序和用户看到的顺序不一致体验很怪。如果遇到这种情况需要监听表格的排序事件把排序后的数据同步到导出数据源里。4.2 筛选、分页、多选场景怎么导后台列表页最常见的三种交互是筛选、分页、多选导出逻辑要针对不同场景分别设计。筛选场景下导出应该和当前筛选条件一致。用户已经在前端筛好了数据列表展示出来的就是筛选结果。这时直接导出tableData就行了因为它就是当前筛选条件下返回的数据。但如果筛选是后端做的前端每次只拿到一页那用户想导出“所有符合条件的数据”时前端手头数据可能不完整。我一般会再调用一次后端列表接口把pageSize传大一些一次性拿到全部筛选结果再导出。分页场景下同样要区分“当前页导出”和“全部页导出”。当前页直接导出tableData全部页需要额外请求或者后端提供导出接口。我的默认处理是加一个下拉菜单让用户自己选导出范围。多选场景是最常见的表格前面有typeselection的勾选列用户勾选了几行然后点导出。这时候可以用selection-change事件拿到选中项handleSelectionChange(rows) { this.selectedRows rows; }导出时直接用selectedRows作为data。如果用户没勾选任何行可以提示“请先选择要导出的数据”或者默认导出全部。这个交互细节最好提前和产品确认避免上线后被吐槽。4.3 动态列与字段格式化动态列是又一个经典问题。需求方说“导出的时候列不固定要根据后端返回的配置来显示”或者“不同角色看到的导出列不一样”这时候fields就不能写死了得用computed动态生成。我实操中的写法是先拿到后端返回的列配置比如// 后端返回 const columnConfig [ { label: 姓名, prop: name }, { label: 部门, prop: dept }, { label: 入职日期, prop: hireDate } ];然后转成fields需要的结构computed: { exportFields() { const fields {}; this.columnConfig.forEach((col) { fields[col.label] { field: col.prop, callback: (value) this.formatCell(col.prop, value) }; }); return fields; } }formatCell里再统一处理格式化逻辑。这种方式的优点很多列配置由后端控制前端加一列不用发版每个列的格式化统一在一个方法里管理代码不会越写越乱。还有一种情况是字段本身在数据里是嵌套的比如const row { user: { name: 张三, dept: 技术部 }, amount: 100 };这种情况下fields里直接写姓名: user.name不一定生效因为组件内部取值的逻辑是直接取当前层级属性。我一般先做一个数据扁平化处理this.exportData this.tableData.map((item) ({ name: item.user.name, dept: item.user.dept, amount: item.amount }));不要试图让组件去解析多级字段先自己处理掉后面查问题也方便。4.4 大数据量导出的前端优化前面提到vue-json-excel在几万行以上会卡那万一项目里确实有这种需求还能不能抢救一下我试过几种办法分享一下实际效果。一是分批导出。把大数据拆成多个小文件导出比如每个文件一万行文件名加上序号。这个方案用户能接受但数据被拆散了后续使用不方便。二是用requestAnimationFrame分割渲染。其实组件内部生成表格是一次性操作我们没法直接介入。如果你需要导出的数据存在且很大可以考虑换用xlsx库来生成真正的Excel它对大数据量的处理更友好。不过这个改动工作量不小不是简单换一个组件就行。三是在导出前做数据精简。比如去掉不必要的字段只保留真正需要导出的列减少生成表格时的DOM节点数。这个方法操作简单能缓解一部分卡顿但治标不治本。四是用v-show控制导出组件的渲染时机。如果整个表格页面已经渲染了上千行DOM再叠加一个很大的导出表格浏览器压力会翻倍。可以等用户点击导出时再创建组件导出完销毁。这个思路实际有效不过前提是数据量还在可控范围内。我的经验判断是如果单次导出超过一万行就要认真考虑后端方案了。不是前端不能做而是可靠性、性能和用户体验都不占优。为了一个导出功能折腾用户浏览器内存不如让后端生成文件来得干脆。5. 常见问题与排查技巧实录5.1 导出后Excel打开提示文件格式损坏这个问题我遇到过好多次尤其是老用户用WPS或旧版Excel时。原因是vue-json-excel导出的.xls文件本质上是HTML Table格式不是真正的BIFF Excel格式。Excel打开时会有格式兼容性提示有时候甚至直接提示文件损坏。处理办法有这么几种改文件后缀为.xls保留现状并引导用户用Excel或WPS打开如果用户要求特别严格不能有任何提示那就放弃这个组件改用xlsx或者后端导出检查导出的内容里有没有非法字符比如HTML标签没有被转义Excel解析时容易报错。我自己的处理是把导出文件的后缀统一设置成.xls不做别的特殊操作。因为绝大多数办公环境都兼容这种格式偶尔有提示用户点“是”也能正常打开。另外一个容易忽视的问题是文件名。如果文件名带了特殊字符比如/、\、:某些环境下下载会失败。文件名最好统一用中文、数字、下划线、横线日期用YYYY-MM-DD格式。5.2 中文列名、中文文件名乱码乱码问题堪称前端导出Excel的“国民级难题”。主要原因在不同操作系统和浏览器下URL编码方式不一样导致文件名传到系统层时解析错误。碰到乱码我一般分两步排查第一步检查fields里的中文表头是否有异常有些复制过来的文本可能带了隐藏字符肉眼看不出来导出后Excel第一行就出现乱码第二步检查文件名建议只传字符串不要传URL编码后的内容也不要对name属性做encodeURIComponent处理组件内部会处理。还有一种情况是Excel打开后表格内容正常但文件名是乱码。这大概率是HTTP响应头里没有设置Content-Disposition的编码但纯前端生成Blob下载时一般不会有这个问题。如果还有可以用file-saver手动控制保存逻辑绕开组件默认的下载方式。5.3 字段值丢失或错位字段值丢失十有八九是fields映射写错了。最常见的问题是字段名大小写不一致比如数据里是userNamefields里写成username结果那一列全是空白。另一个坑是字段值是0或false时被吞掉。因为代码里如果用if (value)做判断0和false都会走false分支导致导出为空。vue-json-excel内部对空值的处理也有类似逻辑所以如果数据里确实有0这种正常值需要在callback里显式处理callback: (value) { return value 0 ? 0 : (value || ); }还有一种是数据源里字段有的行有、有的行没有。比如后端返回的对象结构不一致某些行缺字段。这种情况导出的Excel里会出现错位因为组件内部按表头顺序取值某一行缺了这个字段后面的单元格就会前移。解决办法是先做一次数据清洗把缺失字段补成空字符串或默认值。this.exportData this.tableData.map((item) ({ name: item.name || , dept: item.dept || -, status: item.status undefined ? 未知 : item.status }));5.4 日期、数字格式异常日期格式问题很典型。数据里存的是时间戳或者2024-01-01T00:00:00这种ISO格式Excel打开后显示成一串数字或者变成01/01/2024这种不符合预期的格式。我的做法是不管后端返回什么格式导出前统一转成字符串formatDate(value) { if (!value) return ; const date new Date(value); const year date.getFullYear(); const month String(date.getMonth() 1).padStart(2, 0); const day String(date.getDate()).padStart(2, 0); return ${year}-${month}-${day}; }这样导出的日期永远是YYYY-MM-DDExcel不会自作主张转成其他格式。数字格式的问题主要在长数字上比如身份证号、订单号超过12位后Excel会显示成科学计数法。这个问题很让人头疼因为你看到的数据和用户看到的数据完全不一样。解决思路有两个一是在导出前把长数字转成字符串让Excel不要做数值处理二是导出后用文本格式打开但这对用户来说很麻烦。我实测过vue-json-excel导出时如果值是字符串Excel不会自动转成科学计数法但如果值是数字就有风险。所以遇到身份证号、手机号、订单号这类字段我会特意把它转成字符串并且在callback里做处理callback: (value) { return String(value); }这个方法简单有效能解决大部分长数字变形问题。5.5 ElementUI 表格固定列透明与导出建议这个热搜词挂在和“导出Excel”相关的话题下面其实是一个ElementUI本身的样式问题但我在导出相关页面里也确实踩过。现象是表格设置了固定列fixedright或fixedleft页面滚动或者表格数据刷新时固定列区域出现透明背景色和内容叠加在一起看着像表格坏了。排查下来主要原因是ElementUI 2.x版本的el-table__fixed区域用了position: fixed定位当页面里还使用了transform、filter属性或者开启了will-change时浏览器的合成层会发生变化导致固定列和主体列的层级错乱出现透明或者重影。修复办法是在全局样式中覆盖固定列背景色和层级.el-table__fixed, .el-table__fixed-right { background-color: #fff; z-index: 10; }同时检查外层容器不要轻易加transform。如果表格放在el-dialog、el-drawer里也要注意弹窗自带的动画是否影响了定位。还有一个比较省事的方案是把固定列改成非固定列用普通列展示。对导出功能来说固定列不固定列完全不重要所以别因为样式折腾导出流程。如果实在要固定列建议参考ElementUI官方issue里的修复建议升级版本到2.15.x以上很多类似问题在新版本里已经做了解释和优化。之后再处理导出就不会被表格样式问题分心了。最后的经验补充每次接入这类导出功能我都会在组件里预留好fields和exportData两个独立变量而不是直接拿表格数据去导。这样后续改格式、加列、做权限控制都方便。另一个建议是在导出按钮点击后加上loading状态因为数据量大时导出过程会有几秒卡顿没有反馈用户会以为按钮坏了。vue-json-excel胜在简单真正的Excel样式需求它做不了但也有它不可替代的实用价值。如果你也是在VUE2 ElementUI的项目里做中后台表格导出不妨先用它把流程跑通再根据业务反馈去迭代。工具本身不是重点重点是你对数据流转、字段格式化、边界情况的把控。这些经验才是支撑你在各种导出场景里不慌不忙的底气。
阅读完成 · 觉得有帮助?
咨询建站