
前端导出 excel 遭遇无单元格困境
前端导出Excel表格如何搞定样式定制和单元格编辑?这份实战方案帮你解决
在前端开发工作中,导出Excel文件是一项高频需求。无论是后台管理系统中的数据报表,还是移动端的表单下载,都需要将网页上的数据转换成Excel格式提供给用户。然而在实际开发过程中,很多开发者会遇到一个共同的痛点:基本的导出功能容易实现,但想要给导出的表格添加样式、合并单元格或者让单元格支持编辑,却困难重重。
一、前端导出Excel的常见困境
基础导出方案的局限性
目前前端导出Excel最常用的方案是借助xlsx库,它能够快速将JavaScript对象数组转换为Excel文件。但是这种方案有一个明显的短板,就是几乎不支持样式定制。如果你只是单纯地导出原始数据,xlsx库完全够用;但如果需要给表头加上背景色、设置字体大小、调整列宽或者合并单元格,就会感到力不从心。
举个例子,假设你要导出一份员工信息表,希望表头显示粉色背景、加粗字体,并且每个单元格都有边框。使用xlsx库的话,你需要手动编写大量的样式配置代码,而且最终效果还不一定能达到预期。更重要的是,xlsx库生成的单元格默认是只读的,用户无法直接在导出的Excel文件中编辑数据,这在某些业务场景下非常不便。
为什么简单的HTML转Excel方案不够好
有些开发者可能会想到一个取巧的方法:先用HTML写好表格结构,然后通过Blob对象将其转换成Excel文件。这种方案的原理是将HTML字符串包装成Excel可识别的格式,确实可以实现基本的样式继承。
具体代码如下所示:
const tableHtml = `
<table>
<thead>
<tr>
<th style="background-color: pink; font-weight: bold;">姓名</th>
<th style="background-color: pink; font-weight: bold;">年龄</th>
<th style="background-color: pink; font-weight: bold;">职位</th>
</tr>
</thead>
<tbody>
<tr>
<td>张三</td>
<td>28</td>
<td>工程师</td>
</tr>
<tr>
<td>李四</td>
<td>35</td>
<td>医生</td>
</tr>
</tbody>
</table>
`;
const blob = new Blob([tableHtml], { type: 'application/vnd.ms-excel' });这种方法看起来简单快捷,但实际上存在不少隐患。首先,这种方式生成的Excel文件本质上还是一个HTML文件,只是被浏览器识别为Excel格式。当用户用Excel软件打开时,可能会出现兼容性问题,比如样式丢失、表格错乱等。其次,这种方法很难实现真正的单元格编辑功能,因为HTML转出来的表格在Excel中会被当作静态内容处理。最后,如果需要动态增加行或列,或者对已有数据进行修改,这种方案的灵活性远远不够。
二、exceljs:功能全面的前端Excel处理方案
exceljs的核心优势
exceljs是目前前端领域功能最为完善的Excel处理库之一,它解决了传统方案中的诸多痛点。与xlsx库相比,exceljs提供了更丰富的API接口,涵盖了从创建工作簿、添加工作表、设置单元格样式到保护工作表等几乎所有功能。
exceljs的主要特点包括:
- 支持完整的单元格样式设置,包括字体、颜色、对齐方式、边框、填充等
- 支持单元格合并和拆分
- 支持条件格式和数据验证
- 支持单元格保护和锁定
- 支持图片插入
- 支持公式计算
- 兼容.xlsx和.csv等多种格式
安装和基本使用
在项目中引入exceljs非常简单,只需要通过npm或yarn进行安装:
npm install exceljs安装完成后,就可以在项目中导入并使用它了。下面是一个创建简单工作表的示例:
import ExcelJS from 'exceljs';
// 创建工作簿
const workbook = new ExcelJS.Workbook();
workbook.creator = '我的应用';
workbook.created = new Date();
// 添加工作表
const worksheet = workbook.addWorksheet('员工信息');
// 添加表头
worksheet.columns = [
{ header: '姓名', key: 'name', width: 15 },
{ header: '年龄', key: 'age', width: 10 },
{ header: '职位', key: 'position', width: 20 }
];
// 添加数据
worksheet.addRow({ name: '张三', age: 28, position: '工程师' });
worksheet.addRow({ name: '李四', age: 35, position: '医生' });
worksheet.addRow({ name: '王五', age: 22, position: '学生' });
// 生成文件
const buffer = await workbook.xlsx.writeBuffer();
// 然后可以将buffer保存为文件或通过接口返回给前端下载这段代码看起来简洁明了,但它已经实现了比xlsx库更精细的控制。接下来我们看看如何在此基础上添加样式和编辑功能。
三、轻松搞定Excel样式定制
单元格样式的详细设置
exceljs允许你对每一个单元格进行独立的样式设置。你可以通过访问单元格对象的style属性来实现各种样式效果。
以下是一个完整的样式设置示例:
// 获取表头所在的单元格
const headerRow = worksheet.getRow(1);
headerRow.eachCell((cell) => {
// 设置字体样式
cell.font = {
name: '微软雅黑',
size: 12,
bold: true,
color: { argb: 'FFFFFFFF' } // 白色字体
};
// 设置填充颜色
cell.fill = {
type: 'pattern',
pattern: 'solid',
fgColor: { argb: 'FF69B4' } // 粉色背景
};
// 设置对齐方式
cell.alignment = {
vertical: 'middle',
horizontal: 'center'
};
// 设置边框
cell.border = {
top: { style: 'thin', color: { argb: 'FF000000' } },
left: { style: 'thin', color: { argb: 'FF000000' } },
bottom: { style: 'thin', color: { argb: 'FF000000' } },
right: { style: 'thin', color: { argb: 'FF000000' } }
};
});通过这种方式,你可以精确控制每一个单元格的外观。如果你希望所有表头单元格保持统一的样式,也可以先定义一个样式模板,然后批量应用到指定区域。
合并单元格与自适应列宽
在实际应用中,经常需要合并单元格来制作跨列的表头或者汇总行。exceljs提供了非常直观的合并方法:
// 合并A1到C1的单元格,常用于制作总标题
worksheet.mergeCells('A1:C1');
const titleCell = worksheet.getCell('A1');
titleCell.value = '员工信息统计表';
titleCell.font = { size: 16, bold: true };
titleCell.alignment = { horizontal: 'center', vertical: 'middle' };列宽的设置也很重要,合理的列宽能让表格看起来更加整洁。你可以手动指定每一列的宽度,也可以通过遍历数据来自动计算最佳宽度:
// 手动设置列宽
worksheet.getColumn(1).width = 25;
worksheet.getColumn(2).width = 18;
// 或者根据内容自动调整
worksheet.columns.forEach((column) => {
const maxLength = column.values.reduce((max, value) => {
return Math.max(max, String(value).length);
}, 0);
column.width = maxLength + 2; // 留出一些余量
});四、实现可编辑的单元格
单元格编辑的核心机制
exceljs支持通过数据验证功能来控制用户对单元格的编辑行为。你可以设置单元格为可编辑状态,同时限制用户只能输入特定类型的数据。
例如,如果你希望年龄这一列只能输入数字,可以这样设置:
const ageColumn = worksheet.getColumn(2);
ageColumn.eachCell((cell, rowNumber) => {
if (rowNumber > 1) { // 跳过表头
cell.dataValidation = {
type: 'whole', // 整数类型
operator: 'between',
formulae: [0, 150], // 年龄范围
showErrorMessage: true,
errorTitle: '输入错误',
error: '请输入有效的年龄数值'
};
}
});保护工作表与解锁可编辑区域
在实际业务场景中,你可能希望部分单元格可以被用户编辑,而其他部分则受到保护。exceljs的工作表保护功能正好满足了这一需求。
具体做法是先对整个工作表启用保护,然后将允许编辑的单元格设置为解锁状态:
// 先将所有单元格锁定
worksheet.eachRow((row) => {
row.eachCell((cell) => {
cell.protection = {
locked: true // 默认锁定
};
});
});
// 将需要编辑的单元格解锁,比如数据区域的单元格
for (let row = 2; row <= 4; row++) {
for (let col = 1; col <= 3; col++) {
const cell = worksheet.getCell(row, col);
cell.protection = {
locked: false // 解锁,允许编辑
};
}
}
// 启用工作表保护
await worksheet.protect('mypassword', {
selectLockedCells: true,
selectUnlockedCells: true,
formatCells: false,
formatColumns: false,
formatRows: false
});这样一来,用户打开Excel文件后,只能在指定的数据区域内进行编辑,而表头和其他固定内容则受到保护,不会因为误操作而被修改。
五、完整的导出实现方案
整合样式与编辑功能的完整代码
下面是一个整合了样式设置、单元格合并和工作表保护的完整示例:
async function exportStyledExcel(data) {
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet('员工信息');
// 设置列
worksheet.columns = [
{ header: '姓名', key: 'name', width: 15 },
{ header: '年龄', key: 'age', width: 10 },
{ header: '职位', key: 'position', width: 20 }
];
// 设置表头样式
const headerRow = worksheet.getRow(1);
headerRow.height = 30;
headerRow.eachCell((cell) => {
cell.font = { name: '微软雅黑', size: 11, bold: true, color: { argb: 'FFFFFFFF' } };
cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF4472C4' } };
cell.alignment = { vertical: 'middle', horizontal: 'center' };
cell.border = {
top: { style: 'thin' },
left: { style: 'thin' },
bottom: { style: 'thin' },
right: { style: 'thin' }
};
});
// 添加数据行
data.forEach((item) => {
const row = worksheet.addRow(item);
row.eachCell((cell) => {
cell.alignment = { vertical: 'middle', horizontal: 'center' };
cell.border = {
top: { style: 'thin' },
left: { style: 'thin' },
bottom: { style: 'thin' },
right: { style: 'thin' }
};
});
});
// 解锁数据区域,允许编辑
for (let row = 2; row <= data.length + 1; row++) {
for (let col = 1; col <= 3; col++) {
const cell = worksheet.getCell(row, col);
cell.protection = { locked: false };
}
}
// 保护工作表
await worksheet.protect('password123', {
selectLockedCells: true,
selectUnlockedCells: true
});
// 生成文件并触发下载
const buffer = await workbook.xlsx.writeBuffer();
const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = '员工信息表.xlsx';
a.click();
window.URL.revokeObjectURL(url);
}性能优化与注意事项
当处理大量数据时,exceljs的性能表现依然不错,但还是有一些优化技巧值得注意。比如,尽量避免逐行逐单元格地设置样式,而是使用列样式或行样式批量应用。另外,在循环中添加数据时,可以考虑使用commitEachRow方法来减少内存占用。
还有一点需要注意的是,exceljs生成的Excel文件兼容性很好,支持Office 2007及以上版本,同时也支持WPS等国产办公软件。不过在使用一些高级功能时,最好在目标环境中做一下测试,确保最终效果符合预期。
结语
前端导出Excel表格看似简单,但要真正做到样式精美、功能完善,还需要选择合适的工具和方法。exceljs作为一个功能全面的Excel处理库,不仅解决了样式定制的难题,还提供了单元格编辑、工作表保护等实用功能,能够满足绝大多数业务场景的需求。希望本文的讲解能帮助你更好地掌握前端Excel导出的技巧,让你的项目更加专业和易用。