Quelltext des aktuellen Standes
/*
* Voucher-Etikettendruck (UniFi) – gemeinsame Client-Logik für Voucher-Seite,
* Dashboard-Widget und Ticket-Schnellaktion.
*
* Konfiguration pro Seite über window.VoucherLabelConfig:
* {
* printMethod: 'both' | 'dymo_connect' | 'windows_print',
* logoUrl: '/uploads/voucher_label_logo/...' (oder ''),
* geo: { w,h,pad,gapInner,logoH,logoW,font, ... } (DymoLabel::windowsGeo),
* apiBase: '/unifi/vouchers/dymo-label'
* }
*
* Öffentliche API: window.VoucherLabel
* .windows(code) – Windows-Druckdialog
* .dymo(code) – DYMO-Connect-Modal (Drucker wählen + drucken)
* .buttons(code,opts) – HTML-String mit Druck-Buttons je nach printMethod
*/
(function () {
'use strict';
function cfg() { return window.VoucherLabelConfig || {}; }
function esc(s) {
return String(s == null ? '' : s)
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
.replace(/"/g, '"').replace(/'/g, ''');
}
function fmtCode(code) {
var c = String(code).replace(/[-\s]/g, '');
return c.length >= 10 ? c.slice(0, 5) + '-' + c.slice(5) : String(code);
}
// -----------------------------------------------------------------
// Windows-Druckdialog: Logo oben, Code groß zentriert darunter
// -----------------------------------------------------------------
function printWindows(code) {
var c = cfg();
var g = c.geo || { w: 54, h: 25, pad: 1.5, gapInner: 1, logoH: 9, logoW: 30, font: 8 };
var logo = c.logoUrl || '';
var disp = fmtCode(code);
var css = [
'@page { size: ' + g.w + 'mm ' + g.h + 'mm; margin: 0; }',
'body { margin:0; padding:0; background:#fff; }',
'.lbl { width:' + g.w + 'mm; height:' + g.h + 'mm; display:flex; flex-direction:column;',
' align-items:center; justify-content:center; gap:' + g.gapInner + 'mm;',
' padding:' + g.pad + 'mm; box-sizing:border-box; overflow:hidden; }',
'.lbl img { max-height:' + g.logoH + 'mm; max-width:' + g.logoW + 'mm; object-fit:contain; }',
'.lbl span { font-family:"Courier New",monospace; font-size:' + (g.font * 1.4) + 'pt; font-weight:bold;',
' letter-spacing:.04em; text-align:center; word-break:break-all; color:#000; }'
].join('\n');
var body = '<div class="lbl">'
+ (logo ? '<img src="' + esc(logo) + '" alt="">' : '')
+ '<span>' + esc(disp) + '</span></div>';
var win = window.open('', '_blank', 'width=320,height=220');
if (!win) { alert(window.onitText('Bitte Popup-Fenster für diese Seite erlauben.')); return; }
win.document.write('<!DOCTYPE html><html><head><meta charset="utf-8"><style>' + css + '</style></head><body>' + body + '</body></html>');
win.document.close();
win.focus();
setTimeout(function () {
win.print();
win.onafterprint = function () { win.close(); };
}, 200);
}
// -----------------------------------------------------------------
// DYMO Connect: Label-XML vom Server holen, Drucker wählen, drucken
// -----------------------------------------------------------------
var DYMO_BASES = ['https://localhost:41952', 'http://localhost:41951'];
var modalEl = null;
var state = { base: null, xml: null, code: null };
function ensureModal() {
if (modalEl) return modalEl;
modalEl = document.createElement('div');
modalEl.id = 'vlbl-modal';
modalEl.style.cssText = 'position:fixed;inset:0;background:rgba(15,23,42,.5);z-index:100020;display:none;align-items:center;justify-content:center;';
modalEl.innerHTML =
'<div style="background:#fff;border-radius:.6rem;box-shadow:0 20px 60px rgba(0,0,0,.3);width:min(420px,calc(100vw - 2rem));overflow:hidden;">'
+ '<div style="display:flex;align-items:center;justify-content:space-between;padding:1rem 1.25rem;border-bottom:1px solid #e5e7eb;font-weight:600;">'
+ '<span>🏷️ ' + window.onitText('Voucher-Etikett drucken') + '</span>'
+ '<button type="button" id="vlbl-close" style="background:none;border:none;font-size:1.3rem;line-height:1;cursor:pointer;color:#6b7280;">×</button>'
+ '</div>'
+ '<div id="vlbl-body" style="padding:1.1rem 1.25rem;font-size:.88rem;color:#374151;"></div>'
+ '</div>';
document.body.appendChild(modalEl);
modalEl.addEventListener('click', function (e) { if (e.target === modalEl) closeModal(); });
modalEl.querySelector('#vlbl-close').addEventListener('click', closeModal);
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && modalEl.style.display === 'flex') closeModal();
});
return modalEl;
}
function setBody(html) {
ensureModal().querySelector('#vlbl-body').innerHTML = html;
}
function openModal() { ensureModal().style.display = 'flex'; }
function closeModal() { if (modalEl) modalEl.style.display = 'none'; }
function openDymo(code) {
state = { base: null, xml: null, code: code };
ensureModal();
openModal();
initDymo();
}
async function initDymo() {
setBody('<div style="text-align:center;padding:1.5rem;color:#6b7280;">⏳ Verbinde mit DYMO Connect…</div>');
// 1) Label-XML vom Server laden
try {
var apiBase = cfg().apiBase || '/unifi/vouchers/dymo-label';
var r = await fetch(apiBase + '?code=' + encodeURIComponent(state.code), { credentials: 'same-origin' });
if (r.ok) {
var data = await r.json();
if (data && data.labelXml) state.xml = data.labelXml;
}
} catch (e) { /* ignore */ }
if (!state.xml) {
setBody('<div style="color:#dc2626;">' + window.onitText('Etikett konnte nicht erzeugt werden. Bitte später erneut versuchen.') + '</div>'
+ footerBtns());
bindFooter();
return;
}
// 2) DYMO Web Service suchen
for (var i = 0; i < DYMO_BASES.length; i++) {
try {
var pr = await fetch(DYMO_BASES[i] + '/DYMO/DLS/Printing/GetPrinters', { signal: AbortSignal.timeout(3000) });
if (pr.ok) { state.base = DYMO_BASES[i]; break; }
} catch (e) { /* try next */ }
}
if (!state.base) {
setBody(
'<div style="color:#dc2626;font-weight:700;margin-bottom:.5rem;">⚠ DYMO Connect nicht gefunden</div>'
+ '<p style="font-size:.83rem;margin:0 0 .4rem;">Bitte sicherstellen, dass <strong>DYMO Connect</strong> installiert und gestartet ist.</p>'
+ '<p style="font-size:.8rem;color:#6b7280;margin:0 0 .75rem;">Bei DYMO Connect zuerst <a href="https://localhost:41952" target="_blank" style="color:#3c8dbc;">https://localhost:41952</a> aufrufen und das Zertifikat akzeptieren, dann erneut versuchen.</p>'
+ '<div style="display:flex;gap:.5rem;"><button type="button" id="vlbl-retry" class="btn btn-primary" style="font-size:.82rem;">Erneut versuchen</button>'
+ '<button type="button" id="vlbl-cancel" class="btn btn-default" style="font-size:.82rem;">' + window.onitText('Schließen') + '</button></div>'
);
bindFooter();
return;
}
// 3) Drucker auflisten
var printers = [];
try {
var pr2 = await fetch(state.base + '/DYMO/DLS/Printing/GetPrinters');
var xml = await pr2.text();
var doc = (new DOMParser()).parseFromString(xml, 'text/xml');
printers = Array.from(doc.querySelectorAll('LabelWriterPrinter Name'))
.map(function (n) { return n.textContent.trim(); })
.filter(Boolean);
} catch (e) { /* ignore */ }
if (printers.length === 0) {
setBody('<div style="color:#d97706;">' + window.onitText('Kein DYMO LabelWriter-Drucker gefunden. Bitte Drucker anschließen und DYMO Connect neu starten.') + '</div>'
+ footerBtns());
bindFooter();
return;
}
var options = printers.map(function (p) {
return '<option value="' + esc(p) + '">' + esc(p) + '</option>';
}).join('');
var preview = (cfg().logoUrl ? '<img src="' + esc(cfg().logoUrl) + '" style="max-height:34px;max-width:150px;object-fit:contain;display:block;margin:0 auto .35rem;">' : '')
+ '<div style="font-family:\'Courier New\',monospace;font-size:1rem;font-weight:bold;text-align:center;letter-spacing:.04em;color:#111;">' + esc(fmtCode(state.code)) + '</div>';
setBody(
'<div style="margin-bottom:.85rem;">'
+ '<label style="font-size:.8rem;font-weight:700;color:#374151;display:block;margin-bottom:.3rem;">Drucker</label>'
+ '<select id="vlbl-printer" class="form-control" style="width:100%;">' + options + '</select>'
+ '</div>'
+ '<div style="margin-bottom:.85rem;">'
+ '<div style="font-size:.8rem;font-weight:700;color:#374151;margin-bottom:.35rem;">Vorschau</div>'
+ '<div style="border:1px dashed #cbd5e1;border-radius:.4rem;padding:.6rem;background:#f8fafc;">' + preview + '</div>'
+ '</div>'
+ '<div style="display:flex;gap:.5rem;align-items:center;">'
+ '<button type="button" id="vlbl-print" class="btn btn-primary">🖨️ Drucken</button>'
+ '<button type="button" id="vlbl-cancel" class="btn btn-default">' + window.onitText('Schließen') + '</button>'
+ '</div>'
+ '<div id="vlbl-status" style="display:none;margin-top:.6rem;font-size:.82rem;"></div>'
);
bindFooter();
var printBtn = modalEl.querySelector('#vlbl-print');
if (printBtn) printBtn.addEventListener('click', doPrint);
}
function footerBtns() {
return '<div style="display:flex;gap:.5rem;margin-top:.9rem;">'
+ '<button type="button" id="vlbl-retry" class="btn btn-primary" style="font-size:.82rem;">Erneut versuchen</button>'
+ '<button type="button" id="vlbl-cancel" class="btn btn-default" style="font-size:.82rem;">' + window.onitText('Schließen') + '</button></div>';
}
function bindFooter() {
var retry = modalEl.querySelector('#vlbl-retry');
var cancel = modalEl.querySelector('#vlbl-cancel');
if (retry) retry.addEventListener('click', initDymo);
if (cancel) cancel.addEventListener('click', closeModal);
}
async function doPrint() {
var sel = modalEl.querySelector('#vlbl-printer');
var btn = modalEl.querySelector('#vlbl-print');
var status = modalEl.querySelector('#vlbl-status');
if (!sel || !state.xml || !state.base) return;
btn.disabled = true;
if (status) { status.style.display = 'block'; status.style.color = '#6b7280'; status.textContent = '⧖ Wird gedruckt…'; }
try {
var body = new URLSearchParams({ printerName: sel.value, labelXml: state.xml });
var r = await fetch(state.base + '/DYMO/DLS/Printing/PrintLabel', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: body.toString()
});
if (r.ok) {
if (status) { status.style.color = '#059669'; status.textContent = '✓ Etikett wurde gedruckt!'; }
} else {
var err = await r.text().catch(function () { return r.statusText; });
if (status) { status.style.color = '#dc2626'; status.textContent = 'Fehler: ' + err; }
}
} catch (e) {
if (status) { status.style.color = '#dc2626'; status.textContent = 'Druckfehler: ' + e.message; }
} finally {
btn.disabled = false;
}
}
// -----------------------------------------------------------------
// Druck-Buttons (HTML) je nach konfigurierter Methode
// -----------------------------------------------------------------
function buttonsHtml(code, opts) {
opts = opts || {};
var method = cfg().printMethod || 'both';
var cls = opts.btnClass || 'btn btn-xs btn-default';
var ec = esc(String(code).replace(/[^A-Za-z0-9\- ]/g, ''));
var html = '';
if (method === 'dymo_connect' || method === 'both') {
html += '<button type="button" class="' + cls + '" onclick="VoucherLabel.dymo(\'' + ec + '\')" title="' + window.onitText('Etikett über DYMO Connect drucken') + '">🏷️ DYMO</button> ';
}
if (method === 'windows_print' || method === 'both') {
html += '<button type="button" class="' + cls + '" onclick="VoucherLabel.windows(\'' + ec + '\')" title="' + window.onitText('Etikett über Windows-Druckdialog drucken') + '">🏷️ Etikett</button>';
}
return html;
}
window.VoucherLabel = {
windows: printWindows,
dymo: openDymo,
buttons: buttonsHtml,
configure: function (c) { window.VoucherLabelConfig = c; }
};
})();