需求:前端生成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 binaryString
if (typeof window !== 'undefined') {
binaryString = window.atob(stringBase64)
} else {
binaryString = Buffer.from(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
}
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 error
if (!input) return input
// toLowerCase() 方法用于把字符串转换为小写。
return input.toLowerCase()
}
import('docxtemplater-image-module-free')
.then(({ default: ImageModule }) => {
// 使用 ImageModule
JSZipUtils.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的数据转为ArrayBuffer
return 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.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)
//返回
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.ndeMtDataDto
nextTick(() => {
// 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. 图片转base64
exportWordFile('./docxs/VTRecords.docx', data, '检查记录.docx')
})
})
.catch(err => {
E_Msg.warn(err.msg || err)
})
.finally(() => {
loadingInstance.close()
})
}
// 多个图片遍历转base64
const exportWordFile = async (path, datas, fileName) => {
//多个图片遍历转base64
for (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 取反。