refactor(ui): Rediseño completo de UI con Nuxt UI 4
- Nuevo layout responsivo mobile-first con tabs inferiores - Sidebar colapsable en desktop con cola de impresión - Sistema de templates reutilizables con localStorage - Soporte Dark/Light mode con UColorModeButton - Composables usePrintQueue y useTemplates para estado global - Componentes modulares: CommandBuilder, QuickActions, PrintQueue, QueueItem - Navegación por tabs: Constructor | Cola | Templates
This commit is contained in:
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@@ -1,158 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Printer Central</title>
|
||||
<style>
|
||||
body { font-family: system-ui, sans-serif; margin: 24px; }
|
||||
fieldset { margin-bottom: 16px; }
|
||||
label { display: inline-block; margin: 6px 8px 6px 0; }
|
||||
input[type="text"], textarea, select { padding: 6px; }
|
||||
.row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
|
||||
.btn { padding: 8px 12px; cursor: pointer; }
|
||||
#result { white-space: pre-wrap; background: #f6f8fa; padding: 8px; border: 1px solid #ddd; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Panel de Impresión (Node → Epson ePOS)</h1>
|
||||
|
||||
<fieldset>
|
||||
<legend>Texto</legend>
|
||||
<div class="row">
|
||||
<textarea id="text" rows="4" cols="60" placeholder="Escribe el texto a imprimir..."></textarea>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label>Align
|
||||
<select id="align">
|
||||
<option value="">(sin cambio)</option>
|
||||
<option value="left">left</option>
|
||||
<option value="center">center</option>
|
||||
<option value="right">right</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>Font
|
||||
<select id="font">
|
||||
<option value="">(sin cambio)</option>
|
||||
<option value="font_a">A</option>
|
||||
<option value="font_b">B</option>
|
||||
<option value="font_c">C</option>
|
||||
<option value="font_d">D</option>
|
||||
<option value="font_e">E</option>
|
||||
<option value="special_a">Special A</option>
|
||||
<option value="special_b">Special B</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>Width
|
||||
<input id="w" type="number" min="1" max="8" step="1" placeholder="1" style="width:5rem" />
|
||||
</label>
|
||||
<label>Height
|
||||
<input id="h" type="number" min="1" max="8" step="1" placeholder="1" style="width:5rem" />
|
||||
</label>
|
||||
<label><input type="checkbox" id="bold"/> Bold</label>
|
||||
<label><input type="checkbox" id="underline"/> Underline</label>
|
||||
<label><input type="checkbox" id="reverse"/> Reverse</label>
|
||||
<label>Smooth <input type="checkbox" id="smooth"/></label>
|
||||
<label>Color
|
||||
<select id="color">
|
||||
<option value="">(default)</option>
|
||||
<option value="color_1">1</option>
|
||||
<option value="color_2">2</option>
|
||||
<option value="color_3">3</option>
|
||||
<option value="color_4">4</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>Feed lines <input id="feedLines" type="number" min="0" max="255" step="1" style="width:5rem"/></label>
|
||||
<label>Cut
|
||||
<select id="cut">
|
||||
<option value="">(no)</option>
|
||||
<option value="no_feed">no_feed</option>
|
||||
<option value="feed">feed</option>
|
||||
<option value="reserve">reserve</option>
|
||||
<option value="no_feed_fullcut">no_feed_fullcut</option>
|
||||
<option value="feed_fullcut">feed_fullcut</option>
|
||||
<option value="reserve_fullcut">reserve_fullcut</option>
|
||||
</select>
|
||||
</label>
|
||||
<button class="btn" id="btnPrintText">Imprimir texto</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Avanzado (componer operaciones)</legend>
|
||||
<div class="row">
|
||||
<button class="btn" data-op="feedLine" data-arg="line:2">Feed 2 líneas</button>
|
||||
<button class="btn" data-op="cut" data-arg="type:feed">Cortar (feed)</button>
|
||||
<button class="btn" id="btnDrawer">Abrir cajón (pulse)</button>
|
||||
<button class="btn" id="btnQRCode">QR demo</button>
|
||||
<button class="btn" id="btnBarcode">Barcode demo</button>
|
||||
<button class="btn" id="btnEnviar">Enviar lote</button>
|
||||
<button class="btn" id="btnLimpiar">Limpiar lote</button>
|
||||
</div>
|
||||
<div>Operaciones en cola:</div>
|
||||
<pre id="ops"></pre>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Resultado</legend>
|
||||
<pre id="result">Listo.</pre>
|
||||
</fieldset>
|
||||
|
||||
<script>
|
||||
const $ = (id) => document.getElementById(id);
|
||||
const ops = [];
|
||||
function refreshOps() { $('ops').textContent = JSON.stringify(ops, null, 2); }
|
||||
refreshOps();
|
||||
|
||||
async function callApi(path, body) {
|
||||
const res = await fetch(path, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body || {}) });
|
||||
const data = await res.json();
|
||||
$('result').textContent = JSON.stringify(data, null, 2);
|
||||
return data;
|
||||
}
|
||||
|
||||
$('btnPrintText').onclick = async () => {
|
||||
const text = $('text').value;
|
||||
const options = {};
|
||||
const align = $('align').value; if (align) options.align = align;
|
||||
const font = $('font').value; if (font) options.font = font;
|
||||
const w = parseInt($('w').value || '');
|
||||
const h = parseInt($('h').value || '');
|
||||
if (!isNaN(w) || !isNaN(h)) options.size = { width: isNaN(w)? undefined : w, height: isNaN(h)? undefined : h };
|
||||
options.style = { em: $('bold').checked, ul: $('underline').checked, reverse: $('reverse').checked };
|
||||
const color = $('color').value; if (color) options.style.color = color;
|
||||
if ($('smooth').checked) options.smooth = true; // optional, not wired in shortcut endpoint
|
||||
const feedLines = parseInt($('feedLines').value || ''); if (!isNaN(feedLines)) options.feedLines = feedLines;
|
||||
const cut = $('cut').value; if (cut) options.cut = cut;
|
||||
await callApi('/api/print/text', { text, options });
|
||||
};
|
||||
|
||||
// Buttons to queue operations
|
||||
document.querySelectorAll('[data-op]').forEach(btn => {
|
||||
btn.onclick = () => {
|
||||
const op = btn.getAttribute('data-op');
|
||||
const arg = btn.getAttribute('data-arg');
|
||||
const item = { op };
|
||||
if (arg) {
|
||||
// parse simple key:value pairs
|
||||
arg.split(',').forEach(kv => { const [k,v] = kv.split(':'); item[k.trim()] = isNaN(v) ? v : Number(v); });
|
||||
}
|
||||
ops.push(item); refreshOps();
|
||||
};
|
||||
});
|
||||
|
||||
$('btnDrawer').onclick = () => { ops.push({ op: 'pulse', drawer: 'drawer_1', time: 'pulse_200' }); refreshOps(); };
|
||||
$('btnQRCode').onclick = () => {
|
||||
ops.push({ op: 'qrcode', data: 'https://example.com', model: 'qrcode_model_2', level: 'level_m', size: 6 });
|
||||
refreshOps();
|
||||
};
|
||||
$('btnBarcode').onclick = () => {
|
||||
ops.push({ op: 'barcode', data: '123456789012', type: 'ean13', hri: 'below', width: 3, height: 80 });
|
||||
refreshOps();
|
||||
};
|
||||
$('btnEnviar').onclick = async () => { await callApi('/api/print', { operations: ops }); };
|
||||
$('btnLimpiar').onclick = () => { ops.length = 0; refreshOps(); };
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
2
public/robots.txt
Normal file
2
public/robots.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
User-Agent: *
|
||||
Disallow:
|
||||
Reference in New Issue
Block a user