所需依赖库
- xlsx
- 通常称为 SheetJS,用于处理Excel文件(.xls和.xlsx格式)。它允许你在客户端(浏览器)和服务器端(Node.js)环境中读取、写入、解析和构建Excel文件。这个库以其功能强大、易于使用和广泛的兼容性而闻名,能够处理各种复杂的Excel文件结构和功能
- 读取和写入Excel文件: xlsx库可以读取和写入.xls(Excel 97-2004)和.xlsx(Excel 2007及以后版本)文件。
- 解析和构建工作簿:它提供了API来解析现有的Excel文件,并允许你修改或构建新的工作簿、工作表、行、列和单元格。
- 公式支持:虽然xlsx库主要关注于数据操作,但它也支持解析和写入Excel公式(尽管在服务器或客户端执行这些公式的能力取决于环境)。
- 样式和格式化:你可以设置单元格的样式,包括字体、颜色、边框、填充等,以及应用数字格式。 图表和图片:虽然xlsx库主要关注于数据,但它也支持读取和写入图表和图片(尽管这些功能可能相对有限)
- 流处理和大型文件:xlsx库支持流处理,这意呀着你可以处理非常大的Excel文件,而不需要一次性将它们全部加载到内存中
- exceljs
- 强大的开源 JavaScript 库,专门用于处理 Excel 文件(特别是 .xlsx 格式)。它可以在 Node.js 环境和浏览器中运行,为开发者提供了丰富的接口来读取、操作和写入 Excel 文件
- 数据读写:支持读取和写入 XLSX 和 JSON 文件格式,方便在不同系统和应用之间交换数据。
- 样式处理:支持多种样式设置,包括字体、对齐、边框、填充等,使得生成的 Excel 文件既美观又实用。
- 数据验证:允许设置数据验证规则,确保用户输入的数据符合预定义的条件。
- 条件格式化:支持根据条件自动格式化单元格,如根据数值大小改变背景色等。
- 表格操作:支持表格的创建、删除、合并等操作,满足复杂的表格处理需求。
- 图片处理:支持在电子表格中插入图片,增强文档的表现力。
- file-saver
- 一个在客户端(浏览器环境)使用的 JavaScript 库,它提供了一种简便的方法来在用户的计算机上保存文件。这个库通过创建一个 Blob 对象和一个指向该对象的 URL(使用 URL.createObjectURL()),然后触发浏览器的下载操作来实现文件的保存。它不需要后端支持,完全在客户端运行,使得在 Web 应用中保存文件变得简单直接
- 保存文件:允许 Web 应用生成文件并触发浏览器的下载提示,让用户可以选择保存文件的位置。
- 支持多种数据类型:可以保存文本文件、图片、JSON 数据等多种类型的文件。
- 易于使用:提供了简单的 API 接口,使得在项目中集成变得非常简单。
实现方案
-
获取 Blob 数据并读取 Excel 文件
首先,将从后端接口获取的 Blob 数据转换为 ArrayBuffer,然后用 SheetJS 读取 Excel 文件
const reader = new FileReader(); reader.readAsArrayBuffer(blob); // 后端返回xlsx的blob数据 reader.onload = async (event: any) => { const arrayBuffer = event.target.result; const _data = new Uint8Array(arrayBuffer); const workbook = read(_data, { type: 'array' }); const sheetName = workbook.SheetNames[0]; const sheet = workbook.Sheets[sheetName]; const endLetter = sheet['!ref']!.split(':')[1][0]; } -
使用 ExcelJS 增加水印
reader.onload = async (event: any) => { const arrayBuffer = event.target.result; const _data = new Uint8Array(arrayBuffer); const workbook = read(_data, { type: 'array' }); const sheetName = workbook.SheetNames[0]; const sheet = workbook.Sheets[sheetName]; const endLetter = sheet['!ref']!.split(':')[1][0]; // 增加水印 const newWorkbook = new ExcelJS.Workbook(); workbook.SheetNames.forEach((_sheetName: any) => { const newWorksheet = newWorkbook.addWorksheet(sheetName); // 读取原工作表数据并写入新工作簿 const worksheet = workbook.Sheets[_sheetName]; const json = utils.sheet_to_json(worksheet, { header: 1 }); json.forEach((row) => { newWorksheet.addRow(row); }); }); // 生成水印 const id = '1.23452384164.123412416'; if (document.getElementById(id) !== null) { document.body.removeChild(document.getElementById(id)!); } const can = document.createElement('canvas'); can.width = 400; can.height = 400; const cans = can.getContext('2d')!; cans?.rotate((-60 * Math.PI) / 220); cans.font = '18px Vedana'; cans.fillStyle = 'rgba(130, 142, 162, 0.2)'; cans.textAlign = 'left'; cans.textBaseline = 'middle'; const repeatX = 3; const repeatY = 4; for (let i = 0; i < repeatX; i++) { for (let j = 0; j < repeatY; j++) { cans?.fillText(`${userInfo.username}_${userInfo.realname}`, i * 400, j * 400); } } const dataURL = can.toDataURL('image/png'); const worksheet = newWorkbook.getWorksheet(1)!; // console.log(worksheet); worksheet.mergeCells(`A1:${endLetter}1`); worksheet.getRow(1).alignment = { horizontal: 'center', vertical: 'middle' }; worksheet.mergeCells(`A2:${endLetter}2`); worksheet.getRow(2).alignment = { horizontal: 'right', vertical: 'middle' }; const imageId = newWorkbook.addImage({ base64: dataURL, extension: 'png', }); worksheet?.addBackgroundImage(imageId); } -
下载文件
const buffer = await newWorkbook.xlsx.writeBuffer(); FileSaver.saveAs(new Blob([buffer], { type: 'application/octet-steam' }), `${name}.xlsx`);
完整代码
async function createWatermark(name: string, data: any) {
const userinfo = {
username: '水印用户名',
realname: '水印用户真实姓名'
}
const reader = new FileReader();
reader.readAsArrayBuffer(data);
reader.onload = async (event: any) => {
// e.target.result ArrayBuffer
const arrayBuffer = event.target.result;
const _data = new Uint8Array(arrayBuffer);
const workbook = read(_data, { type: 'array' });
const sheetName = workbook.SheetNames[0];
const sheet = workbook.Sheets[sheetName];
// console.log(sheet);
const endLetter = sheet['!ref']!.split(':')[1][0];
const newWorkbook = new ExcelJS.Workbook();
workbook.SheetNames.forEach((_sheetName: any) => {
const newWorksheet = newWorkbook.addWorksheet(sheetName);
// 读取原工作表数据并写入新工作簿
const worksheet = workbook.Sheets[_sheetName];
const json = utils.sheet_to_json(worksheet, { header: 1 });
json.forEach((row) => {
newWorksheet.addRow(row);
});
});
// 生成水印
const id = '1.23452384164.123412416';
if (document.getElementById(id) !== null) {
document.body.removeChild(document.getElementById(id)!);
}
const can = document.createElement('canvas');
can.width = 400;
can.height = 400;
const cans = can.getContext('2d')!;
cans?.rotate((-60 * Math.PI) / 220);
cans.font = '18px Vedana';
cans.fillStyle = 'rgba(130, 142, 162, 0.2)';
cans.textAlign = 'left';
cans.textBaseline = 'middle';
const repeatX = 3;
const repeatY = 4;
for (let i = 0; i < repeatX; i++) {
for (let j = 0; j < repeatY; j++) {
cans?.fillText(`${userInfo.username}_${userInfo.realname}`, i * 400, j * 400);
}
}
const dataURL = can.toDataURL('image/png');
const worksheet = newWorkbook.getWorksheet(1)!;
// console.log(worksheet);
worksheet.mergeCells(`A1:${endLetter}1`);
worksheet.getRow(1).alignment = { horizontal: 'center', vertical: 'middle' };
worksheet.mergeCells(`A2:${endLetter}2`);
worksheet.getRow(2).alignment = { horizontal: 'right', vertical: 'middle' };
const imageId = newWorkbook.addImage({
base64: dataURL,
extension: 'png',
});
worksheet?.addBackgroundImage(imageId);
const buffer = await newWorkbook.xlsx.writeBuffer();
FileSaver.saveAs(new Blob([buffer], { type: 'application/octet-steam' }), `${name}.xlsx`);
// writeFile(workbook, `${name}.xlsx`);
};