很多时候在工作中会碰到完全由前端导出word文件的需求,因此特地记录一下比较常用的几种方式。

一、提供一个word模板

该方法提供一个word模板文件,数据通过参数替换的方式传入word文件中,灵活性较差,适用于简单的文件导出。需要依赖:docxtemplater、file-saver、jszip-utils、pizzip

javascript复制代码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。

xml复制代码import { saveAs } from "file-saver";import { asBlob } from "html-docx-js-typescript"; export function downloadWordWithHtmlString(html, name) {let htmlString = `Document${html}`;asBlob(htmlString).then((data) => {saveAs(data, `${name}.docx`);});}`

使用案例:

ini复制代码

word标题

1111合并单元格最长的一项222222222222
1111合并包括此行在内的下面三行
222
3333
50
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。一个简单的案例是:

css复制代码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文件形式为:


下面是我个人总结的比较常见能用到的功能和配置项:

css复制代码// 导出文字1.new Paragraph(text) -> 默认字体样式: 宋体,五号字2.new Paragraph({children: [new TextRun({text: "我是文字内容",size: 16, // 对应word中的字体大小8bold: true, // 是否加粗underline: {type: UnderlineType.SINGLE,color: "#2e32ee",}, // 下划线类型及颜色font: {name: "仿宋", // 只要是word中有的字体类型都可以生效},}),],indent: {left: 100,}, // 离左边距离 类似于margin-leftspacing: {before: 150,after: 200,}, // 离上边和下边的距离 类似于margin-top/bottomalignment: "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("我是页脚")],}),}

下面是一个完整的使用案例:

css复制代码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文件如下:


学习更多vue开发知识请下载​​CRMEB开源商城​​附件学习