Vue如何导出页面为word格式
作者:代码无边,回头是岸
vue导出页面为word格式
1、构建页面需要的css并以js的方式导出
//构建打印页面的需要的css文件已js的形式并导出 export const cssJs =` html, body { position: relative; height: 100%; } .page_layout { position: relative; height: 100%; display: flex; & .layout_content { flex-grow: 1; display: flex; flex-direction: column; } } ...
2、引入css的js文件,并创建文本流 ,发送给后端进行处理,并返回进行解析生成下载
//添加的文件 //name要打印的页面类名,cssJs是这个页面的css import cssJs from "../cssJs" export function handerWord(name,cssJs){ let html =document.getElementsByClassName(name)[0].innerHTML;// 获取要导出部分的内容 let htmlDom = `<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <title>标题</title> <link rel="stylesheet" href="https://cdn.bootcss.com/iview/2.14.0/styles/iview.css" rel="external nofollow" /> <style> ${cssJs} </style> </head> <body> <div class="resume_preview_page" style="margin:0 auto;width:1200px"> ${html} </div> </body> </html>`; let htmlDom_ = new Blob([htmlDom],{ "type" : "text/html;charset=utf-8" }) let formdata = new FormData(); formdata.append('file', htmlDom_, `sdf.html`);//sdf.html是设置文件名 axios({ method: 'post', url: url, data:formdata, responseType:'blob',//这里如果不设置,下载会打不开文件 }).then(res=>{ console.log('download res',res); //通过后台返回 的word文件流设置文件名并下载 var blob = new Blob([res.data], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document;charset=utf-8'}); //application/vnd.openxmlformats-officedocument.wordprocessingml.document这里表示doc类型 var downloadElement = document.createElement('a'); var href = window.URL.createObjectURL(blob); //创建下载的链接 downloadElement.href = href; downloadElement.download ='s.doc'; //下载后文件名 document.body.appendChild(downloadElement); downloadElement.click(); //点击下载 document.body.removeChild(downloadElement); //下载完成移除元素 window.URL.revokeObjectURL(href); //释放掉blob对象 }) }
vue项目导出导出word遇到的bug
闲着没事总结一下vue项目中导出word模板中遇到的bug,给各位看官做个参考。
安装依赖
– docxtemplater是一种邮件合并工具,它以编程方式使用,处理条件、循环,并且可以扩展为表格、HTML、图像等。
npm install docxtemplater pizzip --save
– jszip-utils是与jszip一起使用的跨浏览器的工具库
npm install jszip-utils --save
– jszip是一个用于创建、读取和编辑.zip文件的JavaScript库,且API的使用也很简单。
npm install jszip --save
– FileSaver 是在客户端保存文件的解决方案,非常适合需要生成文件,或者保存不应该发送到外部服务器的敏感信息的应用。
npm install file-saver --save
– 引入处理图片的插件1
npm install docxtemplater-image-module-free --save
– 引入处理图片的插件2
npm install angular-expressions --save
word模板
准备一个word模板文件(按自己所需最后导出的样式),放到该vue项目资源文件夹下, 模板word样式如下所示:
注:
// 表格信息 tableData: [ { number: 1, // 序号 name: "设备1", // 设备名称 num: 1, // 数量 salePrice: 10, // 销售单价 saleTotal: 10, // 销售合计 remark: "啦啦啦" // 备注 }, { number: 2, // 序号 name: "设备2", // 设备名称 num: 2, // 数量 salePrice: 20, // 销售单价 saleTotal: 40, // 销售合计 remark: "啦啦啦" // 备注 }, { number: 3, // 序号 name: "设备3", // 设备名称 num: 3, // 数量 salePrice: 30, // 销售单价 saleTotal: 90, // 销售合计 remark: "啦啦啦" // 备注 } ], // let data= { year:2021, month:9, tableData:this.tableData, imglist:this.imglist }
必须使用英文下的花括号;
{变量名} => 包含的为属性变量;
{#数组名}{数组里面对象的字段}{/数组名}=>循环的写法为以{#数组名}开头,中间为{循环体的变量名},以{/数组名}结尾;
{#数组名}{%数组里面对象的字段}{/数组名}=>对于循环展示图片,中间变量名需要添加%——{%变量名}
对于word模板:
1.文件类型:
模板后缀必须为.docx,最好是直接新建一个docx文件,不建议直接更改文件后缀名,转为文件流的时候还是原先后缀,导致报错;
2.位置:
对于模板的存放位置,如果模板文件使用vue-cli2的时候,放在static目录下。使用vue-cli3的时候,放在public目录下。
实现功能
1.先创建一个工具类,exportBriefDataDocx.js
import PizZip from 'pizzip' import docxtemplater from 'docxtemplater' import JSZipUtils from 'jszip-utils' import {saveAs} from 'file-saver' /** * 将base64格式的数据转为ArrayBuffer * @param {Object} dataURL base64格式的数据 */ function base64DataURLToArrayBuffer(dataURL) { const base64Regex = /^data:image\/(png|jpg|jpeg|svg|svg\+xml);base64,/; if (!base64Regex.test(dataURL)) { return false; } const stringBase64 = dataURL.replace(base64Regex, ""); let binaryString; if (typeof window !== "undefined") { binaryString = window.atob(stringBase64); } else { binaryString = new Buffer(stringBase64, "base64").toString("binary"); } const len = binaryString.length; const bytes = new Uint8Array(len); for (let i = 0; i < len; i++) { const ascii = binaryString.charCodeAt(i); bytes[i] = ascii; } return bytes.buffer; } /** * 导出word,支持图片 * @param {Object} tempDocxPath 模板文件路径 * @param {Object} wordData 导出数据 * @param {Object} fileName 导出文件名 * @param {Object} imgSize 自定义图片尺寸 */ export const exportWord = (tempDocxPath, wordData, fileName,imgSize) => { //这里要引入处理图片的插件 var ImageModule = require('docxtemplater-image-module-free'); const expressions = require("angular-expressions"); // 读取并获得模板文件的二进制内容 JSZipUtils.getBinaryContent(tempDocxPath, function(error, content) { console.log("到死",tempDocxPath) if (error) { throw error; } expressions.filters.size = function(input, width, height) { return { data: input, size: [width, height], }; }; function angularParser(tag) { const expr = expressions.compile(tag.replace(/'/g, "'")); return { get(scope) { return expr(scope); }, }; } // 图片处理 let opts = {} opts = { //图像是否居中 centered: true }; opts.getImage = (chartId) => { //console.log(chartId);//base64数据 //将base64的数据转为ArrayBuffer return base64DataURLToArrayBuffer(chartId); } opts.getSize = function(img, tagValue, tagName) { //自定义指定图像大小 if(imgSize.hasOwnProperty(tagName)){ return imgSize[tagName]; }else{ return [300, 300]; } } // 创建一个PizZip实例,内容为模板的内容 let zip = new PizZip(content); // 创建并加载docxtemplater实例对象 let doc = new docxtemplater(); doc.attachModule(new ImageModule(opts)); doc.loadZip(zip); doc.setData(wordData); try { // 用模板变量的值替换所有模板变量 doc.render(); } catch (error) { // 抛出异常 let e = { message: error.message, name: error.name, stack: error.stack, properties: error.properties }; console.log(JSON.stringify({ error: e })); throw error; } // 生成一个代表docxtemplater对象的zip文件(不是一个真实的文件,而是在内存中的表示) let out = doc.getZip().generate({ type: "blob", mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document" }); // 将目标文件对象保存为目标类型的文件,并命名 saveAs(out, fileName); }); } /** * 将图片的url路径转为base64路径 * 可以用await等待Promise的异步返回 * @param {Object} imgUrl 图片路径 */ export function getBase64Sync(imgUrl) { return new Promise(function(resolve, reject) { // 一定要设置为let,不然图片不显示 let image = new Image(); //图片地址 image.src = imgUrl; // 解决跨域问题 image.setAttribute("crossOrigin", '*'); // 支持跨域图片 // image.onload为异步加载 image.onload = function() { let canvas = document.createElement("canvas"); canvas.width = image.width; canvas.height = image.height; let context = canvas.getContext("2d"); context.drawImage(image, 0, 0, image.width, image.height); //图片后缀名 let ext = image.src.substring(image.src.lastIndexOf(".") + 1).toLowerCase(); //图片质量 let quality = 0.8; //转成base64 let dataurl = canvas.toDataURL("image/" + ext, quality); //返回 resolve(dataurl); }; }) }
2.实现页面
<template> <div class="home"> <el-button @click="exportWord" size="small" type="primary">导出word</el-button> </div> </template> <script> import {exportWord,getBase64Sync} from '@/utils/exportBriefDataDocx' export default { name: "home", data() { return { // 表格信息 tableData: [ { number: 1, // 序号 name: "设备1", // 设备名称 num: 1, // 数量 salePrice: 10, // 销售单价 saleTotal: 10, // 销售合计 remark: "啦啦啦" // 备注 }, { number: 2, // 序号 name: "设备2", // 设备名称 num: 2, // 数量 salePrice: 20, // 销售单价 saleTotal: 40, // 销售合计 remark: "啦啦啦" // 备注 }, { number: 3, // 序号 name: "设备3", // 设备名称 num: 3, // 数量 salePrice: 30, // 销售单价 saleTotal: 90, // 销售合计 remark: "啦啦啦" // 备注 } ], //图片信息 imglist:[ { imgUrl: "https://img2.baidu.com/it/u=2709954499,581919391&fm=253&fmt=auto&app=138&f=JPEG?w=468&h=518" }, { imgUrl: "https://img0.baidu.com/it/u=1462004956,1440895436&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=353" } ], //传入模板数据 docxData:{} }; }, methods: { // 导出docx async exportWord() { for (let i in this.imglist) { this.imglist[i].imgUrl = await getBase64Sync(this.imglist[i].imgUrl) } let data= { year:2021, month:9, tableData:this.tableData, imglist:this.imglist } let imgSize = { //控制导出的word图片大小 imgurl:[200, 200], }; exportWord("static/模板文件.docx", data, "/导出文档.docx", imgSize); } } }; </script>
注:
报错:Uncaught Error: Error: Can’t find end of central directory : is this a zip f
这个报错原因是找不到模板位置,
项目使用vue-cli2的时候,放在static目录下。
exportWord("/模板文件.docx", data, "/导出文档.docx", imgSize);
或者
exportWord("static/模板文件.docx", data, "/导出文档.docx", imgSize);
项目使用vue-cli3的时候,放在public目录下。
exportWord("/模板文件.docx", data, "/导出文档.docx", imgSize);
成果展示:
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。