js使用docxtemplater生成word

前端通过javaScript生成word文档,看这一篇文章就够了

# 一、成果展示

下载模板文件tag-example.docx

# 二、安装依赖

npm install docxtemplater docxtemplater-image-module-free pizzip file-saver -S
1

说明:由于 docxtemplater 免费版生成的word不支持图片插入,所以引入社区开源免费的 docxtemplater-image-module-free 进行补充图片无法插入的问题

# 三、源码实现

创建文件 renderDoc.js

import Docxtemplater from "docxtemplater";
import PizZip from "pizzip";
import PizZipUtils from "pizzip/utils/index.js";
import {saveAs} from "file-saver";
import ImageModule from 'docxtemplater-image-module-free'

function loadFile(url, callback) {
  PizZipUtils.getBinaryContent(url, callback);
}

const imageOptions = {
  centered: false,
  getImage(url) {
    return new Promise(function (resolve, reject) {
      PizZipUtils.getBinaryContent(
        url,
        function (error, content) {
          if (error) {
            return reject(error);
          }
          return resolve(content);
        }
      );
    });
  },
  getSize(img, url, tagName) {
    return new Promise(function (resolve, reject) {
      const image = new Image();
      image.src = url;
      image.onload = function () {
        switch (tagName) {
          // img
          case 'img':
            resolve([100, 100 * image.height / image.width]);
            break;
          default:
            resolve([image.width, image.height]);
        }
      };
      image.onerror = function (e) {
        console.log(
          "img, url, tagName : ",
          img,
          url,
          tagName
        );
        alert(
          "An error occured while loading " +
          url
        );
        reject(e);
      };
    });
  },
};

/**
 * 生成doc文档
 * @param templateUrl 文档模板
 * @param data 模板中的数据
 * @param fileName 导出的文件名称
 */
export function renderDoc({templateUrl = '/docx/tag-example.docx', data, fileName = 'output'}) {
  let url = templateUrl

  loadFile(url, function (
    error,
    content
  ) {
    if (error) {
      throw error;
    }
    const zip = new PizZip(content);
    const doc = new Docxtemplater(zip, {
      paragraphLoop: true,
      linebreaks: true,
      modules: [new ImageModule(imageOptions)]
    });

    try {
      // render the document (replace all occurences of {first_name} by John, {last_name} by Doe, ...)
      // doc.render();
      doc.renderAsync(data).then(() => {
        const out = doc.getZip().generate({
          type: "blob",
          mimeType:
            "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
        });
        // Output the document using Data-URI
        saveAs(out, `${fileName}.docx`);
      });
    } catch (error) {
      // The error thrown here contains additional information when logged with JSON.stringify (it contains a properties object containing all suberrors).
      function replaceErrors(key, value) {
        if (value instanceof Error) {
          return Object.getOwnPropertyNames(value).reduce(function (
              error,
              key
            ) {
              error[key] = value[key];
              return error;
            },
            {});
        }
        return value;
      }

      console.log(JSON.stringify({error: error}, replaceErrors));

      if (error.properties && error.properties.errors instanceof Array) {
        const errorMessages = error.properties.errors
          .map(function (error) {
            return error.properties.explanation;
          })
          .join("\n");
        console.log("errorMessages", errorMessages);
        // errorMessages is a humanly readable message looking like this :
        // 'The tag beginning with "foobar" is unopened'
      }
      throw error;
    }

  });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124

# 四、如何使用

import {renderDoc} from 'renderDoc.js'
const data = {
  last_name: '小',
  first_name: '明',
  img: '/sponsor-qrcode/qrcode-alipay.png'
}
renderDoc({
  templateUrl: '',
  data,
  fileName: 'output'
})
1
2
3
4
5
6
7
8
9
10
11

# 五、常用语法

# 1、基础文本

  • docx
Hello {name} !
1
  • data
{
    name: "John";
}
1
2
3
  • 渲染结果
Hello John !
1

# 2、图片渲染

{%img}
1
  • data
{
    "img": "/images/1.png"
}
1
2
3

# 3、条件渲染

  • docx
{#hasKitty}Cat’s name: {kitty}{/hasKitty}
{#hasDog}Dog’s name: {dog}{/hasDog}
1
2
  • data
{
    "hasKitty": true,
    "kitty": "Minie"
    "hasDog": false,
    "dog": null
}
1
2
3
4
5
6
  • 渲染结果
Cat’s name: Minie
1

else条件渲染

  • docx
{#repo}
Repo name : {name}
{/repo}
{^repo}
No repos :(
{/repo}
1
2
3
4
5
6
  • data
{
  "repo": []
}
1
2
3
  • 渲染结果
No repos :(
1

# 4、循环渲染

  • docx
{#products}
{name}, {price} $
{/products}
1
2
3
  • data
{
    "products": [
        { name: "Windows", price: 100 },
        { name: "Mac OSX", price: 200 },
        { name: "Ubuntu", price: 0 }
    ]
}
1
2
3
4
5
6
7
  • 渲染结果
Windows, 100 $
Mac OSX, 200 $
Ubuntu, 0 $
1
2
3

或者

  • docx
{#products} {.} {/products}
1
  • data
{
   "products": [
       "Windows",
       "Mac OSX",
       "Ubuntu"
   ]
}
1
2
3
4
5
6
7
  • 渲染结果
Windows Mac OSX Ubuntu
1

# 5、多行表格循环渲染

  • docx
Name Age Phone Number
{#users}{name} {age} {phone}{/}
  • data
{
    users: [
        { name: "John", age: 22, phone: "+33653454343" },
        { name: "Mary", age: 25, phone: "+33666666666" },
    ],
}
1
2
3
4
5
6
  • 渲染结果
Name Age Phone Number
John 22 +33653454343
Mary 25 +33666666666

更多模板语法请查阅官网文档:https://docxtemplater.com/docs/tag-types/ (opens new window)

# 六、注意事项

如果代码、标签和数据都没有写错,但是就是渲染不出来,请将标签换行。

有可能出现的问题:

{#hasImg} {%img} {/hasImg}
1

如果以上模板图片无法渲染,请将标签换行再次尝试,如:

{#hasImg} 
    {%img}
{/hasImg}
1
2
3