当前位置: 首页 > news >正文

VUE3+TypeScript项目,使用html2Canvas+jspdf生成PDF并实现--分页--页眉--页尾

使用html2Canvas+JsPDF生成pdf,并实现分页添加页眉页尾

1.封装方法htmlToPdfPage.ts

/**
    path: src/utils/htmlToPdfPage.ts
    name: 导出页面为PDF格式 并添加页眉页尾
**/
/**
 * 封装思路
 * 1.将页面根据A4大小分隔边距,避免内容被中间截断
 * 所有元素层级不要太深,只有一个表格需要再深入判断,向上统计高度
 * const parentElement = document.querySelector('.el-form');
   const childElements = parentElement.childNodes;
   const filteredChildElements = Array.from(childElements).filter((node) => node.nodeType === Node.ELEMENT_NODE);
 * 2.根据元素的层级关系,循环childElements计算元素的高度
 * 3.将页面转换成Canvas,根据canvas的高度来截取分页图片高度
 * 4.使用pdfjs截取canvas生成的图片根据A4高度逐渐加入到pdf中,pdf.addPage(),pdf.addImage()
   addImage(图片canvas资源,格式,图片宽度的x轴,图片高度的y轴,图片宽度,图片高度)
*
 */
import { uploadFile } from '@/api/common'
import html2Canvas from 'html2canvas'
import JsPDF from 'jspdf'

const A4_WIDTH = 595
const A4_HEIGHT = 842
const pdf = new JsPDF({
  unit: 'pt',
  format: 'a4',
  orientation: 'p'
})
// 转换为canvas
const toCanvas = async (element:any) => {
  // canvas元素
  // debugger
  const canvas = await html2Canvas(element, {
    allowTaint: true, // 允许渲染跨域图片
    scale: window.devicePixelRatio * 2, // 增加清晰度
    useCORS: true // 允许跨域
  })
  const canvasWidth = canvas.width // 获取canavs转化后的宽度
  const canvasHeight = canvas.height // 获取canvas转化后的高度
  const height = Math.floor(595 * canvasHeight / canvasWidth) // 根据595宽度比例计算canvas的高度

  // 转化成图片Data
  const canvasData = await canvas.toDataURL('image/jpeg', 1.0)
  return { canvasWidth, canvasHeight, height, data: canvasData }
}

export const htmlToPdfPage: any = {
  async getPdf (title:any) {
    return new Promise((resolve, reject) => {
      html2Canvas(document.querySelector('#pdfPage') as any, {
        allowTaint: true, // 允许渲染跨域图片
        scale: window.devicePixelRatio * 2, // 增加清晰度
        useCORS: true // 允许跨域
      }).then(async (canvas) => {
        // 内容的宽度
        const contentCanvasWidth = canvas.width
        // 内容高度
        const contentCanvasHeight = canvas.height
        // 按照a4纸的尺寸[595,842]计算内容canvas一页高度
        const oneCanvasHeight = Math.floor(contentCanvasWidth * 842 / 595)
        // 未生成pdf的html页面高度
        let remainingHeight = contentCanvasHeight
        // 页面偏移
        let position = 0 // 上下边距分别为10
        // 每页宽度,A4比例下canvas内容高度
        const imgWidth = 595
        const imgHeight = 595 * contentCanvasHeight / contentCanvasWidth
        // ************************************  计算页码 start  ********************************************
        const headerDom: any = document.querySelector('#pdfPage-header')
        const { height: imgHeaderHeight, canvasHeight: headerCanvasHeight } = await toCanvas(headerDom)
        const footerDom: any = document.querySelector('#pdfPage-footer')
        const { height: imgFooterHeight, canvasHeight: footerCanvasHeight } = await toCanvas(footerDom)
        // 一页高度减去页眉页尾后内容的高度
        const contentHeight = oneCanvasHeight - headerCanvasHeight - footerCanvasHeight
        // 总页数
        const totalPage = Math.ceil(contentCanvasHeight / contentHeight)
        // ************************************  计算页码 end  ********************************************

        // canvas转图片数据
        const pageData = canvas.toDataURL('image/jpeg', 1.0)
        // 新建JsPDF对象
        const PDF = new JsPDF('' as any, 'pt', 'a4')
        let pageNumber = 1 // 页数
        // 判断是否分页
        if (remainingHeight < oneCanvasHeight) {
          await addHeader(PDF, pageNumber, totalPage)
          PDF.addImage(pageData, 'JPEG', 0, imgHeaderHeight, imgWidth, imgHeight)
          await addFooter(PDF)
          position -= 842
        } else {
          while (remainingHeight > 0) {
            if (position === 0) {
              await addHeader(PDF, pageNumber, totalPage)
              PDF.addImage(pageData, 'JPEG', 0, imgHeaderHeight, imgWidth, imgHeight)
              await addFooter(PDF)
            } else {
              PDF.addImage(pageData, 'JPEG', 0, position + (pageNumber * imgHeaderHeight) + ((pageNumber - 1) * imgFooterHeight), imgWidth, imgHeight)
              await addHeader(PDF, pageNumber, totalPage)
              await addFooter(PDF)
            }
            position -= 842
            remainingHeight -= oneCanvasHeight
            pageNumber += 1
            if (remainingHeight > 0) {
              PDF.addPage()
            }
          }
        }
        // 保存文件--测试
        PDF.save(title + '.pdf')
        resolve(1)
        // 上传文件--实现功能
        // const formData = new FormData()
        // formData.append('file', PDF.output('blob'), title + '.pdf')
        // uploadFile(formData).then((res:any) => {
        //   resolve(res)
        // }).catch((err:any) => {
        //   reject(err)
        // })
      })
    })
  }
}
// 添加页眉
const addHeader = async (pdf: any, currentPage?: any, totalPage?: any) => {
  const headerDom: any = document.querySelector('#pdfPage-header')
  const newHeaderDom = headerDom.cloneNode(true)
  if (currentPage && totalPage) {
    newHeaderDom.querySelector('#pdfPage-page').innerText = currentPage
    newHeaderDom.querySelector('#pdfPage-total').innerText = totalPage
  }
  document.documentElement.append(newHeaderDom)
  const { height: imgHeaderHeight, data: headerCanvas } = await toCanvas(newHeaderDom)
  // const imgHeaderHeight = 595 * headerHegith / headerWidth
  await pdf.addImage(headerCanvas, 'JPEG', 0, 0, A4_WIDTH, imgHeaderHeight)
}
// 添加页尾
const addFooter = async (pdf: any, currentPage?: any, totalPage?: any) => {
  const footerDom: any = document.querySelector('#pdfPage-footer')
  const newFooterDom = footerDom.cloneNode(true)
  if (currentPage && totalPage) {
    newFooterDom.querySelector('#footer-page').innerText = currentPage
    newFooterDom.querySelector('#footer-total').innerText = totalPage
  }
  document.documentElement.append(newFooterDom)
  const { height: imgFooterHeight, data: footerCanvas } = await toCanvas(newFooterDom)
  // const imgFooterHeight = 595 * footerHegith / footerWidth
  await pdf.addImage(footerCanvas, 'JPEG', 0, A4_HEIGHT - imgFooterHeight, A4_WIDTH, imgFooterHeight)
}
export default htmlToPdfPage

2.页面调用

<template>
  <div class="page">
    <button @click="exportToPdf">导出 PDF</button>
	<!-- 页眉 -->
    <div class="page-header" id="pdfPage-header" v-if="isExportPdf">
      ......
      <div class="row-between mt20">
      	......
        <div v-if="isExportPdf"> 页码 Page: <span id="pdfPage-page">1</span> of <span id="pdfPage-total">5</span></div>
      </div>
    </div>
    <!-- 内容 -->
    <!-- 此案例通过page-one固定了每页高度,动态数据可根据每页高度获取最近dom元素,添加margin-top,避免内容中间截断 -->
    <div class="page-content"  id="pdfPage" v-loading="pageLoading">
    	<div :class="isExportPdf ? 'page-flex page-one' : 'page-flex'"></div>
    	<div :class="isExportPdf ? 'page-flex page-one' : 'page-flex'"></div>
    <div>
    <!-- 页尾 -->
    <div class="page-footer" id="pdfPage-footer" v-if="isExportPdf">
      	......
        <div v-if="isExportPdf"> 页码 Page: <span id="footer-page">1</span> of <span id="footer-total">5</span></div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, getCurrentInstance, reactive, computed, onMounted, nextTick } from 'vue'
import htmlToPdfPage from '@/utils/htmlToPdfPage'

disabledFalg.value = route.query.type === 'view'
const isExportPdf = ref(false) // 是否为导出pdf状态

const exportToPdf = async () => {
  disabledFalg.value = true
  isExportPdf.value = true
  await nextTick()
  setTimeout(async () => {
    htmlToPdfPage.getPdf('pdf-title').then((res:any) => {
      disabledFalg.value = false
      isExportPdf.value = false
      if(res) {
		// 业务逻辑处理
	  }
    })
  }, 100)
}
</script>

<style lang="scss" scoped>
  .page{
  padding: 10px 20px;
  font-size: 15px;
  background-color: #ffffff;
  }
  .page-header {
    width: 960px;
    padding: 20px 50px 0 50px;
    margin: 0 auto;
    height: 150px;
  }
  .page-content {
    width: 960px;
    margin: 0 auto;
    padding: 0px 50px;
    font-family: Arial, Helvetica, sans-serif;
    background-color: #ffffff;
  }
  .page-footer {
    width: 960px;
    padding: 0px 50px 10px 50px;
    height: 90px;
    margin: 0 auto;
  }
  .page-one {
    height: 1118px;
  }
  .page-flex {
    display: flex;
    flex-direction: column;
  }

相关文章:

  • 使用LLaMAFactory微调Qwen大模型
  • QT计算器开发
  • kubesphere 终端shell连不上的问题
  • FPGA Verilog/VHDl 中的锁存latch
  • leetcoed0044. 通配符匹配 hard
  • 【stm32--HAL库DMA+USART+空闲中断不定长收发数据】
  • 《探秘SQL的BETWEEN:解锁数据范围查询的深度奥秘》
  • [HCIA]网络基础
  • Canvas粒子系统终极指南:从基础运动到复杂交互的全流程实现
  • Java 环境变量配置指南
  • 【数学建模】(启发式算法)蚁群算法(Ant Colony Optimization)的详解与应用
  • 深度学习项目--基于SE的ResNet50V2网络结构探究实验
  • 蓝桥杯省模拟赛 互质的数
  • HCIP(VLAN综合实验)
  • 安装Ollama,本地部署deepseek
  • 如何去评估一个系统的高可用
  • 流程引擎/状态机简介以及选型
  • Centos7安装cat美化工具lolcat
  • 使用 flutter_blue_plus 连接蓝牙
  • 3月28号
  • 秘鲁总统任命前司法部长阿拉纳为新总理
  • 泽连斯基:将带领乌代表团前往土耳其,已准备好与普京会谈
  • 夜读|尊重生命的棱角
  • 通化市委书记孙简升任吉林省副省长
  • 青海规范旅游包车行为:不得引导外省籍旅游包车违规驻地运营
  • 秦洪看盘|预期改善,或迎来新的增量资金