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

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?什么是点击劫持?如何防御

相关文章:

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

目录 需求 一、方案调研 二、wkhtmltopdf使用 如何使用 文档简要说明 三、后端服务 四、前端服务 往期回顾 需求 最近在做报表类的统计项目&#xff0c;其中有很多指标需要汇总&#xff0c;网页内容有大量的echart图表&#xff0c;做成一个网页去浏览&#xff0c;同时…...

ip地址是手机号地址还是手机地址

在数字化生活的浪潮中&#xff0c;IP地址、手机号和手机地址这三个概念如影随形&#xff0c;它们各自承载着网络世界的独特功能&#xff0c;却又因名称和功能的相似性而时常被混淆。尤其是“IP地址”这一术语&#xff0c;经常被错误地与手机号地址或手机地址划上等号。本文旨在…...

【大数据技术】搭建完全分布式高可用大数据集群(Hadoop+MapReduce+Yarn)

搭建完全分布式高可用大数据集群(Hadoop+MapReduce+Yarn) jdk-8u361-linux-x64.tarhadoop-3.3.6.tar.gz注:请在阅读本篇文章前,将以上资源下载下来。 写在前面 本文主要介绍搭建完全分布式高可用集群Hadoop+MapReduce+Yarn的详细步骤。 注意: 统一约定将软件安装包存放…...

从零开始:OpenCV 图像处理快速入门教程

文章大纲 第1章 OpenCV 概述 1.1 OpenCV的模块与功能  1.2 OpenCV的发展 1.3 OpenCV的应用 第2章 基本数据类型 2.1 cv::Vec类 2.2 cv&#xff1a;&#xff1a;Point类 2.3 cv&#xff1a;&#xff1a;Rng类 2.4 cv&#xff1a;&#xff1a;Size类 2.5 cv&#xff1a;&…...

springboot简单应用

快速开发Springboot项目实现简单的增删改查&#xff0c;前期需要准备&#xff1a;idea与postman安装 Maven&#xff0c;MySQL&#xff08;8&#xff09;&#xff0c;JDK(21) 目录 前言 springboot 使用3.0版本&#xff0c;JDK使用21,MySQL使用8版本 开发环境IDEA使用2024版本 …...

【DeepSeek】DeepSeek小模型蒸馏与本地部署深度解析DeepSeek小模型蒸馏与本地部署深度解析

一、引言与背景 在人工智能领域&#xff0c;大型语言模型&#xff08;LLM&#xff09;如DeepSeek以其卓越的自然语言理解和生成能力&#xff0c;推动了众多应用场景的发展。然而&#xff0c;大型模型的高昂计算和存储成本&#xff0c;以及潜在的数据隐私风险&#xff0c;限制了…...

C#项目引用VB.NET 类库项目,生成一个EXE,这是什么原理

C#项目引用VB.NET 类库项目&#xff0c;生成一个EXE&#xff0c;这是什么原理 在C#项目中引用VB.NET类库项目并生成一个EXE文件&#xff0c;主要基于.NET框架的通用性以及编译原理。以下是详细的原理分析&#xff1a; 1. .NET框架的通用性 公共语言运行时&#xff08;CLR&…...

qt使用MQTT协议连接阿里云demo

qt使用Mqtt协议连接阿里云。 在配置好qt关于MQTT的环境之后&#xff0c;主要就是根据MQTT的连接参数进行连接即可。 环境配置推荐链接QT编译并部署QtMqtt相关环境跑测demo【超详细教程】_mqtt qt开发教程-CSDN博客 连接核心代码,主要就是根据阿里云的MQTT相关参数进行配置实现连…...

HTML中的图片标签详解及路径使用【学术投稿-第五届环境资源与能源工程国际学术会议(ICEREE 2025)】

官网&#xff1a;www.iceree.org 会议时间&#xff1a;2025年2月21-23日 会议地点&#xff1a;中国-昆明 简介 第五届环境资源与能源工程国际学术会议&#xff08;ICEREE 2025&#xff09;将于2025年2月21日至23日在中国昆明隆重举行。主要围绕“能源工程和能源技术”、“环…...

【低功耗 Power 学习专栏 -- Power domian 和 power rail】

文章目录 power rail(followpin) 和 Power domain1. Power Domain2. Power Rail3. Followpin4. Power Stripe5. IR Drop芯片中电源管理设计 举例 power rail(followpin) 和 Power domain followpin 指两部分&#xff0c;一个就是 STD cell 上下的 VDD, VSS。同时&#xff0c;f…...

PythonStyle MVC 开发框架

在 Python 中&#xff0c;MVC&#xff08;Model - View - Controller&#xff0c;模型 - 视图 - 控制器&#xff09;是一种常见的软件设计模式&#xff0c;它将应用程序分为三个主要部分&#xff0c;各自承担不同的职责&#xff0c;以提高代码的可维护性、可扩展性和可测试性。…...

RTOS基础(TODO)

&#xff08;TODO&#xff09; 读完FreeRTOS内核源码&#xff0c;需要多久&#xff1f; 有哪些情况下是RTOS满足不了需求&#xff0c;必须得上嵌入式Linux系统的&#xff1f; 如何用树莓派 Pico 学习 RTOS&#xff1f; 树莓派 Pico 使用 RP2040 微控制器&#xff0c;基于 AR…...

八、Spring Boot 日志详解

目录 一、日志的用途 二、日志使用 2.1 打印日志 2.1.1 在程序中获取日志对象 2.1.2 使用日志对象打印日志 2.2、日志框架介绍 2.2.1 门面模式(外观模式) 2.2.2 门面模式的实现 2.2.3 SLF4J 框架介绍 2.3 日志格式的说明 2.4 日志级别 2.4.1 日志级别的分类 2.4.2…...

Java实战经验分享

1. 项目优化与性能提升 面试问题&#xff1a; 聊聊你印象最深刻的项目&#xff0c;或者做了哪些优化 你在项目中如何解决缓存穿透问题&#xff1f; 缓存穿透是我们做缓存优化时最常遇到的问题&#xff0c;特别是当查询的对象在数据库中不存在时&#xff0c;缓存层和数据库都会…...

前端控制器模式

前端控制器模式 概述 前端控制器模式&#xff08;Front Controller Pattern&#xff09;是一种设计模式&#xff0c;它将应用程序中的所有用户请求统一交由一个控制器处理。这种模式在MVC&#xff08;Model-View-Controller&#xff09;架构中尤为常见&#xff0c;它能够简化…...

Linux之安装docker

一、检查版本和内核是否合格 Docker支持64位版本的CentOS 7和CentOS 8及更高版本&#xff0c;它要求Linux内核版本不低于3.10。 检查版本 cat /etc/redhat-release检查内核 uname -r二、Docker的安装 1、自动安装 Docker官方和国内daocloud都提供了一键安装的脚本&#x…...

BUUCTF_XSS-Lab

xss XSS&#xff08;Cross - Site Scripting&#xff09;即跨站脚本攻击&#xff0c;是一种常见的 Web 安全漏洞。攻击者通过在目标网站注入恶意脚本&#xff08;通常是 JavaScript&#xff09;&#xff0c;当其他用户访问该网站时&#xff0c;这些恶意脚本会在用户的浏览器中执…...

springBoot使用

1.什么是SpringBoot SpringBoot是由Pivotal团队提供的一套开源框架&#xff0c;可以简化spring应用的创建及部署。SpringBoot是伴随Spring4.0的时候发布的一个框架。SpringBoot用来简化Spring应用的开发&#xff0c;约定大于配置&#xff0c;去繁从简。 从本质上来说&#xff…...

DS图(中)(19)

文章目录 前言一、图的遍历广度优先遍历深度优先遍历 二、最小生成树Kruskal算法Prim算法两种方法对比 总结 前言 承上启下&#xff0c;我们来学习下图的中篇&#xff01;&#xff01;&#xff01; 一、图的遍历 图的遍历指的是遍历图中的顶点&#xff0c;主要有 广度优先遍历 …...

Vue Dom截图插件,截图转Base64 html2canvas

安装插件 npm install html2canvas --save插件使用 <template><div style"padding: 10px;"><div ref"imageTofile" class"box">发生什么事了</div><button click"toImage" style"margin: 10px;&quo…...

进程地址空间(比特课总结)

一、进程地址空间 1. 环境变量 1 &#xff09;⽤户级环境变量与系统级环境变量 全局属性&#xff1a;环境变量具有全局属性&#xff0c;会被⼦进程继承。例如当bash启动⼦进程时&#xff0c;环 境变量会⾃动传递给⼦进程。 本地变量限制&#xff1a;本地变量只在当前进程(ba…...

从WWDC看苹果产品发展的规律

WWDC 是苹果公司一年一度面向全球开发者的盛会&#xff0c;其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具&#xff0c;对过去十年 WWDC 主题演讲内容进行了系统化分析&#xff0c;形成了这份…...

无法与IP建立连接,未能下载VSCode服务器

如题&#xff0c;在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈&#xff0c;发现是VSCode版本自动更新惹的祸&#xff01;&#xff01;&#xff01; 在VSCode的帮助->关于这里发现前几天VSCode自动更新了&#xff0c;我的版本号变成了1.100.3 才导致了远程连接出…...

Frozen-Flask :将 Flask 应用“冻结”为静态文件

Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是&#xff1a;将一个 Flask Web 应用生成成纯静态 HTML 文件&#xff0c;从而可以部署到静态网站托管服务上&#xff0c;如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...

数据链路层的主要功能是什么

数据链路层&#xff08;OSI模型第2层&#xff09;的核心功能是在相邻网络节点&#xff08;如交换机、主机&#xff09;间提供可靠的数据帧传输服务&#xff0c;主要职责包括&#xff1a; &#x1f511; 核心功能详解&#xff1a; 帧封装与解封装 封装&#xff1a; 将网络层下发…...

大学生职业发展与就业创业指导教学评价

这里是引用 作为软工2203/2204班的学生&#xff0c;我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要&#xff0c;而您认真负责的教学态度&#xff0c;让课程的每一部分都充满了实用价值。 尤其让我…...

dify打造数据可视化图表

一、概述 在日常工作和学习中&#xff0c;我们经常需要和数据打交道。无论是分析报告、项目展示&#xff0c;还是简单的数据洞察&#xff0c;一个清晰直观的图表&#xff0c;往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server&#xff0c;由蚂蚁集团 AntV 团队…...

Java编程之桥接模式

定义 桥接模式&#xff08;Bridge Pattern&#xff09;属于结构型设计模式&#xff0c;它的核心意图是将抽象部分与实现部分分离&#xff0c;使它们可以独立地变化。这种模式通过组合关系来替代继承关系&#xff0c;从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...

GruntJS-前端自动化任务运行器从入门到实战

Grunt 完全指南&#xff1a;从入门到实战 一、Grunt 是什么&#xff1f; Grunt是一个基于 Node.js 的前端自动化任务运行器&#xff0c;主要用于自动化执行项目开发中重复性高的任务&#xff0c;例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...

从 GreenPlum 到镜舟数据库:杭银消费金融湖仓一体转型实践

作者&#xff1a;吴岐诗&#xff0c;杭银消费金融大数据应用开发工程师 本文整理自杭银消费金融大数据应用开发工程师在StarRocks Summit Asia 2024的分享 引言&#xff1a;融合数据湖与数仓的创新之路 在数字金融时代&#xff0c;数据已成为金融机构的核心竞争力。杭银消费金…...