用docx-templates生成word文档,只看这一篇就够了!!

6,490 阅读3分钟

背景描述

项目中需要生成word报告给客户。

项目本身以前就在前端生成了html格式的报告,打印之后可以生成PDF文档。所以一开始的方向是调研html转docx的方案,但是先后使用了几个库都不太行,包括Spire.doc这样的收费方案。主要的原因是报告的格式相对来说比较复杂,文字和图片都有,因此转出来的结果很不理想。

在这种情况下开始调研利用模板生成word文档的库,于是就发现了docx-templates这个库。实际用下来文档比较丰富,容易上手,效果令人满意。相比较其他的收费库(如docxtemplater)而言,虽然功能稍微欠缺一点,star数少一些,但是它是MIT license的,而且还在持续维护中。用爱发电,不能要求的太多。

image.png

这个库能干啥?

这个库能做的:

  1. 替换Word模板中的文字
  2. 实现FOR和IF操作
  3. 在文档指定位置插入图片
  4. 在模板里写JavaScript代码(!)
  5. 在文档指定位置插入HTML,并让word尝试转换

不能做的:

  1. 以编程从头开始编写文档;你需要先有一个模板
  2. 嵌套模板,即一个主模板里嵌套了子模板

简单Demo

docx-templates是一个npm库,可以在前端或者node中使用。这里在node中demo,因为实践中也是这样做的。

安装

安装过程很简单,运行npm命令即可:

npm install docx-templates

或者是yarn命令:

yarn add docx-templates

Node用法

package.json中添加"type": "module"

在index.js中添加如下内容

import {createReport} from 'docx-templates';
import fs from 'fs';

const template = fs.readFileSync('myTemplate.docx');

const buffer = await createReport({
  template,
  data: {
    title: 'Test',
    description: 'This is a test!'
  },
  cmdDelimiter: ['<<','>>'],
});

fs.writeFileSync('report.docx', buffer)

docx-templates的主要用法就是使用createReport这个函数。第一个参数是读取的模板,第二个参数是数据,直接放入一个对象,第三个参数则是用来定义模板中包裹数据的分隔符。分隔符默认是+++,这里自定义成<<>>

创建myTemplate.docx,也就是我们要用的模板,添加如下内容:

image.png

运行 node index.js,即可得到填充好的文档:

image.png

怎么样,过程不难吧!

进阶用法

FOR和END-FOR

在报告中,经常会遇到列表类的数据,可以使用FOR和END-FOR的命令来实现展示。

模板中:

<<FOR person IN project.people>>
<<$person.name>>
<<END-FOR person>>

请注意,循环中对当前元素在做操作时需要加上$前缀,就像上面的$person一样。

可以支持嵌套循环

<<FOR company IN companies>>
<<$company.name>>
<<FOR person IN $company.people>>
<<INS $person.firstName>>
<<FOR project IN $person.projects>>
    <<$project.name>>
<<END-FOR project>>
<<END-FOR person>>
<<END-FOR company>>

如果想拿到循环到当前元素的Index,这里支持用$idx来取到这个值,但是只能取到最内层循环的值。

表格也可以用这种方法填充数据:

模板: image.png 数据:

data: {
    title: 'Test',
    description: 'This is a test!',
    items: [
        {
            name: 'A',
            description: 'A is good'
        },
        {
            name: 'B',
            description: 'B is awesome'
        }
    ]
  }

成品: image.png

IF和END-IF

根据IF条件式的值,来有条件的显示包含的内容:

<<IF person.name === 'Alice'>>
<<person.title>>
<<END-IF>>

目前还没有支持else的功能。

插入图片

利用的是IMAGE标签来插入图片,这个标签里面需要放入一个返回image对象的函数。该函数的定义会放在createReportadditionalJsContext里面。

模板示例:

<<IMAGE injectImage(chart.base64, chart.width, chart.height)>>

代码:

additionalJsContext: {
    // width height: cm
    injectImage: (base64: string, width: number, height: number) => {
        return { width: width, height: height, data: base64, extension: '.png' };
    }
}

image对象需要包括四个必需的参数:

  1. width: 图片宽度,单位厘米
  2. height: 图片高度,单位厘米
  3. data: 图片的数据,可以是ArrayBuffer或base64字符串(去掉前缀)
  4. extension:图片的格式,支持'.png''.gif''.jpg''.jpeg''.svg'

Tips:

  1. 插入图片的版式与你Word中默认的粘贴图片的版式一致,我的默认是“嵌入式”。
  2. 如果你的图片尺寸原来是以像素为单位的,那需要进行单位转换,因为Word中的尺寸都是物理的尺寸,与打印的尺寸保持一致的,而像素在屏幕上的大小是与分辨率(DPI)相关的,大小并不绝对。换算关系式:CM = PX / Resolution (DPI) * 2.54。Word默认的DPI是220。
  3. 如果在FOR循环中插入图片,FOR和IMAGE写在同一行的时候才会让图片尽量插入在同一行;否则每张图片都会另起一行。

INS和EXEC运行js代码

INS

INS和=号还有空白是相同的效果,也就是<<name>><<=name>> <<INS name>>效果是相同的。它会插入后面这段js代码的值。

比如在实际应用中,如果有一段Unix的时间戳,我们要根据客户需要在模板中显示特定的时间格式,就可以在additionalJsContext中加入函数

// 此处引入了Day.js库
dateInFormat: (date: number, format: string) => {
    return dayjs(date).format(format);
}

在模板中插入

<<dateInFormat(date, `YYYY-MM-DD`)>>

就可以在Word中显示YYYY-MM-DD格式的时间了。

EXEC

EXEC与INS类似,都能执行js代码,但是区别在于EXEC不会去插入它后面这段js代码的值。因此可以用它来定义常量或者函数。 EXEC也有缩写方式,使用!可以起到同样的效果。

插入HTML

使用docx-templates可以插入HTML,并且让Word将其尝试转换为Word中的格式。这种用法在某些情况下很有用,甚至是唯一的可行方案。例如,如果要动态设定一段文字的颜色,就没法在Word模板中直接做到这一点。这时候就可以用HTML动态指定文字颜色样式。

在word模板中你可以这样直接插入HTML,其中desiredColor就是你传给它的颜色:

<<HTML `
<body>
  <p>
    <strong style="color: ${desiredColor};">This paragraph should be colored and strong</strong>
  </p>
</body>
`>>

你也可以将生成HTML的方法放到Word里面:

<<HTML insertHtml()>>

additionalJsContext中加入函数

insertHtml: ()=>{
    return htmlHelper.getHtml(templateHtml, data)
}

然后就可以用你想用的方法来生成HTML传进去了。

注意几点:

  1. 不要传特别复杂样式的HTML进去,word识别不了的
  2. 经过测试,word只能识别p标签上的样式,识别不了div标签的样式。我猜是p正好对应着word里的段落的概念,能够对的上,div就没有对应的,所以不行
  3. 段前段后用margin,行高用line-spacing设置

总结

docx-templates这个库使用起来还是比较全面的,基本上能够满足大部分生成Word的需求。

文章稍微有点标题党,欢迎互相交流学习!