目录
- 一、实现效果
- 二、所需插件
- 三、word文档模板
- 四、封装js 文件
- 五、实现导出word文档
- 总结
一、实现效果
以填写并导出房屋出租审批表为例,首先填写表格相应内容后,点击" 导出 "按钮实现word文档的导出功能,界面如下所示:
最后导出word文档如下所示:
二、所需插件
这里使用npm对以下所需依赖进行安装,并在后面封装的js文件(导出word文档主要实现方法)中引入 。
-- 安装 docxtemplater | |
npm install docxtemplater pizzip --save | |
-- 安装 jszip-utils | |
npm install jszip-utils --save | |
-- 安装 jszip | |
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之前,需要准备一个word模板文件(按自己所需最后导出的样式),放到该vue项目public文件夹下, 房屋出租审批表模板word样式如下所示:
需要填写的部分都被定义为变量或者json对象数组,具体格式如下:
1. 单一变量使用 { } 包含,例如:
{ user } 、{ area }
2. json数组格式,则包裹一个循环对象,例如:
原格式为:
"thinglist": [ | |
{ time :"2022-4-1",thing: "在家"}, | |
{ time :"2022-4-2",thing: "上班"}, | |
] |
在模板文件中表示为:
{#thinglist} | |
{time}-{thing} | |
{/thinglist} |
如果对象是图片地址时,需要在对象前加上% ,例如:
原格式为:
imglist:[ | |
{ imgUrl: " "}, | |
{ imgUrl: " "}, | |
] |
在模板文件中表示为:
{#imglist} | |
{%imgUrl} | |
{/imglist} |
四、封装js 文件
这部分主要是实现word文档导出含图片的主要实现方法,包括将图片的url路径转为base64路径、base64转二进制、以及导出图片的处理,可以直接复制粘贴在页面引入使用,具体代码如下:
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) { | |
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); | |
}; | |
}) | |
} |
五、实现导出word文档
1. 首先是前端部分,这里使用ElementPlus进行前端页面实现,代码如下:
<template> | |
<div class="page-css"> | |
<el-card class="box-card" shadow="never"> | |
<div class="search-data"> | |
<el-button type="success" @click="editVisible=true">填写审批表</el-button> | |
</div> | |
</el-card> | |
<el-dialog v-model="editVisible" title="房屋出租审批表" width="50%" custom-class="role-mask"> | |
<div> | |
<div class="tablename"> | |
<h2>房屋出租审批表</h2> | |
</div> | |
<table class="tb" border="1"> | |
<tr> | |
<td height="60">承租人</td> | |
<td colspan="2" width="180"> | |
<input class="inputone" v-model="user"/> | |
</td> | |
<td colspan="2" width="125">房屋面积</td> | |
<td colspan="2" width="175"> | |
<input class="inputtwo" v-model="area" /> | |
平方米 | |
</td> | |
</tr> | |
<tr> | |
<td height="60">年租金</td> | |
<td colspan="2" > | |
<input class="inputtwo" v-model="annualrent" /> | |
元/年 | |
</td> | |
<td colspan="2" >出租用途</td> | |
<td colspan="2" > | |
<input class="inputone" v-model="purpose"/> | |
</td> | |
</tr> | |
<tr> | |
<td height="300">房屋平面示意图</td> | |
<td colspan="6"> | |
<div v-for="(item,index) in imglist"> | |
<img style="width: 60%;" :src="item.imgUrl"/> | |
</div> | |
</td> | |
</tr> | |
</table> | |
</div> | |
<template #footer> | |
<span class="dialog-footer"> | |
<el-button type="info" @click="editVisible=false">取消</el-button> | |
<el-button type="primary" @click="exportWordFile" >导出</el-button> | |
</span> | |
</template> | |
</el-dialog> | |
</div> | |
</template> |
实现过程遇到一个问题:使用el-dialog弹出框时,想固定其在页面居中、距离页面顶部以及底部的固定距离,但是里面的表格内容却超出其显示范围,该如何实现喃?css设置如下:
/* 弹出框居中显示 */ | |
/deep/.el-dialog { | |
left: 50%; | |
top: 50%; | |
transform: translate(-50%, -50%); | |
margin: 0px ; | |
} | |
/* 弹出框超出部分滑动 */ | |
/deep/.el-dialog__body { | |
height: 75vh; | |
overflow: hidden; | |
overflow-y: auto; | |
} |
包括更改el-dialog弹出框头部以及底部区域样式,css设置如下:
/deep/.el-dialog__header { | |
width: 100%; | |
background-color:#f8f8f8 ; | |
} | |
/deep/ .el-dialog__footer { | |
width: 100%; | |
border-top: 1px #ebebeb solid ; | |
} |
2.然后在页面内引入封装js里的exportWord以及getBase64Sync方法,data部分定义的是双向绑定填写的内容以及图片地址,考虑到图片可能不知一张,需要循环对其处理转为base64路径,代码如下:
// 引入将图片的url路径转为base64路径的方法 | |
for (let i in this.imglist) { | |
this.imglist[i].imgUrl = await getBase64Sync(this.imglist[i].imgUrl) | |
} |
完整代码如下所示:
<script> | |
import {exportWord,getBase64Sync} from '@/assets/js/outword.js' | |
export default { | |
data () { | |
return { | |
editVisible:false, | |
user:'', | |
area:'', | |
annualrent:'', | |
purpose:'', | |
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" | |
} | |
] | |
} | |
}, | |
methods:{ | |
async exportWordFile (){ | |
for (let i in this.imglist) { | |
this.imglist[i].imgUrl = await getBase64Sync(this.imglist[i].imgUrl) | |
} | |
let data= { | |
user:this.user, | |
area:this.area, | |
annualrent:this.annualrent, | |
purpose:this.purpose, | |
imglist:this.imglist | |
} | |
let imgSize = { | |
//控制导出的word图片大小 | |
imgurl:[200, 200], | |
}; | |
exportWord("/房屋出租审批表.docx", data, "房屋出租审批表.docx", imgSize); | |
} | |
} | |
} | |
</script> |