- A+
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助
很多时候在工作中会碰到完全由前端导出word文件的需求,因此特地记录一下比较常用的几种方式。
一、提供一个word模板
该方法提供一个word模板文件,数据通过参数替换的方式传入word文件中,灵活性较差,适用于简单的文件导出。需要依赖:docxtemplater、file-saver、jszip-utils、pizzip。
import Docxtemplater from "docxtemplater"; import { saveAs } from "file-saver"; import JSZipUtils from "jszip-utils"; import PizZip from "pizzip"; export function downloadWithTemplate(path, data, fileName) { JSZipUtils.getBinaryContent(path, (error, content) => { if (error) throw error; const zip = new PizZip(content); const doc = new Docxtemplater().loadZip(zip); doc.setData({ ...data.form, // 循环项参数 list: data.list, outsideList: data.outsideList, }); try { doc.render(); } catch (error) { const e = { message: error.message, name: error.name, stack: error.stack, properties: error.properties, }; ElMessage.error("文件格式有误!"); throw error; } const out = doc.getZip().generate({ type: "blob", mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", }); saveAs(out, fileName); }); } let data = { form: { title: "这是word标题", test: "这是表单1的数据", test1: "111", test2: 222, test3: 333, }, outsideList: [ { list: [ { index: 0, table: "表格第一项", table1: "表格第二项", table2: "表格第三项", }, { index: 1, table: "表格第一项", table1: "表格第二项", table2: "表格第三项", }, ], }, { list: [ { index: 0, table: "表格第一项", table1: "表格第二项", table2: "表格第三项", }, { index: 1, table: "表格第一项", table1: "表格第二项", table2: "表格第三项", }, ], }, ], }; downloadWithTemplate("template.docx", data, "模板word.docx")
调用downloadWithTemplate方法即可导出如下文件:
注: 上述方法中的path参数为你在vue项目中存放公共文件的位置,在vue2中为static文件夹下,在vue3中为public文件夹下。
二、根据html代码转换为word文件(推荐)
顾名思义,这个方法就是将我们在页面上书写的html代码直接转换成word文件,这也是我最推荐的一种方法,因为大部分的样式可控,且毕竟是我们较为熟悉的方式。需要插件: html-docx-js-typescript、file-saver。
import { saveAs } from "file-saver"; import { asBlob } from "html-docx-js-typescript"; export function downloadWordWithHtmlString(html, name) { let htmlString = ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> ${html} </body> </html> `; asBlob(htmlString).then((data) => { saveAs(data, `${name}.docx`); }); }
使用案例:
<div ref="word"> <h3 style="text-align: center">word标题</h3> <table border="1" cellspacing="0" width="600" style="font-size: 12px; color: #000; text-align: center" > <tr height="50"> <td width="100">1111</td> <td widt="200" colspan="2">合并单元格</td> <td width="300">最长的一项</td> </tr> <tr height="100"> <td width="100">222</td> <td width="100">222</td> <td width="100">222</td> <td width="100">222</td> </tr> </table> <table width="600" border="1" cellspacing="0"> <tr height="50"> <td width="100">1111</td> <td rowspan="3">合并包括此行在内的下面三行</td> </tr> <tr height="100"> <td>222</td> </tr> <tr height="300"> <td>3333</td> </tr> <tr> <td>50</td> </tr> </table> </div> let word = ref(null); downloadWordWithHtmlString(word.value.innerHTML, 'html字符串word.docx');
生成的word文件可以看到效果和在网页中的html代码一样:
另外需要注意的是,若是需要在word中添加分页符,在需要分页的内容处添加CSS属性page-break-before即可。此时在浏览器上打印出innerHTML值会发现:
mdn上介绍page-break-before属性已经被break-before属性替代,但是经过我实际测试发现当html字符串是page-break: always时生成的word文件没有分页效果,反而是将其替换回page-break-before后实现了分页效果。若有大神知道这是什么问题还望不吝赐教。 因此需要在downloadWordWithHtmlString方法中添加一句正则: htmlString = htmlString.replace( /break-(after|before): page/g, "page-break-$1: always;" );
,此时就能实现分页效果。
三、使用docx插件
第二种方法有个很致命的问题就是它无法在生成的word文件中添加图片页眉,我搜遍了npm也只找到一个能添加文字页眉的插件: html-docx-ts。要想实现这个需求,就需要用到docx插件。 docx官网的介绍是"Easily generate and modify .docx files with JS/TS. Works for Node and on the Browser.",意味着是一个专门用于生成word和修改word的文件。该插件就需要一个一个去配置你要生成的项,然后组合成一个word。一个简单的案例是:
import { Document, Paragraph, Header, TextRun, Table, TableRow, TableCell, WidthType, Packer, } from "docx"; import { saveAs } from "file-saver"; const document = new Document({ sections: [ { headers: { default: new Header({ children: [new Paragraph("我是页眉")], }), }, children: [ new Paragraph({ children: [ new TextRun({ text: "我是文字内容", size: 16, bold: true, }), ], }), new Table({ columnWidths: [1500, 7500], rows: [ new TableRow({ children: [ new TableCell({ width: { size: 1500, type: WidthType.DXA, }, children: [ new Paragraph({ alignment: "center", children: [ new TextRun({ text: "测试", size: 24, font: { name: "楷体", }, }), ], }), ], }), ], }), ], }), ], }, ], }); Packer.toBlob(document).then((blob) => { saveAs(blob, "test.docx"); });
导出的word文件形式为:
面是我个人总结的比较常见能用到的功能和配置项:
// 导出文字 1.new Paragraph(text) -> 默认字体样式: 宋体,五号字 2.new Paragraph({ children: [ new TextRun({ text: "我是文字内容", size: 16, // 对应word中的字体大小8 bold: true, // 是否加粗 underline: { type: UnderlineType.SINGLE, color: "#2e32ee", }, // 下划线类型及颜色 font: { name: "仿宋", // 只要是word中有的字体类型都可以生效 }, }), ], indent: { left: 100, }, // 离左边距离 类似于margin-left spacing: { before: 150, after: 200, }, // 离上边和下边的距离 类似于margin-top/bottom alignment: "center", // 对齐方式 pageBreakBefore: true, // 是否在这段文字前加入分页符 }) // 导出表格 new Table({ columnWidths: [1500, 7500], // 表示单行有几项,总宽度是9000,对应宽度; rows: [ new TableRow({ children: [ new TableCell({ width: { size: 1500, // 需与columnWidths的第一项对应 type: WidthType.DXA, // 官网的介绍是Value is in twentieths of a point // 因为表格的总宽度是以twips(每英寸的1/20)为单位进行计算的 }, children: [ new Paragraph({ alignment: "center", children: [ new TextRun({ text: "测试", size: 24, font: { name: "楷体", }, }), ], }), ], }), new TableCell({ width: { size: 7500, type: WidthType.DXA, }, children: [ new Paragraph('ccc'), ], margins: { top: 500, bottom: 500, left: 500 } // 类似于单元格内容的padding }), ], }), ], }) // 导出图片 new Paragraph({ children: [ new ImageRun({ data: "base64", // 图片需转成base64的形式 transformation: { width: 100, height: 30, }, // 图片宽高 }), ], }) // 设置页眉页脚 headers: { default: new Header({ children: [new Paragraph("我是页眉")], }), }, footers: { default: new Footer({ children: [new Paragraph("我是页脚")], }), }
下面是一个完整的使用案例:
const document = new Document({ sections: [ { headers: { default: new Header({ children: [ new Paragraph({ children: [ new ImageRun({ data: "data:image/jpeg;base64,...", transformation: { width: 150, height: 150, }, }), ], }), ], }), }, footers: { default: new Footer({ children: [new Paragraph("我是页脚")], }), }, children: [ new Paragraph("第一行直接默认形式"), new Paragraph({ children: [ new TextRun({ text: "下一页", }), ], pageBreakBefore: true, }), new Table({ columnWidths: [1500, 7500], rows: [ new TableRow({ children: [ new TableCell({ width: { size: 1500, type: WidthType.DXA, }, children: [ new Paragraph({ alignment: "center", children: [ new TextRun({ text: "测试", size: 24, font: { name: "楷体", }, }), ], }), ], }), new TableCell({ width: { size: 7500, type: WidthType.DXA, }, children: [ new Paragraph({ children: [ new ImageRun({ data: "data:image/jpeg;base64,...", transformation: { width: 150, height: 150, }, }), ], }), ], margins: { top: 500, bottom: 500, left: 500, }, }), ], }), ], }), ], }, ], }); Packer.toBlob(document).then((blob) => { saveAs(blob, "test.docx"); });
此时导出的word文件如下: