html轉PDF文件最完美的方案(wkhtmltopdf)

目錄

需求

一、方案調研

二、wkhtmltopdf使用

如何使用

文檔簡要說明

三、后端服務

四、前端服務

往期回顧


需求

最近在做報表類的統計項目,其中有很多指標需要匯總,網頁內容有大量的echart圖表,做成一個網頁去瀏覽,同時需要轉成PDF格式下載瀏覽,更重要的是pdf格式再打開后,需要自定義頁眉、頁腳,頁碼,支持文本的選中、復制、粘貼,同時左側也要有正常的頁簽導航,點擊哪里到哪里。

一、方案調研

經過調研主要有以下幾種方式生成pdf,但是每個方案都有缺陷,跟我們的需求相差。

方案優點缺點
window.print()1、兼容性最好
2、可以將任意內容導出成 pdf 文檔, 甚至是非改頁面上的內容

1、調用方法時部分條件下導出pdf需要用戶手動選擇

2、生成的pdf不支持生成頁簽導航

3、頁眉頁腳不適合自定義

?jspdf +?html2canvas1、在jspdf上將生成效果不佳的部分可以轉成圖片,適用于對樣式有要求的場景
2、將亂碼部分轉為了圖片,解決了中文亂碼問題
3、沒有預覽點擊即可保存

1、如果內容包含echart圖表或者其它圖表,該內容需要轉圖片
2、生成的pdf實際為圖片,不支持復制
3、不同瀏覽器生成可能會有略微差異(頁面周邊留白部分差異)
?4、由于整體效果為圖片,導致pdf文件較大(兩頁2.5MB左右)

5、pdf分頁不好處理

6、不支持生成頁簽導航

wkhtmltopdf

1、支持自定義頁眉頁腳頁碼

2、支持文本選中粘貼復制

3、支持將html的h標簽自動生成pdf

1、需要結合后端去實現生成接口返回給前端下載

2、wkhtmltopdf?使用 WebKit 渲染引擎,這意味著它在某些情況下可能無法完全支持所有現代 CSS 和 JavaScript 特性,特別是那些依賴于最新瀏覽器特性的功能

3、wkhtmltopdf?對 JavaScript 的支持有限。雖然它可以在某些情況下執行簡單的 JavaScript,但復雜的交互式內容可能無法正確渲染。

前兩種是純前端去實現的方案,一是用瀏覽器打印功能實現,這種方案簡單粗暴,但是需要手動觸發,不支持自定義頁眉頁腳頁碼,瀏覽器也不支持生成頁簽導航。第二種把整個頁面生成圖片,完整還原了樣式但是,跟我們的要求差太遠。第三種是wkhtmltopdf,底層是C++去實現的,能夠高效地將 HTML 內容轉換為高質量的 PDF 文件。下面主要介紹下wkhtmltopdf使用。

二、wkhtmltopdf使用

官網入口:wkhtmltopdf

如何使用

  1. 下載預編譯的二進制文件或從源代碼構建

下載鏈接:wkhtmltopdf

以下是適配所有操作系統的包,我們根據自己的系統不同的下載包

以centeros7為例

1.首先我們下載我們需要的包

?我的是x86_64的,下載完成后將包傳到服務器

?運行命令安裝

rpm -Uvh wkhtmltox-0.12.6-1.centos7.x86_64.rpm

?報錯!!!

原因是缺少依賴,我們來安裝下依賴

yum install fontconfig libX11 libXext libXrender libjpeg libpng  xorg-x11-fonts-Type1

yum install -y xorg-x11-fonts-75dpi

?再次運行安裝命令

查看版本

wkhtmltopdf --version

?

大功告成!? YYDS!?

安裝完成后我們來使用它

  1. 創建要轉換為PDF或者圖像的HTML文檔
  2. 通過命令運行工具生成PDF

比如我要將Google網頁保存為pdf,則可以直接運行命令

wkhtmltopdf?http://google.com?google.pdf

文檔簡要說明

官方文檔說明:https://wkhtmltopdf.org/usage/wkhtmltopdf.txt

強烈建議查看官方文檔,以下(基于0.12.6的版本)

1. 基本命令

wkhtmltopdf [選項] <輸入文件或URL> <輸出PDF文件>

示例:

wkhtmltopdf input.html output.pdf

2.大綱(必要實現)

大綱就是PDF閱讀器中,用于顯示導航跳轉的部分,不屬于PDF文檔中的一部分,主要是方便閱讀器瀏覽導航使用。

Wkhtmltopdf 用 patched qt 支持PDF大綱(也稱為書簽),可以通過設置--outline?(默認選項)選項實現。

大綱是根據?<h?>(h1–h6) 標簽生成的,有關如何實現的詳細說明,請參見目錄部分。

如果?<h?>?標簽在HTML文檔中嵌套的層級非常深,那么大綱樹的層級也會變得非常深。可以通過--outline-depth選項來設置大綱的層級深度。

詳細使用參考這篇文章哈哈哈

wkhtmltopdf 0.12.6 中文文檔(精心整理)-CSDN博客

原理是:wkhtmltopdf將整個帶css的html文檔轉為了pdf,因此想要?將我們前端畫的好看的頁面生成pdf,需要將html文檔傳給wkhtmltopdf。

三、后端服務

?我們需要寫一個后端服務,通過接口將前端繪制的漂亮頁面整個以api的方式傳給后端,后端將文檔內容整理后,調用wkhtmltopdf的命令來生成pdf,然后返回文件流給前端提供下載。

npm為我們提供了調用wkhtmltopdf服務的插件

wkhtmltopdf - npm

以下是簡單用法,以官方最新為準

var wkhtmltopdf = require('wkhtmltopdf');// URL
wkhtmltopdf('http://google.com/', { pageSize: 'letter' }).pipe(fs.createWriteStream('out.pdf'));// HTML
wkhtmltopdf('<h1>Test</h1><p>Hello world</p>').pipe(res);// Stream input and output
var stream = wkhtmltopdf(fs.createReadStream('file.html'));// output to a file directly
wkhtmltopdf('http://apple.com/', { output: 'out.pdf' });// Optional callback
wkhtmltopdf('http://google.com/', { pageSize: 'letter' }, function (err, stream) {// do whatever with the stream
});// Repeatable options
wkhtmltopdf('http://google.com/', {allow : ['path1', 'path2'],customHeader : [['name1', 'value1'],['name2', 'value2']]
});// Ignore warning strings
wkhtmltopdf('http://apple.com/', { output: 'out.pdf',ignore: ['QFont::setPixelSize: Pixel size <= 0 (0)']
});
// RegExp also acceptable
wkhtmltopdf('http://apple.com/', { output: 'out.pdf',ignore: [/QFont::setPixelSize/]
});

以下是我寫的一個簡單的node server.js調用案列

const express = require('express');
const path = require('path');
const app = express();
const port = 3002;// 引入 cors 中間件
const cors = require('cors');// 使用 cors 中間件
app.use(cors());const fs = require('fs');// 解析 JSON 請求體,設置最大限制為 50MB
app.use(express.json({ limit: '50mb' }));// 解析 application/x-www-form-urlencoded 請求體,設置最大限制為 50MB
app.use(express.urlencoded({ extended: true, limit: '50mb' }));// PDF生成高并發處理
function getPdfHeavyTask(html) {const wkhtmltopdf = require('wkhtmltopdf');const options = {output: `./pdfs/demo.pdf`,pageSize: 'letter',orientation: 'portrait',marginTop: '1.8cm',marginBottom: '1.2cm',marginLeft: '1cm',marginRight: '1cm',encoding: 'UTF-8',dpi: 300,zoom: 1,title: 'pdf生成demo',enableSmartShrinking: true,javascriptDelay: 1000,noStopSlowScripts: true,headerHtml: './template/header.html', // 設置頁眉模板footerHtml: './template/footer.html' // 設置頁腳模板};return new Promise((resolve) => {wkhtmltopdf(html, options, (err, stream) => {if (err) {resolve({ status: 500, data: err });return;}resolve({ status: 200, data: stream });});});
}app.post('/generate-pdf', async (req, res) => {const { content, css } = req.body;let html = `<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"><title>pdf生成demo</title><style>body { font-family: "Microsoft YaHei", "SimSun", sans-serif; }${css}</style></head><body>${content}</body></html>`;// 高并發生成異步任務處理const { status, data } = await getPdfHeavyTask(html);// PDF生成失敗if (status === 500) {res.status(500).send(data);return;}// PDF生成成功讀取const filePath = path.resolve(__dirname, './pdfs/demo.pdf');const fileStream = fs.createReadStream(filePath);const stat = fs.statSync(filePath);res.setHeader('Content-Length', stat.size);res.setHeader('Content-Type', 'application/pdf');res.setHeader('Content-Disposition', 'attachment; filename=demo.pdf');fileStream.pipe(res);
});app.listen(port, () => {console.log(`Server running at http://localhost:${port}`);
});

頁眉頁腳代碼根據自己的需求添加即可

案例:header.html 自定義頁碼

 <!DOCTYPE html><html><head><script>function subst() {var vars = {};var query_strings_from_url = document.location.search.substring(1).split('&');for (var query_string in query_strings_from_url) {if (query_strings_from_url.hasOwnProperty(query_string)) {var temp_var = query_strings_from_url[query_string].split('=', 2);vars[temp_var[0]] = decodeURI(temp_var[1]);}}var css_selector_classes = ['page', 'frompage', 'topage', 'webpage', 'section', 'subsection', 'date', 'isodate', 'time', 'title', 'doctitle', 'sitepage', 'sitepages'];for (var css_class in css_selector_classes) {if (css_selector_classes.hasOwnProperty(css_class)) {var element = document.getElementsByClassName(css_selector_classes[css_class]);for (var j = 0; j < element.length; ++j) {element[j].textContent = vars[css_selector_classes[css_class]];}}}}</script></head><body style="border:0; margin: 0;" onload="subst()"><table style="border-bottom: 1px solid black; width: 100%"><tr><td class="section"></td><td style="text-align:right">Page <span class="page"></span> of <span class="topage"></span></td></tr></table></body></html>

四、前端服務

前端只需要將我們的html和css通過接口傳給后端即可

try {const htmlContent = document.getElementById('report-content').outerHTML// 使用fetch API獲取CSS文件const response = await fetch('../../assets/core-report.css')const css = await response.text()this.http.post('/generate-pdf',{content: htmlContent, // 網址或者HTML文檔css,},undefined,{responseType: 'arraybuffer',observe: 'response',}).subscribe((response: any) => {if (!response) {this.dloading = falsethrow new Error('生成 PDF 失敗')}this.downloadProgress = 100// 將 ArrayBuffer 轉換為 Blob 對象const blob = new Blob([response.body], { type: 'application/pdf' })// 創建一個 URL 對象const url = URL.createObjectURL(blob)// 下載 PDF 文件const a = document.createElement('a')a.href = urla.download = `demo.pdf`document.body.appendChild(a)a.click()document.body.removeChild(a)URL.revokeObjectURL(url)},(error) => {console.error('PDF生成失敗:', error)})} catch (error) {console.error('PDF生成失敗:', error)}

我們通過腳本獲取到html文檔,通過fetch直接將文件內容獲取,然后通過接口將兩個參數傳給后端,后端通過將兩個內容組裝成完整html,調用wkhtmltopdf,生成pdf,在通過文件流返回前端下載。這樣生成的pdf,支持文本選中、復制、搜索,同時它會根據H標簽識別頁簽導航內容,實現頁簽點擊導航,YYDS!

注意點:

1:如果內容中存在canvas或者圖片需要轉base64傳給后端,或者使用cdn鏈接

2:css3中的樣式不支持,比如:陰影,以及flex布局不支持

3:內容被切分

在每個章節的標題或者其他地方我們往往不希望標題被切成兩半,分別出現在兩個頁面當中。因此,我們需要添加如下樣式:

.title {page-break-before: always;page-break-after: always;page-break-inside: avoid;
}

4: 表格切分

文檔中會出現大量的表格。如果希望放置表格被切分也是同樣的處理方式?

table tr {word-break: break-all;page-break-before: always;page-break-after: always;page-break-inside: avoid;
}

歡迎在評論區交流。

如果文章對你有所幫助,??關注+點贊??鼓勵一下!博主會持續更新。。。。

往期回顧

?CSS多欄布局-兩欄布局和三欄布局

?border邊框影響布局解決方案

?css 設置字體漸變色和陰影

css 重置樣式表(Normalize.css)

?css實現元素居中的6種方法?

Angular8升級至Angular13遇到的問題

前端vscode必備插件(強烈推薦)

Webpack性能優化

vite構建如何兼容低版本瀏覽器

前端性能優化9大策略(面試一網打盡)!

vue3.x使用prerender-spa-plugin預渲染達到SEO優化

?vite構建打包性能優化

?vue3.x使用prerender-spa-plugin預渲染達到SEO優化

?ES6實用的技巧和方法有哪些?

?css超出部分顯示省略號

vue3使用i18n 實現國際化

vue3中使用prismjs或者highlight.js實現代碼高亮

什么是 XSS 攻擊?什么是 CSRF?什么是點擊劫持?如何防御

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

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

相關文章

示例:JAVA調用deepseek

近日&#xff0c;國產AI DeepSeek在中國、美國的科技圈受到廣泛關注&#xff0c;甚至被認為是大模型行業的最大“黑馬”。在外網&#xff0c;DeepSeek被不少人稱為“神秘的東方力量”。1月27日&#xff0c;DeepSeek應用登頂蘋果美國地區應用商店免費APP下載排行榜&#xff0c;在…

.NET周刊【2月第1期 2025-02-02】

國內文章 dotnet 9 已知問題 默認開啟 CET 導致進程崩潰 https://www.cnblogs.com/lindexi/p/18700406 本文記錄 dotnet 9 的一個已知且當前已修問題。默認開啟 CET 導致一些模塊執行時觸發崩潰。 dotnet 使用 ColorCode 做代碼著色器 https://www.cnblogs.com/lindexi/p/…

AES200物理機部署DeepSeek-R1蒸餾模型

AES200物理機部署DeepSeek-R1模型 華為官方官宣自己的NPU支持DeepSeek-R1模型部署&#xff0c;華為的大模型推理部署依托于其大模型推理引擎&#xff1a;MindIE&#xff0c;但是根據MindIE的文檔&#xff0c;其只支持以下硬件&#xff1a; 表1 MindIE支持的硬件列表 類型配置…

【后端開發】系統設計101——Devops,Git與CICD,云服務與云原生,Linux,安全性,案例研究(30張圖詳解)

【后端開發】系統設計101——Devops&#xff0c;Git與CICD&#xff0c;云服務與云原生&#xff0c;Linux&#xff0c;安全性&#xff0c;案例研究&#xff08;30張圖詳解&#xff09; 文章目錄 1、DevopsDevOps與SRE與平臺工程的區別是什么&#xff1f;什么是k8s&#xff08;Ku…

正泰中間電磁繼電器【8腳10A】DC24V 待機功率

需求&#xff1a;繼電器能耗測試。 1.連接24V2A的電源&#xff0c; 2. 穩定功率為 1.4W 3. 正泰中間電磁繼電器【8腳10A】直流DC24V 注&#xff1a;聯通時電磁繼電器會輕微發熱 4.電磁繼電器的工作原理基于電流的磁效應 電磁激勵&#xff1a;電磁繼電器主要由線圈、鐵芯、銜…

計算機視覺核心任務

1. 計算機視頻重要分類 計算機視覺的重要任務可以大致分為以下幾類&#xff1a; 1. 圖像分類&#xff08;Image Classification&#xff09; 識別圖像屬于哪個類別&#xff0c;例如貓、狗、汽車等。 應用場景&#xff1a;物品識別、人臉識別、醫療影像分類。代表模型&#…

責任鏈模式(Chain Responsibility)

一、定義&#xff1a;屬于行為型設計模式&#xff0c;包含傳遞的數據、創建處理的抽象和實現、創建鏈條、將數據傳遞給頂端節點&#xff1b; 二、UML圖 三、實現 1、需要傳遞處理的數據類 import java.util.Date;/*** 需要處理的數據信息*/ public class RequestData {priva…

MFC 基礎

windows桌面應用分為兩種類型&#xff1a; 基于文檔視圖類型 和 基于對話框類型。 通常具有復雜交互控件的程序即為基于對話框類型&#xff0c;相對而言比較復雜&#xff0c;而基于文檔視圖類的應用交互形式比較單一&#xff0c;相對簡單。下面給出基于mfc框架的最基本的桌面程…

npm無法加載文件 因為此系統禁止運行腳本

安裝nodejs后遇到問題&#xff1a; 在項目里【node -v】可以打印出來&#xff0c;【npm -v】打印不出來&#xff0c;顯示npm無法加載文件 因為此系統禁止運行腳本。 但是在winr&#xff0c;cmd里【node -v】,【npm -v】都也可打印出來。 解決方法&#xff1a; cmd里可以打印出…

JVM春招快速學習指南

1.說在前面 在Java相關崗位的春/秋招面試過程中&#xff0c;JVM的學習是必不可少的。本文主要是通過《深入理解Java虛擬機》第三版來介紹JVM的學習路線和方法&#xff0c;并對沒有過JVM基礎的給出閱讀和學習建議&#xff0c;盡可能更加快速高效的進行JVM的學習與秋招面試的備戰…

DeepSeek API 調用 - Spring Boot 實現

DeepSeek API 調用 - Spring Boot 實現 1. 項目依賴 在 pom.xml 中添加以下依賴&#xff1a; <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId></depe…

認識Electron 開啟新的探索世界一

一、Electron輕松入門 1.搭建開發環境&#xff1a; 一般情況下開發者會使用node.js來創建electron項目&#xff0c;node.js是一個基于Chrome V8引擎的javascript運行環境&#xff0c;所以首先需要到官網去下載安裝node.js 下載鏈接&#xff1a;https://nodejs.org/enhttps://no…

MySQL下載過程

MySQL Enterprise Edition Downloads | Oracle mysql官方下載網址&#xff08;9.2版本&#xff09; 下面的示例是5.7的包&#xff0c;過程是一樣的 port&#xff1a;3308&#xff08;默認的是3306&#xff0c;筆者下了一個占用了該端口&#xff09; root&#xff1a;123456 問題…

【學術投稿】第五屆計算機網絡安全與軟件工程(CNSSE 2025)

重要信息 官網&#xff1a;www.cnsse.org 時間&#xff1a;2025年2月21-23日 地點&#xff1a;中國-青島 簡介 第五屆計算機網絡安全與軟件工程&#xff08;CNSSE 2025&#xff09;將于2025年2月21-23日在中國-青島舉行。CNSSE 2025專注于計算機網絡安全、軟件工程、信號處…

Qt:QWidget核心屬性

目錄 QWidget核心屬性 enab geometry WindowFrame的影響 windowTitle windowIcon qrc文件管理資源 windowOpacity cursor font toolTip focusPolicy styleSheet QWidget核心屬性 在Qt中使用QWidget類表示"控件"&#xff0c;如按鈕、視圖、輸入框、滾動…

Linux TCP 編程詳解與實例

一、引言 在網絡編程的領域中&#xff0c;TCP&#xff08;Transmission Control Protocol&#xff09;協議因其可靠的數據傳輸特性而被廣泛應用。在 Linux 環境下&#xff0c;使用 C 或 C 進行 TCP 編程可以實現各種強大的網絡應用。本文將深入探討 Linux TCP 編程的各個方面&…

原生redis實現分布式鎖

用 原生 Redis&#xff08;Jedis、Lettuce&#xff09; 實現分布式鎖&#xff0c;可以參考 Redisson 的原理&#xff0c;但需要自己處理鎖的自動續期、故障恢復等細節。核心思路是使用 Redis 的 SET NX EX 或 SET PX NX 命令來實現互斥鎖&#xff0c;并利用 Lua 腳本 保障原子性…

論文筆記:Rethinking Graph Neural Networks for Anomaly Detection

目錄 摘要 “右移”現象 beta分布及其小波 實驗 《Rethinking Graph Neural Networks for Anomaly Detection》&#xff0c;這是一篇關于圖&#xff08;graph&#xff09;上異常節點診斷的論文。 論文出處&#xff1a;ICML 2022 論文地址&#xff1a;Rethinking Graph Ne…

神經網絡常見激活函數 6-RReLU函數

文章目錄 RReLU函數導函數函數和導函數圖像優缺點pytorch中的RReLU函數tensorflow 中的RReLU函數 RReLU 隨機修正線性單元&#xff1a;Randomized Leaky ReLU 函數導函數 RReLU函數 R R e L U { x x ≥ 0 a x x < 0 \rm RReLU \left\{ \begin{array}{} x \quad x \ge 0…

Vue(6)

一.路由板塊封裝 &#xff08;1&#xff09;路由的封裝抽離 目標&#xff1a;將路由板塊抽離出來 好處&#xff1a;拆分板塊&#xff0c;利于維護 // 路由的使用步驟 5 2 // 5個基礎步驟 // 1. 下載 v3.6.5 // 2. 引入 // 3. 安裝注冊 Vue.use(Vue插件) // 4. 創建路由對象…