VUE3+TypeScript項目,使用html2Canvas+jspdf生成PDF并實現--分頁--頁眉--頁尾

使用html2Canvas+JsPDF生成pdf,并實現分頁添加頁眉頁尾

1.封裝方法htmlToPdfPage.ts

/**path: src/utils/htmlToPdfPage.tsname: 導出頁面為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元素// debuggerconst 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的高度// 轉化成圖片Dataconst 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 = 595const 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 -= 842remainingHeight -= oneCanvasHeightpageNumber += 1if (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 = currentPagenewHeaderDom.querySelector('#pdfPage-total').innerText = totalPage}document.documentElement.append(newHeaderDom)const { height: imgHeaderHeight, data: headerCanvas } = await toCanvas(newHeaderDom)// const imgHeaderHeight = 595 * headerHegith / headerWidthawait 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 = currentPagenewFooterDom.querySelector('#footer-total').innerText = totalPage}document.documentElement.append(newFooterDom)const { height: imgFooterHeight, data: footerCanvas } = await toCanvas(newFooterDom)// const imgFooterHeight = 595 * footerHegith / footerWidthawait 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 = trueisExportPdf.value = trueawait nextTick()setTimeout(async () => {htmlToPdfPage.getPdf('pdf-title').then((res:any) => {disabledFalg.value = falseisExportPdf.value = falseif(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;}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/73799.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/73799.shtml
英文地址,請注明出處:http://en.pswp.cn/web/73799.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

5.Excel:從網上獲取數據

一 用 Excel 數據選項卡獲取數據的方法 連接。 二 要求獲取實時數據 每1分鐘自動更新數據。 A股市場_同花順行情中心_同花順財經網 用上面方法將數據加載進工作表中。 在表格內任意區域右鍵&#xff0c;刷新。 自動刷新&#xff1a; 三 缺點 Excel 只能爬取網頁上表格類型的…

《深度剖析SQL之WHERE子句:數據過濾的藝術》

在當今數據驅動的時代&#xff0c;數據處理和分析能力已成為職場中至關重要的技能。SQL作為一種強大的結構化查詢語言&#xff0c;在數據管理和分析領域占據著核心地位。而WHERE子句&#xff0c;作為SQL中用于數據過濾的關鍵組件&#xff0c;就像是一把精準的手術刀&#xff0c…

華為eNSP-配置靜態路由與靜態路由備份

一、靜態路由介紹 靜態路由是指用戶或網絡管理員手工配置的路由信息。當網絡拓撲結構或者鏈路狀態發生改變時&#xff0c;需要網絡管理人員手工修改靜態路由信息。相比于動態路由協議&#xff0c;靜態路由無需頻繁地交換各自的路由表&#xff0c;配置簡單&#xff0c;比較適合…

Docker 快速入門指南

Docker 快速入門指南 1. Docker 常用指令 Docker 是一個輕量級的容器化平臺&#xff0c;可以幫助開發者快速構建、測試和部署應用程序。以下是一些常用的 Docker 命令。 1.1 鏡像管理 # 搜索鏡像 docker search <image_name># 拉取鏡像 docker pull <image_name>…

基礎認證-單選題(一)

單選題 1、下列關于request方法和requestlnStream方法說法錯誤的是(C) A 都支持取消訂閱響應事件 B 都支持訂閱HTTP響應頭事件 C 都支持HttpResponse返回值類型 D 都支持傳入URL地址和相關配置項 2、如需修改Text組件文本的透明度可通過以下哪個屬性方法進行修改 (C) A dec…

Logback使用和常用配置

Logback 是 Spring Boot 默認集成的日志框架&#xff0c;相比 Log4j&#xff0c;它性能更高、配置更靈活&#xff0c;并且天然支持 Spring Profile 多環境配置。以下是詳細配置步驟及常用配置示例。 一、添加依賴&#xff08;非 Spring Boot 項目&#xff09; 若項目未使用 Sp…

MySQL基礎語法DDLDML

目錄 #1.創建和刪除數據庫 ?#2.如果有lyt就刪除,沒有則創建一個新的lyt #3.切換到lyt數據庫下 #4.創建數據表并設置列及其屬性,name是關鍵詞要用name包圍 ?編輯 #5.刪除數據表 #5.查看創建的student表 #6.向student表中添加數據,數據要與列名一一對應 #7.查詢studen…

在windows下安裝windows+Ubuntu16.04雙系統(下)

這篇文章的內容主要來源于這篇文章&#xff0c;為正式安裝windowsUbuntu16.04雙系統部分。在正式安裝前&#xff0c;若還沒有進行前期準備工作&#xff08;1.分區2.制作啟動u盤&#xff09;&#xff0c;見《在windows下安裝windowsUbuntu16.04雙系統(上)》 二、正式安裝Ubuntu …

Ubuntu24.04 離線安裝 MySQL8.0.41

一、環境準備 1.1 官方下載MySQL8.0.41 完整包 1.2 上傳包 & 解壓 上傳包名稱是&#xff1a;mysql-server_8.0.41-1ubuntu24.04_amd64.deb-bundle.tar # 切換到上傳目錄 cd /home/MySQL8 # 解壓&#xff1a; tar -xvf mysql-server_8.0.41-1ubuntu24.04_amd64.deb-bundl…

記錄一次Dell服務器更換內存條報錯解決過程No memory found

文章目錄 問題問題分析解決流程總結 問題 今天給服務器添加了幾個內存條&#xff0c;開啟后報錯 No memory found No useable DlMMs found. Verify the DlMMsare properly seated and that they are installed in the correct sockets. 問題分析 這個錯誤說明服務器在啟動時沒…

Apache HttpClient使用

一、Apache HttpClient 基礎版 HttpClients 是 Apache HttpClient 庫中的一個工具類&#xff0c;用于創建和管理 HTTP 客戶端實例。Apache HttpClient 是一個強大的 Java HTTP 客戶端庫&#xff0c;用于發送 HTTP 請求并處理 HTTP 響應。HttpClients 提供了多種方法來創建和配…

Maven版本統一管理

多模塊的項目&#xff0c;怎么方便管理 模塊的版本號呢&#xff1f; 可以使用 ${revision} 配合 flatten-maven-plugin插件 <plugin><groupId>org.codehaus.mojo</groupId><artifactId>flatten-maven-plugin</artifactId><version>1.1.0&…

時序數據庫 InfluxDB(一)

時序數據庫 InfluxDB&#xff08;一&#xff09; 數據庫種類有很多&#xff0c;比如傳統的關系型數據庫 RDBMS&#xff08; 如 MySQL &#xff09;&#xff0c;NoSQL 數據庫&#xff08; 如 MongoDB &#xff09;&#xff0c;Key-Value 類型&#xff08; 如 redis &#xff09…

E5071C數據保存教程:SNP文件/CSV導出+遠程傳輸步驟一鍵收藏

Keysight E5071C 網絡分析儀支持多種數據存儲方式&#xff0c;以下是詳細的操作步驟和注意事項&#xff1a; 1. 內部存儲&#xff08;儀器內存&#xff09; 保存測量數據&#xff1a; 軌跡數據&#xff1a;按 Save/Recall → Save Trace Data → 選擇存儲格式&#xff08;如 …

業務相關

目錄 一、Spark 1.spark主要用來計算什么&#xff1f; 隨便說段代碼 2.spark 運行命令說一個&#xff0c;平常用哪些參數&#xff0c;怎么考慮的 3.spark shuffle的代碼有哪些&#xff0c;平日哪些操作涉及到shuffle了 4.計算中遇到最難解決的是什么&#xff1f; 5.Spark …

LLM之RAG實戰(五十二)| 如何使用混合搜索優化RAG 檢索

在RAG項目中&#xff0c;大模型生成的參考內容&#xff08;專業術語稱為塊&#xff09;來自前一步的檢索&#xff0c;檢索的內容在很大程度上直接決定了生成的效果&#xff0c;因此檢索對于RAG項目至關重要&#xff0c;最常用的檢索方法是關鍵字搜索和語義搜索。本文將分別介紹…

[學成在線]07-視頻轉碼

視頻轉碼 視頻上傳成功后需要對視頻進行轉碼處理。 首先我們要分清文件格式和編碼格式&#xff1a; 文件格式&#xff1a;是指.mp4、.avi、.rmvb等這些不同擴展名的視頻文件的文件格式 &#xff0c;視頻文件的內容主要包括視頻和音頻&#xff0c;其文件格式是按照一定的編碼…

Leetcode算法方法總結

1. 雙指針法解決鏈表/數組題目 只要數組有序&#xff0c;就要想到雙指針做法。還有二分法 回文串一般也會用到雙指針&#xff0c;回文串的長度由于可能是奇數也可能是偶數&#xff0c;所以在尋找時&#xff0c;既需要尋找奇數長度的回文串&#xff0c;也需要尋找偶數長度的回文…

一周掌握Flutter開發--9. 與原生交互(上)

文章目錄 9. 與原生交互核心場景9.1 調用平臺功能&#xff1a;MethodChannel9.1.1 Flutter 端實現9.1.2 Android 端實現9.1.3 iOS 端實現9.1.4 使用場景 9.2 使用社區插件9.2.1 常用插件9.2.2 插件的優勢 總結 9. 與原生交互 Flutter 提供了強大的跨平臺開發能力&#xff0c;但…

基于Flask的通用登錄注冊模塊,并代理跳轉到目標網址

實現了用戶密碼的加密&#xff0c;代理跳轉到目標網址&#xff0c;不會暴露目標路徑&#xff0c;未登錄的情況下訪問proxy則自動跳轉到登錄頁&#xff0c;使用時需要修改配置項config&#xff0c;登錄注冊頁面背景快速修改&#xff0c;可以實現登錄注冊模塊的快速復用。 1.app…