js 将html 某个dom 导出pdf,并处理分页

1、场景

由前端实现的报表(含echarts图和table表格),需要将整个报表进行导出下载,但不包括页面中的导航栏及其他元素。

2、问题

1)在导出pdf时 ,如果页面长度超过一页,插件无法只能识别分页,会将内容切割,如下所示:
在这里插入图片描述
2)目的:实现当前页放不下该页面元素时,插入空白元素将该元素下挤,实现分页但不分割内容

3、解决方案

通过计算dom元素的高度逐一拆分,动态增加dom元素使布局满足分页但不分割内容,效果如下:
在这里插入图片描述
注意:
1)计算高度时,切莫忘记margin;
2)由于通过dom.children 获取子元素,逐一累计并拆分,在添加空白元素节点后,获取的子集也会增加,注意循环中的计数单位自增,对空白元素做标记,如果进入到循环则排除;
3)由于本案例中是将页面元素直接做的变化,再pdf下载后,需移除所有增加的空白元素,恢复页面布局;
4)html2PDF插件中的dom元素参数,只能传页面中有的元素,如若隐藏或深度克隆但未添加到html中的元素,则识别不到,控制台会报错(隐藏了的元素也无法导出);
5)如果将克隆的元素添加到页面中,会丢失echarts图表,故此方案的操作对象为要导出的原dom对象;
6)若页面中的有重叠的元素,会影响高度的计算,修改getCurrentHeight即可;

4、代码如下:

1)methods

 printingPage() { //pdf下载事件
        $store.commit("smartReport/setData", { key: "loading", value: true });//开启loading
		let pdfDomTitle = PDFRef.value.querySelector(".title-bd");
        let btnsDom = pdfDomTitle.querySelectorAll(".report-btn");//获取需要隐藏的btns
        let pdfTitle = pdfDomTitle.querySelector(".title").innerHTML;//获取报表title
        btnsDom.forEach((item) => {
          item.style.display = "none";
        });
        pdfPaging("A4", PDFRef.value); //新增空白元素拼凑分页
        html2PDF(PDFRef.value, {
          jsPDF: {
            unit: "px",
            format: "A4",
          },
          imageType: "image/jpeg",
          success: function (pdf) {
            pdfRemoveAllPaging(); //恢复默认:去掉分页,恢复默认布局
            btnsDom.forEach((item) => {
              item.style.display = "block";
            });
            pdf.save(pdfTitle + ".pdf");//下载保存
            $store.commit("smartReport/setData", {//隐藏loading
              key: "loading",
              value: false,
            });
          },
        });
      },
 }; 
  1. pdfUtil.js
/**
 * pdfUtil.js
 * @type: 纸张类型:A4,A3
 * @dom:元素
 * **/
//厘米
let pageHeightByType = {
    'A4': 29.7,
    'A3': 42
}
//粗略估计 2020 A4纸张在1920屏幕下对应的高度
let pageHeight = 2020; //纸张高度:
let countHeight = 0; //累计高度
let countFunTimes = 0;//记录方法调用次数
//开始分页
export function pdfPaging(type, dom) { 
    countFunTimes++; 
    let domH = getCurrentHeight(dom);//当前元素的高度
    if (countFunTimes == 1) { //第一次进入该方法:判断纸张大小;是否需要分页
        pageHeight = pageHeightByType[type.toUpperCase()];
        pageHeight = pageHeight ? pageHeight : 29.7;//默认为A4大小
        pageHeight = getPageHeight(pageHeight)
        if (pageHeight > domH) {//内容高度未超过一页,不需要分页
            countHeight = domH
            return;
        }
    }
    //内容高度超过一页,需要分页
    let children = dom.children;
    //getAllChildrenHeight(dom)<=domH 排除行内布局,仅适用于不换行的行内布局
    if (children.length > 0 && getAllChildrenHeight(dom) <= (domH + 1)) {
        for (let i = 0; i < children.length; i++) {
            let child = children[i]
            if (child.getAttribute("class") == 'blockDIvId_pdf_self') continue;
            let childH = getCurrentHeight(child);
            if (childH > pageHeight) {
                pdfPaging(1, child)
            } else if (pageHeight > childH + countHeight) { //该元素高度childH+未分页元素高度countHeight 小于  页面高度
                countHeight += childH;
            } else if (child.children.length >= 2) {
                pdfPaging(1, child)
            } else { //该元素高度childH+未分页元素高度countHeight 超过 页面高度,在该元素之前插入空白div
                addChildBefore(child, pageHeight - countHeight);
                i++;//添加空白元素后,影响了children数组
                countHeight = childH;
            }
        }
    } else { //仅一个元素,无法拆分 
        let divH = domH > pageHeight ? domH % pageHeight : countHeight
        addChildBefore(dom, pageHeight - divH);
        return
    }  
}

function getCurrentHeight(curNode) {//获取当前元素高度
    let childStyle = curNode.currentStyle || window.getComputedStyle(curNode);
    let childMarginHeight = parseInt(childStyle.marginTop) + parseInt(childStyle.marginBottom)
    let childH = +curNode.offsetHeight + childMarginHeight;
    // console.log(childH, childMarginHeight)
    return childH
}

function getPageHeight(pageHeight) { //获取pdf页面高度
    let heightUint = 2020 / 29.7;//粗略估计 1910 为 A4纸张在1920屏幕下对应的高度
    let widthScale = window.innerWidth; // 当前窗口的宽度 
    // 尺寸换算
    const comparedHeight = widthScale / 1920; 
    // 通过比例计算高度, 
    return comparedHeight * heightUint * pageHeight;
}

function addChildBefore(curNode, height) {//在curNode节点前添加节点
    let blockDIv = document.createElement("div");
    blockDIv.style.height = height + 'px';
    blockDIv.style.background = 'white';
    blockDIv.setAttribute('class', 'blockDIvId_pdf_self');
    curNode.parentNode.insertBefore(blockDIv, curNode); //在当前元素前面添加
    countHeight = 0;
}
//移除所有分页、参数恢复默认设置
export function pdfRemoveAllPaging() {
    let divArr = document.getElementsByClassName("blockDIvId_pdf_self");
    for (let i = 0; i < divArr.length; i++) {
        let divItem = divArr[i];
        divItem.parentNode.removeChild(divItem);
        i--;
    }
    countFunTimes = 0;//恢复默认设置
    countHeight = 0;
}
//判断是否为行内布局,仅适用于不换行的行内布局
function getAllChildrenHeight(dom) {
    let children = dom.children;
    let h = 0;
    for (let i = 0; i < children.length; i++) {
        h += getCurrentHeight(children[i]) - 1;//减一是为了避免小数点引起的误差,height会默认向上取整
    }
    return h;
}

【 完 】
【 如有其他方案,欢迎留言或私信交流 】

猜你喜欢

转载自blog.csdn.net/weixin_40507164/article/details/122859427