vue + docxtemplater 导出 word 文档

一、痛点

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;//转成base64let 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转成ArrayBufferreturn 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.allrenderData.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.hqwc.cn/news/212642.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PostgreSQL Patroni 3.0 新功能规划 2023年 纽约PG 大会 (音译)

开头还是介绍一下群&#xff0c;如果感兴趣PolarDB ,MongoDB ,MySQL ,PostgreSQL ,Redis, Oceanbase, Sql Server等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请联系 liuaustin3 &#xff0c;&#xff08;…

81基于matlab GUI的图像处理

基于matlab GUI的图像处理&#xff0c;功能包括图像颜色处理&#xff08;灰度图像、二值图像、反色变换、直方图、拉伸变换&#xff09;&#xff1b;像素操作&#xff08;读取像素、修改像素&#xff09;、平滑滤波&#xff08;均值平滑、高斯平滑、中值平滑&#xff09;、图像…

在arm 64 环境下使用halcon算法

背景&#xff1a; halcon&#xff0c;机器视觉领域神一样得存在&#xff0c;在windows上&#xff0c;应用得特别多&#xff0c; 但是arm环境下使用得很少。那如何在arm下使用halcon呢。按照官方说明&#xff0c;arm下只提供了运行时环境&#xff0c;并且需要使用价值一万多人民…

UE5 UI教程学习笔记

参考资料&#xff1a;https://item.taobao.com/item.htm?spma21n57.1.0.0.2b4f523cAV5i43&id716635137219&ns1&abbucket15#detail 基础工程&#xff1a;https://download.csdn.net/download/qq_17523181/88559312 1. 介绍 工程素材 2. 创建Widget UE5 UI系统的…

工业一体全国产方案,米尔T113核心板

入门级HMI屏作为嵌入式系统中重要组成部分&#xff0c;大部分都是串口屏&#xff1b;其功能简单、成本低等特点&#xff0c;使用历史悠久、应用广泛&#xff0c;而随着信息技术的快速发展&#xff0c;行业需求不断升级&#xff0c;工程师使用了大量串口屏后&#xff0c;发现串口…

请你说一下Vue中v-if和v-for的优先级谁更高

v-if 与 v-for简介 v-ifv-forv-if & v-for使用 v-if 与 v-for优先级比较 vue2 中&#xff0c;v-for的优先级高于v-if 例子进行分析 vue3 v-if 具有比 v-for 更高的优先级 例子进行分析 总结 在vue2中&#xff0c;v-for的优先级高于v-if在vue3中&#xff0c;v-if的优先级高…

加速软件开发:自动化测试在持续集成中的重要作用!

持续集成的自动化测试 如今互联网软件的开发、测试和发布&#xff0c;已经形成了一套非常标准的流程&#xff0c;最重要的组成部分就是持续集成&#xff08;Continuous integration&#xff0c;简称CI&#xff0c;目前主要的持续集成系统是Jenkins&#xff09;。 那么什么是持…

linux高级篇基础理论六(firewalld,防火墙类型,,区域,服务端口,富语言)

♥️作者&#xff1a;小刘在C站 ♥️个人主页&#xff1a; 小刘主页 ♥️不能因为人生的道路坎坷,就使自己的身躯变得弯曲;不能因为生活的历程漫长,就使求索的 脚步迟缓。 ♥️学习两年总结出的运维经验&#xff0c;以及思科模拟器全套网络实验教程。专栏&#xff1a;云计算技…

11.docker的网络-docker0的理解及bridge网桥模式的介绍与实例

1.docker0的基本理解 安装完docker服务后&#xff0c;我们首先查看一下宿主机的网络配置 ifconfig我们可以看到&#xff0c;docker服务会默认在宿主机上创建一个虚拟网桥docker0&#xff0c;该网桥网络的名字称为docker0。它在内核层连通了其他物理或者虚拟网卡&#xff0c;这…

【JavaSE】基础笔记 - 异常(Exception)

目录 1、异常的概念和体系结构 1.1、异常的概念 1.2、 异常的体系结构 1.3 异常的分类 2、异常的处理 2.1、防御式编程 2.2、异常的抛出 2.3、异常的捕获 2.3.1、异常声明throws 2.3.2、try-catch捕获并处理 3、自定义异常类 1、异常的概念和体系结构 1.1、异常的…

Vue3中如何响应式解构 props

目录 1&#xff0c;前言2&#xff0c;解决2.1&#xff0c;利用插件&#xff0c;实现编译时转换2.2&#xff0c;toRef 和 toRefs 1&#xff0c;前言 Vue3 中为了保持响应性&#xff0c;始终需要以 props.x 的方式访问这些 prop。这意味着不能够解构 defineProps 的返回值&#…

新王加冕,GPT-4V 屠榜视觉问答

当前&#xff0c;多模态大型模型&#xff08;Multi-modal Large Language Model, MLLM&#xff09;在视觉问答&#xff08;VQA&#xff09;领域展现了卓越的能力。然而&#xff0c;真正的挑战在于知识密集型 VQA 任务&#xff0c;这要求不仅要识别视觉元素&#xff0c;还需要结…