前端通过javaScript生成word文档,看这一篇文章就够了
# 一、成果展示
# 二、安装依赖
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
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
2
3
4
5
6
7
8
9
10
11
# 五、常用语法
# 1、基础文本
- docx
Hello {name} !
1
- data
{
name: "John";
}
1
2
3
2
3
- 渲染结果
Hello John !
1
# 2、图片渲染
{%img}
1
- data
{
"img": "/images/1.png"
}
1
2
3
2
3
# 3、条件渲染
- docx
{#hasKitty}Cat’s name: {kitty}{/hasKitty}
{#hasDog}Dog’s name: {dog}{/hasDog}
1
2
2
- data
{
"hasKitty": true,
"kitty": "Minie"
"hasDog": false,
"dog": null
}
1
2
3
4
5
6
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
2
3
4
5
6
- data
{
"repo": []
}
1
2
3
2
3
- 渲染结果
No repos :(
1
# 4、循环渲染
- docx
{#products}
{name}, {price} $
{/products}
1
2
3
2
3
- data
{
"products": [
{ name: "Windows", price: 100 },
{ name: "Mac OSX", price: 200 },
{ name: "Ubuntu", price: 0 }
]
}
1
2
3
4
5
6
7
2
3
4
5
6
7
- 渲染结果
Windows, 100 $
Mac OSX, 200 $
Ubuntu, 0 $
1
2
3
2
3
或者
- docx
{#products} {.} {/products}
1
- data
{
"products": [
"Windows",
"Mac OSX",
"Ubuntu"
]
}
1
2
3
4
5
6
7
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
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
2
3