1. 安装必要的库
我们需要以下库来完成任务:
docxtemplater
: 用于填充Word模板。pizzip
: 用于读取和解压缩docx文件。jszip-utils
: 用于加载二进制文件。file-saver
: 用于保存生成的文件。angular-expressions
: 用于在模板中进行数据运算。docxtemplater-image-module-free
: 用于在文档中插入图片
使用以下命令安装这些依赖:
npm install docxtemplater pizzip jszip-utils file-saver angular-expressions docxtemplater-image-module-free
使用Docxtemplater和JSZip在Vue项目中生成Word文档
在Vue.js项目中,有时我们需要根据模板生成带有动态数据和图片的Word文档。本文将介绍如何使用Docxtemplater
、JSZip
以及其他相关库来实现这一功能。
1. 安装必要的库
我们需要以下库来完成任务:
docxtemplater
: 用于填充Word模板。pizzip
: 用于读取和解压缩docx文件。jszip-utils
: 用于加载二进制文件。file-saver
: 用于保存生成的文件。angular-expressions
: 用于在模板中进行数据运算。docxtemplater-image-module-free
: 用于在文档中插入图片。
使用以下命令安装这些依赖:
npm install docxtemplater pizzip jszip-utils file-saver angular-expressions docxtemplater-image-module-free
2. 设置环境
在项目中创建一个JavaScript文件,例如docx.js
,导入所需的库:
import Docxtemplater from 'docxtemplater';
import PizZip from 'pizzip';
import JSZipUtils from 'jszip-utils';
import { saveAs } from 'file-saver';
import expressions from 'angular-expressions';
import ImageModule from 'docxtemplater-image-module-free';
3. 实现导出功能
3.1 基本文档生成函数
这个函数用于读取模板文件,填充数据,并生成Word文档。函数内部包含读取文件的异步操作,并且处理了可能的错误。
/**
* 导出Word文档
* @param {String} tempDocxPath 模板文件路径
* @param {Object} data 需要填充的数据
* @param {String} fileName 导出的文件名
* @returns {Promise<Blob>} 返回包含生成文档的Blob对象
*/
export const exportDocx = (tempDocxPath, data, fileName) => {
return new Promise((resolve, reject) => {
// 模拟异步操作,如加载文件等
setTimeout(() => {
// 使用JSZipUtils读取模板文件的二进制内容
JSZipUtils.getBinaryContent(tempDocxPath, (error, content) => {
if (error) {
reject(error); // 处理读取文件的错误
return;
}
// 使用PizZip解压缩文件
const zip = new PizZip(content);
// 使用Docxtemplater加载解压缩的内容
const doc = new Docxtemplater().loadZip(zip);
doc.setData(data);
try {
// 填充数据
doc.render();
} catch (error) {
// 处理渲染时的错误
reject(error);
return;
}
// 生成Blob对象
const out = doc.getZip().generate({
type: 'blob',
mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
});
// 保存文件
saveAs(out, fileName);
resolve(out);
});
}, 1000); // 模拟延迟操作
});
};
3.2 带图片的文档生成
如果文档中需要插入图片,可以使用docxtemplater-image-module-free
插件。这一部分代码相对复杂,因为涉及到图像处理和数据转换。
/**
* 获取包含图片的Word文档的Blob对象
* @param {String} tempDocxPath 模板文件路径
* @param {Object} data 需要填充的数据
* @param {String} fileName 导出的文件名
* @returns {Promise<Blob>} 返回包含生成文档的Blob对象
*/
export const getDocxToBlob = (tempDocxPath, data, fileName) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
const ImageModule = require('docxtemplater-image-module-free');
const expressions = require('angular-expressions');
const assign = require('lodash/assign');
const last = require('lodash/last');
// 定义自定义过滤器和解析器
expressions.filters.lower = function(input) {
return input ? input.toLowerCase() : input;
};
function angularParser(tag) {
tag = tag.replace(/^\.$/, 'this').replace(/(’|‘)/g, "'").replace(/(“|”)/g, '"');
const expr = expressions.compile(tag);
return {
get: function(scope, context) {
let obj = {};
const index = last(context.scopePathItem);
const scopeList = context.scopeList;
const num = context.num;
for (let i = 0, len = num + 1; i < len; i++) {
obj = assign(obj, scopeList[i]);
}
obj = assign(obj, { $index: index });
return expr(scope, obj);
}
};
}
// 读取模板文件
JSZipUtils.getBinaryContent(tempDocxPath, (error, content) => {
if (error) {
reject(error);
return;
}
// 定义图像处理选项
let opts = {
centered: true,
getImage: (chartId) => {
return base64DataURLToArrayBuffer(chartId);
},
getSize: function(img, tagValue, tagName) {
return [70, 100];
}
};
const zip = new PizZip(content);
const doc = new Docxtemplater().loadZip(zip);
doc.attachModule(new ImageModule(opts));
doc.setOptions({ parser: angularParser });
doc.setData(data);
try {
doc.render();
} catch (error) {
reject(error);
return;
}
const out = doc.getZip().generate({
type: 'blob',
mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
});
resolve(out);
});
}, 1000);
});
};
4. 辅助函数
这些辅助函数用于处理图像数据,将图片转换为Base64格式,并进一步转换为ArrayBuffer格式,以便在文档中使用。
/**
* 将图片URL转换为Base64格式
* @param {String} imgUrl 图片的URL
* @returns {Promise<String>} 返回Base64格式的图片数据
*/
export function getBase64Sync(imgUrl) {
return new Promise((resolve, reject) => {
let image = new Image();
image.src = imgUrl;
image.setAttribute('crossOrigin', '*');
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;
let dataurl = canvas.toDataURL('image/' + ext, quality);
resolve(dataurl);
};
});
}
4.2 将Base64转换为ArrayBuffer
/**
* 将Base64格式的数据转换为ArrayBuffer
* @param {String} dataURL Base64格式的数据
* @returns {ArrayBuffer} 转换后的ArrayBuffer
*/
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 = window.atob(stringBase64);
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;
}
5. 使用示例
在Vue组件中,可以调用这些函数来生成和下载Word文档。
import { exportDocx, getDocxToBlob } from '@/utils/docx';
export default {
methods: {
generateDocument() {
const data = {
firstName: 'John',
lastName: 'Doe'
};
const templatePath = 'path/to/template.docx';
const fileName = 'generated-document.docx';
exportDocx(templatePath, data, fileName).then((blob) => {
console.log('文档生成成功');
}).catch((error) => {
console.error('文档生成失败', error);
});
}
}
}
6. 完整代码
6.1 使用页面
<template>
<div class="examinee" v-loading="loading">
<!-- 整个组件的容器 -->
<div class="container" id="admissionTicket">
<!-- 遍历 examineeList,每个考场生成一个页面 -->
<div
class="page-a4"
:id="'pdf' + index"
v-for="(item, index) in examineeList"
:key="index"
>
<!-- 显示考场编号的标题 -->
<div class="title">
<span>第-{{ index }}-考场</span>
</div>
<!-- 考生信息的包装器 -->
<div class="a4-wrap">
<!-- 遍历 item 中的每个考生,展示其信息 -->
<div class="person-item" v-for="(per, pIndex) in item" :key="pIndex">
<img class="icon" :src="per.oneInchPhoto" alt="" />
<div class="text">
姓名:{{ per.talentName }}
{{ per.talentGender == 0 ? "男" : "女" }}
</div>
<div class="text">座位号:{{ per.seatNum }}</div>
<div class="text">准考证:{{ per.ticketNum }}</div>
<div class="text">身份证号:</div>
<div class="text">{{ per.talentIdCard }}</div>
<div class="text">本人签字:</div>
</div>
</div>
</div>
</div>
<!-- 导出为 PDF 的按钮 -->
<div class="export-btn">
<el-button
type="success"
icon="el-icon-download"
size="mini"
v-loading.fullscreen.lock="pageLoading"
element-loading-text="正在生成PDF请稍后..."
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(255, 255, 255, 0.8)"
@click="previewImage"
>导 出</el-button>
</div>
</div>
</template>
<script>
// 导入生成PDF和处理图像所需的库
import html2Canvas from "html2canvas";
import JsPDF from "jspdf";
import { getBase64Sync } from "@/utils/docx";
import { getTalentInfo } from "@/api/exam/examInfo";
export default {
name: "examinee",
data() {
return {
pageLoading: false, // 指示页面是否正在加载
loading: false, // 指示数据是否正在获取
examineeList: {}, // 存储考生列表
};
},
created() {
this.getList(); // 组件创建时获取考生列表
},
methods: {
// 获取考生信息
getList() {
const id = this.$route.query.id;
this.loading = true; // 设置 loading 状态为 true
getTalentInfo({ examId: id })
.then((res) => {
// 将图像 URL 转换为 base64 格式(如果需要)
// for (const key in res.data) {
// res.data[key].forEach(async (ele) => {
// ele.oneInchPhoto = await getBase64Sync(ele.oneInchPhoto);
// });
// }
this.examineeList = res.data; // 更新考生列表数据
this.loading = false; // 设置 loading 状态为 false
})
.finally(() => {
this.loading = false; // 确保请求完成后将 loading 状态设置为 false
});
},
// 生成考生信息的 PDF 预览
async previewImage() {
this.pageLoading = true; // 设置页面加载状态为 true
try {
let itemDomList = document.getElementsByClassName("page-a4");
let imageDataList = []; // 存储每个页面的图像数据
for (let i = 0; i < itemDomList.length; i++) {
let pdfDom = document.getElementById("pdf" + (i + 1));
await html2Canvas(pdfDom, {
useCORS: true, // 允许跨域
allowTaint: false,
backgroundColor: "#fff", // 设置背景颜色为白色
scale: 2, // 缩放因子,提升图像质量
async: false,
}).then(function (canvas) {
let imageData = canvas.toDataURL("image/jpeg", 0.5);
imageDataList.push(imageData); // 将图像数据添加到列表中
});
}
let PDF = new JsPDF("p", "mm", "a4"); // 创建新的 PDF 实例
let width = PDF.internal.pageSize.getWidth();
let height = PDF.internal.pageSize.getHeight();
for (let i = 0; i < imageDataList.length; i++) {
PDF.addImage(imageDataList[i], "JPEG", 10, 10, 190, 277); // 添加图像到 PDF
if (i !== imageDataList.length - 1) {
PDF.addPage(); // 如果不是最后一页,添加新的页面
}
}
let pdfData = PDF.output("datauristring");
var a = document.createElement("a");
var event = new MouseEvent("click");
a.download = "考场信息"; // 自定义下载的文件名
a.href = pdfData;
a.dispatchEvent(event); // 触发下载
this.pageLoading = false; // 设置页面加载状态为 false
} catch (error) {
console.log(error); // 打印错误信息
this.pageLoading = false; // 确保出错时页面加载状态也设置为 false
}
},
},
};
</script>
<style lang="scss" scoped>
.examinee {
width: 100%;
height: 100%;
position: relative;
background: #fff;
overflow: hidden;
.container {
.page-a4 {
width: 840px;
height: 1188px;
margin: 10px auto;
.title {
text-align: center;
font-size: 16px;
margin-bottom: 5px;
}
.a4-wrap {
height: calc(100% - 27px);
border: 1px solid #7b7b7b;
padding: 4px;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
.person-item {
width: 16.3%;
border: 1px solid #333;
margin-bottom: 3px;
padding: 5px;
.icon {
display: block;
margin: 0 auto 5px;
height: 80px;
}
.text {
font-size: 12px;
// line-height: 12px;
}
}
}
}
}
.export-btn {
position: fixed;
top: 110px;
right: 20px;
z-index: 111;
}
}
</style>
6.1.1主要点
- 数据获取:
getList
方法从服务器获取数据,并将其存储在examineeList
中。 - PDF 生成:
previewImage
方法使用html2canvas
截取每个页面的内容,并用JsPDF
将这些内容合成 PDF。 - 样式:使用了 Scoped SCSS 样式,使样式仅应用于此组件。
- 导出按钮:提供了一个导出为 PDF 的按钮。
6.2 docx 文件:
import Docxtemplater from 'docxtemplater'
import PizZip from 'pizzip'
import JSZipUtils from 'jszip-utils'
import { saveAs } from 'file-saver'
import expressions from 'angular-expressions'
import ImageModule from 'docxtemplater-image-module-free'
/**
* 导出DOCX文件
*
* 这个函数用于从给定的模板文件生成一个DOCX文件,并将其保存为指定名称的文件。
*
* @param {String} tempDocxPath - 模板文件的URL路径。
* @param {Object} data - 用于填充模板的动态数据对象。
* @param {String} fileName - 导出文件的名称,包括扩展名。
* @returns {Promise} - 返回一个Promise对象,成功时解析为生成的Blob对象。
*/
export const exportDocx = (tempDocxPath, data, fileName) => {
return new Promise((resolve, reject) => {
// 使用setTimeout模拟异步操作,实际上可以用实际的异步操作代替
setTimeout(() => {
// 通过JSZipUtils获取模板文件的二进制内容
JSZipUtils.getBinaryContent(tempDocxPath, (error, content) => {
if (error) {
reject(error)
return
}
// 将二进制内容加载到PizZip实例中
const zip = new PizZip(content)
// 创建Docxtemplater实例并加载zip文件
const doc = new Docxtemplater().loadZip(zip)
// 设置模板数据
doc.setData(data)
try {
// 渲染文档,根据数据替换模板中的占位符
doc.render()
} catch (error) {
console.log({ error })
reject(error)
return
}
// 生成最终的Blob对象
const out = doc.getZip().generate({
type: 'blob',
mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
})
// 使用file-saver库保存文件
saveAs(out, fileName)
resolve(out)
}, 1000) // 模拟1秒延迟
})
})
}
/**
* 获取DOCX文件的Blob对象(支持图片处理)
*
* 这个函数与exportDocx类似,但它允许处理DOCX模板中的图片,并返回生成的Blob对象。
*
* @param {String} tempDocxPath - 模板文件的URL路径。
* @param {Object} data - 用于填充模板的动态数据对象。
* @param {String} fileName - 导出文件的名称,包括扩展名。
* @returns {Promise} - 返回一个Promise对象,成功时解析为生成的Blob对象。
*/
export const getDocxToBlob = (tempDocxPath, data, fileName) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
// 引入处理图片的模块和自定义表达式解析器
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) {
if (!input) return input
return input.toLowerCase()
}
// 自定义Angular表达式解析器
function angularParser(tag) {
tag = tag
.replace(/^\.$/, 'this')
.replace(/(’|‘)/g, "'")
.replace(/(“|”)/g, '"')
const expr = expressions.compile(tag)
return {
get: function (scope, context) {
let obj = {}
const index = last(context.scopePathItem)
const scopeList = context.scopeList
const num = context.num
for (let i = 0, len = num + 1; i < len; i++) {
obj = assign(obj, scopeList[i])
}
// 将索引值添加到数据对象中
obj = assign(obj, { $index: index })
return expr(scope, obj)
}
}
}
// 读取模板文件的二进制内容
JSZipUtils.getBinaryContent(tempDocxPath, (error, content) => {
if (error) {
reject(error)
return
}
// 自定义过滤器,用于调整图像大小
expressions.filters.size = function (input, width, height) {
return {
data: input,
size: [width, height],
}
}
let opts = {
centered: true // 图像是否居中
}
opts.getImage = (chartId) => {
// 将base64格式的图像数据转换为ArrayBuffer
return base64DataURLToArrayBuffer(chartId)
}
opts.getSize = function (img, tagValue, tagName) {
// 自定义图像大小
return [70, 100]
}
// 加载模板文件并创建Docxtemplater实例
const zip = new PizZip(content)
const doc = new Docxtemplater().loadZip(zip)
// 附加图像模块
doc.attachModule(new ImageModule(opts))
// 设置自定义的Angular表达式解析器
doc.setOptions({ parser: angularParser })
// 设置模板数据
doc.setData(data)
try {
// 渲染文档
doc.render()
} catch (error) {
console.log({ error })
reject(error)
return
}
// 生成Blob对象
const out = doc.getZip().generate({
type: 'blob',
mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
})
resolve(out)
}, 1000) // 模拟1秒延迟
})
})
}
/**
* 获取DOCX文件的Blob对象并自动保存(支持图片处理)
*
* 这个函数与getDocxToBlob类似,但在生成Blob对象后会直接保存文件。
*
* @param {String} tempDocxPath - 模板文件的URL路径。
* @param {Object} data - 用于填充模板的动态数据对象。
* @param {String} fileName - 导出文件的名称,包括扩展名。
* @returns {Promise} - 返回一个Promise对象,成功时解析为生成的Blob对象。
*/
export const getDocxToBlobTwo = (tempDocxPath, data, fileName) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
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) {
if (!input) return input
return input.toLowerCase()
}
function angularParser(tag) {
tag = tag
.replace(/^\.$/, 'this')
.replace(/(’|‘)/g, "'")
.replace(/(“|”)/g, '"')
const expr = expressions.compile(tag)
return {
get: function (scope, context) {
let obj = {}
const index = last(context.scopePathItem)
const scopeList = context.scopeList
const num = context.num
for (let i = 0, len = num + 1; i < len; i++) {
obj = assign(obj, scopeList[i])
}
obj = assign(obj, { $index: index })
return expr(scope, obj)
}
}
}
JSZipUtils.getBinaryContent(tempDocxPath, (error, content) => {
if (error) {
reject(error)
return
}
expressions.filters.size = function (input, width, height) {
return {
data: input,
size: [width, height],
}
}
let opts = {
centered: true
}
opts.getImage = (chartId) => {
return base64DataURLToArrayBuffer(chartId)
}
opts.getSize = function (img, tagValue, tagName) {
return [70, 100]
}
const zip = new PizZip(content)
const doc = new Docxtemplater().loadZip(zip)
doc.attachModule(new ImageModule(opts))
doc.setOptions({ parser: angularParser })
doc.setData(data)
try {
doc.render()
} catch (error) {
console.log({ error })
reject(error)
return
}
const out = doc.getZip().generate({
type: 'blob',
mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
})
saveAs(out, fileName) // 保存文件
resolve(out)
}, 1000) // 模拟1秒延迟
})
})
}
/**
* 将图片的URL路径转换为base64格式的数据
*
* 该函数创建一个图像对象,将其绘制到画布上,然后将图像转换为base64格式的数据URL。
*
* @param {String} imgUrl - 图片的URL路径。
* @returns {Promise} - 返回一个Promise对象,成功时解析为base64格式的图片数据。
*/
export function getBase64Sync(imgUrl) {
return new Promise((resolve, reject) => {
let image = new Image()
image.src = imgUrl
image.setAttribute("crossOrigin", '*') // 处理跨域图片
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
let dataurl = canvas.toDataURL("image/" + ext, quality)
resolve(dataurl)
}
})
}
/**
* 将base64格式的数据转换为ArrayBuffer
*
* 该函数用于将base64编码的图像数据转换为ArrayBuffer,以便在DOCX文件中插入图像。
*
* @param {String} dataURL - base64格式的图像数据。
* @returns {ArrayBuffer} - 返回转换后的ArrayBuffer对象。
*/
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) // 在浏览器环境中解码base64
} else {
binaryString = Buffer.from(stringBase64, "base64").toString("binary") // 在Node.js环境中解码base64
}
const len = binaryString.length
const bytes = new Uint8Array(len)
for (let i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i)
}
return bytes.buffer
}