vue 组装下载 word文档
记录一下遇到的问题,要求生成合同信息文档,有word模板,只需要把固定的数据填充进去,进行字段替换,组装生成并下载文档。话不多说,开始吧
1、安装所需插件
npm install docxtemplater pizzip jszip-utils
- docxtemplater: docxtemplater 是一个邮件合并工具,以编程方式使用并处理条件、循环,并且可以扩展以插入图像、html或表格,这个插件可以通过预先写好的word,excel等文件模板生成对应带数据的文件。
- pizzip: 一个使用 Javascript 创建、读取和编辑 .zip 文件的库,具有可爱且简单的 API,这个插件用来创建,读取或编辑.zip的文件(同步的,还有一个插件是jszip,异步的)。
- jszip-utils: 与jszip/pizzip一起使用,jszip-utils 提供一个getBinaryContent(path, data)接口,path即是文件的路径,支持AJAX get请求,data为读取的文件内容。
2、准备模板文件
准备一个word文档,在需要替换文字的地方添加标识:
如图,此处在红色方框中有两个标识{aname}
和{bname}
,运行时代码会替换掉此处的标识。
docxtemplater基本语法:
{%img} 图片
{#list}{/list} 循环、if判断
{#list}{/list}{^list}{/list} if else
{str} 文字
但是如果遇到需要打对勾的地方,那就要进行判断了
实现如下:传入的变量allowed
为true
时,才会渲染打√
的效果。此时使用if else语句,在后面加入{^allowed}{/allowed}
。
表格渲染的话就需要用到循环语句,可以在上面docxtemplater基本语法找到:
渲染这个表格的话,需要在每行的开头和结尾加入表格数据数组变量名称,比我把表格数组放在了table
数组中所以循坏就要写成{#table} ... ... {/table}
。
3、准备代码
<script>
import Docxtemplater from 'docxtemplater';
import PizZip from 'pizzip';
import JSZipUtils from 'jszip-utils';
export default {
methods: {
async generateWordDocument() {
// 导入模板数据
const templateURL = process.env.BASE_URL + 'template.docx';
const templateContent = await JSZipUtils.getBinaryContent(templateURL);
const zip = new PizZip(templateContent);
const doc = new Docxtemplater().loadZip(zip);
// 准备数据
const data = {
aname: "这是第一个名字", // 用实际的数据替代
bname: "这是第二个名字" // 用实际的数据替代
};
// 填充数据
doc.setData(data);
doc.render();
// 生成并下载文档
const outputBlob = doc.getZip().generate({ type: 'blob' });
const downloadLink = document.createElement('a');
downloadLink.href = URL.createObjectURL(outputBlob);
downloadLink.download = '交易合同.docx';
downloadLink.click();
}
}
};
</script>
先写一个Demo稍微测试一下,发现可以成功替换掉{aname}
和{bname}
两个变量,那么咱们就继续改造一下,把他变成一个组件,随时取用(包裹buttonname
变量的$t()
函数是国际化多语言函数,使用时根据自己的情况去除或者修改)。
<template>
<div>
<!-- 生成Word文档按钮 -->
<el-button :size="size" :style="styleobj" :type="type" @click="generateWordDocument">{{ $t(buttonname) }}</el-button>
</div>
</template>
<script>
import Docxtemplater from 'docxtemplater';
import PizZip from 'pizzip';
import JSZipUtils from 'jszip-utils';
import { purchaseorderDetail } from '@/api/order';
export default {
name: 'DownloadWord',
props: {
/**
* 查询id
*/
id: {
type: [String, Number],
},
/**
* 按钮大小尺寸
*/
size: {
type: String,
default: 'small'
},
/**
* word模板名字
*/
docname: {
type: String,
default: 'template.docx'
},
/**
* 生成下载文档名字
*/
downloadname: {
type: String,
default: '交易合同.docx'
},
/**
* 按钮style样式
*/
styleobj: {
type: Object,
default: () => ({
color: '#206266',
})
},
/**
* 按钮名称
*/
buttonname: {
type: String,
default: '下载合同'
},
/**
* 按钮类型
*/
type: {
type: String,
default: 'text'
}
},
data() {
return {
// 使用默认值初始化 wordData 对象
wordData: {}
}
},
methods: {
/**
* 生成 Word 文档并下载
* @async
* @author zhuzhihui
* @date 2023/08/29
*/
async generateWordDocument() {
try {
const res = await purchaseorderDetail(this.id);
if (res.status === 200) {
// 使用响应数据更新 wordData 属性
this.wordData = this.prepareWordData(res.data);
// 加载并渲染模板
const doc = await this.loadAndRenderTemplate();
// 生成并下载文档
this.generateAndDownloadDocument(doc);
} else {
this.$message.error(res.message);
}
} catch (error) {
console.error(error);
this.$message.error('生成文档时出错。');
}
},
/**
* 准备填充到 Word 模板的数据
* @param {Object} data - 响应数据
* @returns {Object} - 填充数据
* @author zhuzhihui
* @date 2023/08/29
*/
prepareWordData(data) {
// 准备并返回 wordData 对象
const {
supplier_name,
purchase_name,
create_time,
buy_address,
departure,
destination,
documents,
more_less,
latest_ship_date,
terms_delivery,
commodity_inspection_agency,
commodity_inspection_report,
shipment_method,
partial_shipment,
commodity_inspection,
product_info // 商品信息
} = data;
const wordData = {
supplier_name, // 采购商名称
purchase_name, // 供应商名称
total_price: 0, // 总价
sea: shipment_method == 1, // 是否海运
train: shipment_method == 2, // 是否火车运输
air: shipment_method == 3, // 是否空运
truck: shipment_method == 4, // 是否卡车运输
transport: shipment_method == 5, // 是否联合运输
buy_address, // 收货地址
allowed: partial_shipment == 1, // 是否分批装运
not_allowed: partial_shipment != 1, // 是否不分批装运
inspect: commodity_inspection == 1, // 是否需要商检
departureBool: terms_delivery == 'FOB' || terms_delivery == 'FCA', // 是否[起运地/目的地]条款为FOB/FCA
destinationBool: terms_delivery != 'FOB' && terms_delivery != 'FCA', // 取反[起运地/目的地]条款为FOB/FCA
departure, // 起运地
destination, // 目的地
documents, // 单据
create_time, // 协议生成日期
more_less, // 短溢装比例
latest_ship_date, // 最迟到装运日期
terms_delivery, // 交货条条款
commodity_inspection_agency, // 商检机构
commodity_inspection_report, // 商检报告
table: [] // 表格数据
}
// 准备表格数据
wordData.total_price = 0
wordData.table = []
product_info.forEach(item => {
let cost_total_price = ( Number(item.buy_num) * Number(item.cost_price) ).toFixed(2)
wordData.total_price = (Number(wordData.total_price) + (Number(item.buy_num) * Number(item.cost_price))).toFixed(2)
wordData.table.push({
product_name: item.product_name,
spec: item.spec,
unit_name: item.unit_name,
buy_num: item.buy_num,
cost_price: item.cost_price,
cost_total_price
})
})
return wordData;
},
/**
* 加载并渲染 Word 模板
* @async
* @returns {Docxtemplater} - 加载渲染后的文档模板
* @author zhuzhihui
* @date 2023/08/29
*/
async loadAndRenderTemplate() {
const templateURL = process.env.BASE_URL + this.docname;
const templateContent = await JSZipUtils.getBinaryContent(templateURL);
const zip = new PizZip(templateContent);
return new Docxtemplater().loadZip(zip);
},
/**
* 生成并下载文档
* @param {Docxtemplater} doc - 加载渲染后的文档模板
* @author zhuzhihui
* @date 2023/08/29
*/
generateAndDownloadDocument(doc) {
// 填充并渲染模板
doc.setData(this.wordData);
doc.render();
// 生成并下载文档
const outputBlob = doc.getZip().generate({ type: 'blob' });
const downloadLink = document.createElement('a');
downloadLink.href = URL.createObjectURL(outputBlob);
downloadLink.download = this.downloadname;
downloadLink.click();
// 重置 wordData 属性
this.wordData = {};
},
}
};
</script>
到此为止,一个下载word文档的按钮组件就大功告成了,从请求接口到产出文档一气呵成。