前端實現導出 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