瀏覽器列印問題
如果您曾嘗試從網路應用程式列印收據,您會知道其中的痛苦:window.print() 會開啟列印對話框,將頁面呈現為 PDF,並且完全不支援自動裁切或自訂紙張寬度等 ESC/POS 指令。
對於熱感收據印表機來說,這是一個致命的缺點。您需要:
- 靜默列印 — 無對話框,無需使用者互動
- ESC/POS 指令 — 用於自動裁切、粗體文字、對齊、條碼
- 可靠的編碼 — UTF-8 到印表機的正確代碼頁
- 跨平台支援 — Mac、Windows、Linux
解決方案是本機 WebSocket 橋接 — 一個在使用者電腦上執行的小型後台應用程式,接收來自您網路應用程式的列印作業,並將原始 ESC/POS 指令發送到印表機。
WebSocket 橋接如何運作
您的網路應用程式 → WebSocket (ws://localhost:8765) → Print Agent → 熱感印表機
流程很簡單:
- 您的網路應用程式開啟到 ws://localhost:8765 的 WebSocket 連線
- 您發送一個描述收據的 JSON 有效負載
- Print Agent 將其轉換為 ESC/POS 並發送到印表機
- 印表機靜默列印 — 無對話框,無驅動程式互動
MenuForma Print Agent 實作了這個橋接。它是免費、開源的,並在 Mac、Windows 和 Linux 上執行。
API 參考
連線
const ws = new WebSocket('ws://localhost:8765');
ws.onopen = () => {
console.log('Print Agent connected');
};
ws.onerror = () => {
console.log('Print Agent not running');
};
檢查 Agent 狀態
發送 ping 以驗證 Agent 正在執行:
ws.send(JSON.stringify({ type: 'ping' }));
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'pong') {
console.log('Agent version:', data.version);
}
};
列印收據
const receipt = {
type: 'print',
printer: 'auto', // 或特定 IP,例如 '192.168.1.100'
data: {
storeName: 'My Restaurant',
storeAddress: '123 Main St',
orderNumber: '#1042',
tableNumber: 'Table 5',
items: [
{ name: 'Margherita Pizza', qty: 1, price: 14.90 },
{ name: 'Sparkling Water', qty: 2, price: 3.50 },
],
subtotal: 21.90,
tax: 1.97,
total: 23.87,
paymentMethod: 'Card',
footer: 'Thank you for dining with us!',
}
};
ws.send(JSON.stringify(receipt));
React 整合範例
import { useEffect, useRef, useCallback, useState } from 'react';
function usePrintAgent() {
const wsRef = useRef(null);
const [connected, setConnected] = useState(false);
useEffect(() => {
const connect = () => {
const ws = new WebSocket('ws://localhost:8765');
ws.onopen = () => setConnected(true);
ws.onclose = () => {
setConnected(false);
setTimeout(connect, 3000); // retry
};
wsRef.current = ws;
};
connect();
return () => wsRef.current?.close();
}, []);
const print = useCallback((receiptData) => {
if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) {
throw new Error('Print Agent not connected');
}
wsRef.current.send(JSON.stringify({ type: 'print', data: receiptData }));
}, []);
return { connected, print };
}
Shopify 整合
對於 Shopify 商店,您可以從訂單 webhook 或瀏覽器擴充功能觸發列印:
// 在您的 Shopify 店面或管理擴充功能中
async function printShopifyOrder(order) {
const ws = new WebSocket('ws://localhost:8765');
ws.onopen = () => {
ws.send(JSON.stringify({
type: 'print',
data: {
storeName: order.shop_name,
orderNumber: order.name,
items: order.line_items.map(item => ({
name: item.title,
qty: item.quantity,
price: parseFloat(item.price),
})),
total: parseFloat(order.total_price),
paymentMethod: order.payment_gateway,
}
}));
};
}
印表機設定
列印有效負載中的「printer」欄位接受:
| 值 | 描述 |
|---|---|
| "auto" | 使用 Print Agent 設定中配置的印表機 |
| "192.168.1.100" | 直接透過 IP 連線到網路印表機 |
| "USB" | 使用第一個偵測到的 USB 印表機 |
錯誤處理
ws.onmessage = (event) => {
const response = JSON.parse(event.data);
if (response.type === 'print_success') {
console.log('Printed successfully');
} else if (response.type === 'print_error') {
console.error('Print failed:', response.error);
// 顯示備用 UI 或重試
}
};
偵測 Print Agent 是否已安裝
在您的 UI 中顯示列印功能之前,請檢查 Agent 是否正在執行:
async function isPrintAgentAvailable() {
return new Promise((resolve) => {
const ws = new WebSocket('ws://localhost:8765');
const timeout = setTimeout(() => {
ws.close();
resolve(false);
}, 1000);
ws.onopen = () => {
clearTimeout(timeout);
ws.close();
resolve(true);
};
ws.onerror = () => {
clearTimeout(timeout);
resolve(false);
};
});
}
// 用法
const agentAvailable = await isPrintAgentAvailable();
if (!agentAvailable) {
// 顯示下載提示
window.open('/print-agent', '_blank');
}
下載 Print Agent
MenuForma Print Agent 是免費的,適用於 Mac、Windows 和 Linux。它支援 USB、網路和藍牙熱感印表機,並實作了本指南中描述的完整 WebSocket API。
有關完整的 API 文件和開源程式碼,請訪問 Print Agent 頁面。
Related Articles
- 台灣餐飲業迎來「大內卷時代」:缺工與高成本夾擊下,數位轉型成唯一出路
- 掃碼點餐還要收10%服務費?台港網友熱議背後的餐飲體驗升級挑戰
- 2025香港餐飲大數據揭秘:逾240萬筆討論顯示「科技驅動」成中小餐廳突圍關鍵