VUE使用docxtemplater导出word(带图片) 踩坑 表格循环空格 ,canvas.toDataURL图片失真模糊问题

参考:https://www.codetd.com/article/15219743

  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
  1. 关键代码JS部分
/**
 * 导出word文档(带图片) doc.js
 * 
 */
import Docxtemplater from 'docxtemplater'
import PizZip from 'pizzip'
import JSZipUtils from 'jszip-utils'
import { saveAs } from 'file-saver'

/**
 * 将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 = Buffer.from(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;
}

export const ExportBriefDataDocx = (tempDocxPath, data, fileName, imgSize) => {
    console.log(111, tempDocxPath, data, fileName, imgSize)
    //这里要引入处理图片的插件
    var ImageModule = require('docxtemplater-image-module-free');
    var expressions = require('angular-expressions')
    var assign = require('lodash/assign')
    var last = require("lodash/last")
    expressions.filters.lower = function (input) {
        // This condition should be used to make sure that if your input is
        // undefined, your output will be undefined as well and will not
        // throw an error
        if (!input) return input
        // toLowerCase() 方法用于把字符串转换为小写。
        return input.toLowerCase()
    }
    function angularParser(tag) {
        tag = tag
            .replace(/^.$/, 'this')
            .replace(/(|)/g, "'")
            .replace(/(|)/g, '"')
        const expr = expressions.compile(tag)
        return {
            get: function (scope, context) {
                let obj = {}
                const index = last(context.scopePathItem)
                const scopeList = context.scopeList
                const num = context.num
                for (let i = 0, len = num + 1; i < len; i++) {
                    obj = assign(obj, scopeList[i])
                }
                //word模板中使用 $index+1 创建递增序号
                obj = assign(obj, { $index: index })
                return expr(scope, obj)
            }
        }
    }
    JSZipUtils.getBinaryContent(tempDocxPath, (error, content) => {
        if (error) {
            console.log(error)
        }
        expressions.filters.size = function (input, width, height) {
            return {
                data: input,
                size: [width, height],
            };
        };
       
        let opts = {}

        opts = {
            //图像是否居中
            centered: true
        };
        opts.getImage = (chartId) => {
            //将base64的数据转为ArrayBuffer
            return base64DataURLToArrayBuffer(chartId);
        }
        opts.getSize = function (img, tagValue, tagName) {
            //自定义指定图像大小
            if (imgSize.hasOwnProperty(tagName)) {
                return imgSize[tagName];
            } else {
                return [200, 200];
            }
        }




        // 创建一个JSZip实例,内容为模板的内容        
        const zip = new PizZip(content)
        // 创建并加载 Docxtemplater 实例对象
        
        // 设置模板变量的值
        
		let doc = new Docxtemplater();
        doc.attachModule(new ImageModule(opts));
        doc.loadZip(zip);
        doc.setOptions({parser:angularParser});
        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)
    })
}

/**
 1. 将图片的url路径转为base64路径
 2. 可以用await等待Promise的异步返回
 3. @param {Object} imgUrl 图片路径
 */
export function getBase64Sync(imgUrl) {
    return new Promise(function (resolve, reject) {
        // 一定要设置为let,不然图片不显示
        let image = new Image();
        //图片地址
        image.src = imgUrl;
        // 解决跨域问题
        image.setAttribute("crossOrigin", '*');  // 支持跨域图片
        // 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);
        };
    })
}
  1. 导出函数
    vue 中引入上述js文件和方法
import {ExportBriefDataDocx,getBase64Sync} from './doc.js'
async ExportBriefDataDocx(){
            //图片转base64
			 let imgurl= await getBase64Sync('图片路径') 
			 let imgurl2= await getBase64Sync('图片路径')
			 let  data= {
				name:this.name,
				imgurl:this.imgurl,
				imgurl2:this.imgurl2
			}
	        let imgSize = {
			//控制导出的word图片大小,可自定义
	          imgurl:[200, 200],
	          imgurl2:[200, 200],
	        };
	        exportWord("/我的模板.docx", data, `${this.listname}.docx`, imgSize);
	        //docx模板放在public文件夹下,如果是vue2放在static下
	        // ExportBriefDataDocx("/static/我的模板.docx", data, `${listname}.docx`, imgSize);
	      }	
  1. 模板内容
    自己准备一个docx文档,然后里面标注好需替换的参数
    列表循环-- {#list}{name}{/list}
    单个参数–{}
    图片–{%imgUrl}
    在这里插入图片描述
    大概就这些,我也是从参考链接里看到的,至此基本能解决大部分问题,但是我还遇到了两个问题,所以自己记录补充一下
  2. 表格循环打印会多出空格
    在这里插入图片描述
    我想循环表格出来,但是输入数据,出来后实际是这样的
let  data= {
      ld:[{data1:1,data2:2},{data1:1,data2:2}],
    }      
    ExportBriefDataDocx("/static/wd.docx", data, `${listname}.docx`, imgSize);

在这里插入图片描述
多出了很多空格,我想着去掉模板中的换行符,像这样
在这里插入图片描述
我本来是想着这样就能少一行,就是正常表格了,但是其实报错了
message: ‘The filetype for this file could not be identified, is this file corrupted ?’, stack: ‘Error: The filetype for this file could not be ide…//./node_modules/jszip-utils/lib/index.js:110:25)’
大概就是类型不对,读不出来什么的
后来灵光一闪,想到参考链接里各种数据都是在表格里的,应该没问题,所以我就想着开始结束都放进去,就像这样
在这里插入图片描述
然后结果就对了
在这里插入图片描述

7.保存图片模糊问题 quality = 1也没啥用

改成这样,导出后的图片能经得起缩放

export function getBase64Sync(imgUrl,width,height) {
//传入你想要的宽高,最好大一点,这个不会影响导出后的大小,这里的宽高可以理解为分辨率,就是canvas绘制的时候的大小,imgSize里的才是导出到文档的大小
    return new Promise(function (resolve, reject) {
        // 一定要设置为let,不然图片不显示
        let image = new Image();
        //图片地址
        image.src = imgUrl;
        // 解决跨域问题
        image.setAttribute("crossOrigin", '*');  // 支持跨域图片
        // image.onload为异步加载
        image.onload = function () {
            let canvas = document.createElement("canvas");
            canvas.width = width*2;   
            canvas.height = height*2; //宽高放大两倍
            let context = canvas.getContext("2d");
            context.drawImage(image, 0, 0); 
            //图片后缀名
            let ext = image.src.substring(image.src.lastIndexOf(".") + 1).toLowerCase();
            //图片质量
            //let quality = 1;
            //转成base64
            let dataurl = canvas.toDataURL("image/"+ext);
           //返回
            resolve(dataurl);
        };
    })
}