js动态生成表格代码(【实战分享】js生成word(docx),以及将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版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!