1、配置word文件
注意:配置一个docx文件保存在public文件下。比如:信息模版.docx

2、安装插件
"docxtemplater": "^3.67.5",
"file-saver": "^2.0.5",
"docx-preview": "^0.3.7",
"pizzip": "^3.2.0",
"docxtemplater-image-module-free": "^1.1.1",
"echarts": "^6.0.0",
3、引入插件
import { saveAs } from "file-saver";
import { renderAsync } from "docx-preview";
// 导入图片模块(用于插入 echarts 图表)
import ImageModule from "docxtemplater-image-module-free";
// 导入 echarts
import * as echarts from "echarts";
4、加载Word模板文件
const fileName = "info.docx";
const response = await fetch(`/${fileName}`);
if (!response.ok) {
throw new Error(`模板文件不存在,状态码:${response.status}`);
}
const arrayBuffer = await response.arrayBuffer();
5、echarts 格式转换方法
const 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;
};
6、初始化docxtemplater
const PizZip = require("pizzip");
const Docxtemplater = require("docxtemplater");
const zip = new PizZip(arrayBuffer);
// 创建图片模块(用于插入 ECharts 图表)
const imageModuleOptions = {
centered: true, // 图片居中,在 word 模板中定义方式为 {%%image}
fileType: "docx",
getImage: (chartId) => {
return base64DataURLToArrayBuffer(chartId);
},
getSize: () => {
return [600, 300]; // 图片尺寸:宽度 600px,高度 300px
},
};
const imageModule = new ImageModule(imageModuleOptions);
console.log("✅ ImageModule 已创建");
7、创建 Docxtemplater 实例
const doc = new Docxtemplater(zip, {
modules: [imageModule], // 启用图片模块
nullGetter: () => "",
});
8、准备图表数据(statisticsList)
const statisticsList = [
{
sensorName: '风速 1',
average: 25.5,
maximum: 32.2,
minimum: 18.8,
sum: 51
},
{
sensorName: '大气压 1',
average: 10.6,
maximum: 10.6,
minimum: 10.6,
sum: 10.6
},
{
sensorName: '温度',
average: 13.99,
maximum: 9.9,
minimum: 10.0,
sum: 1566.6
},
{
sensorName: '湿度',
average: 11.2,
maximum: 9.9,
minimum: 10.0,
sum: 1254.2
},
{
sensorName: '温度 1',
average: 13.99,
maximum: 9.9,
minimum: 10.0,
sum: 1566.6
},
{
sensorName: '湿度 1',
average: 11.2,
maximum: 9.9,
minimum: 10.0,
sum: 1254.2
},
{
sensorName: '东北温度',
average: 13.99,
maximum: 9.9,
minimum: 10.0,
sum: 1566.6
}
];
9、生成 ECharts 图表
let chartImageBase64 = null;
if (statisticsList.length > 0) {
try {
// 创建临时 div 用于渲染图表
const chartContainer = document.createElement("div");
chartContainer.style.width = "800px";
chartContainer.style.height = "400px";
chartContainer.style.position = "absolute";
chartContainer.style.left = "-9999px";
document.body.appendChild(chartContainer);
// 初始化 echarts 实例
const chart = echarts.init(chartContainer);
// 准备图表数据
const categories = statisticsList.map(
(item) => item.sensorName || ""
);
const averageData = statisticsList.map(
(item) => item.average || 0
);
const maximumData = statisticsList.map(
(item) => item.maximum || 0
);
const minimumData = statisticsList.map(
(item) => item.minimum || 0
);
// 配置图表选项
const option = {
title: {
text: "数据统计图表",
left: "center",
},
tooltip: {
trigger: "axis",
},
legend: {
data: ["平均值", "最大值", "最小值"],
bottom: 0,
},
xAxis: {
type: "category",
data: categories,
axisLabel: {
rotate: 45,
interval: 0,
},
},
yAxis: {
type: "value",
},
series: [
{
name: "平均值",
type: "line",
data: averageData,
smooth: true,
},
{
name: "最大值",
type: "line",
data: maximumData,
smooth: true,
},
{
name: "最小值",
type: "line",
data: minimumData,
smooth: true,
},
],
};
chart.setOption(option);
// 等待图表渲染完成
await new Promise((resolve) => setTimeout(resolve, 500));
// 获取图表的 base64 图片
chartImageBase64 = chart.getDataURL({
pixelRatio: 2, // 导出的图片分辨率比例,默认为 1
backgroundColor: "#fff",
});
console.log(
"✅ ECharts 图表已生成,base64 长度:",
chartImageBase64.length
);
// 清理
chart.dispose();
document.body.removeChild(chartContainer);
} catch (error) {
console.error("生成 ECharts 图表失败:", error);
}
}
10、准备图片数据用于插入到 Word 文档)
const imageList = [];
if (chartImageBase64) {
imageList.push({
image: chartImageBase64,
});
}
11、准备模板数据(只包含图表)
const data = {
// ECharts 图表图片列表(使用 {%%image} 标签)
chartList: imageList,
};
12、设置数据并渲染
doc.setData(data);
doc.render();
13、生成文档
const zipData = doc.getZip();
const docContent = zipData.generate({
type: "uint8array",
mimeType:
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
});
14、生成 blob
const blob = new Blob([docContent], {
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
});
this.wordBlob = blob;
this.downloadFileName = `信息-${new Date().getTime()}.docx`;
15、转换为 ArrayBuffer 用于预览
const outputArrayBuffer = await blob.arrayBuffer();
this.loading = false;
16、等待 DOM 更新后再渲染
await this.$nextTick();
17、使用 docx-preview 在页面上渲染
if (this.$refs.wordContent) {
try {
this.$refs.wordContent.innerHTML = "";
await renderAsync(
outputArrayBuffer,
this.$refs.wordContent,
null,
{
className: "docx-wrapper",
inWrapper: true,
ignoreWidth: false,
ignoreHeight: false,
ignoreFonts: false,
breakPages: true,
ignoreLastRenderedPageBreak: true,
experimental: false,
trimXmlDeclaration: true,
useBase64URL: false,
useMathMLPolyfill: true,
showChanges: false,
showComments: false,
showInserted: true,
showDeleted: false,
}
);
console.log("文档渲染成功");
this.$message.success("Word 文档生成并显示成功!");
} catch (renderError) {
console.error("文档渲染失败:", renderError);
this.$refs.wordContent.innerHTML = `
<div style="padding: 40px; text-align: center; color: #f56c6c;">
<p style="font-size: 16px; margin-bottom: 10px;">文档渲染失败</p>
<p style="font-size: 14px; color: #999;">错误信息: ${
renderError.message || "未知错误"
}</p>
<p style="font-size: 14px; color: #999; margin-top: 10px;">您可以点击"下载文档"按钮下载 Word 文件查看</p>
</div>
`;
this.$message.warning("文档预览失败,但可以下载查看");
}
}
18、下载函数
downloadWord() {
if (this.wordBlob && this.downloadFileName) {
saveAs(this.wordBlob, this.downloadFileName);
this.$message.success("文档下载成功!");
} else {
this.$message.warning("文档尚未生成,请先生成文档");
}
},
19、全部代码:
<template>
<div class="word-container">
<div class="word-card">
<div class="header-actions">
<el-button type="primary" @click="downloadWord" :disabled="!wordBlob">
<i class="el-icon-download"></i> 下载文档
</el-button>
<el-button @click="generateWord" :loading="loading">
<i class="el-icon-refresh"></i> 重新生成
</el-button>
</div>
<div v-if="loading" class="loading">
<i class="el-icon-loading"></i>
<p>正在生成 Word 文档...</p>
</div>
<div v-else-if="!wordBlob" class="error-message">
<p>文档生成失败,请重试</p>
</div>
<div v-else ref="wordContent" class="word-content"></div>
</div>
</div>
</template>
<script>
import { saveAs } from "file-saver";
import { renderAsync } from "docx-preview";
// 导入图片模块(用于插入 echarts 图表)
import ImageModule from "docxtemplater-image-module-free";
// 导入 echarts
import * as echarts from "echarts";
export default {
name: "InfoPage",
data() {
return {
loading: false,
wordBlob: null,
downloadFileName: "",
};
},
mounted() {
this.generateWord();
},
methods: {
async generateWord() {
try {
this.loading = true;
// 1. 加载Word模板文件
const fileName = "info.docx";
const response = await fetch(`/${fileName}`);
if (!response.ok) {
throw new Error(`模板文件不存在,状态码:${response.status}`);
}
const arrayBuffer = await response.arrayBuffer();
// echarts 格式转换方法
const 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;
};
// 2. 初始化docxtemplater
const PizZip = require("pizzip");
const Docxtemplater = require("docxtemplater");
const zip = new PizZip(arrayBuffer);
// 创建图片模块(用于插入 ECharts 图表)
const imageModuleOptions = {
centered: true, // 图片居中,在 word 模板中定义方式为 {%%image}
fileType: "docx",
getImage: (chartId) => {
return base64DataURLToArrayBuffer(chartId);
},
getSize: () => {
return [600, 300]; // 图片尺寸:宽度 600px,高度 300px
},
};
const imageModule = new ImageModule(imageModuleOptions);
console.log("✅ ImageModule 已创建");
// 3. 创建 Docxtemplater 实例
const doc = new Docxtemplater(zip, {
modules: [imageModule], // 启用图片模块
nullGetter: () => "",
});
console.log("✅ Docxtemplater 实例已创建,包含 ImageModule");
// 4. 准备图表数据(statisticsList)
const statisticsList = [
{
sensorName: '风速 1',
average: 25.5,
maximum: 32.2,
minimum: 18.8,
sum: 51
},
{
sensorName: '大气压 1',
average: 10.6,
maximum: 10.6,
minimum: 10.6,
sum: 10.6
},
{
sensorName: '温度',
average: 13.99,
maximum: 9.9,
minimum: 10.0,
sum: 1566.6
},
{
sensorName: '湿度',
average: 11.2,
maximum: 9.9,
minimum: 10.0,
sum: 1254.2
},
{
sensorName: '温度 1',
average: 13.99,
maximum: 9.9,
minimum: 10.0,
sum: 1566.6
},
{
sensorName: '湿度 1',
average: 11.2,
maximum: 9.9,
minimum: 10.0,
sum: 1254.2
},
{
sensorName: '东北温度',
average: 13.99,
maximum: 9.9,
minimum: 10.0,
sum: 1566.6
}
];
// 5. 生成 ECharts 图表
let chartImageBase64 = null;
if (statisticsList.length > 0) {
try {
// 创建临时 div 用于渲染图表
const chartContainer = document.createElement("div");
chartContainer.style.width = "800px";
chartContainer.style.height = "400px";
chartContainer.style.position = "absolute";
chartContainer.style.left = "-9999px";
document.body.appendChild(chartContainer);
// 初始化 echarts 实例
const chart = echarts.init(chartContainer);
// 准备图表数据
const categories = statisticsList.map(
(item) => item.sensorName || ""
);
const averageData = statisticsList.map(
(item) => item.average || 0
);
const maximumData = statisticsList.map(
(item) => item.maximum || 0
);
const minimumData = statisticsList.map(
(item) => item.minimum || 0
);
// 配置图表选项
const option = {
title: {
text: "数据统计图表",
left: "center",
},
tooltip: {
trigger: "axis",
},
legend: {
data: ["平均值", "最大值", "最小值"],
bottom: 0,
},
xAxis: {
type: "category",
data: categories,
axisLabel: {
rotate: 45,
interval: 0,
},
},
yAxis: {
type: "value",
},
series: [
{
name: "平均值",
type: "line",
data: averageData,
smooth: true,
},
{
name: "最大值",
type: "line",
data: maximumData,
smooth: true,
},
{
name: "最小值",
type: "line",
data: minimumData,
smooth: true,
},
],
};
chart.setOption(option);
// 等待图表渲染完成
await new Promise((resolve) => setTimeout(resolve, 500));
// 获取图表的 base64 图片
chartImageBase64 = chart.getDataURL({
pixelRatio: 2, // 导出的图片分辨率比例,默认为 1
backgroundColor: "#fff",
});
console.log(
"✅ ECharts 图表已生成,base64 长度:",
chartImageBase64.length
);
// 清理
chart.dispose();
document.body.removeChild(chartContainer);
} catch (error) {
console.error("生成 ECharts 图表失败:", error);
}
}
// 6. 准备图片数据(用于插入到 Word 文档)
const imageList = [];
if (chartImageBase64) {
imageList.push({
image: chartImageBase64,
});
}
// 7. 准备模板数据(只包含图表)
const data = {
// ECharts 图表图片列表(使用 {%%image} 标签)
chartList: imageList,
};
// 8. 设置数据并渲染
doc.setData(data);
doc.render();
console.log("✅ 模板渲染完成");
// 9. 生成文档
const zipData = doc.getZip();
const docContent = zipData.generate({
type: "uint8array",
mimeType:
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
});
// 10. 生成 blob
const blob = new Blob([docContent], {
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
});
this.wordBlob = blob;
this.downloadFileName = `信息-${new Date().getTime()}.docx`;
// 11. 转换为 ArrayBuffer 用于预览
const outputArrayBuffer = await blob.arrayBuffer();
this.loading = false;
// 12. 等待 DOM 更新后再渲染
await this.$nextTick();
// 13. 使用 docx-preview 在页面上渲染
if (this.$refs.wordContent) {
try {
this.$refs.wordContent.innerHTML = "";
await renderAsync(
outputArrayBuffer,
this.$refs.wordContent,
null,
{
className: "docx-wrapper",
inWrapper: true,
ignoreWidth: false,
ignoreHeight: false,
ignoreFonts: false,
breakPages: true,
ignoreLastRenderedPageBreak: true,
experimental: false,
trimXmlDeclaration: true,
useBase64URL: false,
useMathMLPolyfill: true,
showChanges: false,
showComments: false,
showInserted: true,
showDeleted: false,
}
);
console.log("文档渲染成功");
this.$message.success("Word 文档生成并显示成功!");
} catch (renderError) {
console.error("文档渲染失败:", renderError);
this.$refs.wordContent.innerHTML = `
<div style="padding: 40px; text-align: center; color: #f56c6c;">
<p style="font-size: 16px; margin-bottom: 10px;">文档渲染失败</p>
<p style="font-size: 14px; color: #999;">错误信息: ${
renderError.message || "未知错误"
}</p>
<p style="font-size: 14px; color: #999; margin-top: 10px;">您可以点击"下载文档"按钮下载 Word 文件查看</p>
</div>
`;
this.$message.warning("文档预览失败,但可以下载查看");
}
}
} catch (error) {
this.$message.error("生成 Word 文档失败: " + (error.message || "未知错误"));
this.loading = false;
// 处理模板错误
if (error.properties && error.properties.errors) {
const errors = error.properties.errors;
console.error(`发现 ${errors.length} 个错误:`, errors);
this.$message.error(`模板有 ${errors.length} 个错误,请检查控制台`);
}
}
},
downloadWord() {
if (this.wordBlob && this.downloadFileName) {
saveAs(this.wordBlob, this.downloadFileName);
this.$message.success("文档下载成功!");
} else {
this.$message.warning("文档尚未生成,请先生成文档");
}
},
},
};
</script>
<style lang="scss" scoped>
.word-container {
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background: linear-gradient(135deg, #74b9ff 0%, #0984e3 50%, #005f99 100%);
padding: 20px;
}
.word-card {
width: 100%;
max-width: 1200px;
background: #ffffff;
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
padding: 20px;
animation: fadeInUp 0.5s ease-out;
min-height: 600px;
display: flex;
flex-direction: column;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 400px;
color: #0984e3;
i {
font-size: 32px;
margin-bottom: 20px;
animation: rotating 2s linear infinite;
}
p {
font-size: 16px;
color: #666;
}
}
@keyframes rotating {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.header-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-bottom: 20px;
padding-bottom: 20px;
border-bottom: 1px solid #eee;
}
.word-content {
width: 100%;
min-height: 400px;
max-height: 80vh;
overflow-y: auto;
position: relative;
:deep(.docx-wrapper) {
background: #fff;
padding: 40px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
border-radius: 4px;
margin: 0 auto;
max-width: 800px;
min-height: 400px;
}
}
.error-message {
display: flex;
align-items: center;
justify-content: center;
min-height: 400px;
color: #f56c6c;
font-size: 16px;
}
@media (max-width: 768px) {
.word-card {
padding: 30px 20px;
max-width: 100%;
}
}
</style>
20、下载后的文档截图:


1406

被折叠的 条评论
为什么被折叠?



