懶得看的朋友,先說最終解決辦法,主力為 前端依靠外掛 bookjs-easy(點選直接跳轉官網)並跳轉到下面的第三點檢視
接下來詳細記錄下整個試探的方向和歷程
專案需求:是生成一個頁數達到大幾十頁的pdf,然後這個pdf包含表格、折線圖、圖片等,且橫豎幅交叉,即豎版頁面和橫板頁面交叉
1.首先我們討論的方法是直接呼叫瀏覽器的頁面列印+生成pdf,這個試過後很大的問題就是1:頁面比較模糊,2:檔案過於大了,很容易就幾十幾百兆,達不到標準,當時就直接pass掉了
2.讓我(後端)獲取html模板然後填充資料再轉成pdf,這個當時測試的時候發現很清晰,檔案也很小(畢竟是程式碼生成的,肯定比頁面列印的截圖要清楚多)感覺能行,就按這個方向往下做了,其實這是最大的坑的開始。
翻了一大堆部落格檔案之後,我統計了幾種html轉成pdf的方法:itext、html2pdf、wkhtmltopdf
<!-- 先把後面幾種方法用到的依賴全放在這,有需要測試之類的可以看看(最終我的需求 解決辦法用不到這個,簡單版pdf應該可以用) -->
<dependency> <groupId>com.itextpdf</groupId> <artifactId>itextpdf</artifactId> <version>5.5.11</version> </dependency> <!-- https://mvnrepository.com/artifact/com.itextpdf.tool/xmlworker --> <dependency> <groupId>com.itextpdf.tool</groupId> <artifactId>xmlworker</artifactId> <version>5.5.11</version> </dependency> <!-- https://mvnrepository.com/artifact/com.itextpdf/html2pdf --> <dependency> <groupId>com.itextpdf</groupId> <artifactId>html2pdf</artifactId> <version>3.0.3</version> </dependency> <!-- https://mvnrepository.com/artifact/org.thymeleaf/thymeleaf --> <dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf</artifactId> <version>3.1.2.RELEASE</version> </dependency> <dependency> <groupId>org.xhtmlrenderer</groupId> <artifactId>flying-saucer-pdf</artifactId> <version>9.1.20</version> </dependency> <!-- 解決中文字型問題 --> <dependency> <groupId>com.itextpdf</groupId> <artifactId>itext-asian</artifactId> <version>5.2.0</version> </dependency> <dependency> <groupId>org.jfree</groupId> <artifactId>jfreechart</artifactId> <version>1.5.0</version> </dependency> <dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>pdfbox</artifactId> <version>2.0.24</version> </dependency>
2.1第一步,使用了itext,這個直接轉,也可以生成pdf,但是樣式有很大的問題,表格超出,顯示不全等等,而且對html要求嚴格,中文字型也有問題,需要自己安裝解決(這個方法若有需求,網上教程很多,不是本文重點就不說了)
2.2接下來進行到使用 Thymeleaf 插值並生成pdf了,問題還是一樣,替換後的值,所有都是中文顯示不了,得自己安裝、配置字型,最主要的就是樣式依然有問題,太折騰,放棄
package com.example.web.controller.pdftest;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.thymeleaf.templateresolver.FileTemplateResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
/**
* @author hua
* @date 2024/4/18
* 讀取html檔案且使用Thymeleaf插入值,並轉化為pdf檔案
*/
public class htmltopdf2 {
public static void main(String[] args) throws Exception {
// 建立模板解析器,並設定相關屬性
FileTemplateResolver templateResolver = new FileTemplateResolver();
templateResolver.setPrefix("E:/Java/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode("HTML");
templateResolver.setCharacterEncoding("UTF-8");
// 建立Thymeleaf模板引擎物件
TemplateEngine templateEngine = new TemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
// 建立上下文物件,新增要替換的變數
Context context = new Context();
context.setVariable("t1", "wowowo");
context.setVariable("t2", "阿試試211哥");
context.setVariable("t3", "dw打賞q3q");
context.setVariable("t4", "w電腦網123dfsw");
context.setVariable("t5", "與i啊基礎aaa");
context.setVariable("t7", "不參加sdoap");
// 渲染模板並獲取處理後的HTML字串
String processedHtml = templateEngine.process("test1", context);
// 使用Flying Saucer將HTML轉換為PDF
ITextRenderer renderer = new ITextRenderer();
renderer.setDocumentFromString(processedHtml);
renderer.layout();
// 建立輸出流以便寫入PDF
ByteArrayOutputStream baos = new ByteArrayOutputStream();
renderer.createPDF(baos);
// 將PDF位元組流轉為位元組陣列
byte[] pdfBytes = baos.toByteArray();
// 你可以選擇儲存到本地
try (FileOutputStream fos = new FileOutputStream("E:/Java/output.pdf")) {
fos.write(pdfBytes);
}
}
}
2.3然後我使用了html2pdf,這個一樣和itex差不多了多少,問題也是一樣,字型問題卡了很久,是真的煩人,依然得自己下載配置
public static void main(String[] args) {
String htmlFile = "E:\\Java\\temps\\uuuio.html";
String pdfFile = "E:\\Java\\temps\\uuuio.pdf";
try {
htmlTopdf.html2pdf(htmlFile, pdfFile);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void html2pdf(String htmlFile, String pdfFile) throws Exception {
ConverterProperties converterProperties = new ConverterProperties();
DefaultFontProvider dfp = new DefaultFontProvider();
//新增字型庫
dfp.addDirectory("C:/Windows/Fonts");
converterProperties.setFontProvider(dfp);
try (InputStream in = new FileInputStream(new File(htmlFile));
OutputStream out = new FileOutputStream(new File(pdfFile))){
HtmlConverter.convertToPdf(in, out, converterProperties);
}catch (Exception e){
e.printStackTrace();
}
}
2.4接下來登場的才是重量級 wkhtmltopdf ! 剛開始發現轉化樣式還可以,基本不會丟失。
坑來了!!!!當時和公司另一位前端試了一下,wkhtmltopdf不支援轉化vue寫法,不支援es6及以上的寫法,所有的前端程式碼都得用原生寫,還得用原生html渲染資料進去,非常費勁,再強調一下對於我們這種幾十上百頁的pdf來說太低效了
(題外話:而且這時候已經開始考慮橫板豎版怎麼解決了,這裡用的是拼接,即按豎版為pdf1,橫板為pdf2、、、就這樣分開轉換,然後再使用 pdftk 或者 pdfbox 拼接,雖然最後我沒用到,但是這兩個方法真的很好用,有需要的可以看下。)
這個階段我負責把前端得html模板(基本也沒啥了,就剩個表頭)讀過來、插表格、折線圖,其實這就是前端的活,但沒辦法和我配合的這個偷懶、自己也不主動研究,整個過程我無論前後端基本都是我找方法,不說了,來氣。
下面這個就是我使用wkhtmltopdf獲取本地html檔案,進行插值,再轉成pdf的測試程式碼,實用意義不大,只是給真有需要的人,必須需要從後端寫表格折線圖的人可以參考下(程式碼中的t1、t2之類的都是html檔案裡需要預留的佔位符,方便替換,佔位符寫法${t1}),強烈不建議使用這種後端寫前端程式碼的操作,簡直逆天
package com.example.web.controller.pdftest; import org.apache.pdfbox.io.MemoryUsageSetting; import org.apache.pdfbox.multipdf.PDFMergerUtility; import java.io.File; import java.io.IOException; /** * @author hua * @date 2024/4/22 */ public class HtmlToPdfUtil { // public static void main(String[] args) throws IOException, InterruptedException { // //templ表格:這個部分是模擬表格資料及表格html的拼接============================================================================== // // 查詢得到的資料列表 // List<TestObject> dataList = new ArrayList<>(); // 模擬從介面查詢得到的資料 // //往dataList中插入30組隨機數 // int dataLength = 31; // for (int i = 1; i < dataLength; i++) { // TestObject obj = new TestObject(); // obj.setT1("插值" + i); // obj.setT2("插值" + i); // obj.setT3("插值" + i); // obj.setT4("插值" + i); // obj.setT5("插值" + i); // dataList.add(obj); // } // for (int i = dataLength; i < 53; i++) { // TestObject obj = new TestObject(); // obj.setT1(" "); // obj.setT2(" "); // obj.setT3(" "); // obj.setT4(" "); // obj.setT5(" "); // dataList.add(obj); // } // // 將物件列表轉換成二維表格資料的字串形式 // StringBuilder tbodyContentBuilder = new StringBuilder(); // for (int rowIndex = 0; rowIndex < dataList.size(); rowIndex++) { // tbodyContentBuilder.append("<tr>"); // // 對於資料行,從dataList獲取資料 // if (rowIndex < dataList.size()) { // TestObject obj = dataList.get(rowIndex); // if (rowIndex < 26) { // // 前26行,填充左邊五列 // tbodyContentBuilder.append("<td>").append(obj.getT1()).append("</td>"); // tbodyContentBuilder.append("<td>").append(obj.getT2()).append("</td>"); // tbodyContentBuilder.append("<td>").append(obj.getT3()).append("</td>"); // tbodyContentBuilder.append("<td>").append(obj.getT4()).append("</td>"); // tbodyContentBuilder.append("<td>").append(obj.getT5()).append("</td>"); // // 後五列留空 // tbodyContentBuilder.append("<td>").append("a1a6a" + (rowIndex+26)).append("</td>"); // tbodyContentBuilder.append("<td>").append("a1a7a" + (rowIndex+26)).append("</td>"); // tbodyContentBuilder.append("<td>").append("a1a8a" + (rowIndex+26)).append("</td>"); // tbodyContentBuilder.append("<td>").append("a1a9a" + (rowIndex+26)).append("</td>"); // tbodyContentBuilder.append("<td>").append("a1a10a" + (rowIndex+26)).append("</td>"); // } else { // // 假設 rowIndex 和 obj.getT1() 已經有具體的值 // int index6 = tbodyContentBuilder.indexOf("a1a6a" + rowIndex); // if (index6 != -1) { // tbodyContentBuilder.replace(index6, index6 + ("a1a6a" + rowIndex).length(), obj.getT1()); // } // int index7 = tbodyContentBuilder.indexOf("a1a7a" + rowIndex); // if (index7 != -1) { // tbodyContentBuilder.replace(index7, index7 + ("a1a7a" + rowIndex).length(), obj.getT2()); // } // int index8 = tbodyContentBuilder.indexOf("a1a8a" + rowIndex); // if (index8 != -1) { // tbodyContentBuilder.replace(index8, index8 + ("a1a8a" + rowIndex).length(), obj.getT3()); // } // int index9 = tbodyContentBuilder.indexOf("a1a9a" + rowIndex); // if (index9 != -1) { // tbodyContentBuilder.replace(index9, index9 + ("a1a9a" + rowIndex).length(), obj.getT4()); // } // int index10 = tbodyContentBuilder.indexOf("a1a10a" + rowIndex); // if (index10 != -1) { // tbodyContentBuilder.replace(index10, index10 + ("a1a10a" + rowIndex).length(), obj.getT5()); // } // } // } else { // // 對於沒有資料的行,建立空白單元格 // for (int colIndex = 0; colIndex < 10; colIndex++) { // tbodyContentBuilder.append("<td> </td>"); // } // } // tbodyContentBuilder.append("</tr>"); // } // // //templ折線圖:這個部分是折線圖的頁面資料========================================================================================== // // StringBuilder svgContentBuilder = new StringBuilder(); // svgContentBuilder.append("<tr>"); // svgContentBuilder.append("<td colspan=\"10\" rowspan=\"5\">"); // // svgContentBuilder.append("<svg width=\"400\" height=\"200\" style=\"display:block;margin:auto;\">\n" + // " <!-- 第一條線(黑色) -->\n" + // " <polyline points=\"20,180 60,160 100,180 140,140\" stroke=\"black\" fill=\"none\" stroke-width=\"2\"/>\n" + // "\n" + // " <!-- 第二條線(紅色) -->\n" + // " <polyline points=\"160,160 200,180 240,140\" stroke=\"red\" fill=\"none\" stroke-width=\"2\"/>\n" + // "\n" + // " <!-- 第三條線(藍色) -->\n" + // " <polyline points=\"220,180 260,160 300,180 340,140\" stroke=\"blue\" fill=\"none\" stroke-width=\"2\"/>\n" + // "\n" + // " <!-- x軸 -->\n" + // " <line x1=\"20\" y1=\"180\" x2=\"340\" y2=\"180\" stroke=\"black\" stroke-width=\"1\"/>\n" + // "\n" + // " <!-- y軸 -->\n" + // " <line x1=\"20\" y1=\"180\" x2=\"20\" y2=\"20\" stroke=\"black\" stroke-width=\"1\"/>\n" + // "\n" + // " <!-- x軸刻度和標籤 -->\n" + // " <text x=\"20\" y=\"190\" font-size=\"10\">0</text>\n" + // " <text x=\"340\" y=\"190\" font-size=\"10\">X Max</text>\n" + // " <!-- 假設每50單位一個刻度,僅為示例 -->\n" + // " <text x=\"60\" y=\"190\" font-size=\"8\">50</text>\n" + // " <text x=\"120\" y=\"190\" font-size=\"8\">100</text>\n" + // " <text x=\"180\" y=\"190\" font-size=\"8\">150</text>\n" + // " <text x=\"240\" y=\"190\" font-size=\"8\">200</text>\n" + // " <text x=\"300\" y=\"190\" font-size=\"8\">250</text>\n" + // "\n" + // " <!-- y軸刻度和標籤(旋轉-90度) -->\n" + // " <text x=\"10\" y=\"180\" font-size=\"10\" transform=\"rotate(-90 10,180)\">Y Max</text>\n" + // " <!-- 同樣,假設每50單位一個刻度 -->\n" + // " <text x=\"10\" y=\"170\" font-size=\"8\" transform=\"rotate(-90 10,170)\">50</text>\n" + // " <text x=\"10\" y=\"150\" font-size=\"8\" transform=\"rotate(-90 10,150)\">100</text>\n" + // " <text x=\"10\" y=\"130\" font-size=\"8\" transform=\"rotate(-90 10,130)\">150</text>\n" + // " <text x=\"10\" y=\"110\" font-size=\"8\" transform=\"rotate(-90 10,110)\">200</text>\n" + // "\n" + // "</svg>"); // svgContentBuilder.append("</td>"); // svgContentBuilder.append("</tr>"); // // //templtest:======================================================================================================== // //獲取折線圖資料 // List<TestObject> zxList = new ArrayList<>(); // for (int i = 1; i < 9; i++){ // TestObject testObject = new TestObject(); // testObject.setZ1("第"+i+"條線"); // List<Integer> z2 = new ArrayList<>(); // z2.add(i+1); // z2.add(i+20); // z2.add(i+3); // z2.add(i+4); // z2.add(i+30); // z2.add(i+6); // z2.add(i+7); // z2.add(i+80); // z2.add(i+9); // z2.add(i+40); // z2.add(i+11); // z2.add(i+10); // testObject.setZ2(z2); //// testObject.setZ3("rgba(255,0,0,0.5)"); // Random random = new Random(); // testObject.setZ4("rgba("+ random.nextInt(256) +","+ random.nextInt(256) +","+ random.nextInt(256) +","+random.nextInt(256)+")"); // testObject.setZ5(false); // testObject.setZ6(0); // zxList.add(testObject); // } // // StringBuilder zxContentBuilder = new StringBuilder(); // for (TestObject zx : zxList){ //// zxContentBuilder.append(","); // zxContentBuilder.append("{ label:'").append(zx.getZ1()).append("',"); // zxContentBuilder.append("data:").append(zx.getZ2()).append(","); // zxContentBuilder.append("borderColor:'").append(zx.getZ4()).append("',"); //// zxContentBuilder.append("backgroundColor:'").append(zx.getZ4()).append("',"); // zxContentBuilder.append("fill:").append(zx.isZ5()).append(","); // zxContentBuilder.append("lineTension:").append(zx.getZ6()).append(","); // zxContentBuilder.append("borderWidth:1,}"); // zxContentBuilder.append(","); // } // // // 假設這些是從上下文或其他方式獲得的變數值 // Map<String, String> variables = new HashMap<>(); // variables.put("t1", "wqeqweqweqweqweqw"); // variables.put("t2", "2:中文 ENGLISH 9527"); // variables.put("t3", "3:張三"); // variables.put("t4", "4:李四"); // variables.put("t5", "5:王五"); // variables.put("t6", "6:江蘇qwewqeq"); // variables.put("t7", "7:2023年10月21日"); // // // variables.put("t99", String.valueOf(tbodyContentBuilder)); //// variables.put("t88", String.valueOf(svgContentBuilder)); // //座標軸橫座標 //// variables.put("t81", "'一月','二月','三月','四月','五月','六月','七月','八月','九月','十月','十一月','十二月'"); //// variables.put("t82", String.valueOf(zxContentBuilder)); // System.out.println("資料組裝完成,讀取源html檔案,準備替換"); // // // 讀取原始HTML檔案 // String htmlContent = new String(Files.readAllBytes(Paths.get("E:\\Java\\temps\\uuuio.html"))); //// System.out.println("原生html==-->"+htmlContent); // //開始轉化,進行佔位符替換、插值拼接生成新html頁面 // for (Map.Entry<String, String> entry : variables.entrySet()) { // System.out.println("\\$\\{" + entry.getKey() + "\\}"); // htmlContent = htmlContent.replaceAll("\\$\\{" + entry.getKey() + "\\}", entry.getValue()); // } // // System.out.println("替換後的html==-->"+htmlContent); // // 將處理過的HTML內容暫時寫入到記憶體中或者臨時檔案(這一步取決於wkhtmltopdf的具體用法) // // 如果wkhtmltopdf可以直接接受記憶體中的資料,則無需寫入臨時檔案 // // 下面程式碼假設它需要一個臨時檔案: // File tempHtmlFile = File.createTempFile("temp-", ".html"); // Files.write(tempHtmlFile.toPath(), htmlContent.getBytes()); // System.out.println("tempHtmlFile.getAbsolutePath()===="+tempHtmlFile.getAbsolutePath()); // // // 構建wkhtmltopdf命令 // //生成pdf的位置 // String command = "D:\\worksoft\\wkhtmltox\\wkhtmltopdf\\bin\\wkhtmltopdf.exe " + tempHtmlFile.getAbsolutePath() + " E:\\Java\\temps\\uuuio.pdf"; // Process process = Runtime.getRuntime().exec(command); // // 等待轉換完成 // int exitCode = process.waitFor(); // if (exitCode == 0) { // System.out.println("PDF-temp12-轉換成功!"); // } else { // System.err.println("PDF-temp12-轉換失敗!"); // InputStream errorStream = process.getErrorStream(); // //檢視這個errorStream // try (BufferedReader reader = new BufferedReader(new InputStreamReader(errorStream))) { // String line; // while ((line = reader.readLine()) != null) { // System.err.println(line); // } // } // System.err.println("PDF-轉換失敗!"); // } // // 轉換完成後刪除臨時HTML檔案 // tempHtmlFile.delete(); // } }
然後就是非常好用的拼接pdf的方法1:合併橫豎版日報(pdfbox依賴版)2:合併橫豎版日報(pdftk外掛版),雖然最後我沒用到
@ApiOperation(value = "pdfbox依賴版")
@GetMapping("/mergePdf") public Result<String> mergePdf(@RequestParam Long reportId){ try { PDFMergerUtility pdfMerger = new PDFMergerUtility(); // 新增要合併的PDF檔案 pdfMerger.addSource(new File("E:\\Java\\temps\\temp_portrait.pdf")); pdfMerger.addSource(new File("E:\\Java\\temps\\temp_landscape.pdf")); pdfMerger.addSource(new File("E:\\Java\\temps\\outttttt.pdf")); pdfMerger.setDestinationFileName("E:\\Java\\temps\\merge.pdf"); pdfMerger.mergeDocuments(MemoryUsageSetting.setupMainMemoryOnly()); } catch (Exception e) { e.printStackTrace(); } return success("生成成功!"); } @ApiOperation(value = "合併橫豎版日報(pdftk外掛版)") @GetMapping("/mergePdf2") public Result<String> mergePdf2(@RequestParam Long reportId){ try { String wkhtmltopdf = "D:\\worksoft\\wkhtmltox\\wkhtmltopdf\\bin\\wkhtmltopdf.exe "; String pdftk = "D:\\worksoft\\wkhtmltox\\pdftk\\PDFtk\\bin\\pdftk.exe "; // 呼叫wkhtmltopdf生成縱向頁面的PDF Process p1 = Runtime.getRuntime().exec("D:\\worksoft\\wkhtmltox\\wkhtmltopdf\\bin\\wkhtmltopdf.exe --orientation Portrait " +"E:\\Java\\temps\\temp1.html"+ " E:\\Java\\temps\\temp_portrait.pdf"); p1.waitFor(); // 呼叫wkhtmltopdf生成橫向頁面的PDF Process p2 = Runtime.getRuntime().exec("D:\\worksoft\\wkhtmltox\\wkhtmltopdf\\bin\\wkhtmltopdf.exe --orientation Landscape " +"E:\\Java\\temps\\temp12.html"+ " E:\\Java\\temps\\temp_landscape.pdf"); p2.waitFor(); // 呼叫pdftk合併這兩個PDF Process p3 = Runtime.getRuntime().exec("D:\\worksoft\\wkhtmltox\\pdftk\\PDFtk\\bin\\pdftk.exe E:\\Java\\temps\\temp_portrait.pdf E:\\Java\\temps\\temp_landscape.pdf cat output " + "E:\\Java\\temps\\outttttt.pdf"); p3.waitFor(); // 清理臨時檔案 // new File("temp_portrait.pdf").delete(); // new File("temp_landscape.pdf").delete(); } catch (Exception e) { e.printStackTrace(); } return success("生成成功!"); }
3.至此上面後端解決的方法我不能再繼續了,有點走遠了,前端依然不研究,我就又找了個外掛,bookjs-easy! 我的神來了,這個專案很好,但是官網的文件對於第一次看的人來說並不友好(第一次還是要讀一遍,別想偷懶,不然後面更費時間),有些地方容易摸不著北,接下來我指出其中幾個主要步驟或提醒
3.1首先去官網拉程式碼bookjs-easy,然後執行,這時候你發現生成的按鈕(不是預覽列印,是生成pdf直接儲存到本地)請求報錯,說連線伺服器失敗之類的
因為你需要自己啟動一個服務端,用來替換官方的介面(因為2023.07.01以後官方介面就不開放支援了)服務端專案地址(點選即可)這個服務端兩種辦法1是本地,2是docker,我和官網一樣也強烈推薦docker,方便快捷。
訪問你的伺服器後(你得先有docker),依次執行下面的兩行命令,第一行是拉取,第二步是執行,不會引數的不要改動,複製執行即可,如果報埠already使用的,改下埠,比如把命令列中的埠即 第一個3000改成3010 -p 3010:3000
docker pull wuxue107/screenshot-api-server ## -e MAX_BROWSER=[num] 環境變數可選,最大的puppeteer例項數,忽略選項則預設值:1 , 值auto:[可用記憶體]/200M ## -e PDF_KEEP_DAY=[num] 自動刪除num天之前產生的檔案目錄,預設0: 不刪除檔案 docker run -p 3000:3000 -td --rm -e MAX_BROWSER=1 -e PDF_KEEP_DAY=0 -v ${PWD}:/screenshot-api-server/public --name=screenshot-api-server wuxue107/screenshot-api-server
啟動後把你的請求生成pdf地址換成域名+埠,然後模仿官網模板發起請求即可(建議你的前端專案也和這個docker放一個伺服器,能減少很多麻煩)
要注意的是這個 API: http://localhost:3000/api/wkhtmltopdf-book 換成你的介面以後他有時候會自動拼接api/book(至於為什麼還沒時間搞明白),導致請求失敗需要注意,可以在docker中檢視此服務的日誌
會發現日誌還是報錯(檢視日誌的命令列: docker logs 你容器執行的專案id,檢視id的命令列:docker ps -a 最前面的最前面的CONTAINER ID就是i)
著重看這個紅框裡的,這種其實就差一步 http://localhost:3000/ ,前端的請求需要拿掉”/“,這裡前端雖說試過了,但我還是讓他再試一次,就成功請求了,不然又不知道要搞到多久
3.2至於橫豎頁面的解決辦法,第一種,前端直接寫橫的,第二種前端控制旋轉,把已寫好的橫頁轉一下,參考程式碼在下面
<!DOCTYPE html> <html> <head> <style> /* 使整個頁面內容旋轉90度 */ body { transform: rotate(90deg); /* 確保頁面居中旋轉,並適應視口 */ position: absolute; top: 50%; left: 50%; transform-origin: center center; width: 100vh; /* 旋轉後,原寬度變為高度 */ height: 100vw; /* 原高度變為寬度 */ margin-top: -50vh; /* 調整垂直位置 */ margin-left: -50vw; /* 調整水平位置 */ overflow: auto; /* 確保可以滾動檢視未在視口內的內容 */ } /* 可選:如果需要,可以對特定元素進行進一步的調整以最佳化顯示效果 */ /* 例如,調整表格的寬度和高度以適應旋轉後的佈局 */ table { width: 100%; /* 根據需要調整表格寬度 */ height: auto; /* 或調整高度 */ } </style> </head> <body> <h1>標題也會旋轉</h1> <table border="1"> <tr> <td>單元格1</td> <td>單元格2</td> </tr> <tr> <td>單元格3</td> <td>單元格4</td> </tr> </table> <p>這段文字和上面的表格都會隨著頁面旋轉90度。</p> </body> </html>
至此,完整跑通這個功能,這給我折騰的,累了,明天放假,五一快樂!
寫的很倉促,有什麼不清楚的可以留言,看到我會回覆
wkhtmltopdf