✅ 纯前端导出 Excel(XLSX + FileSaver)
技术栈:
xlsx+file-saver
优势:不依赖后端、支持样式、格式、自定义下载名
适用场景:Vue / React / 原生 JS 项目
关键词:前端导出 Excel、JavaScript、xlsx.js、单元格格式、数字格式、日期格式、样式设置、纯前端解决方案
📌 前言:为什么我要自己导出 Excel?
在日常开发中,你是否也遇到过这样的场景:
“这个表格数据能不能导出成 Excel?”
“导出的金额要带千分位和货币符号。”
“日期列要显示成 ‘2024-05-20’ 格式,别是时间戳!”
“表头要加粗、居中、背景色高亮!”
每次提需求给后端,对方一脸无奈:“又要改导出逻辑?又要加样式?前端不能自己搞吗?”
于是,我决定:前端,自己动手,丰衣足食!
📦 1. 安装依赖
npm install xlsx file-saver
xlsx:用于创建和格式化 Excel 文件file-saver:用于在浏览器中保存文件(比XLSX.writeFile更可控)
📥 2. 引入模块
import * as XLSX from 'xlsx';
import { saveAs } from 'file-saver';
🧩 3. 准备数据和表头配置
const data = [
{ name: '张三', salary: 15000, joinDate: '2023-01-15', status: '在职' },
{ name: '李四', salary: 18000, joinDate: '2022-03-20', status: '离职' },
{ name: '王五', salary: 12000, joinDate: '2024-05-10', status: '在职' }
];
const headerConfig = [
{ label: '姓名', key: 'name', width: 15 },
{ label: '薪资', key: 'salary', key: 'salary', format: 'currency', width: 15 },
{ label: '入职日期', key: 'joinDate', format: 'date', width: 15 },
{ label: '状态', key: 'status', width: 10, align: 'center' }
];
🔧 4. 核心导出函数(使用 xlsx + file-saver)
function exportExcelWithFormat(data, headerConfig, filename = '导出数据.xlsx') {
const headers = headerConfig.map(h => h.label);
const keys = headerConfig.map(h => h.key);
// 1. 构建二维数组:第一行为表头
const wsData = [headers];
data.forEach(row => {
const rowData = keys.map(key => row[key]);
wsData.push(rowData);
});
// 2. 创建工作表
const ws = XLSX.utils.aoa_to_sheet(wsData);
// 3. 设置列宽
const colWidths = headerConfig.map(h => ({ wch: h.width || 12 }));
ws['!cols'] = colWidths;
// 4. 设置单元格格式(数字、日期等)
data.forEach((row, rowIndex) => {
const excelRow = rowIndex + 1; // Excel 行号从 1 开始
headerConfig.forEach((h, colIndex) => {
const cellRef = XLSX.utils.encode_cell({ c: colIndex, r: excelRow }); // 如 B2, C3
const cell = ws[cellRef];
if (!cell) return;
// 可选:设置单元格样式(需启用 cellStyles)
if (!cell.s) cell.s = {};
cell.s = {
...cell.s,
font: { sz: 11 },
alignment: { horizontal: h.align || 'left' }
};
});
});
// 5. 设置表头样式
const headerStyle = {
font: { bold: true, color: { rgb: "FFFFFF" }, sz: 12 },
fill: { fgColor: { rgb: "4472C4" } }, // 深蓝色背景
alignment: { horizontal: "center", vertical: "center" }
};
headers.forEach((_, colIndex) => {
const cellRef = XLSX.utils.encode_cell({ c: colIndex, r: 0 }); // 第一行
ws[cellRef].s = headerStyle;
});
// 6. 创建工作簿
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, '数据表');
// 7. 生成二进制字符串(类型为 binary)
const wbout = XLSX.write(wb, {
bookType: 'xlsx',
type: 'binary', // 必须是 binary 或 array,不能是 base64
cellStyles: true // 启用样式支持
});
// 8. 转换为 Blob(FileSaver 需要 Blob)
function s2ab(s) {
const buf = new ArrayBuffer(s.length);
const view = new Uint8Array(buf);
for (let i = 0; i < s.length; i++) {
view[i] = s.charCodeAt(i) & 0xFF;
}
return buf;
}
const blob = new Blob([s2ab(wbout)], {
type: 'application/octet-stream'
});
// 9. 使用 FileSaver 下载
saveAs(blob, filename);
}
🎯 5. 调用导出
exportExcelWithFormat(data, headerConfig, '员工信息表.xlsx');
🧠 关键点解析
| 问题 | 解决方案 |
|---|---|
file-saver 无法直接使用 XLSX.writeFile? | 改用 XLSX.write 生成二进制数据,再转为 Blob |
| 样式不生效? | 必须设置 cellStyles: true 且 type: 'binary' |
| 中文乱码? | file-saver 和 xlsx 都支持 UTF-8,一般无问题 |
s2ab 是什么? | 将 binary 字符串转为 ArrayBuffer,用于创建 Blob |
📌 6. 为什么推荐 xlsx + file-saver?
| 对比项 | XLSX.writeFile | xlsx + file-saver |
|---|---|---|
| 灵活性 | 低 | 高(可自定义 Blob、类型、触发时机) |
| 框架兼容性 | 一般 | 好(React/Vue 中更可控) |
| 样式支持 | 需配置 | 同样需 cellStyles: true |
| 是否需要额外库 | 否 | 是(需 file-saver) |
✅ 推荐在 Vue/React 项目中使用
file-saver,更符合现代开发习惯。
✅ 总结
通过 xlsx + file-saver,我们可以:
- 🔹 完全在前端生成带格式的 Excel
- 🔹 精准控制 数字、日期、货币格式
- 🔹 自定义 字体、颜色、对齐、背景色
- 🔹 实现 跨框架通用 的导出能力
🎉 再也不用求后端!前端也能导出“专业级”Excel!
💾 附:完整代码供参考
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>前端导出Excel</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
background-color: #f5f5f5;
}
.container {
max-width: 800px;
margin: 0 auto;
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
text-align: center;
color: #333;
}
.table-container {
overflow-x: auto;
margin-bottom: 20px;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
}
th, td {
border: 1px solid #ccc;
padding: 8px;
text-align: left;
}
th {
background-color: #4472C4;
color: white;
font-weight: bold;
}
button {
display: block;
margin: 20px auto;
padding: 10px 20px;
font-size: 16px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
</style>
</head>
<body>
<div class="container">
<h1>前端导出Excel示例</h1>
<div class="table-container">
<table id="dataTable">
<thead>
<tr>
<th>姓名</th>
<th>薪资</th>
<th>入职日期</th>
<th>状态</th>
</tr>
</thead>
<tbody>
<tr>
<td>张三</td>
<td>15000</td>
<td>2023-01-15</td>
<td>在职</td>
</tr>
<tr>
<td>李四</td>
<td>18000</td>
<td>2022-03-20</td>
<td>离职</td>
</tr>
<tr>
<td>王五</td>
<td>12000</td>
<td>2024-05-10</td>
<td>在职</td>
</tr>
</tbody>
</table>
</div>
<button id="exportBtn">导出为Excel</button>
</div>
<script>
// 模拟数据源
const data = [
{ name: '张三', salary: 15000, joinDate: '2023-01-15', status: '在职' },
{ name: '李四', salary: 18000, joinDate: '2022-03-20', status: '离职' },
{ name: '王五', salary: 12000, joinDate: '2024-05-10', status: '在职' }
];
// 表头配置
const headerConfig = [
{ label: '姓名', key: 'name', width: 15 },
{ label: '薪资', key: 'salary', format: 'currency', width: 15},
{ label: '入职日期', key: 'joinDate', format: 'date', width: 15 },
{ label: '状态', key: 'status', width: 15 }
];
/**
* 导出Excel核心函数
*/
function exportExcelWithFormat(data, headerConfig, filename = '导出数据.xlsx') {
const headers = headerConfig.map(h => h.label);
const keys = headerConfig.map(h => h.key);
// 构建二维数组
const wsData = [headers];
data.forEach(row => {
const rowData = keys.map(key => row[key]);
wsData.push(rowData);
});
// 创建工作表
const ws = XLSX.utils.aoa_to_sheet(wsData);
// 设置列宽
const colWidths = headerConfig.map(h => ({ wch: h.width || 12 }));
ws['!cols'] = colWidths;
// 遍历表头列,设置格式和样式
headerConfig.forEach((h, colIndex) => {
const cellRef = XLSX.utils.encode_cell({ c: colIndex, r: 0 }); // A1, B1, C1...
const cell = ws[cellRef];
// 设置表头样式(加粗、背景色、居中)
cell.s = {
font: { bold: true, color: { rgb: "FFFFFF" }, sz: 12 },
fill: { fgColor: { rgb: "4472C4" } },
alignment: { horizontal: "center", vertical: "center" }
};
});
data.forEach((row, rowIndex) => {
const excelRow = rowIndex + 1; // 数据从第1行开始
headerConfig.forEach((h, colIndex) => {
const cellRef = XLSX.utils.encode_cell({ c: colIndex, r: excelRow });
const cell = ws[cellRef];
if (!cell) return;
// 设置数字格式
if (h.format === 'currency') {
cell.z = '#,##0.000_);[Red]\\(#,##0.000\\)';
} else if (h.format === 'date') {
// 将字符串转为 Date 对象
const dateStr = row[h.key];
if (dateStr) {
const dateObj = new Date(dateStr);
cell.v = "2025-10-22";
cell.t = 'd';
cell.z = 'yyyy-mm-dd';
}
}
// 设置数据行样式
if (!cell.s) cell.s = {};
cell.s = {
...cell.s,
font: { sz: 12 },
alignment: { horizontal: h.align || 'right' }
};
});
});
// 创建工作簿
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, '数据表');
// 生成二进制字符串
const wbout = XLSX.write(wb, {
bookType: 'xlsx',
type: 'binary',
cellStyles: true // 启用样式支持
});
// 转换为 ArrayBuffer
function s2ab(s) {
const buf = new ArrayBuffer(s.length);
const view = new Uint8Array(buf);
for (let i = 0; i < s.length; i++) {
view[i] = s.charCodeAt(i) & 0xFF;
}
return buf;
}
// 创建 Blob 并下载
const blob = new Blob([s2ab(wbout)], {
type: 'application/octet-stream'
});
saveAs(blob, filename);
}
// 绑定按钮事件
document.getElementById('exportBtn').addEventListener('click', () => {
exportExcelWithFormat(data, headerConfig, '员工信息表.xlsx');
});
</script>
</body>
</html>
附:示例截图:
导出效果
⚠️ 注意:
file-saver的新版(2.x)API 更稳定,推荐使用。