前端将html导出pdf文件解决分页问题

news2025/1/23 0:56:10
  • 这是借鉴了qq_251025116大佬的解决方案并优化升级完成的,原文链接

1.安装依赖

    npm install jspdf html2canvas

2.使用方法

    import htmlToPdffrom './index.js'
    const suc = () => {
	  message.success('success');
	};
    //记得在需要打印的div上面添加 id
  let dom = document.querySelector('#testPdf');
  let pdf = new htmlToPdf(dom, '测试', 'splitPages', ',', suc, 2);
  pdf.outPutPdfFn('测试');

3.使用说明

 //splitPages 是需要添加的类名,需要再那个地方分页就在那个地方添加类名 splitPages
    
 //如:
         <SpeciesIdentification class="splitPages" :baseData="baseData" />

        <SpeciesIdentificationSecond class="splitPages" :baseData="baseData" />

        <RepetPic class="splitPages" :baseData="baseData" />

        <RepetTable class="splitPages" :baseData="baseData" />

        <GeneralAnnotation class="splitPages" :baseData="baseData" />

导出效果:

在这里插入图片描述

具体代码 :

import jsPDF from "jspdf";
import html2canvas from "html2canvas";

/*
* 使用说明
* ele:需要导出pdf的容器元素(dom节点 不是id)
* pdfFileName: 导出文件的名字 通过调用outPutPdfFn方法也可传参数改变
* splitClassName: 避免分段截断的类名 当pdf有多页时需要传入此参数 , 避免pdf分页时截断元素  如表格<tr class="itemClass"></tr>
* breakClassName:自定义分页符类名,默认为break_page,添加改类名的标签被自动分页到下一页
* sucCallback:成功回调函数
* scale:增强图片清晰度 数字也打越清晰
* 调用方式 先 let pdf = new htmlToPdf(ele, 'pdf' ,'itemClass');
* 若想改变pdf名称 pdf.outPutPdfFn(fileName);  outPutPdfFn方法返回一个promise 可以使用then方法处理pdf生成后的逻辑
* */

class htmlToPdf {
    constructor(ele, pdfFileName, splitClassName = "itemClass", breakClassName = "break_page", sucCallback = () => { }, scale = 2) {
        this.ele = ele;
        this.pdfFileName = pdfFileName;
        this.splitClassName = splitClassName;
        this.breakClassName = breakClassName;
        this.sucCallback = sucCallback;
        this.scale = scale;
        this.A4_WIDTH = 595;
        this.A4_HEIGHT = 842;
        this.pageHeight = 0
        this.pageNum = 1
    };

    async getPDF(resolve) {
        let ele = this.ele;
        let pdfFileName = this.pdfFileName
        let eleW = ele.offsetWidth // 获得该容器的宽
        let eleH = ele.scrollHeight // 获得该容器的高
        let eleOffsetTop = ele.offsetTop // 获得该容器到文档顶部的距离
        let eleOffsetLeft = ele.offsetLeft // 获得该容器到文档最左的距离
        let canvas = document.createElement("canvas")
        let abs = 0
        let win_in = document.documentElement.clientWidth || document.body.clientWidth // 获得当前可视窗口的宽度(不包含滚动条)
        let win_out = window.innerWidth // 获得当前窗口的宽度(包含滚动条)
        if (win_out > win_in) {
            abs = (win_out - win_in) / 2 // 获得滚动条宽度的一半
        } canvas.width = eleW * 2 // 将画布宽&&高放大两倍
        canvas.height = eleH * 2
        let context = canvas.getContext("2d")
        context.scale(this.scale, this.scale) // 增强图片清晰度
        context.translate(- eleOffsetLeft - abs, - eleOffsetTop)
        html2canvas(ele, {
            useCORS: true // 允许canvas画布内可以跨域请求外部链接图片, 允许跨域请求。
        }).then(async canvas => {
            let contentWidth = canvas.width
            let contentHeight = canvas.height
            // 一页pdf显示html页面生成的canvas高度;
            this.pageHeight = (contentWidth / this.A4_WIDTH) * this.A4_HEIGHT
            // 这样写的目的在于保持宽高比例一致 this.pageHeight/canvas.width = a4纸高度/a4纸宽度// 宽度和canvas.width保持一致
            // 未生成pdf的html页面高度
            let leftHeight = contentHeight
            // 页面偏移
            let position = 0
            // a4纸的尺寸[595,842],单位像素,html页面生成的canvas在pdf中图片的宽高
            let imgWidth = this.A4_WIDTH - 10 // -10为了页面有右边距
            let imgHeight = (this.A4_WIDTH / contentWidth) * contentHeight
            let pageData = canvas.toDataURL("image/jpeg", 1.0)
            let pdf = jsPDF("", "pt", "a4");
            // 有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(841.89)
            // 当内容未超过pdf一页显示的范围,无需分页
            if (leftHeight < this.pageHeight) { // 在pdf.addImage(pageData, 'JPEG', 左,上,宽度,高度)设置在pdf中显示;
                // pdf.addImage(pageData, "JPEG", 5, 0, imgWidth, imgHeight)
                pdf.addImage(pageData, 'JPEG', 6, 40, imgWidth - 12, imgHeight - 80);
            } else { // 分页
                while (leftHeight > 0) {
                    pdf.addImage(pageData, "JPEG", 6, position + 40, imgWidth - 12, imgHeight - 80)
                    leftHeight -= this.pageHeight
                    position -= this.A4_HEIGHT
                    // 避免添加空白页
                    if (leftHeight > 0) {
                        pdf.addPage()
                    }
                }
            } pdf.save(pdfFileName + ".pdf", { returnPromise: true }).then(() => { // 去除添加的空div 防止页面混乱
                let doms = document.querySelectorAll('.emptyDiv')
                for (let i = 0; i < doms.length; i++) {
                    doms[i].remove();
                }
            });
            this.ele.style.height = '';
            this.sucCallback()
            resolve();
        })

    };
    // 输出pdf
    async outPutPdfFn(pdfFileName) {
        return new Promise((resolve, reject) => { // 进行分割操作,当dom内容已超出a4的高度,则将该dom前插入一个空dom,把他挤下去,分割
            let target = this.ele;
            this.pageHeight = target.scrollWidth / this.A4_WIDTH * this.A4_HEIGHT;
            this.ele.style.height = 'initial';
            pdfFileName ? this.pdfFileName = pdfFileName : null;
            this.pageNum = 1; // pdf页数
            this.domEach(this.ele)
            // 异步函数,导出成功后处理交互
            this.getPDF(resolve, reject);
        })
    };
    domEach(dom) {
        let childNodes = dom.childNodes
        childNodes.forEach((childDom, index) => {
            if (this.hasClass(childDom, this.splitClassName)) {
                let node = childDom;
                let eleBounding = this.ele.getBoundingClientRect();
                let bound = node.getBoundingClientRect();
                let offset2Ele = bound.top - eleBounding.top
                let currentPage = Math.ceil((bound.bottom - eleBounding.top) / this.pageHeight); // 当前元素应该在哪一页
                if (this.pageNum < currentPage) {
                    this.pageNum++
                    let divParent = childDom.parentNode; // 获取该div的父节点
                    let newNode = document.createElement('div');
                    newNode.className = 'emptyDiv';
                    newNode.style.background = 'white';
                    newNode.style.height = (this.pageHeight * (this.pageNum - 1) - offset2Ele + 30) + 'px'; // +30为了在换下一页时有顶部的边距
                    newNode.style.width = '100%';
                    let next = childDom.nextSibling;
                    // 获取div的下一个兄弟节点
                    // 判断兄弟节点是否存在
                    if (next) { // 存在则将新节点插入到div的下一个兄弟节点之前,即div之后
                        divParent.insertBefore(newNode, node);
                    } else { // 不存在则直接添加到最后,appendChild默认添加到divParent的最后
                        divParent.appendChild(newNode);
                    }
                }
            }
            if (this.hasClass(childDom, this.breakClassName)) {
                this.pageNum++
                console.log('break_page');
                let eleBounding = this.ele.getBoundingClientRect();
                let bound = childDom.getBoundingClientRect();
                let offset2Ele = bound.top - eleBounding.top
                // 剩余高度
                let alreadyHeight = offset2Ele % this.pageHeight
                let remainingHeight = this.pageHeight - alreadyHeight + 20
                childDom.style.height = remainingHeight + 'px'
            }
            if (childDom.childNodes.length) {
                this.domEach(childDom)
            }
        })
    }
    hasClass(element, cls) {
        return (`` + element.className + ``).indexOf(`` + cls + ``) > -1;
    }
}

export default htmlToPdf;

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

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

相关文章

operator-sdk入门(mac)

1. 安装operator-sdk brew install operator-sdk 2. 安装kubebuilder brew install kubebuilder 3.初始化一个operator脚手架 3.1 新建一个文件夹 redis-operator 3.2 执行初始化 operator-sdk init --domain lyl.com --repo github.com 参数介绍 可以通过operator-sdk --…

HTML静态网页成品作业(HTML+CSS)——图书出版社介绍设计制作(6个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;未使用Javacsript代码&#xff0c;共有6个页面。 &#x1f3f7;️想要…

idea手动导入插件

idea有时候连接不上 我们去手动下载压缩包 插件网址 选择下载的压缩包导入 导入成功

Intel CPU体系结构

原文来自一文解析&#xff0c;Linux内核——Intel CPU体系结构 本文主要介绍Intel CPU体系结构&#xff0c;以供读者能够理解该技术的定义、原理、应用。 &#x1f3ac;个人简介&#xff1a;一个全栈工程师的升级之路&#xff01; &#x1f4cb;个人专栏&#xff1a;计算机杂记…

RabbitMQ(任务模型,交换机(广播,订阅,通配符订阅))

一.WorkQueues模型 WorkQueues(任务模式):让多个消费者绑定到一个队列&#xff0c;共同消费队列中的消息。 架构: 所需场景: 当消息处理比较耗时的时候&#xff0c;可能生产消息的速度会远远大于消息的消费速度。长此以往&#xff0c;消息就会堆积越来越多&#xff0c;无法及…

大数据开发-Hadoop之MapReduce

文章目录 MapReduce原理剖析MapReduce之Map阶段MapReduce之Reduce阶段WordCount分析多文件WordCount分析 实战wordCount案例开发 MapReduce原理剖析 MapReduce是一种分布式计算模型,主要用于搜索领域&#xff0c;解决海量数据的计算问题MapReduce由两个阶段组成&#xff1a;Ma…

热红外图像直方图修正显示

热红外图像的直方图修正是一种用于增强图像对比度和可视化细节的技术。下面是一个使用Python和OpenCV库实现直方图均衡化的示例代码&#xff1a; import cv2 import numpy as np# 读取热红外图像 image cv2.imread(thermal_image.png, cv2.IMREAD_GRAYSCALE)# 对图像进行直方…

植被生长动态与多时间尺度干旱事件的关联性研究

随着全球气候变暖的趋势愈发明显&#xff0c;干旱事件不仅发生的频率增加&#xff0c;其持续时间和影响范围也在不断扩大。干旱对生态环境造成了严重破坏&#xff0c;导致生物多样性减少、土地退化和水资源短缺&#xff1b;对农业生产而言&#xff0c;干旱会导致作物减产甚至绝…

Java精品项目--第5期基于SpringBoot的高速收费系统的设计分析与实现

项目使用技术栈 SpringBootMavenShiroMySQLMybatis-PlusJavaJDK1.8HTML 系统介绍 项目截图

【详识C语言】程序环境和预处理

本章重点&#xff1a; 程序的翻译环境 程序的执行环境 详解&#xff1a;C语言程序的编译链接 预定义符号介绍 预处理指令 #define 宏和函数的对比 预处理操作符#和##的介绍 命令定义 预处理指令 #include 预处理指令 #undef 条件编译 程序的翻译环境和执行环境 在ANSI C的任何…

2024.3.6每日一题

LeetCode 找出数组中的 K -or 值 题目链接&#xff1a;2917. 找出数组中的 K-or 值 - 力扣&#xff08;LeetCode&#xff09; 题目描述 给你一个下标从 0 开始的整数数组 nums 和一个整数 k 。 nums 中的 K-or 是一个满足以下条件的非负整数&#xff1a; 只有在 nums 中&…

Jenkins-Android源码编译【架构设计】(适用鸿蒙/自动化/多产品/持续迭代)

文章目录 Jenkins-Android源码编译【架构设计】&#xff08;适用鸿蒙/自动化/多产品/持续迭代&#xff09;通俗介绍Jenkins框架设计Jenkins部署系统/插件配置JOB配置 源码编译环境准备AOSP编译基本框架编译脚本aosp_build_sciptsjenkins_build_sciptsStage1Stage2Stage3Stage4P…

灵魂指针,教给(一)

欢迎来到白刘的领域 Miracle_86.-CSDN博客 系列专栏 C语言知识 先赞后看&#xff0c;已成习惯 创作不易&#xff0c;多多支持&#xff01; 一、内存和地址 1.1 内存 在介绍知识之前&#xff0c;先来想一个生活中的小栗子&#xff1a; 假如把你放在一个有100间屋子的酒店…

上海亚商投顾:沪指震荡微涨 AI手机、军工板块集体走强

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 沪指昨日低开后震荡回升&#xff0c;黄白二线分化明显&#xff0c;银行等权重板块走势较强。AI手机概念股持续…

pytorch_retinaface训练Resnet50_Final.pth过程+无图版安装Nvidia+CUDA驱动GPU

背景 当前处于人脸检测分支&#xff0c;项目就是retinaface官方的代码加上数据集目录结构&#xff0c;目的是训练出最后的模型文件Resnet50_Final.pth 代码 https://gitee.com/congminglst/pytorch_-retinaface.git 项目结构与设计 图片数据集采用widerface&#xff0c; 前…

STM32CubeIDE基础学习-STM32CubeIDE软件快捷键介绍

STM32CubeIDE基础学习-STM32CubeIDE软件快捷键介绍 文章目录 STM32CubeIDE基础学习-STM32CubeIDE软件快捷键介绍前言第1章 查看快捷键方法第2章 设置快捷键方法第3章 常用快捷键示例总结 前言 这个STM32CubeIDE软件使用的是Eclipse框架的开发环境&#xff0c;所以所使用的快捷…

AntV L7初体验

本案例使用L7库和Mapbox GL JS创建的简单地图可视化示例&#xff0c;加载点数据。 文章目录 1. 引入 CDN 链接2. 导出模块3. 创建地图3.1. 注册 token3.2. 创建地图实例 4. 创建场景5.创建点图层6. 演示效果7. 代码实现 1. 引入 CDN 链接 <!-- 1.引入CDN链接 --> <!--…

泰迪智能科技-2024年高校大数据人才培养探索模式

随着数字经济的高速发展&#xff0c;对于大数据人才的需求日益增长。产业数字化和数字产业化之间的关系&#xff0c;已经成为推动社会发展的关键。为此&#xff0c;高校及产业界需要紧密配合&#xff0c;以培养出符合时代需求的大数据人才。 数字产业化与产业数字化高速发…

HarmonyOS NEXT应用开发案例集

概述 随着应用代码的复杂度提升&#xff0c;为了使应用有更好的可维护性和可扩展性&#xff0c;良好的应用架构设计变得尤为重要。本篇文章将介绍一个应用通用架构的设计思路&#xff0c;以减少模块间的耦合、提升团队开发效率&#xff0c;为开发者呈现一个清晰且结构化的开发…

windows11配置电脑IP

windows11配置电脑IP 选择"开始>设置>“网络&Internet >以太网”。在 "属性"下&#xff0c;编辑IP地址&#xff0c;子网掩码&#xff0c;网关以及DNS。