前端javascript在线生成excel,word模板-通用场景(免费)

发布于:2025-08-29 ⋅ 阅读:(23) ⋅ 点赞:(0)


在前端开发过程中不免会在线生成excel,word文件等功能,涉及到根据上传的实际模板或在线模板来进行内容的填写生成,故此会对下面的内容进行封装

一、Exceljs+FileSaver生成excel模板(带表格循环内容)

模板格式内容: ${占位符}

// 当页面渲染完毕后马上调用下面的函数,这个函数是在当前页面 - 设置 - 生命周期 - 页面加载完成时中被关联的。
export function didMount() {
  console.log(`「页面 JS」:当前页面地址 ${location.href}`);
  this.utils.loadScript('https://cdn.bootcdn.net/ajax/libs/exceljs/4.4.0/exceljs.min.js');
  this.utils.loadScript('https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js');
}
//示例
export function exportExcel() {
  const templateUrl = ''; // 在线模板 URL
  const sheetName = "Sheet1"
  const variables = {
    code: "销售报表",
    address: "广东省深圳市....",
    numberField_mete69xp: 506,
    amount: 666
  };
  const tableData = [
    { index: 1, brand: "手机", objectID: "12312SDF", name: '华为手机', model: "mate40pro", unit: "部", number: 10 },
    { index: 2, brand: "电脑", objectID: "sdfs123123", name: '联想笔记本', model: "Air15", unit: "台", number: 20 },
    { index: 3, brand: "游戏机", objectID: "A12345", name: 'PS5', model: "ps5pro", unit: "台", number: 5 }
  ];
  const startRow = 11
  const fileName = "模板内容.xlsx"
  generateExcel(templateUrl, sheetName, variables, tableData, startRow, fileName)
}

/**
 * templateUrl: 模板的url下载地址 模板 URL
 * sheetName: 需要填充的sheet页名称,默认值Sheet1
 * variables: 普通占位符的字典数据
 *  {
      code: "销售报表",
      address: "广东省深圳市福围街道方图集团大厦",
    }
    这里的code对象excel的模板格式 ${code}  ,address对于模板的 ${address}
   tableData: 循环表格迭代数据,用于填充excel表中存在多条表格数据的场景
    [
      { index: 1, brand: "手机", objectID: "12312SDF", name: '华为手机', model: "mate40pro", unit: "部", number: 10 },
      { index: 2, brand: "电脑", objectID: "sdfs123123", name: '联想笔记本', model: "Air15", unit: "台", number: 20 },
      { index: 3, brand: "游戏机", objectID: "A12345", name: 'PS5', model: "ps5pro", unit: "台", number: 5 }
    ]
    对于excel模板的 ${index},${brand}...  excel模板中只需要设置一行数据即可. 注意事项: 这边传进来的{}字典数据顺序表格的行行数据需要按照填写内容的行位置依次填入,顺序不能够错乱.
    startRow: 表格写入的行位置,用于js判断在多少行才写入表格数据
    fileName: 生成的文件名
 */
async function generateExcel(templateUrl, sheetName = "Sheet1", variables, tableData, startRow, fileName) {
  const response = await fetch(templateUrl);
  const arrayBuffer = await response.arrayBuffer();

  const workbook = new ExcelJS.Workbook();
  await workbook.xlsx.load(arrayBuffer);

  const ws = workbook.getWorksheet(sheetName);

  // --- 1. 替换普通占位符 ---
  ws.eachRow(row => {
    row.eachCell(cell => {
      if (cell.value && typeof cell.value === 'string') {
        Object.keys(variables).forEach(key => {
          cell.value = cell.value.replace(`\${${key}}`, variables[key]);
        });
      }
    });
  });

  // --- 2.循环表格数据 ---
  // --- 保存合并单元格 ---
  const merges = Array.isArray(ws._merges)
    ? ws._merges
    : ws._merges instanceof Map
      ? [...ws._merges.values()]
      : Object.values(ws._merges);

  const templateRow = ws.getRow(startRow);
  // --- 插入空行 ---
  if (!tableData || tableData.length === 0) return;
  if (tableData.length > 1) {
    ws.spliceRows(startRow + 1, 0, ...Array(tableData.length - 1).fill([]));
  }

  // --- 填充数据行并复制样式 ---
  // 动态生成列字段(取第一个对象的 key 顺序)
  const columns = Object.keys(tableData[0]);

  tableData.forEach((item, i) => {
    const rowNumber = startRow + i;
    const row = ws.getRow(rowNumber);

    // 复制模板行样式
    templateRow.eachCell({ includeEmpty: true }, (cell, colNumber) => {
      const targetCell = row.getCell(colNumber);
      targetCell.style = { ...cell.style };
    });

    // 动态填充数据
    columns.forEach((field, colIndex) => {
      row.getCell(colIndex + 1).value = item[field] || "";
    });

    row.commit();
  });



  // --- 重新应用合并单元格 ---
  // 先取消原有合并
  Object.values(ws._merges).forEach((merge) => {
    ws.unMergeCells(merge.top, merge.left, merge.bottom, merge.right);
  });
  // 然后重新应用合并单元格
  merges.forEach((merge) => {
    const offset = tableData.length - 1; // 插入的数据行数
    if (merge.top >= startRow) {
      ws.mergeCells(
        merge.top + offset,
        merge.left,
        merge.bottom + offset,
        merge.right
      );
    } else {
      ws.mergeCells(merge.top, merge.left, merge.bottom, merge.right);
    }
  });

  // --- 3. 输出并下载 ---
  const buffer = await workbook.xlsx.writeBuffer();
  const blob = new Blob([buffer], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" });
  saveAs(blob, fileName);
}

注意事项: 如果是填充表格数据, 这边传进来的表格数据的{}字典数据顺序要跟实际excel的表格的行行数据列位置一致,需要按照填写内容的行位置依次填入,顺序不能够错乱.

二、exceljstemplate模板(源码)-可以填充图片,多sheet页,自适应(通用)

基于 exceljs 库的 .xlsx 模板文件填充引擎。理论上支持 exceljs 库的所有 api

  • 普通标签占位符格式:{{xxx}}{{xxx.xxx}}
  • 迭代标签占位符格式:{{@@xxx.xxx}}

项目地址:GithubGitee

支持浏览器和 node.js 环境下使用。可参考 test 目录下的 test.htmltest.js

// 当页面渲染完毕后马上调用下面的函数,这个函数是在当前页面 - 设置 - 生命周期 - 页面加载完成时中被关联的。
export function didMount() {
  console.log(`「页面 JS」:当前页面地址 ${location.href}`);
  this.utils.loadScript('https://cdn.bootcdn.net/ajax/libs/exceljs/4.4.0/exceljs.min.js');
}

// 判断是否浏览器环境
const isBrowser = typeof window !== "undefined" && typeof document !== "undefined";

/** 获取 URL 文件 */
export async function fetchUrlFile(url = '') {
  if (isBrowser) {
    const response = await fetch(url);
    if (!response.ok) throw new Error(`Failed to fetch ${url}, status: ${response.status}`);
    return response.arrayBuffer();
  } else {
    const { get } = /^https:\/\//.test(url) ? require("https") : require("http");
    return new Promise((resolve, reject) => {
      get(url, (res) => {
        if (res.statusCode !== 200) reject(new Error(`Failed to fetch ${url}, status: ${res.statusCode}`));
        const chunks = [];
        res.on("data", (chunk) => chunks.push(chunk));
        res.on("end", () => resolve(Buffer.concat(chunks)));
      }).on("error", reject);
    });
  }
}

/** 加载工作簿 */
export async function loadWorkbook(input) {
  const workbook = new ExcelJS.Workbook(); // 创建工作簿
  const httpRegex = /^https?:\/\//;

  if (isBrowser) {
    if (typeof input === "string" && httpRegex.test(input)) {
      const arrayBuffer = await this.fetchUrlFile(input);
      await workbook.xlsx.load(arrayBuffer);
    } else if (input instanceof Blob || input instanceof ArrayBuffer) {
      await workbook.xlsx.load(input);
    } else {
      throw new Error("Unsupported input type in browser environment.");
    }
  } else {
    if (typeof input === "string") {
      if (httpRegex.test(input)) {
        const buffer = await this.fetchUrlFile(input);
        await workbook.xlsx.load(buffer);
      } else {
        await workbook.xlsx.readFile(input);
      }
    } else if (input instanceof Buffer || input instanceof ArrayBuffer) {
      await workbook.xlsx.load(input);
    } else if (typeof (input).pipe === "function") {
      await workbook.xlsx.read(input);
    } else {
      throw new Error("Unsupported input type in Node.js environment.");
    }
  }

  return workbook;
}

/** 保存工作簿 */
export async function saveWorkbook(workbook, output) {
  if (isBrowser) {
    const buffer = await workbook.xlsx.writeBuffer();
    const blob = new Blob([buffer], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" });
    const link = document.createElement("a");
    link.href = URL.createObjectURL(blob);
    link.download = output;
    link.click();
    URL.revokeObjectURL(link.href);
  } else {
    await workbook.xlsx.writeFile(output);
  }
}

/** 占位符范围 */
function placeholderRange(
  worksheet,
  placeholder = "{{#placeholder}}",
  clearMatch = true
) {
  console.log("worksheet", worksheet)
  console.log("placeholder", placeholder)
  let result = null;
  worksheet.eachRow((row, rowNumber) => {
    row.eachCell((cell, colNumber) => {
      if (typeof cell.value === "string" && cell.value.includes(placeholder)) {
        result = { start: { row: rowNumber, col: colNumber }, end: { row: rowNumber, col: colNumber } };
        if (clearMatch) cell.value = cell.value.replace(new RegExp(placeholder, "g"), "");
      }
    });
  });
  return result;
}

/**
 * 处理单元格标签(普通标签替换和迭代标签信息收集)
 * @param {string} target 单元格值
 * @param {Record<string, any>} worksheetData 工作表数据
 * @param {Array<{iterStartRow: number; iterFieldNames: string[]; iterFieldName: string}>} iterationTags 迭代标签信息
 * @param {string[]} iterFieldNames 行迭代字段
 * @param {number} rowNumber 行号
 * @returns {string} 处理后的单元格值
 */
export function processCellTags(target, worksheetData, iterationTags, iterFieldNames, rowNumber) {

  // 匹配所有 {{xxx.xxx}} 占位符
  const regex = /{{\s*[\w.]+\s*}}/g;
  if (regex.test(target)){

    const placeholders = target.match(regex);

    if (!placeholders) return target;

    placeholders.forEach((placeholder) => {
      // 去掉 {{ }} 两侧空格
      const keyPath = placeholder.replace(/{{\s*|\s*}}/g, '');
      const fields = keyPath.split('.');

      let value = worksheetData;
      let isMatched = true;

      for (let i = 0; i < fields.length; i++) {
        if (value && fields[i] in value) {
          value = value[fields[i]];
        } else {
          isMatched = false;
          break;
        }
      }

      // 替换占位符
      if (isMatched) {
        // 如果是完整单元格且 value 不是对象,直接替换为纯值
        if (target.trim() === placeholder.trim() && typeof value !== 'object') {
          target = value;
        } else {
          // 多占位符或部分替换
          target = target.split(placeholder).join(value || '');
        }
      }
    });
  }
  // 迭代标签信息搜集
  else if (/{{@@\w+\.\w+}}/.test(target)) {
    // 单元格中仅匹配一个迭代标签占位符
    // TODO 单元格中含多个不同的迭代标签
    const iterFieldName = target.match(/{{@@(\w+)\.\w+}}/)[1];
    // 数据存在且为数组类型
    if (
      iterFieldName in worksheetData &&
      Array.isArray(worksheetData[iterFieldName]) &&
      worksheetData[iterFieldName].length > 0
    ) {
      if (iterFieldNames.length === 0) {
        iterFieldNames.push(iterFieldName);
        iterationTags.push({ iterStartRow: rowNumber, iterFieldNames: iterFieldNames, iterFieldName: iterFieldName });
      } else {
        if (!iterFieldNames.includes(iterFieldName)) {
          iterFieldNames.push(iterFieldName);
          // 迭代标签字段长度不一致,取最长的(后续按最大长度复制行)
          const lastIterationTag = iterationTags[iterationTags.length - 1];
          if (worksheetData[iterFieldName].length > worksheetData[lastIterationTag.iterFieldName].length) {
            lastIterationTag.iterFieldName = iterFieldName;
          }
        }
      }
    }
  }
  console.log("target", target)
  return target;
}



/**
 * 获取工作表合并信息
 * @param {ExcelJS.Worksheet} worksheet
 * @returns {Array<{start: {row: number; col: number}; end: {row: number; col: number}}>}
 */
export function sheetMergeInfo(worksheet) {
  return worksheet.model.merges.map((merge) => {
    // C30:D30
    const [startCell, endCell] = merge.split(":");
    return {
      start: { row: worksheet.getCell(startCell).row, col: worksheet.getCell(startCell).col },
      end: { row: worksheet.getCell(endCell).row, col: worksheet.getCell(endCell).col },
    };
  });
}

/**
 * 填充Excel模板
 * @param {ExcelJS.Workbook} workbook
 * @param {Array<Record<string, any>>} workbookData - 包含模板数据的数组对象
 * @param {boolean} parseImage - 是否解析图片,默认为 false
 * @returns {Promise<ExcelJS.Workbook>}
 */
export async function fillTemplate(workbook, workbookData, parseImage = false) {
  // 第一步:复制行并替换占位符
  // 工作表待合并单元格信息
  const sheetDynamicMerges = {};
  // NOTE 工作簿的sheetId是按工作表创建的顺序从1开始递增
  let sheetIndex = 0;
  workbook.eachSheet((worksheet, sheetId) => {
    const sheetData = workbookData[sheetIndex++];
    if (!(sheetData && typeof sheetData === "object" && !Array.isArray(sheetData))) {
      return;
    }
    // 普通标签替换和迭代标签信息收集
    /** @type {Array<{iterStartRow: number; iterFieldNames: string[]; iterFieldName: string}>} */
    const sheetIterTags = [];
    worksheet.eachRow((row, rowNumber) => {
      // 行迭代字段
      const rowIterFields = [];
      row.eachCell((cell, colNumber) => {
        const cellType = cell.type;
        // 字符串值
        // || ExcelJS.ValueType.SharedString
        if (cellType === ExcelJS.ValueType.String) {
          cell.value = this.processCellTags(cell.value, sheetData, sheetIterTags, rowIterFields, rowNumber);
        }
        // 富文本值  && cell.value.richText
        else if (cellType === ExcelJS.ValueType.RichText) {
          cell.value.richText.forEach((item) => {
            item.text = this.processCellTags(item.text, sheetData, sheetIterTags, rowIterFields, rowNumber);
          });
        }
      });
    });
    // 迭代标签处理
    if (sheetIterTags.length === 0) {
      return;
    }
    // 合并单元格信息
    // NOTE 合并信息是静态的,不会随着行增加而实时更新
    const sheetMerges = this.sheetMergeInfo(worksheet);
    // 迭代行并替换迭代字段占位符
    let iterOffset = 0;
    sheetIterTags.forEach(({ iterStartRow, iterFieldNames, iterFieldName }, iterTagIndex) => {
      // 调整后的起始行
      const adjustedStartRow = iterStartRow + iterOffset;
      const iterDataLength = sheetData[iterFieldName].length;
      // 多行的情况下,需要复制多行
      if (iterDataLength > 1) {
        // 一次性复制多行
        // NOTE 复制的行不会复制合并信息
        worksheet.duplicateRow(adjustedStartRow, iterDataLength - 1, true);
        // 筛选出与当前模板行相关的合并单元格信息,并应用到其复制的行
        const merges = sheetMerges.filter((merge) => {
          return merge.start.row <= iterStartRow && merge.end.row >= iterStartRow;
        });
        if (merges.length > 0) {
          if (!sheetDynamicMerges[sheetId]) {
            sheetDynamicMerges[sheetId] = [];
          }
          // NOTE 在浏览器环境,动态增加的行会使其后面的行取消合并单元格
          const startFixIndex = isBrowser ? (iterTagIndex === 0 ? 1 : 0) : 1;
          for (let i = startFixIndex; i < iterDataLength; i++) {
            for (const merge of merges) {
              sheetDynamicMerges[sheetId].push([
                merge.start.row + i + iterOffset,
                merge.start.col,
                merge.end.row + i + iterOffset,
                merge.end.col,
              ]);
            }
          }
        }
      }
      // 替换迭代行中的占位符
      for (let i = 0; i < iterDataLength; i++) {
        const currentRow = worksheet.getRow(adjustedStartRow + i);
        // 遍历当前行的单元格
        currentRow.eachCell((cell, colNumber) => {
          // 字符串值
          if (cell.type === ExcelJS.ValueType.String) {
            if (typeof cell.value === "string") {
              // 遍历单元格中的多个迭代字段
              iterFieldNames.forEach((iterField) => {
                // 单元格中包含当前迭代字段的占位符
                if (cell.value.includes(`{{@@${iterField}\.`)) {
                  // 当前迭代字段索引数据
                  const currentIterFieldData = sheetData[iterField][i];
                  if (currentIterFieldData !== undefined) {
                    // 迭代字段数据
                    for (const field in currentIterFieldData) {
                      const placeholder = `{{@@${iterField}.${field}}}`;
                      if (cell.value.includes(placeholder)) {
                        // 完全匹配,替换单元格内容为迭代字段数据
                        if (cell.value.length === placeholder.length && typeof currentIterFieldData[field] !== "object") {
                          cell.value = currentIterFieldData[field];
                          break;
                        }
                        // 包含其他内容,部分替换为迭代字段数据
                        else {
                          cell.value = cell.value.replace(
                            new RegExp(placeholder, "g"),
                            currentIterFieldData[field] || ""
                          );
                        }
                      }
                    }
                  } else {
                    // 清空单元格内容
                    cell.value = null;
                  }
                }
              });
            }

          }
          // TODO 迭代标签单元格为富文本值
        });
      }
      // 更新行号偏移量
      iterOffset += iterDataLength - 1;
    });
    // 修正在浏览器环境,动态增加的行会使其后面的行取消合并单元格
    if (isBrowser) {
      const iterRows = sheetIterTags.map(({ iterStartRow }) => iterStartRow);
      sheetMerges.forEach((merge) => {
        // 迭代后的偏移行
        let mergeOffset = 0;
        sheetIterTags.forEach(({ iterStartRow, iterFieldName }) => {
          if (Array.isArray(sheetData[iterFieldName])) {
            if (!iterRows.includes(merge.start.row) && merge.start.row > iterStartRow) {
              mergeOffset += sheetData[iterFieldName].length - 1;
            }
          }
        });
        if (mergeOffset) {
          if (!sheetDynamicMerges[sheetId]) {
            sheetDynamicMerges[sheetId] = [];
          }
          sheetDynamicMerges[sheetId].push([
            merge.start.row + mergeOffset,
            merge.start.col,
            merge.end.row + mergeOffset,
            merge.end.col,
          ]);
        }
      });
    }
  });

  // 第二步:动态行单元格合并处理
  if (Object.keys(sheetDynamicMerges).length > 0) {
    // 将工作簿保存到内存中的缓冲区
    const buffer = await workbook.xlsx.writeBuffer();
    // 从缓冲区重新加载工作簿
    await workbook.xlsx.load(buffer);
    // 处理合并单元格
    workbook.eachSheet((worksheet, sheetId) => {
      if (sheetDynamicMerges[sheetId]) {
        sheetDynamicMerges[sheetId].forEach((merge) => {
          try {
            worksheet.mergeCells(merge);
          } catch (error) {
            console.warn(`Fail to merge cells ${merge}`);
          }
        });
      }
    });
  }

  // 第三步:填充图片
  parseImage && (await this.fillImage(workbook));

  return workbook;
}

/**
 * 填充图片
 * @param {ExcelJS.Workbook} workbook
 */
export async function fillImage(workbook) {
  const filledImageMap = new Map();
  const invalidImageSet = new Set();
  const urlRegex = /https?:\/\/[^\s]+(?:\.(jpe?g|png|gif))?/i;
  const base64Regex = /data:image\/(jpeg|gif|png);base64,[^\s]+/i;
  // NOTE eachSheet、eachRow、eachCell都是同步方法,不会等待异步操作完成
  // 遍历每个工作表
  for (let i = 0; i < workbook.worksheets.length; i++) {
    const worksheet = workbook.worksheets[i];
    const sheetMerges = sheetMergeInfo(worksheet);
    // 遍历每一行
    for (let rowNumber = 1; rowNumber <= worksheet.rowCount; rowNumber++) {
      const row = worksheet.getRow(rowNumber);
      // 遍历每个单元格
      for (let colNumber = 1; colNumber <= row.cellCount; colNumber++) {
        const cell = row.getCell(colNumber);
        if (typeof cell.value !== "string") {
          continue;
        }
        let targetRegex = null;
        let imageId = 0;
        // URL图片
        if (urlRegex.test(cell.value)) {
          targetRegex = urlRegex;
          const matches = cell.value.match(urlRegex);
          const imageUrl = matches[0];
          const imageExt = matches[1] || "png";
          if (invalidImageSet.has(imageUrl)) {
            continue;
          }
          if (filledImageMap.has(imageUrl)) {
            imageId = filledImageMap.get(imageUrl);
          } else {
            let fileContent = null;

            try {
              fileContent = await this.fetchUrlFile(imageUrl);
            } catch (imgErr) {
              invalidImageSet.add(imageUrl);
              console.warn(`Fail to load image ${imageUrl}`);
              continue;
            }

            // 将图片添加到工作簿中
            imageId = workbook.addImage({
              buffer: fileContent,
              extension: imageExt === "jpg" ? imageExt : "jpeg",
            });
            filledImageMap.set(imageUrl, imageId);
          }
        }
        // Base64图片
        else if (base64Regex.test(cell.value)) {
          targetRegex = base64Regex;
          const matches = cell.value.match(base64Regex);
          const imageData = matches[0];
          const imageExt = matches[1];
          if (filledImageMap.has(imageData)) {
            imageId = filledImageMap.get(imageData);
          } else {
            imageId = workbook.addImage({
              base64: imageData,
              extension: imageExt,
            });
            filledImageMap.set(imageData, imageId);
          }
        }
        if (!targetRegex) {
          continue;
        }
        // 将图片添加到工作表中
        const merge = sheetMerges.find((merge) => {
          return merge.start.row === rowNumber && merge.start.col === colNumber;
        });
        // 坐标系基于零,A1 的左上角将为 {col:0,row:0},右下角为 {col:1,row:1}
        worksheet.addImage(imageId, {
          // 左上角
          tl: {
            col: merge ? merge.start.col - 1 : colNumber - 1,
            row: merge ? merge.start.row - 1 : rowNumber - 1,
          },
          // 右下角
          br: {
            col: merge ? merge.end.col : colNumber,
            row: merge ? merge.end.row : rowNumber,
          },
        });
        // 去除图片地址
        cell.value = cell.value.replace(targetRegex, "");
      }
    }
  }

  return workbook;
}

/**
 * 渲染Xlsx模板
 * @param {string|ArrayBuffer|Blob|Buffer} input - 输入数据,可以是本地路径、URL地址、ArrayBuffer、Blob、Buffer
 * @param {Array<Record<string, any>>} data - 包含模板数据的数组对象
 * @param {string} output - 输出文件路径或文件名
 * @param {{parseImage?: boolean; beforeSave?: (workbook: ExcelJS.Workbook) => void|Promise<void>}} options 配置项
 * @returns {Promise<void>}
 */
export async function renderXlsxTemplate(input = '', data = [], output = '', options = { parseImage: false, beforeSave: undefined }) {
  const workbook = await this.loadWorkbook(input);
  // 填充模板
  await this.fillTemplate(workbook, data, options.parseImage === true);

  if (options.beforeSave) await options.beforeSave(workbook);
  await this.saveWorkbook(workbook, output);
}


/** 点击处理函数
 * 示例
 */
export async function handleXlsxTemplate() {
  const xlsxFile =
    "https://raw.githubusercontent.com/cshaptx4869/exceljs-xlsx-template/refs/heads/main/test/assets/template.xlsx";
  const officialsealFile =
    "https://raw.githubusercontent.com/cshaptx4869/exceljs-xlsx-template/refs/heads/main/test/assets/officialseal.png";
  const imageUrl = "https://s2.loli.net/2025/03/07/ELZY594enrJwF7G.png";

  const data = [
    {
      name: "John",
      items: [
        { no: "No.1", name: "JavaScript" },
        { no: "No.2", name: "CSS" },
        { no: "No.3", name: "HTML" },
        { no: "No.4", name: "Node.js" },
        { no: "No.5", name: "Three.js" },
        { no: "No.6", name: "Vue" },
        { no: "No.7", name: "React" },
        { no: "No.8", name: "Angular" },
        { no: "No.9", name: "UniApp" },
      ],
      projects: [
        { name: "Project 1", description: "Description 1", image: imageUrl },
        { name: "Project 2", description: "Description 2", image: imageUrl },
        { name: "Project 3", description: "Description 3", image: imageUrl },
      ],
    },
    {
      name: "SGW",
      user: {
        first_name: "石国旺",
        last_name: "旺",
      },
      phone: "00874****",
      invoice_date: "15/05/2008",
      invoice_number: "54548",
      items: [
        { name: "description", unit_price: 300 },
        { name: "HTML", unit_price: 400 },
      ],
      subtotal: 700,
      tax: 140,
      grand_total: 840,
    },
  ];

  try {
    await this.renderXlsxTemplate(xlsxFile, data, `template-${Date.now()}.xlsx`, {
      parseImage: true,
      async beforeSave(workbook) {
        const worksheet = workbook.getWorksheet("新报关单");
        if (!worksheet) return;
        const response = await fetch(officialsealFile);
        if (!response.ok) return;
        const buffer = await response.arrayBuffer();
        const imageId = workbook.addImage({ buffer, extension: "png" });
        const range = placeholderRange(worksheet, "{{#officialseal}}");
        if (range) {
          worksheet.addImage(imageId, { tl: { col: range.start.col, row: range.start.row - 4 }, ext: { width: 200, height: 200 } });
        }
      },
    });
  } catch (error) {
    console.error("Error processing Excel file:", error);
  }
}

input

output

三、docx-templates生成word模板(已封装)

在这里插入图片描述

/**
* 尊敬的用户,你好:页面 JS 面板是高阶用法,一般不建议普通用户使用,如需使用,请确定你具备研发背景,能够自我排查问题。当然,你也可以咨询身边的技术顾问或者联系宜搭平台的技术支持获得服务(可能收费)。
* 我们可以用 JS 面板来开发一些定制度高功能,比如:调用阿里云接口用来做图像识别、上报用户使用数据(如加载完成打点)等等。
* 你可以点击面板上方的 「使用帮助」了解。
*/

// 当页面渲染完毕后马上调用下面的函数,这个函数是在当前页面 - 设置 - 生命周期 - 页面加载完成时中被关联的。
export function didMount() {
  console.log(`「页面 JS」:当前页面地址 ${location.href}`);
  // console.log(`「页面 JS」:当前页面 id 参数为 ${this.state.urlParams.id}`);
  // 更多 this 相关 API 请参考:https://www.yuque.com/yida/support/ocmxyv#OCEXd
  // document.title = window.loginUser.userName + ' | 宜搭';
  //创建,编辑,读取zip压缩等文件
  this.utils.loadScript('https://cdn.jsdelivr.net/npm/pizzip@3.2.0/dist/pizzip.min.js');
  //处理word模板
  this.utils.loadScript('https://cdnjs.cloudflare.com/ajax/libs/docxtemplater/3.36.0/docxtemplater.min.js');
  //用于saveAs生成文件
  this.utils.loadScript('https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js');
  //可选,用于读取二进制文件
  this.utils.loadScript('https://cdnjs.cloudflare.com/ajax/libs/jszip-utils/0.1.0/jszip-utils.min.js');
}
//示例
export function generateDoc(){
  //demoUrl代表导出的word文档模板路径,
  let demoUrl = ''; //在线的word的url地址
  //docxData代表模板文档里定义的dept、applyDate 等字段整合给docxData传入即可。
  let docxData = {
    name: "张三",
    address: "北京市海淀区",
    items: [
      { product: "苹果", qty: 3, price: 5 },
      { product: "香蕉", qty: 2, price: 4 },
      { product: "橘子", qty: 5, price: 3 },
    ],
  }
  //fileName代表导出的文件名,方面重命名等操作
  let fileName = "生成word模板文件.docx"
  exportWordDocx(demoUrl, docxData, fileName)
}

/**
 * 封装方法
 * 接收三个入参,demoUrl代表导出的word文档模板路径,docxData代表模板文档里定义的dept、applyDate 等字段整合给docxData传入即可。fileName代表导出的文件名,方面重命名等操作
 * 将demoUrl传入给JSZipUtils.getBinaryContent方法读取模板文件的二进制内容,之后创建一个PizZip实例,内容为模板的内容,再创建并加载docxtemplater实例对象。

  使用doc.setData方法设置模板变量的值,对象的键需要和模板上的变量名一致,值就是你要放在模板上的值。
  这里有一个地方需要注意的是:如果你的定义放在模板上的值为null或者undefined,最后导出来的word文档里,相对应的地方会直接显示undefined。解决方法:doc.setOptions 方法里的nullGetter值返回设置为空即可。
  最后,通过saveAs方法导出Word文档。
 */
export const exportWordDocx = (demoUrl, docxData, fileName) => {
  // 读取并获得模板文件的二进制内容
  JSZipUtils.getBinaryContent(
    demoUrl,
    function (error, content) {
      // 抛出异常
      if (error) {
        throw error;
      }

      // 创建一个PizZip实例,内容为模板的内容
      let zip = new PizZip(content);
      // 创建并加载docxtemplater实例对象
      let doc = new docxtemplater().loadZip(zip);
      // 去除未定义值所显示的undefined
      doc.setOptions({
        nullGetter: function () {
          return "";
        }
      }); // 设置角度解析器
      // 设置模板变量的值,对象的键需要和模板上的变量名一致,值就是你要放在模板上的值

      doc.setData({
        ...docxData,
      });

      try {
        // 用模板变量的值替换所有模板变量
        doc.render();
      } catch (error) {
        // 抛出异常
        let e = {
          message: error.message,
          name: error.name,
          stack: error.stack,
          properties: error.properties,
        };
        console.log(JSON.stringify({ error: e }));
        throw error;
      }

      // 生成一个代表docxtemplater对象的zip文件(不是一个真实的文件,而是在内存中的表示)
      let out = doc.getZip().generate({
        type: "blob",
        mimeType:
          "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
      });
      // 将目标文件对象保存为目标类型的文件,并命名
      saveAs(out, fileName);
    }
  );
}

网站公告

今日签到

点亮在社区的每一天
去签到