在现代 Web 应用开发中,数据导出功能是用户常用的交互需求之一。本文将结合我在实现 Excel 文件导出过程中遇到的实际问题与应对策略,帮助开发者规避常见坑点,提升实现效率。
问题一:引入 xlsx 库时出现导入错误
错误现象:
import XLSX from 'xlsx'
// ? Error: export 'default' (imported as 'XLSX') was not found in 'xlsx'
原因分析:
xlsx
所使用的 SheetJS(即 xlsx 库)本质上是一个 CommonJS 模块,其设计并未包含默认导出(default export)。当使用 Webpack 或 Vite 等现代构建工具处理此类模块时,若尝试通过 default import 方式引入,系统因无法找到对应导出结构而抛出异常。
export default
解决方案:
采用命名空间导入方式替代默认导入:
import * as XLSX from 'xlsx'
// ? 正确:将所有导出内容作为一个对象导入
如此即可正常使用诸如
XLSX.utils
、
XLSX.read
、
XLSX.writeFile
等核心方法进行文件读写操作。
问题二:导出后样式未生效(如背景色、文字居中、加粗等)
错误现象:
生成的 Excel 文件中,所有单元格均无任何格式设置,包括字体加粗、对齐方式和填充颜色等。
原因分析:
官方版本的
xlsx
库仅支持基础的数据解析与写入功能,不提供对单元格样式的控制能力,例如颜色、边框、对齐或字体加粗等高级格式均无法通过该库直接实现。
解决方案:
推荐替换为社区持续维护并增强功能的分支库:
xlsx-js-style
该版本在保留原有 API 的基础上扩展了完整的样式支持。
import * as XLSX from 'xlsx-js-style'
// ? 支持样式的增强版本
安装命令如下:
npm install xlsx-js-style
完整实现:封装通用导出工具函数
基于上述两个关键问题的解决思路,我封装了一个可复用的前端导出工具,具备以下特性:
- 支持自定义表头与字段映射关系
- 全局单元格内容居中显示
- 表头行自动应用背景色及加粗样式
- 智能计算列宽,兼容中文与英文混合场景
- 适用于多个不同表格页面的快速集成
核心代码实现:
import * as XLSX from "xlsx-js-style"
/**
* 通用导出函数:支持自定义表头和对应字段
* @param {Object} params
* @param {Array} params.header 显示在第一行的表头名称数组,例如 ['姓名', '年龄']
* @param {Array} params.key 对应数据的字段名数组,例如 ['name', 'age']
* @param {Array} params.data 数据源(对象数组)
* @param {String} params.filename 文件名
* @param {Boolean} params.autoWidth 是否自动调整宽度
* @param {String} params.headerBgColor 表头背景色,例如 'FFC000'(不带 # 号)
*/
export const exportCommonToExcel = ({ header, key, data, filename, autoWidth = true, headerBgColor = "FFC000" }) => {
const wb = XLSX.utils.book_new()
// 1. 过滤和映射数据:根据 key 数组提取数据,保证列顺序与 header 一致
const arrData = data.map((item) => {
return key.map((k) => item[k])
})
// 2. 将表头添加到第一行
arrData.unshift(header)
// 3. 生成 sheet
const ws = XLSX.utils.aoa_to_sheet(arrData)
const range = XLSX.utils.decode_range(ws["!ref"])
for (let R = range.s.r; R <= range.e.r; ++R) {
for (let C = range.s.c; C <= range.e.c; ++C) {
const cellAddress = XLSX.utils.encode_cell({ r: R, c: C })
if (!ws[cellAddress]) continue
const cellStyle = {
alignment: {
horizontal: "center",
vertical: "center",
},
}
if (R === 0) {
cellStyle.fill = {
fgColor: { rgb: headerBgColor },
}
cellStyle.font = {
bold: true,
color: { rgb: "000000" },
}
}
ws[cellAddress].s = cellStyle
}
}
if (autoWidth) {
auto_width(ws, arrData)
}
XLSX.utils.book_append_sheet(wb, ws, filename)
XLSX.writeFile(wb, (filename || "export") + ".xlsx")
}
// 设置导出的宽度
function auto_width(ws, data) {
/*set worksheet max width per col*/
const colWidth = data.map((row) =>
row.map((val) => {
/*if null/undefined*/
if (val == null) {
return { wch: 10 }
} else {
// 防止数据过长导致需要滑动距离过长
if (val.toString().length > 400) {
return { wch: val.toString().length / 40 }
} else {
const isChinese = /[\u4e00-\u9fa5]/.test(val.toString())
return {
wch: isChinese
? val.toString().length * 2 + 2
: val.toString().length + 2 < 10
? 10
: val.toString().length + 2,
}
}
}
}),
)
/*start in the first row*/
const result = colWidth[0]
for (let i = 1; i < colWidth.length; i++) {
for (let j = 0; j < colWidth[i].length; j++) {
if (result[j]["wch"] < colWidth[i][j]["wch"]) {
result[j]["wch"] = colWidth[i][j]["wch"]
}
}
}
ws["!cols"] = result
}
使用示例:
import { exportCommonToExcel } from '@/utils/excel-export'
// 原始数据
const tableData = [ { id: 1, name: '张三', age: 28, department: '技术部', secret: 'xxx' }, { id: 2, name: '李四', age: 32, department: '产品部', secret: 'yyy' } ]
// 导出
exportCommonToExcel({
header: ['员工ID', '姓名', '年龄', '部门'], // 自定义表头
key: ['id', 'name', 'age', 'department'], // 指定字段(忽略 secret)
data: tableData,
filename: '员工信息表',
headerBgColor: '4472C4' // 可选:自定义表头颜色(蓝色)
})
进阶场景:实现“导出全部数据”功能
问题背景:
当前页面数据采用分页展示(例如每页显示 10 条记录),但用户点击“导出全部”按钮时,期望导出数据库中的全部 1000 条数据,而非仅限当前页。
可行方案如下:
方案一:前端统一拉取并生成(适用于数据量小于 1 万条)
利用已有的前端导出逻辑,先请求服务端获取完整数据集,再由浏览器本地完成文件生成:
import { exportCommonToExcel } from '@/utils/excel-export';
async function handleExportAll() {
try {
this.loading = true; // 开启 loading
// 1. 准备查询参数,复用当前列表的搜索条件
const params = {
...this.searchParams,
page: 1,
pageSize: 99999 // 关键点:请求所有数据
};
// 2. 请求后端数据
const res = await api.getUserList(params);
const allData = res.data.list; // 拿到完整的列表
// 3. 调用工具函数导出
exportCommonToExcel({
header: ['ID', '姓名', '手机号'],
key: ['id', 'name', 'phone'],
data: allData, // 这里传入的是刚刚请求到的【全部数据】
filename: '所有用户数据'
});
this.$message.success('导出成功');
} catch (error) {
console.error(error);
this.$message.error('导出失败');
} finally {
this.loading = false; // 关闭 loading
}
}
优点:
- 样式配置完全由前端掌控,灵活性高
- 可复用现有导出工具和样式规则
- 无需后端额外开发接口
缺点:
当数据量超过一定阈值(如 >1万条)时,可能导致页面卡顿甚至内存溢出。
方案二:后端生成并返回文件流(适用于大数据量,建议 >1 万条)
对于超大规模数据集,应交由服务端处理文件生成任务,并以流的形式返回给前端下载:
async function handleExportAll() {
try {
const res = await api.exportUsers(this.searchParams, {
responseType: 'blob' // 接收二进制流
})
// 创建下载链接
const blob = new Blob([res.data], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
})
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = '用户数据.xlsx'
link.click()
window.URL.revokeObjectURL(url)
} catch (error) {
this.$message.error('导出失败')
}
}
总结
本文围绕 Excel 导出功能的三大典型难题进行了深入剖析与实践验证:
- 导入报错问题:通过使用
import * as XLSX from 'xlsx-js-style'语法进行命名空间导入,避免因缺少 default export 导致的构建失败。
- 样式失效问题:弃用原生
xlsx改用支持样式的
xlsx-js-style增强版库,实现丰富的单元格格式控制。
- 全量数据导出问题:根据数据规模选择合适策略——小数据量由前端统一获取并导出;大数据量则交由后端生成文件流,保障性能与稳定性。
最终达成一个兼具灵活性、可复用性与视觉表现力的 Excel 导出解决方案,适配多种业务场景下的实际需求。


雷达卡


京公网安备 11010802022788号







