vue3+docxtemplater导出word(包含表格、判断、echarts生成的图片)

1,004 阅读3分钟

1.安装依赖

// 安装 docxtemplater
npm install docxtemplater pizzip  --save
// 安装 jszip-utils
npm install jszip-utils --save 
// 安装 jszip
npm install jszip --save
// 安装 FileSaver
npm install file-saver --save
// 引入处理图片的插件1
npm install docxtemplater-image-module-free --save
// 引入处理图片的插件2
npm install angular-expressions --save

2.word模版参考官网:Demo of Docxtemplater with all modules active | docxtemplater

word的变量:{name}

对象:

image.png 数组:

image.png

image.png word的表格-数据结构为数组

image.png

判断:{#isShowChart} 内容 {/}

word的图片:{%chart}(这里其实是image 但我导出的是echarts转的图 就用的chart)

word模版位置:

image.png

3.图片处理及导出的公共方法

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'
import expressions from 'angular-expressions'

//图片的处理(这个方法是docxtemplater官网复制的 直接用了)
function base64Parser(tagValue) {
  const base64Regex =
  /^(?:data:)?image\/(png|jpg|jpeg|svg|svg\+xml);base64,/;

const validBase64 =
  /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
    if (
        typeof tagValue !== "string" ||
        !base64Regex.test(tagValue)
    ) {
        return false;
    }
    const stringBase64 = tagValue.replace(base64Regex, "");
    if (!validBase64.test(stringBase64)) {
        throw new Error(
            "Error parsing base64 data, your data contains invalid characters"
        );
    }
    // For nodejs, return a Buffer
    if (typeof Buffer !== "undefined" && Buffer.from) {
        return Buffer.from(stringBase64, "base64");
    }

    // For browsers, return a string (of binary content) :
    const 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;
}

//导出(参考大佬的方法)
export const ExportBriefDataDocx = (tempDocxPath, data, fileName, imgSize) => {
  expressions.filters.lower = function(input) {
    if (!input) return input
    return input.toLowerCase()
  }
 
  JSZipUtils.getBinaryContent(tempDocxPath, (error, content) => {
    if (error) {
      console.log(error)
    }
    expressions.filters.size = function(input, width, height) {
      return {
        data: input,
        size: [width, height]
      }
    }
    const imageOptions = {
      getImage(tagValue) {
          return base64Parser(tagValue);
      },
      //图片在word的大小
      getSize(img, tagValue, tagName, context) {
          return [650, 370];
      },
  };
    // 创建一个JSZip实例,内容为模板的内容
    const zip = new PizZip(content)
    // 创建并加载 Docxtemplater 实例对象
    // 设置模板变量的值
    const doc = new Docxtemplater()
    doc.attachModule(new ImageModule(imageOptions))
    doc.loadZip(zip)
    doc.setOptions({
      nullGetter: function() { // 设置空值 undefined 为''
        return ''
      }
    });
    data = flattenObject(data);
    doc.setData(data)
    try {
      // 呈现文档,会将内部所有变量替换成值,
      doc.render()
    } catch (error) {
      const e = {
        message: error.message,
        name: error.name,
        stack: error.stack,
        properties: error.properties
      }
      console.log('err', { error: e })
      // 当使用json记录时,此处抛出错误信息
      throw error
    }
    // 生成一个代表Docxtemplater对象的zip文件(不是一个真实的文件,而是在内存中的表示)
    const out = doc.getZip().generate({
      type: 'blob',
      mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
    })
    // 将目标文件对象保存为目标类型的文件,并命名
    saveAs(out, fileName)
  })
}

4.页面上的echarts组件及数据的处理

<template>
    <div>
     <a-card v-show="false">
        <div ref="chart" style="width: 1000px; height: 600px"></div>
      </a-card>
    </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted, nextTick,toRef,watch } from 'vue';
import { xxxApi } from '@/api';
import {message} from 'ant-design-vue';
import * as echarts from 'echarts';
import { ExportBriefDataDocx } from '@/utils/exportFile';

 const wordData = ref(null);
 const dataLoading = ref(false);
 const emit = defineEmits(['dataLoading']);
// 获取数据/word/generateDRGWordDate
const getData = async () => {
  try {
    dataLoading.value = true;
    emit('dataLoading',dataLoading.value);
    let req = { };
    const res = await xxxApi.getWordData(req);
    if (res.code === 200 && res.data) {
      wordData.value ={...res.data,isShowChart:res.data.departmentList.length>2};
        await nextTick(); // 确保DOM已经渲染完成
        renderChart();
    }
  } catch (err) {
    throw err;
  }finally{
    dataLoading.value = false;
    emit('dataLoading',dataLoading.value);
  }
};
const chart = ref(null);
let chartInstance = null;
// xx结余排行
const renderChart = () => {
  if (chart.value) {
    chartInstance = echarts.init(chart.value);
    chartInstance.clear();
    const departmentList = [...wordData.value.departmentList];
    let sortList = departmentList.sort((a, b) => b.balanceMoney - a.balanceMoney).filter(item=>item.department!=='all');
    const data = sortList.map(item => item.balanceMoney);
    const xData = sortList.map(item => item.department);
    const option = {
      title: {
        text: 'xx结余排行'
      },
      tooltip: {},
      grid: {
       // right: 20,
       bottom: '30%'
      },
      xAxis: {
        axisLabel: {
          interval: 0, //代表显示所有x轴标签显示
          rotate: 45 //代表逆时针旋转45度
        },
        data: xData,
        nameTextStyle: {
          height: 200
        }
      },
      yAxis: {},
      series: [
        {
          name: 'Sales',
          type: 'bar',
          label: {
            show: true,
            position: 'outside'
          },
          data: data
        },
        
      ],
      // 渲染的动画关掉 注意一些特殊的echarts
      animation: false
    };
    chartInstance.setOption(option);
  }
};

// 获取echarts图片
const getUrl = chartInstance => {
  const chartImage = chartInstance.getDataURL({
    type: 'png',
    pixelRatio: 2,
    backgroundColor: '#fff'
  });
  return chartImage;
};
const exportDocx = () => {
    wordData.value = { ...wordData.value, 
    isShowChart:wordData.value.departmentList.length > 2,
    // 获取echarts图片
    chart: getUrl(chartInstance),
 };

  const imgSize = [150, 150];
  // 导出为docx文件
  // 导出逻辑,例如使用插件导出为docx文件
  ExportBriefDataDocx(
    '/template.docx', // 模板路径
    wordData.value, // 数据
    'xxx分析报告.docx', // 文件名
    imgSize
  );
};
// 销毁ECharts实例
onUnmounted(() => {
  if (chartInstance != null && chartInstance.dispose) {
    chartInstance.dispose();
  }
});···
const toExport =async () => {
  // 最后一个echarts渲染结束时调用
  chartInstance.on('finished',exportDocx())
}
defineExpose({
  toExport
})
</script>