首页IT科技js动态生成表格代码(【实战分享】js生成word(docx),以及将word转成pdf解决方案分享)

js动态生成表格代码(【实战分享】js生成word(docx),以及将word转成pdf解决方案分享)

时间2025-05-03 13:44:40分类IT科技浏览4532
导读:本文将记录如何用js生成word文件,并在node服务器端将word转换成pdf。记录的代码均是在真实业务场景中使用成功...

本文将记录如何用js生成word文件            ,并在node服务器端将word转换成pdf           。记录的代码均是在真实业务场景中使用成功的代码                 ,没有记录中间踩坑的过程                  。想直接抄答案的家人们可以跳转到1.2 程序编写部分      ,最终效果图可在1.2 程序编写部分中4. 效果展示模块查看      。

如果有更好的解决方案         ,也欢迎大家在评论区讨论           、分享~

本文demo存放地址:github.com/ChicKo1108/…

一                  、DocxTemplater:使用js生成word

老铁们                 ,话不多说        ,先上链接:Docxtemplater | Word, Powerpoint, Excel generation using templates in your application | docxtemplater

DocxTemplater是一个基于模板生成最终文件的插件      ,它通过一个简单的{tag}语法将预设的数据填入模板Word或者Powerpoint中                  ,帮助开发者快速生成最终文件        。

由于DocxTemplater是基于{tag}变量替换           ,得到最终的文件   ,因此文件的样式是非常可控的                 。在开发过程中                  ,设计师只需要出一版最终的成品word              ,开发者将内容替换成对应的{tag}即可(再也不用被设计师追着还原设计稿了!)         。

DocxTemplater是一个收费的库,但是它拥有免费的开源版本               ,对于我所涉及的业务                 ,使用免费版本完全可以解决      。

开源版包括的功能包括:

{tag}替换 条件判断 循环 图片渲染

除免费版外   ,它还拥有pro plan(950€ per\year)      、Entreprise plan(24500€ per\year)            ,具体功能大家可以前往官网查看                 。(但是白嫖的永远是最香的!)

由于我的业务只涉及生成word和pdf                 ,所以本文只介绍word的相关内容      ,如果需要处理ppt         ,大家可前往官网自行学习            。

1.1 模板语法

在了解模板语法之前                 ,我们需要先创建一个tempalte.docx模板文件   。

变量替换

变量使用{key}标签        ,在tempalte.docx文件中输入以下模板:

Hello, my name is {name}.

然后准备以下数据:

let data = { name: "千万", }

最终我们生成的docx文件将会是:

Hello, my name is 千万.

条件判断

条件判断使用{#key}开始      ,使用{/key}结束                  ,最简单的用法是使用Boolean类型数据进行填充                 。

Hello, my name is {name}. {#hasAge}Im {age} years old.{/hasAge} {#hasWeight}My weight is {weight}.{/hasWeight}

然后准备以下数据:

let data = { name: "千万", hasAge: true, age: 23, hasWeight: false, }

最终我们生成的docx文件将会是:

Hello, my name is 千万. Im 23 years old.

除了Boolean类型的数据以外           ,我们也可以填充其他类型:

type 是否显示模块 false / 空数组 不显示 非空数组 显示   ,且将循环渲染内部元素 对象 显示                  ,且使用对象内部变量替换{tag} 其他真值 显示

如果变量填充了数组              ,其实就是我们下面要介绍的循环语法,在下面一小节中再进行介绍               。

在这里简单说一下填充对象的情况:

准备word模板如下:

总价格:{price} {#product} ${productName}: ${price} {/product}

准备数据如下:

let data = { price: 159, product: { productName: pencel, price: 1.2 } }

最终我们生成的docx文件将会是:

总价格:159 pencel:$1.2

循环

循环的标志与条件判断相同               ,但对应的变量应使用Array来填充。

{#examScoreList} {exam}: {score} {/examScoreList}

然后我们填充以下数据:

let data = { examScoreList: [ { exam: 数学, score: 60 }, { exam: 语文, score: 50 }, { exam: 英语, score: 40 }, ], }

最终我们生成的docx文件将会是:

数学: 60 语文: 50 英语: 40 表格循环

值得注意的是                 ,循环不仅仅可以循环一段普通文字   ,我们也可以对表格进行循环            ,包括:循环行和循环整个表              。如果想要循环渲染多个表格                 ,只需要在表格外面使用循环语法即可      ,不在此处过多赘述                  。下面展示循环渲染一个表中的行的写法:

上图中可以看到         ,我在表格的第二行中使用了循环语法进行填写                 ,这样我们最终生成的文档中        ,表头和尾就不会被循环      ,第二行将会被多次渲染                  ,结果如下:

图片

图片使用{%image}进行标注即可           ,对于图片的数据传入需要特殊处理   ,后面的部分会进行介绍   。

总结

根据以上语法                  ,我们就可以准备对应的word模板文件了              ,大部分场景下应该都可以满足           。在准备模板的时候,固定的文案和样式直接保留在文档中即可               ,包括页眉        、页脚                 ,各个段落的行距                 、间距   ,文字的字体         、大小等                  。其他需要根据真实数据渲染的值            ,就用标签标注上      。准备好模板文件以后                 ,就可以开始脚本函数的编写了        。

PS: 要善用表格进行排版布局!

1.2 程序编写

安装所需库

npm install docxtemplater npm install docxtemplater-image-module-free // 图片模块      ,没有图片需求可以不装 npm install pizzip // 处理模板文件用到         ,且只能使用该库

客户端生成

1. 获取模板文件的binaryString

function getFileBinaryString(templateFile) { // templateFile是File对象 return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = (e) => { resolve(e.target.result); } reader.onerror = reject; reader.readAsBinaryString(templateFile); }); }

这里使用到了FileReader类                 ,用于将模板文件转换为binaryString        ,需要注意浏览器的兼容性                 。

如果对兼容性有要求      ,可以是使用pizzip/utils中提供的方法getBinaryContent                  ,但是此库对ts兼容性比较差           ,因此我在实际代码中使用了FileReader         。

import PizZipUtils from "pizzip/utils/index.js"; function loadFile(url, callback) { PizZipUtils.getBinaryContent(url, callback); } 2. 生成最终文件(无需图片) // generate-doxc.js import PizZip from pizzip; import DocxTemplater from docxtemplater; function getFileBinaryString(templateFile) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = (e) => { resolve(e.target.result); } reader.onerror = reject; reader.readAsBinaryString(templateFile); }); } export async function generateDocxFile(template, fileData) { return new Promise((resolve, reject) => { getFileBinaryString(template) .then(templateData => { const zip = new PizZip(templateData); const doc = new DocxTemplater() .loadZip(zip) .render(fileData); // fileData是我们需要定义好   ,传给docxtempale的数据      。 const out = doc.getZip().generate({ type: blob, mimeType: application/vnd.openxmlformats-officedocument.wordprocessingml.document, }); resolve(out); }) .catch(reject); }); } 3. 准备数据                  ,生成最终文件

接下来我们准备一个<input type="file" />的文件输入框(你也可以使用网络请求              ,或者任何方式拿到文件,只要最终获得二进制数据就可以)               ,用来获取模板文件                 。同时准备好相应的数据                 ,来对模板进行填充            。

// App.jsx import { saveAs } from file-saver; import { generateDocxFile } from ./utils/generate-docx; const fileData = { intro: 国际劳动节   ,又称五一国际劳动节      、劳动节                 、国际示威游行日            ,是纪念工人和劳工运动的斗争和成果的日子   。国际劳动节是一项由国际劳工运动所推动的节日                 ,全世界劳工和工人阶级在一般会在五朔节(5月1日)举行的庆祝节日      ,而美国和加拿大在9月第一个星期一举行                 。是世界上80多个国家的劳动节               。, activities: [ { name: 阿尔及利亚, activity: 在阿尔及利亚         ,5月1日是公共假日                 ,以庆祝劳动节。 }, { name: 安哥拉, activity: 5月1日在安哥拉被承认为公共假日        ,称为劳动节              。 }, { name: 埃及, activity: 在埃及      ,5月1日被称为劳动节                  ,是一个带薪的公共假期                  。在传统上           ,埃及总统会主持正式的五一节庆祝活动   。 }, { name: 加纳, activity: 5月1日是加纳的一个节日   ,属于庆祝全国所有工人           。工会和劳工协会以游行的形式来庆祝劳动节                  。加纳也会举行阅兵式                  ,通常由工会大会秘书长和各地区的区域秘书致辞      。来自不同工作地点的工人通过条幅和衣着表明他们的公司        。 } ] } function App() { const handleFileChange = async (e) => { const file = e.target.files[0]; const out = await generateDocxFile(file, fileData); saveAs(out, `${new Date().getTime()}.docx`); } return ( <div className="App"> <input type="file" onChange={handleFileChange} /> </div> ) } export default App; 4. 效果展示

模板文件如下:

生成结果如下:

5. 图片处理

如果需要在模板中使用图片              ,我们需要安装docxtemplater-image-module-free模块                 。

引入了此模块后,需要在加载模板文件后               ,载入image模块                 ,然后异步填入数据         。

// generate-docx.js // 将图片处理为base64   ,给模板使用 function convertImgToBase64(url, outputFormat) { return new Promise((resolve, reject) => { let canvas = document.createElement( CANVAS, ); const ctx = canvas.getContext(2d), img = new Image(); img.crossOrigin = Anonymous; img.onload = function () { canvas.height = img.height; canvas.width = img.width; ctx.drawImage(img, 0, 0); var dataURL = canvas.toDataURL(outputFormat || image/png); canvas = null; resolve(dataURL); }; img.onerror = function (e) { reject(e); }; img.src = url; }); } const imageOpts = { // 图片的配置 centered: false, getImage: function (tagValue, tagName) { // 将图片转成base64 return new Promise((resolve) => { if (typeof tagValue === string && base64Regex.test(tagValue)) { return resolve(tagValue); } else { convertImgToBase64(tagValue).then((base64) => { return resolve(base64Parser(base64)); }); } }); }, // 设置图片宽高            ,可以根据tagName为每一张图片设置不同宽高 getSize: function (img, tagValue, tagName) { // img是图片Buffer                 ,tagValue是图片初始值      ,tagName是图片在模板中定义的标签key值 return [150, 150]; // [宽, 高] } }; export async function generateDocxFile(template, fileData) { return new Promise((resolve, reject) => { getFileBinaryString(template) .then(templateData => { const zip = new PizZip(templateData); const doc = new DocxTemplater() .loadZip(zip) .attachModule(new ImageModule(imageOpts)) // 载入模块 .compile(); // 异步填充数据 doc.resolveData(fileData) .then(() => { doc.render(); const out = doc.getZip().generate({ type: blob, mimeType: application/vnd.openxmlformats-officedocument.wordprocessingml.document, }); docxLists.push({ file: out, fileName }); resolve(); }); }) .catch(reject); }); }

对于有图片的文档生成         ,需要异步载入数据                 ,且图片数据需要处理为base64        ,上述代码给出了处理图片的一种解决方案      ,如果大家有更高效的方法也可以自行使用      。

node服务器端生成

该库同样支持在node中使用                  ,其思想与在浏览器端基本一致           ,在node端可以直接使用buffer   ,下面贴出官方给出的代码示例                 。

const PizZip = require("pizzip"); const Docxtemplater = require("docxtemplater"); const fs = require("fs"); const path = require("path"); // Load the docx file as binary content const content = fs.readFileSync( path.resolve(__dirname, "input.docx"), "binary" ); const zip = new PizZip(content); const doc = new Docxtemplater(zip, { paragraphLoop: true, linebreaks: true, }); // Render the document (Replace {first_name} by John, {last_name} by Doe, ...) doc.render({ first_name: "John", last_name: "Doe", phone: "0652455478", description: "New Website", }); const buf = doc.getZip().generate({ type: "nodebuffer", // compression: DEFLATE adds a compression step. // For a 50MB output document, expect 500ms additional CPU time compression: "DEFLATE", }); // buf is a nodejs Buffer, you can either write it to a file or res.send it with express for example. fs.writeFileSync(path.resolve(__dirname, "output.docx"), buf);

1.3 总结

docxTemplater是一个通过模板文件生成word的库                  ,它能最大程度的保证最终生成的word的样式的完整和还原            。代码搭建好后              ,对于类似的业务,开发者们只需要编写更多的模板文件               ,并且把精力集中在对数据的处理上即可   。

配合e-charts或其他图表库                 ,也可以让我们实现报表文件的生成                 。

此外   ,对于pizzip这个库            ,它本身是对jszip库的一个升级                 ,拥有对zip文件的操作能力      ,可以直接解压或者生成zip包         ,我们可以直接通过此库对批量生成的文件进行打包处理                 ,打包主要用的api如下:

const zip = new pizZip(); zip.file(fileName, fileBuffer); // 生成的文件名 以及 文件的 arrayBuffer zip.generate({ type: blob }), `documents.zip`); // 生成zip文件

二            、使用libre office将word转换成pdf

在进行此部分业务时        ,原本想在前端把所有的工作都做好      ,但是没有找到在客户端就直接转换的方法               。因此                  ,此部分在服务器端进行解决。

首先需要在机器上安装libre office软件           ,具体方法可以自行搜索              。

安装好后   ,项目中安装libreoffice-convert库                  ,这个库对libre office的转换方法进行了封装              ,直接调用其中方法就好:

const path = require(path); const fs = require(fs); const libre = require(libreoffice-convert); async function docx2pdf(docxBuf, outputPath) { libre.convert(docxBuf, .pdf, undefined, (err, outputBuf) => { if (err) { console.log(`Error converting file: ${err}`); } fs.writeFileSync(outputPath, outputBuf); }); } const inputBuf = fs.readFileSync(path.join(__dirname, sample.docx)); let outputPath = path.join(__dirname, sample.pdf); docx2pdf(inputBuf, outputPath);

如果是zip文件,同样可以安装jsZip或者pizZip进行解压   、打包等处理                  。这里更推荐使用jsZip               ,因为文档更加丰富                 ,且对ts支持更好   。

三                 、结束语

这是我第二次遇到此类业务   ,所以本着学习               、记录、分享的心态            ,将内容分享到平台上           。在开发过程中遇到了很多“坑            ”                 ,并没有在本文中记录                  。本文主要还是以记录最终成功的代码为主      ,把内容分享给其他有同样需求的家人们      。毕竟轮子已经这么完善了         ,当然要好好利用啦!

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
antd form shouldupdate(vue3 antd项目实战——Form表单的重置【使用resetFields()重置form表单数据、清空输入框】) 低光照图像增强论文(【论文笔记】—低照度图像增强—Supervised—RetinexNet—2018-BMVC)