Excel导出添加水印

352 阅读4分钟

所需依赖库

  • 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 接口,使得在项目中集成变得非常简单。

实现方案

  1. 获取 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];   
      }
    
  2. 使用 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);
      }
    
  3. 下载文件

        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`);
    };