192 lines
8.2 KiB
Vue
192 lines
8.2 KiB
Vue
<template>
|
|
<div class="wrap">
|
|
<header class="glass card">
|
|
<h1>Printer Central</h1>
|
|
<p class="subtle">Vista previa y control ePOS (monocromático)</p>
|
|
</header>
|
|
|
|
<main class="grid">
|
|
<section class="glass card">
|
|
<h2>Texto</h2>
|
|
<textarea v-model="form.text" rows="6" placeholder="Escribe el texto a imprimir..." />
|
|
|
|
<div class="controls">
|
|
<label>
|
|
Align
|
|
<select v-model="form.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 v-model="form.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 type="number" min="1" max="8" v-model.number="form.w" /></label>
|
|
<label>Height <input type="number" min="1" max="8" v-model.number="form.h" /></label>
|
|
<label><input type="checkbox" v-model="form.bold" /> Bold</label>
|
|
<label><input type="checkbox" v-model="form.underline" /> Underline</label>
|
|
<label><input type="checkbox" v-model="form.reverse" /> Reverse</label>
|
|
<label>Smooth <input type="checkbox" v-model="form.smooth" /></label>
|
|
<label>
|
|
Color
|
|
<select v-model="form.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 type="number" min="0" max="255" v-model.number="form.feedLines" /></label>
|
|
<label>
|
|
Cut
|
|
<select v-model="form.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>
|
|
</div>
|
|
|
|
<div class="actions">
|
|
<button class="btn" @click="printText">Imprimir texto</button>
|
|
<button class="btn ghost" @click="resetForm">Limpiar</button>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="glass card">
|
|
<h2>Avanzado</h2>
|
|
<div class="chips">
|
|
<button class="chip" @click="pushOp({ op: 'feedLine', line: 2 })">Feed 2</button>
|
|
<button class="chip" @click="pushOp({ op: 'cut', type: 'feed' })">Cortar</button>
|
|
<button class="chip" @click="pushOp({ op: 'pulse', drawer: 'drawer_1', time: 'pulse_200' })">Pulse</button>
|
|
<button class="chip" @click="pushOp({ op: 'qrcode', data: 'https://example.com', model: 'qrcode_model_2', level: 'level_m', size: 6 })">QR</button>
|
|
<button class="chip" @click="pushOp({ op: 'barcode', data: '490123456789', type: 'ean13', hri: 'below', width: 3, height: 80 })">Barcode</button>
|
|
</div>
|
|
<pre class="json">{{ ops }}</pre>
|
|
<div class="actions">
|
|
<button class="btn" @click="sendOps">Enviar lote</button>
|
|
<button class="btn ghost" @click="clearOps">Limpiar lote</button>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="glass card">
|
|
<h2>Resultado</h2>
|
|
<pre class="json result">{{ JSON.stringify(result, null, 2) }}</pre>
|
|
</section>
|
|
</main>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { reactive, ref } from 'vue';
|
|
|
|
const form = reactive({
|
|
text: '',
|
|
align: '',
|
|
font: '',
|
|
w: undefined,
|
|
h: undefined,
|
|
bold: false,
|
|
underline: false,
|
|
reverse: false,
|
|
smooth: false,
|
|
color: '',
|
|
feedLines: undefined,
|
|
cut: ''
|
|
});
|
|
|
|
const ops = ref([]);
|
|
const result = ref({ ok: true, msg: 'Listo.' });
|
|
|
|
function pushOp(op) { ops.value = [...ops.value, op]; }
|
|
function clearOps() { ops.value = []; }
|
|
function resetForm() {
|
|
Object.assign(form, { text: '', align: '', font: '', w: undefined, h: undefined, bold: false, underline: false, reverse: false, smooth: false, color: '', feedLines: undefined, cut: '' });
|
|
}
|
|
|
|
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.value = data;
|
|
}
|
|
|
|
async function printText() {
|
|
const options = {};
|
|
if (form.align) options.align = form.align;
|
|
if (form.font) options.font = form.font;
|
|
if (form.w || form.h) options.size = { width: form.w, height: form.h };
|
|
options.style = { em: form.bold, ul: form.underline, reverse: form.reverse };
|
|
if (form.color) options.style.color = form.color;
|
|
if (form.feedLines != null && form.feedLines !== '') options.feedLines = Number(form.feedLines);
|
|
if (form.cut) options.cut = form.cut;
|
|
await callApi('/api/print/text', { text: form.text, options });
|
|
}
|
|
|
|
async function sendOps() { await callApi('/api/print', { operations: ops.value }); }
|
|
</script>
|
|
|
|
<style>
|
|
:root {
|
|
--bg: #0f1113;
|
|
--fg: #ececec;
|
|
--muted: #a9a9a9;
|
|
--glass: rgba(255,255,255,0.08);
|
|
--border: rgba(255,255,255,0.12);
|
|
--accent: #cfcfcf;
|
|
}
|
|
* { box-sizing: border-box; }
|
|
html, body, #app { height: 100%; }
|
|
body { margin: 0; background: linear-gradient(180deg, #0f1113 0%, #1a1d21 100%); color: var(--fg); font: 14px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, Arial, sans-serif; }
|
|
.wrap { max-width: 1200px; margin: 0 auto; padding: 24px; }
|
|
header h1 { margin: 0 0 4px; font-size: 22px; letter-spacing: 0.2px; }
|
|
.subtle { color: var(--muted); margin: 0; }
|
|
|
|
.grid { display: grid; gap: 16px; grid-template-columns: repeat(12, 1fr); margin-top: 16px; }
|
|
section.card:nth-child(1) { grid-column: span 12; }
|
|
section.card:nth-child(2) { grid-column: span 12; }
|
|
section.card:nth-child(3) { grid-column: span 12; }
|
|
@media (min-width: 860px) {
|
|
section.card:nth-child(1) { grid-column: span 7; }
|
|
section.card:nth-child(2) { grid-column: span 5; }
|
|
section.card:nth-child(3) { grid-column: span 12; }
|
|
}
|
|
|
|
.glass { background: var(--glass); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); border: 1px solid var(--border); border-radius: 14px; }
|
|
.card { padding: 16px; box-shadow: 0 8px 24px rgba(0,0,0,0.24), inset 0 1px 0 rgba(255,255,255,0.05); }
|
|
|
|
h2 { margin: 0 0 12px; font-size: 16px; font-weight: 600; color: var(--accent); }
|
|
textarea { width: 100%; color: var(--fg); background: rgba(255,255,255,0.04); border: 1px solid var(--border); border-radius: 10px; padding: 10px; resize: vertical; }
|
|
select, input[type="number"] { color: var(--fg); background: rgba(255,255,255,0.04); border: 1px solid var(--border); border-radius: 10px; padding: 6px 8px; }
|
|
label { display: inline-flex; gap: 8px; align-items: center; margin: 6px 10px 6px 0; color: var(--muted); }
|
|
|
|
.controls { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
|
|
.actions { margin-top: 12px; display: flex; gap: 8px; }
|
|
.btn { appearance: none; border: 1px solid var(--border); background: rgba(255,255,255,0.08); color: var(--fg); border-radius: 10px; padding: 8px 12px; cursor: pointer; transition: 150ms ease; }
|
|
.btn:hover { background: rgba(255,255,255,0.12); }
|
|
.btn.ghost { background: transparent; }
|
|
|
|
.chips { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 8px; }
|
|
.chip { border: 1px dashed var(--border); background: rgba(255,255,255,0.04); color: var(--fg); border-radius: 999px; padding: 6px 10px; cursor: pointer; }
|
|
|
|
.json { background: rgba(255,255,255,0.03); border: 1px solid var(--border); border-radius: 10px; padding: 10px; white-space: pre-wrap; word-break: break-word; }
|
|
.result { color: #e6e6e6; }
|
|
</style>
|
|
|