Print rendered POS receipt through QZ

This commit is contained in:
metatroncubeswdev 2026-05-07 17:15:51 -04:00
parent bbc7603c5b
commit b5f450bbf7

View File

@ -21,6 +21,10 @@ function getBillingPrinterName(config) {
return config?.qz_billing_printer_name || config?.qz_printer_name || ""; return config?.qz_billing_printer_name || config?.qz_printer_name || "";
} }
function paperWidthMm(config) {
return String(config?.qz_paper_width || DEFAULT_COLUMNS) === "48" ? 80 : 58;
}
function cleanText(value) { function cleanText(value) {
return String(value ?? "") return String(value ?? "")
.normalize("NFKD") .normalize("NFKD")
@ -132,6 +136,65 @@ function bytesToBase64(bytes) {
return btoa(binary); return btoa(binary);
} }
function absoluteUrl(value) {
if (!value || value.startsWith("data:") || value.startsWith("blob:")) {
return value;
}
return new URL(value, window.location.origin).href;
}
function collectPrintableStyles() {
return Array.from(document.querySelectorAll("link[rel='stylesheet'], style"))
.map((node) => {
if (node.tagName === "LINK") {
return `<link rel="stylesheet" href="${absoluteUrl(node.getAttribute("href"))}">`;
}
return node.outerHTML;
})
.join("\n");
}
function buildHtmlReceipt(receiptElement, pos) {
const config = pos?.config || {};
const widthMm = paperWidthMm(config);
const clone = receiptElement.cloneNode(true);
for (const node of clone.querySelectorAll("[src]")) {
node.setAttribute("src", absoluteUrl(node.getAttribute("src")));
}
for (const node of clone.querySelectorAll("[href]")) {
node.setAttribute("href", absoluteUrl(node.getAttribute("href")));
}
return `<!doctype html>
<html>
<head>
<meta charset="utf-8">
<base href="${window.location.origin}/">
${collectPrintableStyles()}
<style>
@page { size: ${widthMm}mm auto; margin: 0; }
html, body {
width: ${widthMm}mm;
margin: 0;
padding: 0;
background: #fff;
}
body {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
.pos-receipt {
width: ${widthMm}mm !important;
max-width: ${widthMm}mm !important;
box-sizing: border-box;
margin: 0 auto !important;
}
</style>
</head>
<body>${clone.outerHTML}</body>
</html>`;
}
class EscPosBuilder { class EscPosBuilder {
constructor(columns) { constructor(columns) {
this.columns = columns; this.columns = columns;
@ -448,6 +511,31 @@ patch(ReceiptScreen.prototype, {
await qz.websocket.connect({ retries: 2, delay: 1 }); await qz.websocket.connect({ retries: 2, delay: 1 });
} }
const receiptElement = this.el?.querySelector?.(".pos-receipt");
if (receiptElement) {
const widthMm = paperWidthMm(this.pos.config);
const config = qz.configs.create(printerName, {
copies: 1,
margins: 0,
units: "mm",
size: { width: widthMm },
rasterize: true,
scaleContent: true,
spool: { size: 1 },
});
await qz.print(config, [{
type: "pixel",
format: "html",
flavor: "plain",
data: buildHtmlReceipt(receiptElement, this.pos),
}]);
if (this.currentOrder) {
this.currentOrder._printed = true;
}
return true;
}
const config = qz.configs.create(printerName, { const config = qz.configs.create(printerName, {
encoding: "CP437", encoding: "CP437",
copies: 1, copies: 1,