自建物流標籤列印系統:從 Loftware 到 GoDex 的轉移之路
公司的物流部門一直使用 Loftware 的 IoT 標籤列印系統來處理出貨標籤。這套系統功能完整、穩定可靠,但有個致命的問題 — 太貴了。
授權費加上標籤機的租賃費用,每年是一筆不小的開支。營運部門開始思考:既然我們有自己的 ERP 系統,能不能自己開發標籤列印功能,搭配相對便宜的標籤機來處理?
於是,這個任務落到了我頭上。
需求與選型
營運部選擇了 GoDex G530+ 作為標籤機。這是一台桌上型熱感應標籤機,價格親民,支援 100mm 寬的標籤紙。我們的物流標籤規格是 100mm x 120mm,分為上方 80mm 的主標籤和下方 40mm 的客戶簽收聯,中間有撕開線。
技術方案上,我決定採用 Web 列印 的方式。相較於直接發送 EZPL 指令到印表機,Web 列印的優勢在於:
- 使用 HTML/CSS 排版,開發效率高
- 不需要額外安裝列印軟體
- 可以在瀏覽器預覽列印結果
- 跨平台相容性較好
聽起來很美好,但現實總是比想像中複雜。
驅動程式的坑
第一個問題出現在安裝印表機時。我在 M4 晶片的 MacBook Pro 上連接 GoDex G530+,macOS 可以偵測到印表機,但在選擇驅動程式時,找不到 GoDex 的選項。
我從 GoDex 官網下載了 macOS 驅動並安裝,但新增印表機時依然看不到 GoDex 驅動。無奈之下,我選擇了看起來最相關的 Zebra ZPL Label Printer。
印表機確實可以印東西了,但問題接踵而來:
- 內容無法滿版,總是有很大的邊距
- 紙張尺寸選項跟實際標籤不符
- 列印方向似乎不對,需要旋轉 180 度
我花了大量時間調整 CSS,嘗試各種 @page 設定、transform: rotate(180deg)、不同的紙張尺寸設定。有時候內容會完全空白,有時候會跑版,有時候會跨越標籤邊界。
問題的根源
在反覆除錯的過程中,我開始懷疑問題不在 CSS,而在驅動程式本身。ZPL 是 Zebra 印表機的語言,而 GoDex 用的是 EZPL。雖然有部分相容,但紙張尺寸定義、列印方向、邊距計算都可能不同。
用終端機檢查系統狀態:
lpstat -p -d
# lpstat: 沒有加入目標。沒有系統預設目標
system_profiler SPPrintersDataType
# Status: The printers list is empty.奇怪的是,印表機明明可以印,但系統卻說沒有印表機。這讓我意識到印表機可能沒有正確註冊到 CUPS 系統。
找到真正的問題
檢查 GoDex 驅動的安裝位置:
ls /Library/Printers/GoDEX/PPDs/
# godex-g530.ppd godex-g530+.ppd ...(一堆 PPD 檔案)
ls /Library/Printers/PPDs/Contents/Resources/ | grep -i godex
# (空的)問題找到了!GoDex 的 PPD 檔案被安裝到 /Library/Printers/GoDEX/PPDs/,但 macOS 只會讀取 /Library/Printers/PPDs/Contents/Resources/ 裡的 PPD 檔案。
PPD(PostScript Printer Description) 是印表機的描述檔,定義了紙張尺寸、解析度、功能選項等參數。錯誤的 PPD 就像給印表機看了一份別人的說明書,自然無法正確運作。
解決方案
建立符號連結,讓 macOS 找到正確的 PPD:
sudo ln -s /Library/Printers/GoDEX/PPDs/godex-g530.ppd \
/Library/Printers/PPDs/Contents/Resources/godex-g530.ppd
sudo ln -s /Library/Printers/GoDEX/PPDs/godex-g530+.ppd \
/Library/Printers/PPDs/Contents/Resources/godex-g530+.ppd重新新增印表機,這次在「選取軟體」中搜尋 godex,終於看到了 GODEX G530+, 1.1.14!
選擇正確的驅動後,之前的問題一掃而空:
- ✅ 紙張尺寸正確
- ✅ 不需要手動旋轉
- ✅ 邊距合理
CSS 列印控制
驅動問題解決後,接下來是排版。物流標籤需要精確控制尺寸,讓 80mm 的主標籤和 40mm 的簽收聯正好對齊撕開線。
@page vs body
CSS 列印有兩個重要的概念需要區分:
@page {
margin: 2mm; /* 控制紙張邊緣的留白 */
}
body {
margin: 0; /* 控制內容與列印區域的距離 */
}簡單說,@page 控制「紙」,body 控制「內容」。對於標籤列印,@page margin 決定了印表機實際可列印的區域。
固定高度分割
為了精確控制 80mm/40mm 的分割,我使用固定高度的 div 包裹:
.label-page {
width: 100%;
height: 116mm; /* 120mm - 4mm 邊距 */
page-break-after: always;
page-break-inside: avoid;
}
.main-label {
height: 78mm; /* 80mm - 2mm 邊距 */
overflow: hidden;
}
.receipt-label {
height: 38mm; /* 40mm - 2mm 邊距 */
overflow: hidden;
}page-break-after: always 確保每張標籤獨立一頁,page-break-inside: avoid 防止內容被分割到不同頁。
條碼與 QR Code
標籤上需要印條碼(給掃描槍讀取)和 QR Code(給司機 App 掃描)。我使用 milon/barcode 這個 Laravel 套件來產生。
條碼:PNG vs SVG
最初使用 PNG 格式:
DNS1D::getBarcodePNG($delivery->number, 'C128', 1.5, 33)列印出來的條碼掃描器讀不到。原因是條紋太細,加上 PNG 在縮放時會模糊。
改用 SVG 後問題解決:
DNS1D::getBarcodeSVG($delivery->number, 'C128', 1.5, 28, 'black', false)SVG 是向量圖,縮放不會失真,條紋邊緣銳利,掃描器可以正確讀取。
QR Code:還是用 PNG
有趣的是,QR Code 用 SVG 反而出問題。因為 SVG 的 QR Code 大小是根據內容長度動態產生的,很難用 CSS 精確控制尺寸,容易超出容器或被截斷。
最終的選擇:
| 類型 | 格式 | 原因 |
|---|---|---|
| 條碼(1D) | SVG | 條紋寬度固定,SVG 清晰 |
| QR Code(2D) | PNG | 用 CSS 控制尺寸較穩定 |
// 條碼
{!! DNS1D::getBarcodeSVG($number, 'C128', 1.5, 28, 'black', false) !!}
// QR Code
<img src="data:image/png;base64,{{ DNS2D::getBarcodePNG($url, 'QRCODE', 4, 4) }}"
style="width: 12mm; height: 12mm;">成果與心得
經過一番折騰,標籤列印系統終於上線運作。相較於 Loftware 的年費,一台 GoDex G530+ 的價格大概只要兩三個月的授權費。更重要的是,我們現在完全掌控了標籤的設計與列印邏輯,未來要調整格式或新增欄位都很容易。