業(yè)Lodop控件)
1. 從“打印”這個(gè)看似簡(jiǎn)單的需求說(shuō)起在Web開(kāi)發(fā)或者桌面應(yīng)用開(kāi)發(fā)中“打印”這個(gè)功能點(diǎn)幾乎每個(gè)項(xiàng)目都會(huì)遇到。它看起來(lái)簡(jiǎn)單不就是把頁(yè)面內(nèi)容輸出到打印機(jī)嗎但真正動(dòng)手實(shí)現(xiàn)時(shí)你會(huì)發(fā)現(xiàn)這里面的水比想象中要深得多。用戶的需求千奇百怪有的要打印整個(gè)網(wǎng)頁(yè)有的只想打印頁(yè)面里一個(gè)特定的表格有的要求精確控制分頁(yè)不能讓一個(gè)訂單信息被切成兩半有的需要套打也就是在已有的票據(jù)、合同模板上把數(shù)據(jù)精準(zhǔn)地填到對(duì)應(yīng)的框里還有的甚至要連接特定的票據(jù)打印機(jī)打印小票或者標(biāo)簽。如果你只用瀏覽器自帶的window.print()大概率會(huì)陷入和CSS樣式、分頁(yè)符、背景圖顯示這些問(wèn)題的纏斗中最后出來(lái)的效果往往和設(shè)計(jì)稿相去甚遠(yuǎn)用戶一句“這打印出來(lái)怎么和屏幕上不一樣”就能讓你頭疼半天。而專(zhuān)門(mén)的前端打印控件比如Lodop就是為了解決這些“不一樣”而生的。今天我們就來(lái)徹底拆解一下這兩種主流的Web打印方案原生的window.print和專(zhuān)業(yè)的 Lodop 控件。我會(huì)結(jié)合自己多年在ERP、OA系統(tǒng)里折騰打印功能的實(shí)戰(zhàn)經(jīng)驗(yàn)告訴你它們各自適合什么場(chǎng)景核心原理是什么以及那些官方文檔里不會(huì)寫(xiě)的“坑”和技巧。2. 原生勇士window.print()的功與過(guò)window.print()是瀏覽器提供給JavaScript的一個(gè)最基礎(chǔ)的打印接口。調(diào)用它就會(huì)彈出操作系統(tǒng)標(biāo)準(zhǔn)的打印對(duì)話框用戶可以選擇打印機(jī)、設(shè)置份數(shù)等然后執(zhí)行打印。它的最大優(yōu)勢(shì)就是“零依賴”和“跨平臺(tái)”因?yàn)樗菫g覽器標(biāo)準(zhǔn)的一部分。2.1window.print的基本用法與局限性使用起來(lái)簡(jiǎn)單到令人發(fā)指// 觸發(fā)打印對(duì)話框 window.print();或者給一個(gè)按鈕綁定點(diǎn)擊事件。但是它的簡(jiǎn)單也意味著控制的粗糙。默認(rèn)情況下它會(huì)嘗試打印整個(gè)當(dāng)前文檔document。這直接導(dǎo)致了幾個(gè)經(jīng)典問(wèn)題非打印元素干擾頁(yè)面上的導(dǎo)航欄、側(cè)邊欄、廣告、按鈕等全都會(huì)被打印出來(lái)。樣式失控屏幕顯示的CSS樣式特別是布局、背景色、背景圖在打印時(shí)可能被瀏覽器忽略或修改以節(jié)省墨水。分頁(yè)噩夢(mèng)內(nèi)容會(huì)在不可預(yù)測(cè)的位置被切斷可能正好把一行文字或一張圖片切成兩半。為了解決這些問(wèn)題我們必須借助打印樣式表Print CSS。這是用好window.print的關(guān)鍵。2.2 打印樣式表為紙張而生的CSS打印樣式表的核心思想是為屏幕screen和打印print準(zhǔn)備兩套不同的CSS規(guī)則。你可以通過(guò)媒體查詢來(lái)定義link relstylesheet hrefscreen.css mediascreen link relstylesheet hrefprint.css mediaprint或者在同一個(gè)CSS文件里/* 屏幕樣式 */ body { font-family: Arial, sans-serif; margin: 20px; background-color: #f5f5f5; } /* 打印樣式 */ media print { body { margin: 0; padding: 0; background-color: white !important; /* 強(qiáng)制白色背景 */ font-size: 12pt; /* 使用印刷常用的pt單位 */ } .no-print { display: none !important; /* 隱藏所有不需要打印的元素 */ } .header, .footer, .sidebar { display: none; } /* 確保超鏈接在打印稿上顯示URL */ a[href]:after { content: ( attr(href) ); font-size: 90%; } /* 避免在元素內(nèi)部發(fā)生分頁(yè) */ table, img, div { page-break-inside: avoid; } /* 在元素之前強(qiáng)制分頁(yè) */ h1 { page-break-before: always; } /* 在元素之后避免分頁(yè) */ h2, h3 { page-break-after: avoid; } }實(shí)操心得1!important的無(wú)奈與必須在打印樣式里你會(huì)發(fā)現(xiàn)自己頻繁地使用!important。這是因?yàn)楹芏郩I框架如Bootstrap或內(nèi)聯(lián)樣式權(quán)重很高為了在打印時(shí)覆蓋它們!important幾乎是唯一可靠的手段。這雖然不符合CSS最佳實(shí)踐但在打印這個(gè)特殊場(chǎng)景下是務(wù)實(shí)的選擇。實(shí)操心得2分頁(yè)屬性 (page-break-*) 的瀏覽器兼容性page-break-inside: avoid;這個(gè)屬性非常有用可以告訴瀏覽器“盡量不要在這個(gè)元素內(nèi)部斷頁(yè)”。但是它的支持度并非完美。對(duì)于復(fù)雜的、高度很大的div容器某些瀏覽器可能還是會(huì)忽略這個(gè)屬性。更可靠的做法是在業(yè)務(wù)邏輯上提前計(jì)算內(nèi)容高度如果發(fā)現(xiàn)內(nèi)容超過(guò)一頁(yè)就主動(dòng)在合適的位置比如兩個(gè)訂單條目之間插入一個(gè)div stylepage-break-before: always;/div來(lái)強(qiáng)制分頁(yè)。2.3 實(shí)戰(zhàn)構(gòu)建一個(gè)可打印的視圖通常我們不會(huì)直接打印整個(gè)業(yè)務(wù)頁(yè)面。而是采用以下流程數(shù)據(jù)準(zhǔn)備從當(dāng)前頁(yè)面或通過(guò)API獲取需要打印的數(shù)據(jù)。渲染隱藏容器在頁(yè)面內(nèi)創(chuàng)建一個(gè)隱藏的div idprintArea。動(dòng)態(tài)構(gòu)建打印內(nèi)容使用JavaScript或前端框架將數(shù)據(jù)渲染成HTML注入到這個(gè)printArea中。這個(gè)HTML結(jié)構(gòu)應(yīng)該盡量簡(jiǎn)單、干凈專(zhuān)為打印優(yōu)化。應(yīng)用打印樣式確保這個(gè)printArea及其內(nèi)容關(guān)聯(lián)了正確的打印CSS。切換上下文并打印function printDiv(divId) { const originalContent document.body.innerHTML; // 保存原頁(yè)面內(nèi)容 const printContent document.getElementById(divId).innerHTML; document.body.innerHTML printContent; // 將body替換為純打印內(nèi)容 window.print(); document.body.innerHTML originalContent; // 恢復(fù)原頁(yè)面 location.reload(); // 強(qiáng)烈建議恢復(fù)后刷新頁(yè)面避免事件綁定等殘留問(wèn)題 }注意上述方法直接重寫(xiě)document.body.innerHTML是一種“暴力”但有效的方案缺點(diǎn)是會(huì)破壞當(dāng)前頁(yè)面的JavaScript狀態(tài)事件監(jiān)聽(tīng)器、Vue/React組件狀態(tài)等。在單頁(yè)應(yīng)用SPA中要格外小心更好的做法是打開(kāi)一個(gè)新窗口或使用iframe來(lái)承載打印內(nèi)容。踩坑記錄新窗口打印的沙箱限制為了不干擾主頁(yè)面打開(kāi)新窗口是更優(yōu)雅的方式function printInNewWindow(htmlContent) { const printWindow window.open(, _blank, width800,height600); printWindow.document.write( !DOCTYPE html html head title打印/title link relstylesheet hrefprint.css mediaprint stylebody { margin:0; }/style /head body${htmlContent}/body /html ); printWindow.document.close(); printWindow.focus(); // 需要等待樣式和圖片加載完成 printWindow.onload function() { printWindow.print(); // printWindow.close(); // 可選打印后自動(dòng)關(guān)閉窗口 }; }這里有個(gè)坑如果htmlContent里有引用站內(nèi)資源如圖片、樣式新窗口的域名about:blank或空可能導(dǎo)致資源加載失敗。解決辦法是將資源路徑轉(zhuǎn)為絕對(duì)路徑或者使用Blob URL和iframe等更復(fù)雜的技術(shù)。3. 專(zhuān)業(yè)選手Lodop 的降維打擊當(dāng)window.print加上打印CSS仍然無(wú)法滿足需求時(shí)——比如需要打印票據(jù)、套打公章、控制針式打印機(jī)走紙、打印條形碼/二維碼——就該專(zhuān)業(yè)控件上場(chǎng)了。LodopC-Lodop是其跨平臺(tái)版本是國(guó)內(nèi)非常流行的一款Web打印控件。它通過(guò)在客戶端安裝一個(gè)本地服務(wù)為瀏覽器提供了強(qiáng)大的本地打印能力。3.1 Lodop 的核心工作原理與部署Lodop 的工作模式可以理解為“瀏覽器擴(kuò)展”“本地服務(wù)”??蛻舳税惭b用戶首次使用前需要下載并安裝 Lodop 的本地客戶端程序。安裝后會(huì)在本地啟動(dòng)一個(gè)服務(wù)通常端口在8000左右。瀏覽器通信網(wǎng)頁(yè)中的JavaScript通過(guò)特定的端口如localhost:8000與這個(gè)本地服務(wù)進(jìn)行HTTP通信。指令驅(qū)動(dòng)JS代碼不直接生成打印內(nèi)容而是向Lodop服務(wù)發(fā)送一系列“繪圖指令”比如“在坐標(biāo)(100,200)處打印一段文本‘Hello World’字體是宋體三號(hào)”。本地渲染與打印Lodop服務(wù)接收這些指令在本地調(diào)用系統(tǒng)的圖形接口GDI進(jìn)行精確渲染然后直接發(fā)送給指定的打印機(jī)驅(qū)動(dòng)完成打印。這種架構(gòu)帶來(lái)了巨大優(yōu)勢(shì)絕對(duì)精確的控制以像素或0.1毫米為單位定位每一個(gè)元素。直接訪問(wèn)打印機(jī)可以調(diào)用打印機(jī)的原生功能如設(shè)置紙張大小、走紙控制、打印濃度等。豐富的打印對(duì)象支持文本、超文本、表格、條形碼、二維碼、圖片、圖表、公章等。無(wú)需打印樣式完全繞過(guò)瀏覽器的渲染引擎樣式由Lodop本地控制。部署時(shí)你需要在頁(yè)面中引入Lodop的JS文件script srchttp://localhost:8000/CLodopfuncs.js?nameCLodop/script !-- 或者使用官方的云加載方式避免端口問(wèn)題 -- script srchttp://localhost:8000/CLodopfuncs.js/script script srchttp://localhost:18000/CLodopfuncs.js/script !-- 備用端口 --并編寫(xiě)一個(gè)健壯的檢測(cè)函數(shù)引導(dǎo)用戶安裝或升級(jí)控件。3.2 Lodop 的基本編程模型LODOP 對(duì)象Lodop 的所有操作都圍繞一個(gè)核心對(duì)象通常命名為L(zhǎng)ODOP進(jìn)行。其基本流程是線性的、指令式的// 1. 獲取或創(chuàng)建LODOP對(duì)象 var LODOP getLodop(); // 通過(guò)引入的JS函數(shù)獲取 // 2. 設(shè)置一個(gè)打印任務(wù) LODOP.PRINT_INIT(打印任務(wù)名稱(chēng)); // 3. 設(shè)定紙張大小這里設(shè)定為寬度80mm高度根據(jù)內(nèi)容自適應(yīng)的連續(xù)紙 LODOP.SET_PRINT_PAGESIZE(3, 80mm, 0, 連續(xù)紙); // 4. 添加打印內(nèi)容指令 // 在坐標(biāo) (10mm, 5mm) 處添加一個(gè)文本項(xiàng) LODOP.ADD_PRINT_TEXT(10mm, 5mm, 60mm, 10mm, 客戶姓名張三); // 設(shè)置這個(gè)文本的字體 LODOP.SET_PRINT_STYLEA(0, FontName, 黑體); LODOP.SET_PRINT_STYLEA(0, FontSize, 11); // 在文本下方添加一個(gè)表格 LODOP.ADD_PRINT_TABLE(20mm, 5mm, 70mm, 50mm, table border1trtd商品/tdtd數(shù)量/td/trtrtd商品A/tdtd2/td/tr/table); // 在底部添加一個(gè)二維碼 LODOP.ADD_PRINT_QRCODE(75mm, 5mm, 30mm, 30mm, https://www.example.com/order/12345); // 5. 執(zhí)行打印 LODOP.PRINT(); // 直接打印 // 或 LODOP.PREVIEW(); // 預(yù)覽后打印核心概念解讀PRINT_INIT(): 必須首先調(diào)用初始化一個(gè)打印任務(wù)。它會(huì)清除之前的設(shè)置。ADD_PRINT_*系列函數(shù)用于在“畫(huà)布”上添加各種元素。每個(gè)元素都有一個(gè)從0開(kāi)始的索引號(hào)ITEMINDEX用于后續(xù)的樣式設(shè)置。SET_PRINT_STYLEA(): 設(shè)置指定索引元素的樣式。樣式是“打標(biāo)簽”式的設(shè)置后對(duì)后續(xù)添加的元素也生效直到被新的樣式設(shè)置覆蓋。坐標(biāo)和尺寸單位默認(rèn)是px但強(qiáng)烈建議使用mm毫米或cm這與實(shí)際紙張對(duì)應(yīng)更直觀。3.3 高級(jí)特性與實(shí)戰(zhàn)技巧3.3.1 套打模板打印這是Lodop的殺手級(jí)功能。套打指在已有印刷好的表單如發(fā)票、支票、提單上打印可變數(shù)據(jù)。步驟是用掃描儀將空白表單掃描成圖片。在Lodop的“設(shè)計(jì)器”LodopFuncs.exe中打開(kāi)該圖片作為背景。在設(shè)計(jì)器里用鼠標(biāo)拖拽的方式在需要填充數(shù)據(jù)的位置如“姓名______”放置文本框、條碼等元素并記錄下它們的精確坐標(biāo)和大小。將設(shè)計(jì)器生成的代碼復(fù)制到你的網(wǎng)頁(yè)程序中動(dòng)態(tài)替換數(shù)據(jù)部分。LODOP.ADD_PRINT_IMAGE(0,0, 210mm,297mm, 背景圖.jpg); // 先打背景 LODOP.ADD_PRINT_TEXT(45mm, 60mm, 40mm, 8mm, customerName); // 在固定位置打姓名 LODOP.SET_PRINT_STYLEA(0, Alignment, 2); // 居中顯示關(guān)鍵技巧背景圖必須使用絕對(duì)路徑file:///或http://且由于瀏覽器安全限制通常需要將圖片和網(wǎng)頁(yè)部署在同一域名下或者將圖片轉(zhuǎn)為Base64編碼嵌入。3.3.2 打印維護(hù)與狀態(tài)回調(diào)LODOP.PRINT()是異步的。如果你需要知道打印是否成功、失敗或者用戶點(diǎn)擊了取消可以設(shè)置回調(diào)函數(shù)。LODOP.On_Return function(TaskID, Value) { console.log(任務(wù)ID:, TaskID, 返回值:, Value); if (Value) { // Value為true通常表示成功發(fā)送到打印機(jī)隊(duì)列并非打印完成 alert(打印指令已發(fā)送); // 可以在這里觸發(fā)后續(xù)業(yè)務(wù)邏輯如更新訂單狀態(tài)為“已打印” } else { alert(打印取消或失敗); } };注意這個(gè)回調(diào)只表示指令是否被Lodop服務(wù)接收并處理不代表打印機(jī)物理打印完成。對(duì)于需要嚴(yán)格依據(jù)打印結(jié)果進(jìn)行后續(xù)流程如出庫(kù)的場(chǎng)景這是個(gè)挑戰(zhàn)。一種變通方案是使用“打印維護(hù)”功能但更常見(jiàn)的業(yè)務(wù)處理是在回調(diào)成功后就更新?tīng)顟B(tài)因?yàn)榻^大多數(shù)情況打印都會(huì)成功。3.3.3 常見(jiàn)踩坑與排查“未安裝控件”或“CLodop未啟動(dòng)”這是最常見(jiàn)的問(wèn)題。確保你的檢測(cè)腳本正確并引導(dǎo)用戶訪問(wèn)http://localhost:8000或http://localhost:18000查看Lodop服務(wù)狀態(tài)。對(duì)于內(nèi)網(wǎng)環(huán)境可能需要IT協(xié)助部署安裝包。打印內(nèi)容空白或錯(cuò)位單位混亂檢查代碼中坐標(biāo)和尺寸單位是否統(tǒng)一?;煊胮x和mm會(huì)導(dǎo)致嚴(yán)重錯(cuò)位。初始化遺漏確保每個(gè)打印任務(wù)前都調(diào)用了LODOP.PRINT_INIT(...)否則會(huì)沿用上一個(gè)任務(wù)的設(shè)置導(dǎo)致混亂。字體缺失在服務(wù)器上設(shè)計(jì)的模板使用了“微軟雅黑”但客戶端電腦沒(méi)有這個(gè)字體就會(huì)回退到默認(rèn)字體可能造成換行、寬度變化而錯(cuò)位。解決方案是使用“通用字體”如宋體、黑體或使用Lodop的下載字體功能ADD_PRINT_SETUP_FONT。性能問(wèn)題當(dāng)打印內(nèi)容非常復(fù)雜如一個(gè)幾百行的表格每行都用多個(gè)ADD_PRINT_TEXT畫(huà)出時(shí)生成指令會(huì)慢。對(duì)于復(fù)雜表格優(yōu)先考慮使用ADD_PRINT_TABLE或ADD_PRINT_HTM它們的性能遠(yuǎn)優(yōu)于無(wú)數(shù)個(gè)單獨(dú)的TEXT。4. 方案對(duì)比與選型指南了解了兩種技術(shù)的細(xì)節(jié)后我們?cè)撊绾芜x擇下面這個(gè)表格從多個(gè)維度進(jìn)行了對(duì)比特性維度window.print(配合打印CSS)Lodop (專(zhuān)業(yè)控件)部署復(fù)雜度零部署純前端技術(shù)瀏覽器原生支持。需客戶端安裝控件對(duì)用戶有額外步驟內(nèi)網(wǎng)環(huán)境需統(tǒng)一部署??刂凭鹊汀R蕾嚍g覽器打印引擎受CSS兼容性和用戶打印機(jī)設(shè)置影響大分頁(yè)和定位不精確。極高。以像素/0.1mm為單位精確定位完全掌控打印輸出。功能范圍基礎(chǔ)。打印網(wǎng)頁(yè)內(nèi)容可通過(guò)CSS控制部分樣式和分頁(yè)。強(qiáng)大且專(zhuān)業(yè)。支持套打、條形碼/二維碼、公章、票據(jù)打印機(jī)控制、存圖打印、批量打印等??缙脚_(tái)/瀏覽器優(yōu)秀。所有現(xiàn)代瀏覽器均支持。依賴控件版本。Windows需安裝ActiveX或C-LodopLinux/Mac有C-Lodop版本但普及度低。開(kāi)發(fā)體驗(yàn)標(biāo)準(zhǔn)Web開(kāi)發(fā)。使用HTML/CSS/JS開(kāi)發(fā)者熟悉。調(diào)試需反復(fù)打印預(yù)覽。特殊API。需要學(xué)習(xí)Lodop特有的指令式API。提供可視化設(shè)計(jì)器設(shè)計(jì)模板方便。適用場(chǎng)景打印網(wǎng)頁(yè)文章、數(shù)據(jù)報(bào)表格式要求不高、表單確認(rèn)頁(yè)等通用、簡(jiǎn)單的打印需求。票據(jù)/單據(jù)套打發(fā)票、運(yùn)單、標(biāo)簽打印快遞面單、資產(chǎn)標(biāo)簽、證卡打印、需要精確分頁(yè)的合同/報(bào)告、連接特殊打印機(jī)針打、標(biāo)簽機(jī)。安全性考慮無(wú)額外風(fēng)險(xiǎn)遵循瀏覽器沙箱規(guī)則。需要用戶安裝本地程序?qū)ζ髽I(yè)內(nèi)網(wǎng)是可控的對(duì)公網(wǎng)環(huán)境用戶可能存在安全疑慮需從可信源下載。選型決策流程圖需求是否只是“把網(wǎng)頁(yè)內(nèi)容變成紙質(zhì)版”如果是且對(duì)格式要求不苛刻優(yōu)先嘗試window.print 打印CSS。這是成本最低的方案。是否需要“套打”在已有印刷品上填空如果是必須選 Lodop。是否需要打印條形碼、二維碼且對(duì)精度有要求如果是 Lodop 更可靠。window.print雖然也能用CSS生成二維碼圖片打印但精度和兼容性不如Lodop原生支持。用戶環(huán)境是否可控如企業(yè)內(nèi)部如果是部署 Lodop 是可行的。如果是面向互聯(lián)網(wǎng)的海量用戶強(qiáng)制安裝客戶端是災(zāi)難應(yīng)極力避免優(yōu)先優(yōu)化window.print方案或?qū)ふ壹僕eb的替代方案如服務(wù)器端生成PDF再打印。是否需要與特定的硬件打印機(jī)如針式打印機(jī)、標(biāo)簽打印機(jī)深度交互控制切刀、走紙如果是只能選擇 Lodop 或類(lèi)似的專(zhuān)業(yè)控件/本地程序。5. 混合方案與未來(lái)趨勢(shì)在實(shí)際項(xiàng)目中尤其是復(fù)雜的管理系統(tǒng)常常采用混合方案。主報(bào)表用window.print對(duì)于列表頁(yè)、統(tǒng)計(jì)圖表等格式相對(duì)自由的報(bào)表采用響應(yīng)式打印CSS讓用戶瀏覽器直接打印。票據(jù)套打用 Lodop對(duì)于發(fā)票、出貨單、收款收據(jù)等需要精確套打的場(chǎng)景集成Lodop。提供一個(gè)“導(dǎo)出為PDF”的選項(xiàng)這常常是最受用戶歡迎的“第三方案”。使用像html2canvasjsPDF這樣的庫(kù)或在后端使用wkhtmltopdf、Puppeteer等服務(wù)端渲染工具將內(nèi)容生成PDF。用戶可以將PDF保存、發(fā)送或打印。PDF打印的格式是固定的不受瀏覽器影響在很多場(chǎng)景下是更好的選擇。關(guān)于“純Web打印”的未來(lái)隨著Web技術(shù)發(fā)展CSS Paged Media標(biāo)準(zhǔn)和Printing APIW3C草案正在推進(jìn)旨在為瀏覽器提供更強(qiáng)大的原生打印控制能力。例如page規(guī)則可以更精細(xì)地控制頁(yè)邊距、頁(yè)眉頁(yè)腳。但在這些標(biāo)準(zhǔn)被所有瀏覽器廣泛支持并達(dá)到工業(yè)級(jí)精度之前對(duì)于專(zhuān)業(yè)的票據(jù)打印需求Lodop這類(lèi)控件在可預(yù)見(jiàn)的未來(lái)仍將是不可替代的務(wù)實(shí)選擇。在我經(jīng)歷過(guò)的多個(gè)供應(yīng)鏈和財(cái)務(wù)系統(tǒng)中window.print滿足了80%的“看看紙面稿”需求而Lodop則啃下了剩下20%但至關(guān)重要的“生產(chǎn)級(jí)單據(jù)打印”的硬骨頭。理解它們的原理和邊界不是為了追求技術(shù)上的“純粹”而是在面對(duì)具體的、有時(shí)甚至有點(diǎn)“土”的業(yè)務(wù)需求時(shí)能拿出最合適、最穩(wěn)定的解決方案。最后一個(gè)小建議無(wú)論用哪種方案在開(kāi)發(fā)初期就準(zhǔn)備好一臺(tái)真實(shí)的打印機(jī)最好是用戶最常用的型號(hào)進(jìn)行測(cè)試虛擬打印機(jī)和真實(shí)打印的輸出效果常常會(huì)有意想不到的差異。