docxtemplater研究word模版---图片方式插入echarts图

该文章已生成可运行项目,

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、下载后的文档截图:

本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值