Skip to content

Word 模板导出实战 - 前端开发必备技能

上次更新 2024年12月26日星期四 7:32:51 字数 0 字 时长 0 分钟

前言

在日常开发工作中,Word 文档导出是一个非常常见的需求。无论是生成合同、制作报表,还是导出证书,都离不开 Word 导出功能。本文将详细介绍如何使用 docxtemplater 等工具库,轻松实现 Word 模板导出功能。

应用场景

在实际业务开发中,Word 导出的应用场景非常广泛:

  1. 合同文档生成

    • 销售合同
    • 劳动合同
    • 租赁协议等
  2. 报告导出

  • 财务报表
    • 工作总结
    • 数据分析报告
  1. 证书制作
  • 获奖证书
    • 培训证书
    • 资格证明
  1. 公文处理
    • 公文模板
    • 通知文件
    • 会议纪要

技术方案对比

常见的 Word 导出方案对比:

方案优点缺点
docxtemplater- 功能强大
- 支持图片
-
- 只支持.docx 格式 高版本付费使用
mammoth- 支持格式转换
- 使用简单
- 功能相对简单
html-docx- 直接转换 HTML- 样式支持有限

所需依赖

首先需要安装以下依赖包:

shell
npm i --save docxtemplater pizzip jszip-utils file-saver docxtemplater-image-module-free angular-expressions

主要用到的库:

核心代码实现

完整代码实现
javascript
/**
 * 导出word文档(带图片)
 */
import Docxtemplater from "docxtemplater";
import PizZip from "pizzip";
import JSZipUtils from "jszip-utils";
import * as fileSaver from "file-saver";
import * as ImageModule from "docxtemplater-image-module-free";
import expressionParser from "docxtemplater/expressions.js";

/**
 * 将base64格式的数据转为ArrayBuffer
 * @param {Object} dataURL base64格式的数据
 */
function base64Parser(dataURL) {
  const base64Regex = /^data:image\/(png|jpg|jpeg|svg|svg\+xml);base64,/;
  if (!base64Regex.test(dataURL)) {
    return false;
  }
  const stringBase64 = dataURL.replace(base64Regex, "");
  let binaryString;
  if (typeof window !== "undefined") {
    binaryString = window.atob(stringBase64);
  } else {
    binaryString = Buffer.from(stringBase64, "base64").toString("binary");
  }
  const len = binaryString.length;
  const bytes = new Uint8Array(len);
  for (let i = 0; i < len; i++) {
    const ascii = binaryString.charCodeAt(i);
    bytes[i] = ascii;
  }
  return bytes.buffer;
}

export const ExportBriefDataDocx = (
  tempDocxPath,
  data,
  fileName,
  imgSize = {}
) => {
  expressionParser.filters.size = function (input, width, height) {
    return {
      data: input,
      size: [width, height],
    };
  };
  function angularParser(tag) {
    tag = tag
      .replace(/^\.$/, "this")
      .replace(/('|‘)/g, "'")
      .replace(/(“|”)/g, '"');
    const expr = expressions.compile(tag);
    return {
      get: function (scope, context) {
        let obj = {};
        const index = last(context.scopePathItem);
        const scopeList = context.scopeList;
        const num = context.num;
        for (let i = 0, len = num + 1; i < len; i++) {
          obj = assign(obj, scopeList[i]);
        }
        //word模板中使用 $index+1 创建递增序号
        obj = assign(obj, { $index: index });
        return expr(scope, obj);
      },
    };
  }
  JSZipUtils.getBinaryContent(tempDocxPath, (error, content) => {
    console.log("content", content);
    if (error) {
      console.log(error);
    }
    // expressions.filters.size = function (input, width, height) {
    //     return {
    //         data: input,
    //         size: [width, height],
    //     };
    // };
    let imageOptions = {
      //图像是否居中
      centered: true,
      getImage(chartId) {
        //将base64的数据转为ArrayBuffer
        return base64Parser(chartId);
      },
      getSize(img, base64, tagName) {
        // 获取默认图片尺寸
        if (imgSize.hasOwnProperty(tagName)) {
          return imgSize[tagName];
        } else {
          return [200, 200];
        }
      },
    };
    // 创建一个JSZip实例,内容为模板的内容
    const zip = new PizZip(content);
    // 创建并加载 Docxtemplater 实例对象
    let doc = new Docxtemplater();
    doc.attachModule(new ImageModule(imageOptions));
    doc.loadZip(zip);
    doc.setOptions({ parser: expressionParser });
    doc.setData(data);
    try {
      doc.render();
    } catch (error) {
      const e = {
        message: error.message,
        name: error.name,
        stack: error.stack,
        properties: error.properties,
      };
      console.log("err", { error: e });
      throw error;
    }
    // 生成一个代表Docxtemplater对象的zip文件
    const out = doc.getZip().generate({
      type: "blob",
      mimeType:
        "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
    });
    fileSaver.saveAs(out, fileName);
  });
};

/**
 * @description 将图片的url路径转为base64路径
 * @param {string} imgUrl - 图片的url路径
 * @returns {Promise<string>} - 返回base64路径
 */
export function getBase64Sync(imgUrl) {
  return new Promise(function (resolve, reject) {
    let image = new Image();
    image.src = imgUrl;
    image.setAttribute("crossOrigin", "*");
    image.onload = function () {
      let canvas = document.createElement("canvas");
      canvas.width = image.width;
      canvas.height = image.height;
      let context = canvas.getContext("2d");
      context.drawImage(image, 0, 0, image.width, image.height);
      let ext = image.src
        .substring(image.src.lastIndexOf(".") + 1)
        .toLowerCase();
      let quality = 0.8;
      let dataurl = canvas.toDataURL("image/" + ext, quality);
      resolve(dataurl);
    };
  });
}

/**
 * @description 获取图片尺寸
 * @param {string} base64 - 图片的base64路径
 * @returns {Promise<{width: number, height: number}>} - 返回图片尺寸
 */
export function getImageSize(base64) {
  const img = new Image();
  img.src = base64;
  return new Promise((resolve, reject) => {
    img.onload = function () {
      resolve({
        width: img.width,
        height: img.height,
      });
    };
  });
}

使用示例

1. 基础循环示例Loop

循环列表image-20240716172716043

2. 表格处理示例Table

image-20240716172154370

支持以下表格类型:

  • 一维表格
  • 二维表格
  • 立式表格
  • grid 表格

3. 合并单元格示例

image-20240716171620250

4. 条件显示示例

image-20240716172438373

注意事项

  1. 该方案仅支持.docx 格式文件
  2. 模板中不要添加多余空格,可能导致解析失败
  3. 图片处理需要注意内存占用
  4. 大文件处理时建议添加进度提示

小结

本文介绍了使用 docxtemplater 实现 Word 模板导出的完整解决方案。通过合理使用模板语法和相关工具库,我们可以轻松实现各类文档的自动化生成。希望本文对你有所帮助!

参考资料

  1. docxtemplater 官方文档: https://docxtemplater.com/docs/
  2. docxtemplater 在线示例: https://docxtemplater.com/demo/

示例

  1. GitHub 项目地址: demo 相关链接
  2. stackblitz 在线示例

关注公众号