前期准备
1、按照依赖的插件docxtemplater、pizzip、jszip-utils、jszip、FileSaver、docxtemplater-image-module-free。
//-- 安装 docxtemplater
npm install docxtemplater pizzip --save
//-- 安装 jszip-utils
npm install jszip-utils --save
//-- 安装 jszip
npm install jszip --save
//-- 安装 FileSaver
npm install file-saver --save
//安装 docxtemplater-image-module-free
npm install --save docxtemplater-image-module-free
2、引入依赖
import docxtemplater from 'docxtemplater'
import PizZip from 'pizzip'
import JSZipUtils from 'jszip-utils'
import {saveAs} from 'file-saver'
import ImageModule from 'docxtemplater-image-module-free '
注:若引入docxtemplater-image-module-free 报错时,可尝试使用require方法引入。
let ImageModule = require('docxtemplater-image-module-free');
3、代码-父组件
我是把table通过插槽的方式注入到子组件的
<template>
<div class="box">
<word
exportFileName="测试666"
templateFileName="templateFile"
:wordData="wordData"
:chartOptions="chartOptions"
>
<template v-slot:table>
<table cellspacing="0">
<caption>
标题
</caption>
<tr>
<td class="key-name">标题1</td>
<td colspan="3">{{ wordData.a }}</td>
</tr>
<tr>
<td class="key-name">标题2</td>
<td colspan="3">{{ wordData.b }}</td>
</tr>
<tr>
<td class="key-name">标题3</td>
<td colspan="3">{{ wordData.c }}</td>
</tr>
<tr>
<td class="key-name">标题4</td>
<td>{{ wordData.d }}</td>
<td class="key-name">标题6</td>
<td>{{ wordData.f }}</td>
</tr>
<tr>
<td class="key-name">标题5</td>
<td>{{ wordData.e }}</td>
<td class="key-name">标题7</td>
<td>{{ wordData.g }}</td>
</tr>
<tr>
<td colspan="4" class="key-name options">审批意见</td>
</tr>
<tr>
<td colspan="4" class="options">
{{ wordData.h }}
</td>
</tr>
<tr v-for="(item, index) in wordData.list" :key="index">
<td class="key-name">姓名</td>
<td>{{ item.name }}</td>
<td class="key-name">电话</td>
<td>{{ item.phone }}</td>
</tr>
</table>
</template>
</word>
</div>
</template>
<script>
// 子组件
import word from "@/components/exportWord.vue";
export default {
components: { word },
data() {
return {
// 导出word
wordData: {
a: "这是标题1内容",
b: "这是标题2内容",
c: "这是标题3内容",
d: "这是标题4内容",
e: "这是标题5内容",
f: "这是标题6内容",
g: "这是标题7内容",
h: "同意",
list: [
{ name: "王五", phone: "18888888888" },
{ name: "王五", phone: "18888888888" },
{ name: "王五", phone: "18888888888" },
{ name: "王五", phone: "18888888888" },
],
},
// 图表
chartOptions: [
// echarts图表的option
// 第一个图表
{
title: {
text: "某站点用户访问来源",
subtext: "纯属虚构",
left: "center",
},
tooltip: {
trigger: "item",
},
legend: {
orient: "vertical",
left: "left",
},
series: [
{
name: "访问来源",
type: "pie",
radius: "50%",
data: [
{ value: 1048, name: "搜索引擎" },
{ value: 735, name: "直接访问" },
{ value: 580, name: "邮件营销" },
{ value: 484, name: "联盟广告" },
{ value: 300, name: "视频广告" },
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: "rgba(0, 0, 0, 0.5)",
},
},
},
],
},
// 第二个图表
{
title: {
text: "未来一周气温变化",
subtext: "纯属虚构",
},
tooltip: {
trigger: "axis",
},
legend: {
data: ["最高气温", "最低气温"],
},
xAxis: {
type: "category",
boundaryGap: false,
data: ["周一", "周二", "周三", "周四", "周五", "周六", "周日"],
},
yAxis: {
type: "value",
axisLabel: {
formatter: "{value} °C",
},
},
series: [
{
name: "最高气温",
type: "line",
data: [10, 11, 13, 11, 12, 12, 9],
markPoint: {
data: [
{ type: "max", name: "最大值" },
{ type: "min", name: "最小值" },
],
},
markLine: {
data: [{ type: "average", name: "平均值" }],
},
},
{
name: "最低气温",
type: "line",
data: [1, -2, 2, 5, 3, 2, 0],
markPoint: {
data: [{ name: "周最低", value: -2, xAxis: 1, yAxis: -1.5 }],
},
markLine: {
data: [
{ type: "average", name: "平均值" },
[
{
symbol: "none",
x: "90%",
yAxis: "max",
},
{
symbol: "circle",
label: {
position: "start",
formatter: "最大值",
},
type: "max",
name: "最高点",
},
],
],
},
},
],
},
],
};
},
};
</script>
4、代码-子组件
<template>
<!-- 导出word模版 -->
<div class="approvalNo-or-opinionNo-list">
<el-button type="primary" size="small" @click="exportWord"
>点击下载</el-button
>
<div id="pdfDom">
<slot name="table"></slot>
</div>
<div
style="width: 600px; height: 300px; margin: 0 auto"
v-for="(item, index) in chartOptions"
:key="index"
>
<Echart :options="item" ref="chart"></Echart>
</div>
</div>
</template>
<script>
import docxtemplater from "docxtemplater";
import JSZipUtils from "jszip-utils";
import { saveAs } from "file-saver";
import JSZip from "jszip";
import PizZip from "pizzip";
import Echart from "@/components/Echarts.vue";
export default {
components: { Echart },
data() {
return {};
},
props: {
// 文档数据
wordData: {
type: Object,
default: {},
},
// 导出文件名字
exportFileName: {
type: String,
default: "word",
},
// 模版文件名字(文件需放在public目录下,文件格式为docx)
templateFileName: {
type: String,
default: "templateFile",
},
// 图表option 数组,可多个图表
chartOptions: {
type: Array,
default: [],
},
},
methods: {
// 导出echarts图片,格式转换
base64DataURLToArrayBuffer(dataURL) {
const base64Regex = /^data:image\/(png|jpg|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;
},
// 导出文档
exportWord() {
const ImageModule = require("docxtemplater-image-module-free");
const that = this;
// 存放echarts 图表 base64图片数组
const str = [];
// 读取并获得模板文件的二进制内容
JSZipUtils.getBinaryContent(
`${that.templateFileName}.docx`,
(error, content) => {
if (error) {
// 抛出异常
throw error;
}
const zip = new PizZip(content); // 创建一个JSZip实例,内容为模板的内容
const doc = new docxtemplater();
doc.loadZip(zip); // 创建并加载docxtemplater实例对象
// 如果有echarts图表
if (that.chartOptions.length > 0) {
for (const iterator of that.$refs["chart"]) {
// 这个getChartImg方法是我的echart组件中获取echarts图表图片的base64的dataurl
iterator.getChartImg((chartImg) => {
str.push({ image: chartImg });
});
}
// 图片处理
const opts = {};
opts.centered = true; // 图片居中,在word模板中定义方式为{%%image}
opts.fileType = "docx";
opts.getImage = (chartId) => {
return that.base64DataURLToArrayBuffer(chartId);
};
opts.getSize = () => {
return [600, 300];
};
const imageModule = new ImageModule(opts);
doc.attachModule(imageModule);
}
doc.setData({ ...that.wordData, chartlist: str }); // 设置模板变量的值
try {
doc.render(); // 用模板变量的值替换所有模板变量
} catch (error) {
const e = {
message: error.message,
name: error.name,
stack: error.stack,
properties: error.properties,
};
console.log(JSON.stringify({ error: e }));
throw error; // 抛出异常
}
// 生成一个代表docxtemplater对象的zip文件(不是一个真实的文件,而是在内存中的表示)
const out = doc.getZip().generate({
type: "blob",
mimeType:
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
});
// 将目标文件对象保存为目标类型的文件,并命名
saveAs(out, `${that.exportFileName}.docx`);
}
);
},
},
};
</script>
<style lang="less">
.approvalNo-or-opinionNo-list {
height: 350px;
overflow-y: auto;
padding-bottom: 20px;
border-bottom: 1px solid #ccc;
> #pdfDom {
table {
text-align: center;
border-bottom: 1px solid #ccc;
width: 93%;
margin: 0 auto;
font-family: "楷体", "楷体_GB2312";
caption {
font-size: 16px;
text-align: center;
line-height: 46px;
color: #333;
font-weight: bold;
}
td {
width: 25%;
height: 32px;
color: #666;
border-left: 1px solid #ccc;
border-top: 1px solid #ccc;
padding: 0 6px;
}
td:last-child {
border-right: 1px solid #ccc;
}
.key-name {
color: #333;
font-weight: 600;
}
.options {
padding: 10px;
text-align: justify;
text-indent: 2em;
}
}
}
}
</style>
5、getChartImg方法
// 获取图表base64图
getChartImg(callback: any) {
const that = this as any;
callback(that.myChart.getDataURL());
},
6、word模版
7、成功导出后的word