vue + docxtemplater 导出 word 文档

news2024/11/16 7:29:31

一、痛点

word 导出 这种功能其实之前都是后端实现的,但最近有个项目没得后端。所以研究下前端导出。
ps: 前端还可以导出 pdf,但是其分页问题需要话精力去计算才可能实现,并且都不是很完善。可参考之前的文章:利用 html2canvas 和 jspdf 导出 echarts ( html页面 )为pdf

二、依赖安装

// 实现word下载的主要三方库
npm install docxtemplater pizzip  --save

// 文件操作;大佬们可以不需要,自己用fs、path等模块实现
npm install jszip jszip-utils --save 

// 文件存储
npm install file-saver --save

// 图片处理模块,没有图片需求可以不装
npm install docxtemplater-image-module-free  --save

三、创建导出word的公用方法 exportWord.js

ps:这个方法大同小异,网上很多

import PizZip from 'pizzip'
import Docxtemplater from 'docxtemplater'
import JSZipUtils from 'jszip-utils'
import { saveAs } from 'file-saver'

// 将图片地址转为base64,导出word图片只能是base64
export function getBase64Sync(imgUrl) {
  return new Promise(function (resolve, reject) {
    // 一定要设置为let,不然图片不显示
    let image = new Image();
    // 解决跨域问题
    image.crossOrigin = 'anonymous';
    //图片地址
    image.src = imgUrl;
    // image.onload为异步加载
    image.onload = function () {
      let canvas = document.createElement('canvas');
      canvas.width = image.width;
      canvas.height = image.height;
      let context = canvas.getContext('2d');
      context.drawImage(image, 0, 0, image.width, image.height);
      //图片后缀名
      let ext = image.src
        .substring(image.src.lastIndexOf('.') + 1)
        .toLowerCase();
      //图片质量
      let quality = 0.8;
      //转成base64
      let dataurl = canvas.toDataURL('image/' + ext, quality);
      //返回
      resolve(dataurl);
    };
  });
}
/**
 * 将base64格式的数据转为ArrayBuffer
 * @param {Object} dataURL base64格式的数据
 */
function base64DataURLToArrayBuffer(dataURL) {
  const base64Regex = /^data:image\/(png|jpg|jpeg|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;
}

/**
 * 导出word,支持图片
 * @param {Object} tempDocxPath 模板文件路径
 * @param {Object} wordData 导出数据
 * @param {Object} fileName 导出文件名
 * @param {Object} imgSize 预留,自定义图片尺寸 => 暂没使用
 */
export const exportWord = (tempDocxPath, wordData, fileName, imgSize) => {
  // 这里要引入处理图片的插件
  var ImageModule = require('docxtemplater-image-module-free');


  JSZipUtils.getBinaryContent(tempDocxPath, function (error, content) {
    if (error) {
      throw error;
    }

    // 图片处理
    let opts = {};
    opts = {
      centered: true, //图像是否居中,true:在word中图片居中
      getImage: (chartId) => { 
        // 将base64转成ArrayBuffer
        return base64DataURLToArrayBuffer(chartId);
      },
      //自定义指定图像大小,此处可动态调试各别图片的大小
      getSize: (img, tagValue, tagName) => {
        // tagName 是指我们自己定义图片使用的字段名,如path、url等
        // if (tagName === 'imgurl') return [700, 350]; //设置图片宽高,tagName :传入的变量
        // return [200, 150]; 
        if (Object.prototype.hasOwnProperty.call(imgSize, tagName)) {
          return imgSize[tagName];
        } else {
          return [150, 150];
        }
      }
    };
    // 创建一个PizZip实例,内容为模板的内容
    let zip = new PizZip(content);
    // 创建并加载docxtemplater实例对象
    let doc = new Docxtemplater();
    doc.attachModule(new ImageModule(opts));
    doc.loadZip(zip);
    // 设置模板变量的值
    doc.setData(wordData);
    try {
      // 用模板变量的值替换所有模板变量
      doc.render();
    } catch (error) {
      // 抛出异常
      let e = {
        message: error.message,
        name: error.name,
        stack: error.stack,
        properties: error.properties
      };
      console.log(JSON.stringify({ error: e }));
      throw error;
    }
    // 生成一个代表docxtemplater对象的zip文件(不是一个真实的文件,而是在内存中的表示)
    let out = doc.getZip().generate({
      type: 'blob',
      mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
    });
    // 将目标文件对象保存为目标类型的文件,并命名
    saveAs(out, fileName);
  });
}

四、组件中调用

前几章都是基础,调用才是重点。

1. 创建模板

导出word其实就是解析我们提供的模板,然后将对应字段填入,最新进行导出即可。所以,模板 至关重要

  • 创建.docx文件
    该文件只能直接创建为docx 或者 另存为docx ;不能直接修改后缀名。
  • vue2 将模板放在static文件下;vue3 将模板放在public文件下
2. 模板语法

在这里插入图片描述

  • 语法用 { } 即可
  • 普通字段直接填入字段名即可
  • 如果字段是图片地址,需要加上 %,例如:
{#imgList} 
    {%pathUrl}
{/imgList}

踩坑:图片这里我一直报错‘%imgUrl’,最后发现必须要换行写,而其他数组可以在一行写。

  • 遍历列表 以 {#list} 开头 … 列表元素字段名 … {/list} 结尾
list: [
 {name:'张三', age:'18'},
 {name:'李四', age:'28'}
]

模板: 在这里插入图片描述
导出实际结果:
在这里插入图片描述

3. 组件调用
<template>
  <div>
    <!-- 页面只有一个echarts 和 导出按钮 -->
    <div id="myChart6" :style="{ width: '800px', height: '800px' }"></div>
    <el-button type="primary" @click="exprodWord">导出word</el-button>
  </div>
</template>

<script>
  import * as echarts from 'echarts';
  import { onMounted } from 'vue'
  import { getBase64Sync, exportWord } from './exportFile'
  export default {
    name: 'WordTemplate',
    setup () {
      let myChartDom = null;

      const wordData = {
        title: '环境工业风险审核报告',
        des: '对于需要判断显示的要用{#isProblem}开始,{/isProblem}结束,isProblem的类型是Boolean,true的时候是显示。如下图,isFull==true的时候,才显示下面这句话',
        userList: [
          {
            indexNo: 1,
            name: '张三',
            age: '18',
            address: '上海',
            imgList: [
              {
                url: 'https://i.postimg.cc/qqcRNJ1y/b3c2e029c5deda297e29680e26a5c48c.jpg'
              },
              {
                url: 'https://i.postimg.cc/9Q5b3J7k/797c9c2bbf47b1ad4632670e508e0d5d.jpg'
              }
            ],
            status: 1,
          },
          {
            indexNo: 2,
            name: '李四',
            age: '28',
            address: '四川',
            imgList: [],
            status: 1,
          },
          {
            indexNo: 3,
            name: '王五',
            age: '38',
            address: '北京',
            imgList: [],
            status: 0,
          },
          {
            indexNo: 4,
            name: '张柳',
            age: '48',
            address: '成都',
            imgList: [],
            status: 0,
          }
        ]

      }
      const exprodWord = async () => { 
        const chartPath = getChartImg(); // 获取到echarts的图片地址
        const renderData = JSON.parse(JSON.stringify(wordData))
        renderData.chartPath = chartPath
        
        // 将图片转成base64是异步操作,需要等待图片base64返回,所以使用Promise.all
        renderData.userList = await Promise.all(
          renderData.userList.map(async item => {
            return {
              ...item,
              imgList: await Promise.all(
                item.imgList.map(async em => {
                  return {
                    ...em,
                    path: await getBase64Sync(em.url)
                  }
                })
              ) 
            };
          })
        )
        
        let imgSize = {
          imgurl: [200, 200], // 定义图片字段名为 'imgurl' 的尺寸, 该实例中没有图片字段名是imgurl,所以不生效
          chartPath: [1000, 800] // 定义图片字段名为 'chartPath' 的尺寸, 即该实例中的echarts图片
          // ... 更多
        }
        console.log('------------renderData', renderData)
        exportWord('template.docx', renderData, '环境工业风险审核报告.docx', imgSize)
      }
      
      // 基于准备好的dom,初始化echarts实例
      const initChart = () => {
        myChartDom = echarts.init(document.getElementById('myChart6'));
        // 绘制图表
        myChartDom.setOption({
          title: {
            text: 'ECharts 入门示例'
          },
          tooltip: {},
          xAxis: {
            data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
          },
          yAxis: {},
          series: [
            {
              name: '销量',
              type: 'bar',
              data: [5, 20, 36, 10, 10, 20]
            }
          ]
        });
      }
      // 获取图表base64图
      const getChartImg = () => {
        return myChartDom.getDataURL({
          pixelRatio: 2, // 解决模糊
          backgroundColor: '#fff'
        });
      }

      onMounted(() => {
        initChart()
      })
      return {
        exprodWord
      }
    }
  }
</script>

<style lang='scss' scoped>
</style>

注意:导出操作可能涉及异步操作,请多使用 Promise.all、nextTick等异步方法,尽量少使用setTimeout

五、导出word 结果

在这里插入图片描述



文章仅为本人学习过程的一个记录,仅供参考,如有问题,欢迎指出!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1243068.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Flink 替换 Logstash 解决日志收集丢失问题

在某客户日志数据迁移到火山引擎使用 ELK 生态的案例中&#xff0c;由于客户反馈之前 Logstash 经常发生数据丢失和收集性能较差的使用痛点&#xff0c;我们尝试使用 Flink 替代了传统的 Logstash 来作为日志数据解析、转换以及写入 ElasticSearch 的组件&#xff0c;得到了该客…

PasteNow for mac剪贴板工具

PasteNow 是一款简单易用的剪贴板管理工具&#xff0c;可帮助用户快速存储和管理剪贴板上的文本和图片内容。用户可以使用 PasteNow 软件快速将文本内容保存到不同的笔记或页面中&#xff0c;也可以方便地将剪贴板上的图片保存到本地或分享给其他应用程序。 此外&#xff0c;P…

Java互联网+医院智能导诊系统源码 自动兼容H5小程序、Uniapp

随着信息和通信技术的进步,智能和移动技术越来越普遍,尤其在医疗保健领域,一些新型卫生信息系统被不断开发出来支持医院和其他卫生保健组织的管理和运作。 智能导诊系统是嵌入到医院和医疗保健卫生中心 HIS系统中的一种专门导诊系统,通过智能语音交互的方式帮助用户完成导诊、分…

XC2303 PFM 升压 DC-DC 变换器 SOT23-3封装 体积小 外围简单 适合小电流产品

XC2303系列产品是一种高效率、低纹波、工作频率高的 PFM 升压 DC-DC 变换器。XC2303系列产品仅需要四个元器,就可完成将低输入的电池电压变换升压到所需的工作电压&#xff0c;非常适合于便携式1~4 节普通电池应用的场合。 电路采用了高性能、低功耗的参考电压电路结构&#xf…

深度学习常见激活函数:ReLU,sigmoid,Tanh,softmax,Leaky ReLU,PReLU,ELU整理集合,应用场景选择

文章目录 1、ReLU 函数&#xff08;隐藏层中是一个常用的默认选择&#xff09;1.1 优点1.2 缺点 2、sigmoid 函数2.1 优点2.2 缺点 3、Tanh 函数3.1 优点3.2 缺点 4、softmax 函数&#xff08;多分类任务最后一层都会使用&#xff09;5、Leaky ReLU 函数5.1 优点5.2 缺点 6、PR…

【JavaEE初阶】 JavaScript相应的WebAPI

文章目录 &#x1f332;WebAPI 背景知识&#x1f6a9;什么是 WebAPI&#x1f6a9;什么是 API &#x1f38d;DOM 基本概念&#x1f6a9;什么是 DOM&#x1f6a9;DOM 树 &#x1f340;获取元素&#x1f6a9;querySelector&#x1f6a9;querySelectorAll &#x1f384;事件初识&am…

WordPress用sql命令批量删除所有文章

有时我们需要将一个网站搬迁到另一个服务器。我们只想保留网站的模板样式&#xff0c;而不需要文章内容。一般情况下我们可以在后台删除已发表的文章&#xff0c;但如果有很多文章&#xff0c;我们则需要一次删除所有文章。 WordPress如何批量删除所有文章 进入网站空间后台&a…

chrome F12 performance 性能分析

本文主要是介绍chrome F12 performance 性能分析&#xff0c;对大家解决编程问题具有一定的参考价值&#xff0c;需要的程序猿们随着小编来一起学习吧&#xff01; 页面加载速度慢&#xff0c;到底是多少秒&#xff0c;瓶颈在哪里&#xff1f; 前端性能工具Chrome performance…

学生心目中的好老师

在教育的世界里&#xff0c;一个好老师可以改变一个学生的人生轨迹。他们不仅传授知识&#xff0c;更是引导学生发现自己的潜力&#xff0c;激发他们对未来的憧憬。那么&#xff0c;如何成为一名学生心目中的好老师呢&#xff1f; 拥有一颗热爱教育的心。深深的热爱着教育事业&…

TikTok美区本土店铺如何做好IP隔离?

为什么要进行IP隔离呢&#xff1f;因为我们无法在国内直接运营Shopee、TikTok、Lazada等平台的本土店&#xff0c;平台识别出店铺登录IP非本土IP&#xff0c;则容易导致店铺风控、被标记为伪本土店&#xff0c;影响店铺经营。 TikTok美区店铺的IP隔离方法和Shopee本土店一致&a…

数字人源码部署一定要找数字人源头工厂公司

今年入局数字人产业的公司都已经赚的盆满钵满,有很多播主还在叫嚣着数字人没用没用。可谓眼光短浅&#xff0c;典型的吃不到葡萄说葡萄说。没用的话&#xff0c;国内所有的互联网巨头为什么都在跑步进场呢。今天我们要讲的是数字人二级市场的产业&#xff0c;所谓二级市场就是中…

SWOT是什么意思?SWOT分析必备的10款软件,别说你还不知道!

在今天快速变化的商业环境中&#xff0c;保持竞争优势并做出明智的决策至关重要。无论你是经验丰富的高管、企业家还是专注的团队领导者&#xff0c;战略思维都是必不可少的。在这个过程中的一个重要工具是SWOT分析软件。 正确的SWOT分析工具可以决定商业战略是否能够创造有意…

Vue3-provide和inject

作用和场景&#xff1a;顶层组件向任意的底层组件传递数据和方法&#xff0c;实现跨层组件通信 跨层传递普通数据&#xff1a; 1.顶层组件通过provide函数提供数据 2.底层组件通过inject函数获取数据 既可以传递普通数据&#xff0c;也可以使用ref传递响应式数据&#xff08…

Docker Swarm总结+service创建和部署、overlay网络以及Raft算法(2/3)

博主介绍&#xff1a;Java领域优质创作者,博客之星城市赛道TOP20、专注于前端流行技术框架、Java后端技术领域、项目实战运维以及GIS地理信息领域。 &#x1f345;文末获取源码下载地址&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&#x1f3fb;…

Postman接口测试工具完整教程

前言 作为软件开发过程中一个非常重要的环节&#xff0c;软件测试越来越成为软件开发商和用户关注的焦点。完善的测试是软件质量的保证&#xff0c;因此软件测试就成了一项重要而艰巨的工作。要做好这项工作当然也绝非易事。 第一部分&#xff1a;基础篇 postman:4.5.1 1.安…

某基金公司赵哥“逆袭”了!!!

赵哥&#xff0c;在上海一家基金公司做运维主管。 平时工作的首要任务&#xff0c;就是保障公司各项信息系统的安全运行。 万一系统运行中出现了一些重要问题&#xff0c;他还要负责进行调查、记录与汇报... 总之&#xff0c;责任很重&#xff0c;该说不说&#xff0c;搞不好…

虚拟机系列:windows 虚拟机相关功能、组件梳理

一. 简介 英文名称中文名称说明Container容器Guarded Host受保护的主机利用远程证明创建并运行受防护的虚拟机Hyper-V├Hyper-V Management ToolsHyper-V 管理工具包含 GUI 管理工具和 Power Shell 的 Hyper-V 模块└Hyper-V PlatformHyper-V 平台├Hyper-V HypervisorHyper-V …

[C++ 从入门到精通] 13.派生类、调用顺序、继承方式、函数遮蔽

&#x1f4e2;博客主页&#xff1a;https://loewen.blog.csdn.net&#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;本文由 丶布布原创&#xff0c;首发于 CSDN&#xff0c;转载注明出处&#x1f649;&#x1f4e2;现…

程序员进阶高管指南,看懂工资最少加5k

从象牙塔毕业跨入社会大染缸&#xff0c;很多人都跟我谈过他们的职业困惑&#xff0c;其中有一些刚刚毕业&#xff0c;有些人已经工作超过10年。基本上是围绕着怎样持续提升&#xff0c;怎样晋升为高级管理者。那么这篇文章&#xff0c;我就来谈一谈程序员到高管的跃升之路。 …

XC3320 离线式、无电感交流输入线性稳压器 可替代KP3310 固定5V输出电压

XC3320 是一款紧凑型无电感设计的离线式线性稳压器。XC3320 可获得 5V输出电压。XC3320 是一种简单可靠的获得偏置供电的离线式电源解决方案。XC3320 集成了 650V 功率 MOSFET&#xff0c;启动控制电路,VDD 电压控制电路,AC 交流信号同步检测电路&#xff0c;低压差稳压器等。该…