Java整合Thymeleaf和wkhtmltopdf實現HTML導出PDF

前端實現導出 PDF 產品報告毫痕,存在幾個問題:

? 1. 是圖片版的 PDF;

? 2. PDF 太大,會卡迟几;

? 3. 可能會把文字裁剪分頁消请;

? 4. 無法滿足平臺提供 Api 接口服務。

核心就是問題3和問題4瘤旨,于是梯啤,考慮后端服務實現導出 PDF 產品報告的方案。

Java 實現 HTML 轉 PDF 技術選型


推薦使用 wkhtmltopdf, Itext存哲,但 wkhtmltopdf 開源免費因宇,Itext 需要考慮版權

參考:https://blog.csdn.net/weixin_43981813/article/details/128135730

參考:https://www.cnblogs.com/IT-study/p/13706690.html

技術實現方案

技術采用模板引擎 + PDF 插件實現七婴。開發(fā)好頁面模板,Thymeleaf 模板引擎渲染靜態(tài) HTML 文件察滑,wkhtmltopdf 將靜態(tài)的 HTML 生成 PDF 文件打厘。整體方案流程如下:


后臺使用 Thymeleaf 模板生成 Html 報告頁面

PDF 接口根據 ID 查詢報告數據

調用 wkhtmltopdf 工具 將 Html 轉為 PDF 文件

關于 wkhtmltopdf

參數文檔: https://wkhtmltopdf.org/usage/wkhtmltopdf.txt

wkhtmltopdf 安裝

Yum 安裝(可能是老版本存在bug,不推薦)

yum -y install wkhtmltopdf

rpm 包安裝

下載最新按照包,如

wget https://objects.githubusercontent.com/github-production-release-asset-2e65be/131323182/4c2dd800-ab8e-11ea-95aa-09875726406d?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20230904%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20230904T082059Z&X-Amz-Expires=300&X-Amz-Signature=8eb5914f5551c3f9a454537895ab41c3884fcb447ca4babf6c57e27fefb46b41&X-Amz-SignedHeaders=host&actor_id=39854904&key_id=0&repo_id=131323182&response-content-disposition=attachment%3B%20filename%3Dwkhtmltox-0.12.6-1.centos8.x86_64.rpm&response-content-type=application%2Foctet-stream

先安裝依賴包

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

wkhtmltox 安裝

rpm -ivh wkhtmltox-0.12.6-1.centos8.x86_64.rpm

若需要路徑執(zhí)行贺辰,可配置

cp /usr/local/bin/wkhtmltopdf /usr/bin/wkhtmltopdf

內網安裝

先下載依賴包到指定目錄户盯,例如下載 openssl 依賴包到指定目錄

yum install --downloadonly --downloaddir=/usr/soft/wktooltopdf/ openssl

之后,拷貝依賴包到內網環(huán)境饲化,執(zhí)行命令

rpm -ivh --force --nodeps *.rpm

rpm -ivh --force --nodeps *.rpm

常見問題

缺少依賴包

手動安裝依賴包

FAQ-linux 安裝 wkhtmltopdf 中文亂碼或者空白解決方法

參考:https://www.cnblogs.com/jluo/p/17403785.html

安裝中文字體莽鸭,或復制已有字體

打開windows c:\Windows\fonts\simsun.ttc

拷貝到linux服務器/usr/share/fonts/目錄下,再次生成pdf中文顯示正常

出現錯誤: wkhtmltopdf:cannot connect to X server

參考:http://www.reibang.com/p/2cfc02961528

需再安裝xvfb

yum install xorg-x11-server-Xvfb

在 /usr/bin/ 目錄下生成腳本 wkhtmltopdf.sh 并寫入命令

sudo vim /usr/bin/wkhtmltopdf.sh?

命令:

xvfb-run -a --server-args="-screen 0, 1024x768x24" /usr/bin/wkhtmltopdf -q $*

更改文件權限并建立連接

chmod a+x /usr/bin/wkhtmltopdf.sh

ln -s /usr/bin/wkhtmltopdf.sh /usr/local/bin/wkhtmltopdf

中文字體安裝

若出現中文亂碼,則可能是缺少字體

阿里巴巴普惠體2.0吃靠,免費無版權硫眨,好用

下載地址: https://done.alibabadesign.com/puhuiti2.0

介紹說明: https://fonts.adobe.com/fonts/alibaba-puhuiti

設置字體集

font-family: alibaba-puhuiti, sans-serif;

font-style: normal;

font-weight: 300;

開發(fā)代碼及配置

靜態(tài)資源目錄位于 *-model 工程下的資源文件,包括以下目錄

templates/ - 模板文件

static/ - 靜態(tài)資源文件

若前端有修改調整巢块,需將更新的文件復制到 *-model 工程下對應目錄礁阁,

靜態(tài)資源復制方案1:maven 插件配置, 用于復制公共的資源

pom.xml 增加插件配置

<plugin>? <!-- 該插件的作用是用于復制 PDF 模板資源文件 -->

? ? <artifactId>maven-resources-plugin</artifactId>

? ? <executions>

? ? ? ? <execution>

? ? ? ? ? ? <id>copy-resources</id>

? ? ? ? ? ? <phase>package</phase>

? ? ? ? ? ? <goals>

? ? ? ? ? ? ? ? <goal>copy-resources</goal>

? ? ? ? ? ? </goals>

? ? ? ? ? ? <configuration>

? ? ? ? ? ? ? ? <resources>

? ? ? ? ? ? ? ? ? ? <resource>

? ? ? ? ? ? ? ? ? ? ? ? <directory>../../*-model/src/main/resources</directory>? <!-- 指定相對路徑,復制 *-model 下的模板靜態(tài)資源 -->

? ? ? ? ? ? ? ? ? ? ? ? <includes>? <!-- 復制模板文件和靜態(tài)資源文件-->

? ? ? ? ? ? ? ? ? ? ? ? ? ? <include>templates/**</include>

? ? ? ? ? ? ? ? ? ? ? ? ? ? <include>static/**</include>

? ? ? ? ? ? ? ? ? ? ? ? </includes>

? ? ? ? ? ? ? ? ? ? </resource>

? ? ? ? ? ? ? ? </resources>

? ? ? ? ? ? ? ? <outputDirectory>src/main/resources</outputDirectory>? <!-- 指定輸出目錄,復制到當前工程資源模型下,用于下一步打包 -->

? ? ? ? ? ? ? ? <skip>true</skip>? <!-- 跳過執(zhí)行,已配置了 package.xml,直接復制到打包文件 -->

? ? ? ? ? ? </configuration>

? ? ? ? </execution>

? ? </executions>

</plugin>

靜態(tài)資源復制方案2:自定義的打包配置,增加資源復制

推薦使用此方法族奢,直接復制資源并打包到目標 zip 包

路徑:/src/main/assemble/package.xml姥闭,增加配置

<fileSet>? <!-- 該插件的作用是用于復制 PDF 模板資源文件 -->

? ? ? <directory>../../*-model/src/main/resources</directory>

? ? ? <outputDirectory>\</outputDirectory>

? ? ? <includes>? <!-- 復制模板文件和靜態(tài)資源文件-->

? ? ? ? ? <include>templates/**</include>

? ? ? ? ? <include>static/**</include>

? ? ? </includes>

? </fileSet>

Java 工具類

由于模板引擎對 JS 的支持有限,固增加 Java 工具類越走,用于模板中處理數據(模板引擎是在服務端執(zhí)行,可執(zhí)行 Java 代碼)

參考 HtmlThymeleafHelper 配置, 注意 ModelAndView 中返回工具列

后端開發(fā) pom.xml 依賴

<dependency>

? ? <groupId>org.springframework.boot</groupId>

? ? <artifactId>spring-boot-starter-thymeleaf</artifactId>

</dependency>

<dependency>

? ? <groupId>org.springframework.boot</groupId>

? ? <artifactId>spring-boot-starter-web</artifactId>

</dependency>

開發(fā)模板

<!DOCTYPE html>

<html? xmlns:th="http://www.thymeleaf.org">

<head>

? ? <meta charset="UTF-8">

? ? <title>title</title>

</head>

<body>

Hello Thymeleaf

<div th:text="${name}">張三(離線數據)</div>

</body>

</html>

后端接口處理 ModelAndView

// 接口返回 ModelAndView, 指定模板, 設置數據

@GetMapping("/template/render")

public ModelAndView templateReportDetail(HttpServletRequest request, @RequestParam String reportId) {

? ? this.initJavaEnv(request);

? ? return this.renderModelAndView("TemplateReportDetail", reportId);

}

// 通過 Session 設置 Java 對象,用于模板中執(zhí)行 Java 方法

private void initJavaEnv(HttpServletRequest request) {

? ? HtmlThymeleafHelper helper = Singleton.get(HtmlThymeleafHelper.class);

? ? request.getSession().setAttribute("helper", helper);

}

// 創(chuàng)建一個模型視圖對象

ModelAndView mav = new ModelAndView();

// 獲取到查詢的數據

Object data = ret.getRetObject();

// 將數據放置到ModelAndView對象view中,第二個參數可以是任何java類型

mav.addObject("sourceData", data);

// 放入模板路徑

mav.setViewName("template");

// 返回ModelAndView對象mav

return mav;

SpringBoot yml 配置

spring:

? mvc:

? ? # 添加static文件夾下其他文件夾可訪問

? ? static-path-pattern: /project/static/**

? ? # 自定義配置項,指定模板路徑

? ? base-template-path: /project/template

? thymeleaf:

? ? cache: true

? ? mode: HTML5

? ? suffix: .html

? ? prefix: classpath:/templates/

? ? encoding: UTF-8

? ? servlet:

? ? ? content-type: text/html

關于模板引擎 Thymeleaf

什么是Thymeleaf棚品?

Thymeleaf 官網是這么解釋的:Thymeleaf is a modern server-side Java template engine for both web and standalone environments.

譯過來就是:Thymeleaf是適用于Web和獨立環(huán)境的現代服務器端Java模板引擎

什么是模板引擎?

模板引擎(這里特指用于Web開發(fā)的模板引擎)是為了使用戶界面與業(yè)務數據(內容)分離而產生的弥姻,它可以生成特定格式的文檔南片,用于網站的模板引擎就會生成一個標準的html文檔。從字面上理解模板引擎庭敦,最重要的就是模板二字,這個意思就是做好一個模板后套入對應位置的數據薪缆,最終以html的格式展示出來秧廉,這就是模板引擎的作用。

不僅如此拣帽,在Java中模板引擎還有很多疼电,模板引擎是動態(tài)網頁發(fā)展進步的產物,在最初并且流傳度最廣的jsp它就是一個模板引擎减拭。jsp是早期官方標準的模板蔽豺,但是由于jsp的缺點比較多也挺嚴重的,所以很多人棄用jsp選用第三方的模板引擎拧粪,市面上開源的第三方的模板引擎也比較多修陡,有Thymeleaf沧侥、FreeMaker、Velocity等模板引擎受眾較廣魄鸦。

聽完了模板引擎的介紹宴杀,相信你也很容易明白了模板引擎在web領域的主要作用:讓網站實現界面和數據分離,這樣大大提高了開發(fā)效率拾因,讓代碼重用更加容易旺罢。


Model、ModelMap绢记、ModelAndView

Model

一般來說扁达,可以用Model來接收各種類型的數據,如果使用來接收一組數據List蠢熄,那么這個時候的Model實際上是ModelMap

ModelMap

主要用于傳遞控制方法處理數據到結果頁面罩驻,也就是說我們把結果頁面上需要的數據放到ModelMap對象中即可,

他的作用類似于request對象的setAttribute方法的作用:用來在一個請求過程中傳遞處理的數據

ModelMap或者Model通過addAttribute方法向頁面?zhèn)鬟f參數

ModelAndView

指模型和視圖的集合护赊,既包含 模型 又包含 視圖

Model和 ModelMap 無需用戶自己創(chuàng)建惠遏,而且需要return 返回指定的頁面路徑

Model和 ModelMap 無需用戶自己創(chuàng)建,而且需要return 返回指定的頁面路徑

public String listCategory2(Model model) {

? ? // 接收查詢的信息

? ? List<Category> cs2= categoryService.list();

? ? // 封裝了查詢的數據

? ? model.addAttribute("test", cs2);

? ? //重要?节吮!需要給出返回model跳轉的路徑

? ? return "listCategory2";

}

ModelAndView的實例是需要我們手動new的,這也是和ModelMap的一個區(qū)別判耕。

而且透绩,ModelAndView 可以自己尋址,只需要return 返回其對象即可壁熄。

public ModelAndView listCategory(){

? //創(chuàng)建一個模型視圖對象

? ? ModelAndView mav = new ModelAndView();

? ? //獲取到查詢的數據

? ? List<Category> cs= categoryService.list();

? ? // //將數據放置到ModelAndView對象view中,第二個參數可以是任何java類型

? ? mav.addObject("cs", cs);

? ? // 放入jsp路徑

? ? mav.setViewName("listCategory");

? ? //返回ModelAndView對象mav

? ? return mav;

}

參考:https://cloud.tencent.com/developer/article/1698750

Thymeleaf 常用標簽

標簽作用示例

th:id替換id<input th:id="${user.id}"/>

th:text文本替換<p text:="${user.name}">bigsai</p>

th:utext支持html的文本替換<p utext:="${htmlcontent}">content</p>

th:object替換對象<div th:object="${user}"></div>

th:value替換值<input th:value="${user.name}" >

th:each迭代<tr th:each="student:${user}" >

th:href替換超鏈接<a th:href="@{index.html}">超鏈接</a>

th:src替換資源<script type="text/javascript" th:src="@{index.js}"></script>

七大基礎對象:

${#ctx} 上下文對象帚豪,可用于獲取其它內置對象。

${#vars}: 上下文變量草丧。

${#locale}:上下文區(qū)域設置狸臣。

${#request}: HttpServletRequest對象。

${#response}: HttpServletResponse對象昌执。

${#session}: HttpSession對象烛亦。

${#servletContext}: ServletContext對象。

常用的工具類:

#strings:字符串工具類

#lists:List 工具類

#arrays:數組工具類

#sets:Set 工具類

#maps:常用Map方法懂拾。

#objects:一般對象類煤禽,通常用來判斷非空

#bools:常用的布爾方法。

#execInfo:獲取頁面模板的處理信息岖赋。

#messages:在變量表達式中獲取外部消息的方法檬果,與使用#{...}語法獲取的方法相同。

#uris:轉義部分URL / URI的方法。

#conversions:用于執(zhí)行已配置的轉換服務的方法选脊。

#dates:時間操作和時間格式化等杭抠。

#calendars:用于更復雜時間的格式化。

#numbers:格式化數字對象的方法知牌。

#aggregates:在數組或集合上創(chuàng)建聚合的方法祈争。

#ids:處理可能重復的id屬性的方法。

引入css(必須要在標簽中加上rel屬性)

<link rel="stylesheet" th:href="@{index.css}">

<link th:href="@{/static/css/index.css}" type="text/css" rel="stylesheet">

引入JavaScript:

<script type="text/javascript" th:src="@{index.js}"></script>

<script type="text/javascript" th:src="@{/js/jquery.js}"></script>

超鏈接:

<a th:href="@{index.html}">超鏈接</a>

變量表達式: ${...}

在Thymeleaf中可以通過${…}進行取值角寸,這點和ONGL表達式語法一致

取JavaBean對象:

使用${對象名.對象屬性}或者${對象名['對象屬性']}來取值

如果該JavaBean如果寫了get方法菩混,也可以通過get方法取值例如${對象.get方法名}

<td th:text="${user.name}"></td>

<td th:text="${user['age']}"></td>

<td th:text="${user.getDetail()}"></td>

取List集合(each):

因為List集合是個有序列表,要遍歷List對其中對象取值扁藕,而遍歷需要用到標簽:th:each,

具體使用為 <tr th:each="item:${userlist}">,其中item就相當于遍歷每一次的對象名

<table bgcolor="#ffe4c4" border="1">

? ? <tr th:each="item:${userlist}">

? ? ? ? <td th:text="${item}"></td>

? ? </tr>

</table>

直接取Map:

很多時候我們不存JavaBean而是將一些值放入Map中沮峡,再將Map存在Model中,我們就需要對Map取值,

可以 ${Map名['key']} 取值亿柑。也可以 ${Map名.key} 取值邢疙,當然也可以 ${map.get('key')}(java語法)取值

<table bgcolor="#8fbc8f" border="1">

? ? <tr>

? ? ? ? <td>place:</td>

? ? ? ? <td th:text="${map.get('place')}"></td>

? ? </tr>

? ? <tr>

? ? ? ? <td>feeling:</td>

? ? ? ? <td th:text="${map['feeling']}"></td>

? ? </tr>

</table>

參考:https://developer.aliyun.com/article/769977

Thymeleaf 控制處理

<input type="text" name="menuName" disabled th:value="${result?.data?.menuName}" class="layui-input">

? 會判斷對象是否為空,如果為空就不會繼續(xù)取值

SPEL處理 null 值

變量為 null 時望薄,顯示默認值

name?:'Unknown'

當 name 變量為 null 時疟游,顯示值 Unknown。等價于 name?name:'Unknown'痕支。

對象為 null 時颁虐,避免調用方法或屬性出錯

placeOfBirth?.city

當 placeOfBirth 為 null 時,不再繼續(xù)調用屬性 city卧须。

code?.toUpperCase()

當 code 為 null 時另绩,不再繼續(xù)調用方法 toUpperCase。

Map 獲取的元素為 null

當 map 中沒有名為 name 的元素時花嘶,這樣寫會報錯 map.name笋籽。

安全的寫法是這樣:map['name']。

如果 map 中的元素為對象時椭员,可以這樣寫:map['user']?.name车海。

List 類型數組越界

數組越界時,錯誤是這樣的:

Caused by: org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "slist[2].score" (template: "exam/papers/edit" - line 117, col 92)

Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1025E: The collection has '1' elements, index '2' is invalid

SPEL 是這樣的 slist[2].score拆撼,但 slist 不夠3個元素(EL1025E: The collection has '1' elements, index '2' is invalid)容劳,因此數組越界了。

解決辦法:添加數組大小的判斷闸度。上面的情況下,用 #lists.size(slist)>=3?slist[2]?.score:0 替換 slist[2].score

參考:https://blog.csdn.net/sayyy/article/details/109385456

參考:https://zhuanlan.zhihu.com/p/90642654

Thymeleaf 調用 Java 方法

1. Java 對象示例存入 Thymeleaf Context 域中蚜印,代碼層面即為:將實例對象存入Request對象中

MethodService md = new MethodService();

mmap.put("methodService",md);

mmap.put("proofsList",proofsList);

<label class="checkbox-inline i-checks"? th:each="data : ${list}">?

? ? <input th:attr="checked=${methodService.contains(data.id,proofsList)?true:false}" type="checkbox" name="proofs[]"? th:value="${data.id}" id="inlineCheckbox1" />

</lable>

Thymeleaf 動態(tài)添加樣式

<li class="treeview" th:classappend="${tree == 'index'}?'active'"></li>

或者

<li class="treeview" th:classappend="${tree == 'index'?'active':''}"></li>

動態(tài)綁定樣式

<li th:class="${tree == 'index'?'active':''}"></li>

Thymeleaf 數組處理

// 數組長度

<p>The greatest <span th:text="${#arrays.length(continents)}"></span> continents.</p>

// 數組包含

<p>Europe is a continent: <span th:text="${#arrays.contains(continents, 'Europe')}"></span>.</p>

// 數組判空

<p>Array of continents is empty <span th:text="${#arrays.isEmpty(continents)}"></span>.</p>

Thymeleaf 遍歷生成復雜的表格

<table class="layui-table" id="tabRank">

? ? <tr>

? ? ? ? <th colspan="2">機構</th>

? ? ? ? <th colspan="2">年份</th>

? ? ? ? <th colspan="2">得分</th>

? ? ? ? <th colspan="2">全球排名</th>

? ? </tr>

? ? <div th:remove="tag" th:if="*{#lists.isEmpty(institution)}">

? ? ? ? <tr>

? ? ? ? ? ? <td colspan="8" style="text-align: center">無排名信息</td>

? ? ? ? </tr>

? ? </div>

? ? <div th:remove="tag" th:if="*{not #lists.isEmpty(institution)}" th:each="institution:${institution}">

? ? ? ? <tr>

? ? ? ? ? ? <td colspan="2" rowspan="4" th:text="${institution.institution}"></td>

? ? ? ? ? ? <tr th:each="rank:${schoolRank}" th:if="${rank.schoolRankInstitution}==${institution.institution}">

? ? ? ? ? ? ? ? <td colspan="2" th:text="${rank.schoolRankYears}"></td>

? ? ? ? ? ? ? ? <td colspan="2" th:text="${rank.schoolRankScore}"></td>

? ? ? ? ? ? ? ? <td colspan="2" th:text="${rank.schoolRankGlobal}"></td>

? ? ? ? ? ? </tr>

? ? ? ? </tr>

? ? </div>

</table>

th:remove="tag"

它在這的作用是生成表格后把div刪除莺禁,但不刪除子元素

th:if="*{#lists.isEmpty(institution)}"

判斷從后臺獲取的數據為空,空則不渲染 tr 標簽

th:if="*{not #lists.isEmpty(institution)}"

判斷從后臺獲取的數據不為空窄赋,不為空則渲染 tr 標簽

<div th:remove="tag" th:each="downPriceEntry,stats:${appPriceInfoVO.downPriceMap}"

? ? th:with="appName = ${downPriceEntry.key}, appChangeNum = ${downPriceEntry.value.size()},

? ? ? ? ? appInfo0 = ${downPriceEntry.value.get(0)}, downPriceList = ${downPriceEntry.value}">

? ? <tr th:if="${appPriceInfoVO.downNum}>0">

? ? ? ? <td class="btbg1" th:text="價格下降" th:rowspan="${appPriceInfoVO.downNum}" th:if="${stats.first}"></td>

? ? ? ? <td th:text="${appName}" th:rowspan="${appChangeNum}"

? ? ? ? ? ? th:class="${stats.index % 2 == 0} ? 'btbg4':'btbg3'"></td>

? ? ? ? <td th:class="${stats.index % 2 == 0} ? 'btbg4':'btbg3'" th:text="${appInfo0.price}"></td>

? ? ? ? <td th:class="${stats.index % 2 == 0} ? 'btbg4':'btbg3'" th:text="${appInfo0.version}"></td>

? ? ? ? <td th:class="${stats.index % 2 == 0} ? 'btbg4':'btbg3'" th:text="${appInfo0.createTime}"></td>

? ? ? ? <td th:class="${stats.index % 2 == 0} ? 'btbg4':'btbg3'" th:text="${appInfo0.language}"></td>

? ? ? ? <td th:class="${stats.index % 2 == 0} ? 'btbg4':'btbg3'">

? ? ? ? ? ? <a th:href="${appInfo0.url}" target="_blank" th:text="${appInfo0.name}"></a>

? ? ? ? </td>

? ? </tr>

? ? <tr th:each="downPriceAppInfo,stat : ${downPriceList}" th:if="${!stat.first}">

? ? ? ? <td th:class="${stats.index % 2 == 0} ? 'btbg4':'btbg3'" th:text="${downPriceAppInfo.price}"></td>

? ? ? ? <td th:class="${stats.index % 2 == 0} ? 'btbg4':'btbg3'" th:text="${downPriceAppInfo.version}"></td>

? ? ? ? <td th:class="${stats.index % 2 == 0} ? 'btbg4':'btbg3'" th:text="${downPriceAppInfo.createTime}"></td>

? ? ? ? <td th:class="${stats.index % 2 == 0} ? 'btbg4':'btbg3'" th:text="${downPriceAppInfo.language}"></td>

? ? ? ? <td th:class="${stats.index % 2 == 0} ? 'btbg4':'btbg3'">

? ? ? ? ? ? <a th:href="${downPriceAppInfo.url}" target="_blank" th:text="${downPriceAppInfo.name}"></a>

? ? ? ? </td>

? ? </tr>

</div>

th:remove:會移除該標簽行哟冬,不會移除其子標簽

th:each:迭代集合或者數組

th:with:臨時變量的聲明

colspan 合并單元格 列

rowspan 合并單元格 行

常見問題

wkhtmltopdf 生成 PDF 的表格行內出現分頁符楼熄、表頭重復、截斷等

增加表格樣式

thead {

? ? display: table-row-group;

}

tr {

? ? page-break-before: always;

? ? page-break-after: always;

? ? page-break-inside: avoid;

}

table {

? ? word-wrap: break-word;

}

table td {

? ? word-break: break-all;

}

說明:wkhtmltopdf 對表格的支持很差浩峡,會導致文件很大可岂,長表格兼容性等問題

參考:https://blog.csdn.net/yellowatumn/article/details/87864601

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市翰灾,隨后出現的幾起案子缕粹,更是在濱河造成了極大的恐慌,老刑警劉巖纸淮,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件平斩,死亡現場離奇詭異,居然都是意外死亡咽块,警方通過查閱死者的電腦和手機绘面,發(fā)現死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來侈沪,“玉大人揭璃,你說我怎么就攤上這事⊥ぷ铮” “怎么了瘦馍?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長皆撩。 經常有香客問我扣墩,道長,這世上最難降的妖魔是什么扛吞? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任呻惕,我火速辦了婚禮,結果婚禮上滥比,老公的妹妹穿的比我還像新娘亚脆。我一直安慰自己,他們只是感情好盲泛,可當我...
    茶點故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布濒持。 她就那樣靜靜地躺著,像睡著了一般寺滚。 火紅的嫁衣襯著肌膚如雪柑营。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天村视,我揣著相機與錄音官套,去河邊找鬼。 笑死,一個胖子當著我的面吹牛奶赔,可吹牛的內容都是我干的惋嚎。 我是一名探鬼主播,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼站刑,長吁一口氣:“原來是場噩夢啊……” “哼另伍!你這毒婦竟也來了?” 一聲冷哼從身側響起绞旅,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤摆尝,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后玻靡,有當地人在樹林里發(fā)現了一具尸體结榄,經...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年囤捻,在試婚紗的時候發(fā)現自己被綠了臼朗。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,646評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡蝎土,死狀恐怖视哑,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情誊涯,我是刑警寧澤挡毅,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站暴构,受9級特大地震影響跪呈,放射性物質發(fā)生泄漏。R本人自食惡果不足惜取逾,卻給世界環(huán)境...
    茶點故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一耗绿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧砾隅,春花似錦误阻、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至儒洛,卻和暖如春精耐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背琅锻。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工黍氮, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留唐含,地道東北人浅浮。 一個月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓沫浆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親滚秩。 傳聞我的和親對象是個殘疾皇子专执,可洞房花燭夜當晚...
    茶點故事閱讀 43,514評論 2 348

推薦閱讀更多精彩內容