需求:前端生成word文件
实现步骤:
- 安装依赖
npm install jszip-utils docxtemplater file-saver pizzip --save
npm install angular-expressions --save
npm install docxtemplater-image-module-free --save
docxtemplater:这个插件可以通过预先写好的word,excel等文件模板生成对应带数据的文件。
file-saver:适合在客户端生成文件的工具,它提供的接口saveAs(blob, “1.docx”)将会使用到,方便我们保存文件。
pizzip:这个插件用来创建,读取或编辑.zip的文件,同步的(还有一个插件是jszip,异步的)。
jszip-utils:与jszip/pizzip一起使用,jszip-utils 提供一个getBinaryContent(path, data)接口,path即是文件的路径,支持AJAX get请求,data为读取的文件内容。
docxtemplater-image-module-free:需要导出图片的话需要这个插件
docxtemplater:不支持jszip,会有报错,因此要使用PizZip
- 编辑Word模板
例如:
- 后台出参:
{tableData:[{name:'张三',age:18},{name:'李四',age:19}],school:'实验中学',logo:'图片的base64格式'
}
- 模板内容:
-
保存模板
-
代码实现
- ①生成Word文档的触发按钮
<bt-button type="secondary" @click="handleGetCheckReport">生成报告</bt-button>
- ②定义公共生成文件方法(固定内容,可直接复制),路径:src/utils/doc.js
/*** 导出word文档(带图片)*/
import Docxtemplater from 'docxtemplater'
import PizZip from 'pizzip'
import JSZipUtils from 'jszip-utils'
import { saveAs } from 'file-saver'
// import * as ImageModule from 'docxtemplater-image-module-free'import * as expressions from 'angular-expressions'
/*** 将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 binaryStringif (typeof window !== 'undefined') {binaryString = window.atob(stringBase64)} else {binaryString = Buffer.from(stringBase64, 'base64').toString('binary')}const len = binaryString.lengthconst bytes = new Uint8Array(len)for (let i = 0; i < len; i++) {const ascii = binaryString.charCodeAt(i)bytes[i] = ascii}return bytes.buffer
}export const exportWord = (tempDocxPath, data, fileName, imgSize) => {console.log(111, tempDocxPath, data, fileName, imgSize)//这里要引入处理图片的插件// var ImageModule = require('docxtemplater-image-module-free')// var expressions = require('angular-expressions')// var assign = require('lodash/assign')// var last = require('lodash/last')expressions.filters.lower = function (input) {// This condition should be used to make sure that if your input is// undefined, your output will be undefined as well and will not// throw an errorif (!input) return input// toLowerCase() 方法用于把字符串转换为小写。return input.toLowerCase()}import('docxtemplater-image-module-free').then(({ default: ImageModule }) => {// 使用 ImageModuleJSZipUtils.getBinaryContent(tempDocxPath, (error, content) => {if (error) {console.log(error)}expressions.filters.size = function (input, width, height) {return {data: input,size: [width, height],}}let opts = {}opts = {//图像是否居中centered: true,}opts.getImage = chartId => {//将base64的数据转为ArrayBufferreturn base64DataURLToArrayBuffer(chartId)}opts.getSize = function (img, tagValue, tagName) {//自定义指定图像大小if (tagName == 'signature') {return [80, 40]} else {return [400, 500]}}// 创建一个JSZip实例,内容为模板的内容const zip = new PizZip(content)// 创建并加载 Docxtemplater 实例对象// 设置模板变量的值let doc = new Docxtemplater()doc.attachModule(new ImageModule(opts))doc.loadZip(zip)// doc.setOptions({ parser: angularParser })doc.setData(data)try {// 呈现文档,会将内部所有变量替换成值,doc.render()} catch (error) {const e = {message: error.message,name: error.name,stack: error.stack,properties: error.properties,}console.log('err', { error: e })// 当使用json记录时,此处抛出错误信息throw error}// 生成一个代表Docxtemplater对象的zip文件(不是一个真实的文件,而是在内存中的表示)const out = doc.getZip().generate({type: 'blob',mimeType:'application/vnd.openxmlformats-officedocument.wordprocessingml.document',})// 将目标文件对象保存为目标类型的文件,并命名saveAs(out, fileName)})}).catch(error => {console.error('Cannot load the module', error)})
}/*** 将图片的url路径转为base64路径* 可以用await等待Promise的异步返回* @param {Object} imgUrl 图片路径*/
export function getBase64Sync(imgUrl) {return new Promise(function (resolve, reject) {console.log(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.widthcanvas.height = image.heightlet 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//转成base64let dataurl = canvas.toDataURL(`image/${ext}`, quality)//返回console.log(dataurl)resolve(dataurl)}})
}
- ③调用接口获取模板内容数据,生成word文件
import { createFile } from '@/src/utils/doc.js'//引用公共方法(即步骤②中的文件)// 生成检测记录
const handleGetCheckRecord = () => {// 1. 调用接口返回数据let query = {recordId: 'b97d3f05d0d9d4d3607956057a999d8c',}const loadingInstance = ElLoading.service({target: '.rankingListBtTable',text: '',})QmsNdeOtherRecordMtApi.checkDetails(query).then(res => {let resData = res.data.ndeMtDataDtonextTick(() => {// 2. 整理后端返回数据,用于生成word文件(后台返回数据格式如下)let data = {//常规文本数据title: '检测规程/版本',// 附件图片文字数据imgList: [{remark: '这是一条备注信息111',img: 'http://gateway-develop.ever-ghsb-develop.svc.cluster.local:8888/system22/app/fileObject/preview?id=4d86dd9a6f84d984b78775a0de891efb',},{remark: '这是一条备注信息2222',img: 'https://images.pexels.com/photos/3291250/pexels-photo-3291250.jpeg?auto=compress&cs=tinysrgb&w=1600&lazy=load',},],// 表格数据tableData: [{ index: '1', materialNo: '检测部位编号1', texture: '' },{ index: '2', materialNo: '检测部位编号2', texture: '' },],}// 3. 图片转base64exportWordFile('./docxs/VTRecords.docx', data, '检查记录.docx')})}).catch(err => {E_Msg.warn(err.msg || err)}).finally(() => {loadingInstance.close()})
}// 多个图片遍历转base64
const exportWordFile = async (path, datas, fileName) => {//多个图片遍历转base64for (let i in datas.imgList) {datas.imgList[i].fileUrl = await getBase64Sync(datas.imgList[i].fileUrl)}let imgSize = {//控制导出的word图片大小imgurl: [400, 500],}nextTick(() => {// 4. 生成文档exportWord(path, datas, fileName, imgSize)})
}
注:
1. 后台数据格式说明
2. 多个图片显示的情况
3. 勾选框的实现
传入的变量category1 为 true 时,才会渲染打√的效果。此时要传入另一个变量category_1 ,值为 category1 取反。