记录--Vue中前端导出word文件
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助
很多时候在工作中会碰到完全由前端导出word文件的需求,因此特地记录一下比较常用的几种方式。
一、提供一个word模板
该方法提供一个word模板文件,数据通过参数替换的方式传入word文件中,灵活性较差,适用于简单的文件导出。需要依赖:docxtemplater、file-saver、jszip-utils、pizzip。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | 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。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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`); }); } |
使用案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | <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。一个简单的案例是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | // 导出文字 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( "我是页脚" )], }), } |
下面是一个完整的使用案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | 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文件如下:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理