File: /home/storage/5/78/dd/wicomm2/public_html/clientes/leitorean/index.html
<!doctype html>
<html lang="pt-br">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Leitor EAN-13</title>
<style>
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; padding: 16px; }
video { width: 100%; max-width: 520px; border-radius: 12px; background: #000; }
.row { display:flex; gap:12px; flex-wrap:wrap; margin-top: 12px; }
button { padding: 10px 14px; border-radius: 10px; border: 1px solid #ddd; background: #fff; cursor:pointer; }
.result { margin-top: 12px; font-size: 18px; }
.hint { color:#666; font-size: 14px; margin-top: 6px; }
.ok { color: #0a7; }
.bad { color: #c22; }
code { background:#f6f6f6; padding:2px 6px; border-radius:8px; }
</style>
</head>
<body>
<h1>Leitor de Código de Barras (EAN-13)</h1>
<video id="video" autoplay muted playsinline></video>
<div class="row">
<button id="start">Iniciar</button>
<button id="stop" disabled>Parar</button>
<button id="copy" disabled>Copiar</button>
</div>
<div class="result">
<strong>EAN-13:</strong> <span id="text">—</span>
<div class="hint" id="hint">Aponte a câmera para um EAN-13 (13 dígitos).</div>
<div class="hint" id="status"></div>
</div>
<script type="module">
import {
BrowserMultiFormatReader,
BarcodeFormat,
DecodeHintType,
NotFoundException
} from "https://cdn.jsdelivr.net/npm/@zxing/browser@0.1.5/+esm";
const video = document.getElementById("video");
const startBtn = document.getElementById("start");
const stopBtn = document.getElementById("stop");
const copyBtn = document.getElementById("copy");
const textEl = document.getElementById("text");
const hintEl = document.getElementById("hint");
const statusEl = document.getElementById("status");
// Hints para focar só em EAN-13
const hints = new Map();
hints.set(DecodeHintType.POSSIBLE_FORMATS, [BarcodeFormat.EAN_13]);
const reader = new BrowserMultiFormatReader(hints, 500); // 500ms entre tentativas
let controls = null;
function isValidEan13(code) {
if (!/^\d{13}$/.test(code)) return false;
const digits = code.split("").map(Number);
const checkDigit = digits[12];
// Soma: posições 1..12 (índice 0..11). ÍMPARES *1, PARES *3 (no padrão EAN)
// (considerando a primeira posição como "1")
let sum = 0;
for (let i = 0; i < 12; i++) {
const pos = i + 1;
sum += digits[i] * (pos % 2 === 0 ? 3 : 1);
}
const calc = (10 - (sum % 10)) % 10;
return calc === checkDigit;
}
function setResult(value) {
textEl.textContent = value ?? "—";
copyBtn.disabled = !value;
}
function setStatus(msg, kind) {
statusEl.textContent = msg ?? "";
statusEl.className = "hint " + (kind || "");
}
async function start() {
setResult(null);
setStatus("", "");
hintEl.textContent = "Abrindo câmera…";
try {
const constraints = { video: { facingMode: "environment" }, audio: false };
controls = await reader.decodeFromConstraints(
constraints,
video,
(result, err) => {
if (result) {
const value = result.getText();
// ZXing pode ler outros formatos em raros casos; reforçamos:
if (!/^\d{13}$/.test(value)) {
setStatus("Lido, mas não parece EAN-13 (precisa ter 13 dígitos).", "bad");
return;
}
if (!isValidEan13(value)) {
setResult(value);
setStatus("13 dígitos lidos, mas o dígito verificador não confere ❌", "bad");
return;
}
setResult(value);
hintEl.textContent = "Lido com sucesso ✅";
setStatus("EAN-13 válido ✔️", "ok");
// opcional: parar automaticamente ao ler válido
stop();
} else if (err && !(err instanceof NotFoundException)) {
console.error(err);
setStatus("Erro ao ler. Ajuste distância/luz/foco.", "bad");
}
}
);
startBtn.disabled = true;
stopBtn.disabled = false;
hintEl.textContent = "Aponte para o EAN-13…";
} catch (e) {
console.error(e);
hintEl.textContent = "Não consegui abrir a câmera. Verifique permissões e se está em HTTPS.";
setStatus("Dica: no iPhone, use Safari e HTTPS para câmera.", "bad");
}
}
function stop() {
if (controls) controls.stop();
controls = null;
startBtn.disabled = false;
stopBtn.disabled = true;
}
startBtn.addEventListener("click", start);
stopBtn.addEventListener("click", stop);
copyBtn.addEventListener("click", async () => {
const value = textEl.textContent;
if (!value || value === "—") return;
await navigator.clipboard.writeText(value);
setStatus("Copiado 📋", "ok");
});
</script>
<p class="hint">
Observações: câmera em celular normalmente exige <code>HTTPS</code> (exceto <code>localhost</code>).
</p>
</body>
</html>