
Vue2 项目里做表格导出最烦的不是写代码而是选方案。如果你已经在用 ElementUI可能第一反应是去翻它的文档有没有现成导出方法——很遗憾ElementUI 的 Table 只负责渲染压根不管导出。社区里各种方案五花八门有自己拼 CSV 的、有用 xlsx 库手写转换的、还有后端生成文件返回下载链接的。我自己的选择是vue-json-excel一个把 JSON 数据直接转成 Excel 文件的库实测下来最省心。这篇文章就把我这段时间用vue-json-excel做 VUE2 ElementUI 表格导出的完整经验写出来从安装配置到踩坑实录希望能帮你少走点弯路。1. 为什么选了 vue-json-excel方案对比和选型思路动手写代码之前先说说我是怎么挑到这个库的。当时项目里有个需求页面上一个 ElementUI 表格要把当前展示的数据原样导成 Excel。看着挺简单实际上坑不少尤其表格列多、字段名和表头不一致的时候导出来的文件经常和页面对不上。1.1 表格导出的几种主流方案优缺点摆一起看我先捋了一下市面上的主流路子大概有四类第一种纯手工拼 CSV。用逗号把数据拼成长字符串加个 BOM 头防止中文乱码然后生成 Blob 下载。优点是零依赖随便一个工具函数就能写缺点也很明显CSV 本质上是个纯文本文件数字会被 Excel 当字符串处理某些特殊字符容易出问题而且多个 Sheet、样式、列宽这些需求完全没法满足。简单场景应急可以生产环境我基本不用。第二种用 SheetJSxlsx 库手写转换。这个库功能非常强能读能写能设置单元格样式虽然社区版有限制基本上算是浏览器端操作 Excel 的标配。但问题是它属于偏底层工具需要自己把 ElementUI Table 的列配置翻译成 sheet 的列结构再手动调用导出方法代码量不小。如果你想做的是“点一下按钮把当前表格导出去”用 xlsx 有种杀鸡用牛刀的感觉。第三种后端生成文件。把筛选条件发给后端让后端生成 Excel 返回下载地址。好处是后端能做大数据量导出不依赖前端内存缺点是前后端要联调小项目里为了一个导出功能去改接口文档有点得不偿失。第四种专门的前端导出组件比如 vue-json-excel。它的思路很简单你给它一份 JSON 数组和一份表头映射关系它自己负责把 JSON 转成 Excel 格式并触发下载。省掉了手动拼 CSV 的脏活也不用像 xlsx 那样关心底层细节适合“以页面展示为核心的导出场景”。我个人最终选了第四种主要理由是团队项目里前端已经够忙了导出的数据量又不至于大到必须走后端用 vue-json-excel 能把成本压到最低。1.2 vue-json-excel 在选型里的核心优势再展开讲一下为什么是它而不是其他同类导出组件。vue-json-excel 的 star 数和维护频率在 Vue2 生态里都算不错的。它的核心机制是接收一个json-data数组格式配合fields属性和name属性fields定义表头与数据字段的映射关系name定义下载文件名。组件内部用FileSaver.js和Blob.js处理文件保存逻辑最后生成一个.xls文件。和微信群、技术社区里经常推荐的Export2Excel基于 xlsx 和 file-saver 二次封装相比vue-json-excel 的使用成本明显更低Export2Excel 需要你先定义一个导出方法在方法里组装好 header 和 data还要处理合并单元格这种高级需求时自己扩展vue-json-excel 只需要在模板里放一个download-excel标签绑定数据就能用甚至不需要写复杂的 JS 方法。另外vue-json-excel 支持自定义表头字段名、支持格式化字段值比如把时间戳转成日期格式再导出、支持多组数据导出到同一个文件的不同 Sheet这几个能力刚好覆盖了大多数后台管理系统的需求。综合下来我把它当成 ElementUI 表格导出的“默认标配”。2. 安装、注册和核心属性解析先把基础打牢方案确定了下一步就是装库、注册组件、写模板。这个库支持 npm 安装也支持直接在 HTML 里引 CDN考虑到现在多数 VUE2 项目都是 vue-cli 或 vite 构建的我下面以 npm 方式为例。2.1 安装与全局注册5分钟跑通最小示例安装很简单在项目根目录执行npm install vue-json-excel -S注意这里用-S因为它是运行时依赖不是开发依赖。装完之后在main.js里注册成全局组件import Vue from vue import DownloadExcel from vue-json-excel Vue.component(download-excel, DownloadExcel)也可以只在某个.vue文件里局部引入script import DownloadExcel from vue-json-excel export default { components: { DownloadExcel } } /script走通最小示例只需要在模板里写这么一段download-excel classexport-btn :datatableData :fieldsjsonFields name用户列表.xls 导出 Excel /download-excel此时你点一下“导出 Excel”浏览器会自动下载一个名叫“用户列表.xls”的文件内容是tableData数组中的数据表头由jsonFields决定。从安装到第一个文件落地耗时不超过五分钟。2.2 fields 字段映射真正决定导出质量的参数fields是这个组件最关键的属性它决定了导出的 Excel 长什么样。直接看一个实际场景页面表格有这几列姓名、手机号、注册时间、状态。后端返回的数据字段名是name、phone、created_at、status但页面表头希望显示为“姓名”“手机号”“注册时间”“状态”。export default { data() { return { jsonFields: { 姓名: name, 手机号: phone, 注册时间: created_at, 状态: status }, tableData: [ { name: 张三, phone: 13800001111, created_at: 2023-01-15 10:20:30, status: 正常 }, { name: 李四, phone: 13900002222, created_at: 2023-02-02 14:30:00, status: 禁用 } ] } } }这里的关键点是fields的键名是Excel 里显示的表头值则是数据对象中的字段名。组件会按照 fields 的书写顺序生成列顺序所以想让哪列在前就把哪列写在前面。除了这种简单的字符串映射fields还支持配置对象形式用来处理更复杂的场景jsonFields: { 姓名: { field: name, callback: (value) { return value ? value : 未填写 } }, 注册时间: { field: created_at, callback: (value) { return value ? value.split( )[0] : } } }这个callback函数可以拿到当前字段的原始值并返回一个转换后的值。我经常用它处理导出时的时间格式化、空值兜底、状态枚举转中文等需求。特别提醒一句callback 里直接返回value就能实现“导出原值”不需要额外写管道函数。2.3 data 属性、name 属性和默认插槽用法再补充几个我实际用到的高频属性data必填要导出的 JSON 数组。可以直接传tableData也可以传一个computed计算属性。比如只导出当前筛选条件下的数据就在计算属性里边 filter 边输出这样“导出”永远和“当前页面看到的数据”保持一致。name选填下载文件名必须带.xls后缀。如果不传默认值是data.xls。这里有个小坑文件名的中文在部分浏览器老版本里会乱码最新版的 Chrome、Edge、Firefox 基本没问题但如果你还在兼容老内核浏览器建议用英文文件名或简单的拼音。默认插槽内容download-excel 导出 Excel /download-excel中间的内容会被渲染成按钮里的文字。你完全可以用el-button typeprimary导出 Excel/el-button代替让导出按钮和 ElementUI 风格统一download-excel :datatableData :fieldsjsonFields name用户列表.xls el-button typeprimary sizesmall iconel-icon-download导出 Excel/el-button /download-excel组件内部实际上是一个普通元素包了一层点击事件所以任何自定义内容都可以放进插槽里不用担心样式被覆盖。3. 项目实操按钮集成、格式化数据、多 Sheet 导出这部分我按一个真实后台页面来拆解。场景是一个用户管理页面上面有搜索区、筛选条件下面是 ElementUI 表格表格右上角有个“导出 Excel”按钮。需求是导出结果和当前筛选条件一致并且导出的注册时间只保留日期部分状态字段显示中文而不是数字。3.1 从页面表格到导出文件完整链路实现先看模板结构。我习惯把导出按钮放在表格工具条区域这样用户一眼能看到template div classuser-page div classtable-toolbar el-input v-modelkeyword placeholder搜索姓名 clearable stylewidth: 200px / el-select v-modelstatusFilter placeholder状态筛选 clearable stylewidth: 120px el-option label正常 value1 / el-option label禁用 value0 / /el-select el-button typeprimary clickloadData查询/el-button download-excel classexport-btn :datafilteredData :fieldsexportFields name用户列表.xls el-button typesuccess sizesmall iconel-icon-download导出 Excel/el-button /download-excel /div el-table :datafilteredData border stripe el-table-column propname label姓名 / el-table-column propphone label手机号 / el-table-column propcreated_at label注册时间 / el-table-column propstatus label状态 / /el-table /div /template脚本部分script export default { data() { return { keyword: , statusFilter: , tableData: [ { id: 1, name: 张三, phone: 13800001111, created_at: 2023-01-15 10:20:30, status: 1 }, { id: 2, name: 李四, phone: 13900002222, created_at: 2023-02-02 14:30:00, status: 0 }, { id: 3, name: 王五, phone: 13700003333, created_at: 2023-03-11 09:15:00, status: 1 } ], exportFields: { 姓名: name, 手机号: phone, 注册时间: { field: created_at, callback: (value) (value ? value.split( )[0] : ) }, 状态: { field: status, callback: (value) (value 1 ? 正常 : 禁用) } } } }, computed: { filteredData() { let list this.tableData if (this.keyword) { list list.filter(item item.name.includes(this.keyword)) } if (this.statusFilter ! ) { list list.filter(item item.status Number(this.statusFilter)) } return list } }, methods: { loadData() { // 通常会在这里请求接口更新 tableData // 这里用模拟数据演示 } } } /script注意几个细节。第一filteredData既给el-table用也传给download-excel这样导出的内容和页面上当前显示的完全一致不会出现“页面筛选了但导出还是全量”的尴尬。第二callback里做格式化避免在tableData上直接改数据从而影响表格渲染。第三el-table的显示格式和导出格式可以不一样比如表格里status直接显示 1/0导出时转成“正常/禁用”这是常见的业务需求。3.2 处理嵌套数据字段名带点的自动取值有些后端接口喜欢返回嵌套对象比如{ id: 123, user: { name: 张三, profile: { phone: 13800001111 } } }这种情况下你可以在fields里写user.name或user.profile.phonevue-json-excel 会按路径自动取值exportFields: { 姓名: user.name, 手机号: user.profile.phone }这是一个非常实用的能力。我的经验是后端返回的数据结构我们不好随便改如果为了导出专门写一层map去铺平数据代码会非常啰嗦。直接用字段路径映射既保持了原始数据的完整性又让导出配置非常清晰。不过要注意如果某条数据里user字段本身是null组件在取值时可能会报错。稳妥的做法是在传给组件之前先做一层兜底处理或者用computed计算属性把null替换成空对象safeData() { return this.tableData.map(item ({ ...item, user: item.user || {} })) }3.3 多个 Sheet 的导出一个按钮搞定多块数据还有一种场景页面里同时有两张表比如“用户列表”和“角色列表”用户希望点一个按钮一次性导出成一个 Excel 文件里面包含两个 Sheet。vue-json-excel 提供了一种方式给组件传data时如果数据是一个数组、且数组每个元素包含name和data它就会把每项渲染成独立的 Sheet。具体写法download-excel :datamultiSheetData :fieldsmultiSheetFields name全量数据.xls el-button typeprimary导出全量数据/el-button /download-excelmultiSheetData() { return [ { name: 用户列表, data: this.userList }, { name: 角色列表, data: this.roleList } ] }但这里有个坑要到踩了才知道fields在这种情况下只对第一个 Sheet 生效。如果两个 Sheet 的列结构不一样后一个 Sheet 的列名会直接取数据对象的键名不会走fields的映射。想做到每个 Sheet 用各自的字段映射目前的官方版本支持得并不好。我自己的解决办法是如果多 Sheet 的列结构差异大就拆成两个download-excel按钮分别导出如果差异不大就在传给组件前预先处理好数据保证每个 Sheet 里的对象键名已经符合表头需求。遇到这种情况千万别硬跟框架较劲换一种实现路径往往更快。4. 常见问题速查导出乱码、文件名失效、样式丢失用这个库几个月下来我也遇到了一些网上讨论比较少的问题。整理一份“问题 - 原因 - 解决方案”的对照表方便你直接排查。问题现象常见原因解决思路导出的 Excel 打开中文乱码生成的 xls 文件编码和 Excel 打开时不一致确认 vue-json-excel 版本新版本内置了 BOM 处理同时检查是否用过第三方编辑器改动过导出文件文件名后缀被浏览器改成.txt下载响应头或 Blob type 不正确确认name属性带.xls不要省略后缀fields 里的 callback 不生效字段名写错或数据是异步加载先打印传给组件的 data确认字段路径和 callback 参数是否正确点击导出没任何反应组件没有正确注册或事件未绑定打开控制台看有无报错确认Vue.component(download-excel, DownloadExcel)已执行导出的数据量和表格不一致数据源传入的是原始数组不是筛选后的数组检查传给:data的是不是计算属性或方法返回值日期格式变成一串数字Excel 把日期字符串识别成了数字格式在 callback 里重新格式化成YYYY-MM-DD HH:mm:ss或者导出时拼上\t强制文本格式这里重点解释两个高频问题。第一个是中文乱码。vue-json-excel 最新版已经处理了 BOM本地生成的文件里会带上 UTF-8 BOMExcel 打开后能正常识别中文。但如果你用老项目锁定了旧版本比如 2.0.2 之前的版本有可能会出现中文乱码。最快的解决办法就是升级到最新版其次是在导出前对数据做一次encodeURIComponent再传进去不推荐绕。如果项目实在升级不了还有一个兜底方案把导出内容改成 CSV 格式并用\ufeff开头注意这是另写导出逻辑的场景了和 vue-json-excel 本身的调用无关。第二个是 ElementUI Table 固定列出现透明/错位的问题。有热搜词提到“elementui 报表的固定列有时候会变透明”这其实和 vue-json-excel 没有直接关系但很多人是因为在做表格导出时频繁操作表格列触发 ElementUI 的固定列重绘 BUG。常见触发场景是动态切换表格列后固定列样式残留了旧的宽度。我的处理方式有两条一是操作完列后调用this.$refs.table.doLayout()二是给表格加一个key强制重新渲染。两者都试过doLayout()的代价更小优先用它。4.1 导出大数据量时浏览器卡顿怎么办还有一个容易被忽略的问题data传几万条数据时浏览器可能会卡一下。原因是 vue-json-excel 在内部要把整个 JSON 转成 Excel 的 XML 结构这个过程是同步的数据量越大主线程占用越久。我的建议是超过 5000 行就不太适合纯前端导出了不是说一定不行而是体验会明显下降。要么限制导出条数加上“最多导出 5000 行”的提示要么直接改成后端导出前端只负责触发和轮询下载状态。这个判断标准不涉及复杂原理就是一个成本和体验的平衡。4.2 vue-json-excel 和 Vue 3 的兼容性问题最后提一句vue-json-excel 这个库本身是为 Vue 2 设计的。如果你看到 Vue 3 项目里有人提到vue-json-excel大概率是在用兼容层或 fork 版本。我在做 vue2 转 vue3 评估时专门测过这个库在 Vue 3 下的表现直接在 setup 里引入注册会报一堆 API 不兼容的警告。所以这里明确一下适用范围vue-json-excel 最舒服的宿主环境是 VUE2 ElementUI如果你的项目已经切 Vue 3要么继续沿用 xlsx 手写导出要么找替代方案。这不代表这个库没有价值。实际上目前还有大量存量项目跑在 VUE2 上尤其是一些中后台管理系统短期内不会整体升级。对这类项目来说vue-json-excel 依然是一个非常省事的导出工具。5. 进阶用法与二次封装让它真正变成项目公共能力一个组件如果只在某个页面里用一次价值是有限的。真正合适的方式是把它二次封装成项目里通用的导出工具让任何页面都能复用。5.1 封装全局导出指令或公共组件我推荐写一个简单的公共组件ExportExcel.vue把常用的配置项提出来template download-excel :dataexportData :fieldsexportFields :namefileName :headerheaderTitle :footerfooterText el-button typeprimary sizesmall iconel-icon-download :loadingloading {{ buttonText || 导出 Excel }} /el-button /download-excel /template script export default { name: ExportExcel, props: { exportData: { type: Array, required: true }, exportFields: { type: Object, required: true }, fileName: { type: String, default: 导出数据.xls }, headerTitle: { type: String, default: }, footerText: { type: String, default: }, buttonText: { type: String, default: } }, data() { return { loading: false } } } /script这里额外提到了header和footer属性vue-json-excel 其实支持在表格上方生成标题行、在表格下方生成汇总行。比如导出月度报表时header可以写“2025年3月用户统计”footer可以写“导出时间2025-03-11 12:00:00”。这个能力很隐蔽文档里提得不明显但实际用起来非常加分。封装完成之后任意页面只需要export-excel :export-datafilteredData :export-fieldsexportFields file-name用户列表.xls header-title用户列表 footer-text由系统自动导出 /代码一下清爽了而且所有导出相关的样式调整都收敛在公共组件里后期改按钮文案、改导出样式只需要动一处。5.2 字段动态生成根据表格列配置自动生成 fields还有一种进阶场景ElementUI 表格的列是后端配置下发的前端用v-for动态渲染列。这时候手写jsonFields就不合适了因为列的配置是动态的。我的做法是写一个工具函数把 ElementUI 的列配置转换成 vue-json-excel 需要的 fieldsexport function generateFieldsFromColumns(columns) { const fields {} columns.forEach(col { if (col.prop) { fields[col.label] col.prop } }) return fields }然后把它塞进计算属性里computed: { exportFields() { return generateFieldsFromColumns(this.dynamicColumns) } }注意一个边界情况如果列配置里有自定义格式化比如formatter函数上面的工具函数是覆盖不到的。碰到这种列要么在函数里追加判断要么仍然选择手动维护 fields。我的原则是动态列用自动生成涉及复杂格式化的列单独拆出去手动配置两种方式共存不强行统一。5.3 导出的 Excel 和页面样式不同步怎么避免还有用户问过页面上明明设置了斑马纹、边框、列宽为什么导出的 Excel 什么都没有这个要解释清楚——vue-json-excel 导出的是数据文件不是页面截图。它不会保留 ElementUI 表格的视觉样式只会生成一个带表头和数据行的普通 Excel 表格。如果你想在导出的 Excel 里看到列宽、颜色、加粗有两个方向一是导出后用前端工具再处理样式xlsx 社区版对样式支持有限要专业样式得上付费版二是换思路直接导出 PDF 或者用后端报表引擎生成带样式的 Excel。从实际业务来看大部分后台管理系统的导出需求只是“数据要能看得清、能二次处理”对样式要求并不高所以这种情况我一般不去折腾。6. 几点补充经验帮你省下调试时间写到最后分享几个我在实际使用中沉淀下来的小经验不一定在每个项目里都适用但遇到了能省不少功夫。第一vite 项目里使用 vue-json-excel 要留意 CommonJS 兼容问题。很多存量 VUE2 项目用的还是 webpack 构建vue-json-excel 没问题。但如果你用 vite 跑 VUE2 项目比如通过vite-plugin-vue2可能要手动配置optimizeDeps把它预构建出来否则加载组件时会报“模块未导出”之类的错误。第二导出按钮的 loading 状态不要依赖组件内部。vue-json-excel 本身没有自动 loading 逻辑如果数据量大点击后按钮可能看起来“没反应”用户容易重复点击。建议在外面包一层状态控制点击导出时先检查数据量超过阈值就提示“正在生成请稍候”或者干脆禁用按钮直到生成完成。第三导出的文件不是真正的.xls格式。严格来说vue-json-excel 生成的是 SpreadsheetML 格式也就是 XML 描述表格数据用.xls后缀只是为了兼容 Excel 双击打开。WPS、LibreOffice 也能正常打开。如果甲方要求必须生成.xlsx后缀这个库就不太合适了建议改用 xlsx 库来做.第四别在 callback 里做耗时的同步操作。callback 是同步执行的如果在里面做大量字符串拼接或循环会直接卡住导出过程。需要异步处理的数据最好提前在传入data之前处理好而不是丢在 callback 里碰运气。第五多个导出按钮同时存在时给组件加key。如果你在同一个页面里根据 tab 切换渲染不同的导出按钮和数据建议给download-excel加一个key属性比如:keyactiveTab避免组件实例复用导致数据串了。这个是我自己踩过的一个挺隐蔽的坑两个 tab 共用一个组件实例第一次导出正常第二次导出发现表头还是上一次的。写在最后的个人体会如果让我给 vue-json-excel 做一个定位我会说它是一款“贴合 Vue2 ElementUI 开发习惯的轻量级导出工具”。它解决的是 80% 后台页面的常规导出需求——绑定数据、定义表头、处理格式化三步走完就能交付。它不是万能的多 Sheet 映射、复杂样式、超大导出量这些场景它做不到但对绝大多数内部管理系统来说它的简单和直观反而是最大的优势。我在这几个月的实践里最大的感受是选技术方案不用追求功能最全要追求问题匹配度。当你的需求就是“把当前页面表格的数据导出来格式要能看得懂字段要能对得上”vue-json-excel 就是那个成本最低、见效最快的答案。如果以后项目升到 Vue 3再换 xlsx 方案也不迟——毕竟工具是死的思路是活的。