拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

Vue2 ElementUI 表格导出:vue-json-excel 实践指南

Vue2 ElementUI 表格导出:vue-json-excel 实践指南

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、样式、列宽这些需求完全没法满足。简单场景应急可以,生产环境我基本不用。

第二种,用 SheetJS(xlsx 库)手写转换。这个库功能非常强,能读能写,能设置单元格样式(虽然社区版有限制),基本上算是浏览器端操作 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 class="export-btn" :data="tableData" :fields="jsonFields" 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 type="primary">导出 Excel</el-button>代替,让导出按钮和 ElementUI 风格统一:

<download-excel :data="tableData" :fields="jsonFields" name="用户列表.xls" > <el-button type="primary" size="small" icon="el-icon-download">导出 Excel</el-button> </download-excel>

组件内部实际上是一个普通元素包了一层点击事件,所以任何自定义内容都可以放进插槽里,不用担心样式被覆盖。

3. 项目实操:按钮集成、格式化数据、多 Sheet 导出

这部分我按一个真实后台页面来拆解。场景是:一个用户管理页面,上面有搜索区、筛选条件,下面是 ElementUI 表格,表格右上角有个“导出 Excel”按钮。需求是导出结果和当前筛选条件一致,并且导出的注册时间只保留日期部分,状态字段显示中文而不是数字。

3.1 从页面表格到导出文件,完整链路实现

先看模板结构。我习惯把导出按钮放在表格工具条区域,这样用户一眼能看到:

<template> <div class="user-page"> <div class="table-toolbar"> <el-input v-model="keyword" placeholder="搜索姓名" clearable style="width: 200px" /> <el-select v-model="statusFilter" placeholder="状态筛选" clearable style="width: 120px"> <el-option label="正常" value="1" /> <el-option label="禁用" value="0" /> </el-select> <el-button type="primary" @click="loadData">查询</el-button> <download-excel class="export-btn" :data="filteredData" :fields="exportFields" name="用户列表.xls" > <el-button type="success" size="small" icon="el-icon-download">导出 Excel</el-button> </download-excel> </div> <el-table :data="filteredData" border stripe> <el-table-column prop="name" label="姓名" /> <el-table-column prop="phone" label="手机号" /> <el-table-column prop="created_at" label="注册时间" /> <el-table-column prop="status" 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.phone,vue-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 :data="multiSheetData" :fields="multiSheetFields" name="全量数据.xls" > <el-button type="primary">导出全量数据</el-button> </download-excel>
multiSheetData() { 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 BOM,Excel 打开后能正常识别中文。但如果你用老项目锁定了旧版本(比如 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 :data="exportData" :fields="exportFields" :name="fileName" :header="headerTitle" :footer="footerText" > <el-button type="primary" size="small" icon="el-icon-download" :loading="loading" > {{ 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-data="filteredData" :export-fields="exportFields" file-name="用户列表.xls" header-title="用户列表" footer-text="由系统自动导出" />

代码一下清爽了,而且所有导出相关的样式调整都收敛在公共组件里,后期改按钮文案、改导出样式只需要动一处。

5.2 字段动态生成:根据表格列配置自动生成 fields

还有一种进阶场景:ElementUI 表格的列是后端配置下发的,前端用v-for动态渲染列。这时候手写jsonFields就不合适了,因为列的配置是动态的。

我的做法是写一个工具函数,把 ElementUI 的列配置转换成 vue-json-excel 需要的 fields:

export 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属性,比如:key="activeTab",避免组件实例复用导致数据串了。这个是我自己踩过的一个挺隐蔽的坑:两个 tab 共用一个组件实例,第一次导出正常,第二次导出发现表头还是上一次的。

写在最后的个人体会

如果让我给 vue-json-excel 做一个定位,我会说它是一款“贴合 Vue2 + ElementUI 开发习惯的轻量级导出工具”。它解决的是 80% 后台页面的常规导出需求——绑定数据、定义表头、处理格式化,三步走完就能交付。它不是万能的,多 Sheet 映射、复杂样式、超大导出量这些场景它做不到,但对绝大多数内部管理系统来说,它的简单和直观反而是最大的优势。

我在这几个月的实践里最大的感受是:选技术方案不用追求功能最全,要追求问题匹配度。当你的需求就是“把当前页面表格的数据导出来,格式要能看得懂,字段要能对得上”,vue-json-excel 就是那个成本最低、见效最快的答案。如果以后项目升到 Vue 3,再换 xlsx 方案也不迟——毕竟工具是死的,思路是活的。

返回列表