Ya genera el codigo e imprime
This commit is contained in:
@@ -13,7 +13,7 @@
|
|||||||
"typecheck:web": "vue-tsc --noEmit -p tsconfig.web.json --composite false",
|
"typecheck:web": "vue-tsc --noEmit -p tsconfig.web.json --composite false",
|
||||||
"typecheck": "npm run typecheck:node && npm run typecheck:web",
|
"typecheck": "npm run typecheck:node && npm run typecheck:web",
|
||||||
"start": "electron-vite preview",
|
"start": "electron-vite preview",
|
||||||
"dev": "electron-vite dev",
|
"dev": "electron-vite dev --watch",
|
||||||
"build": "npm run typecheck && electron-vite build",
|
"build": "npm run typecheck && electron-vite build",
|
||||||
"postinstall": "electron-builder install-app-deps",
|
"postinstall": "electron-builder install-app-deps",
|
||||||
"build:unpack": "npm run build && electron-builder --dir",
|
"build:unpack": "npm run build && electron-builder --dir",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { join } from "path";
|
|||||||
import { electronApp, optimizer, is } from "@electron-toolkit/utils";
|
import { electronApp, optimizer, is } from "@electron-toolkit/utils";
|
||||||
import icon from "../../resources/icon.png?asset";
|
import icon from "../../resources/icon.png?asset";
|
||||||
import { NfcService } from "../services/NfcService";
|
import { NfcService } from "../services/NfcService";
|
||||||
|
import net, { createConnection } from "net";
|
||||||
function createWindow(): void {
|
function createWindow(): void {
|
||||||
// Create the browser window.
|
// Create the browser window.
|
||||||
const mainWindow = new BrowserWindow({
|
const mainWindow = new BrowserWindow({
|
||||||
@@ -60,21 +60,26 @@ app.whenReady().then(() => {
|
|||||||
ipcMain.on("ping", () => console.log("pong"));
|
ipcMain.on("ping", () => console.log("pong"));
|
||||||
|
|
||||||
// HANDLE es bidireccionar ON es unidireccional
|
// HANDLE es bidireccionar ON es unidireccional
|
||||||
ipcMain.handle("nfc:labelReq", async (_event, data: unknown) => {
|
ipcMain.handle("nfc:labelReq", async (_event, data) => {
|
||||||
console.log("nfc:labelReq", data);
|
console.log("nfc:labelReq", data);
|
||||||
try {
|
try {
|
||||||
const response = await fetch("http://localhost:3000/nfc/generate", {
|
const endpoint = "/nfc/generate";
|
||||||
|
const url = data.serverURL + endpoint;
|
||||||
|
console.log("fullUrl = ", url);
|
||||||
|
const response = await fetch(data.serverURL + endpoint, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
card_id: "019cdd39-fc08-7417-b16d-a78794a24c01",
|
card_id: data.card_id,
|
||||||
override: false,
|
override: data.override,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
console.log("Codigos:", response);
|
console.log("Response", response.status);
|
||||||
return 200;
|
const body = await response.json();
|
||||||
|
console.log("Codigos:", body);
|
||||||
|
return body;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
return false;
|
return false;
|
||||||
@@ -82,6 +87,38 @@ app.whenReady().then(() => {
|
|||||||
return data;
|
return data;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle("nfc:printReq", async (_event, data) => {
|
||||||
|
console.log("nfc:printReq", data);
|
||||||
|
const client = new net.Socket();
|
||||||
|
const port = 9100;
|
||||||
|
const host = data.printerURL + port;
|
||||||
|
|
||||||
|
const socket = createConnection(port, data.printerURL);
|
||||||
|
socket.setTimeout(10 * 1000);
|
||||||
|
socket.on("connect", (e) => {
|
||||||
|
console.log("Conectado!", e);
|
||||||
|
socket.write(data.label);
|
||||||
|
socket.end();
|
||||||
|
});
|
||||||
|
/*
|
||||||
|
client.connect(data.printerURL + port, () => {
|
||||||
|
console.log("Connected to ZSim Printer");
|
||||||
|
const res = client.write(data.label, (res) => {
|
||||||
|
console.log("resultado de la impresion", res);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Resultado", res);
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
socket.on("close", () =>
|
||||||
|
console.log("Print job sent and connection closed."),
|
||||||
|
);
|
||||||
|
socket.on("error", (err) =>
|
||||||
|
console.error("Printer Error:", err.message),
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
|
||||||
ipcMain.handle("ping:url", async (_event, url: string) => {
|
ipcMain.handle("ping:url", async (_event, url: string) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
|
|||||||
31
src/preload/index.d.ts
vendored
31
src/preload/index.d.ts
vendored
@@ -1,11 +1,42 @@
|
|||||||
import { ElectronAPI } from "@electron-toolkit/preload";
|
import { ElectronAPI } from "@electron-toolkit/preload";
|
||||||
|
|
||||||
|
export type CodeResponse = {
|
||||||
|
error?: string;
|
||||||
|
data: {
|
||||||
|
label: string;
|
||||||
|
code: string;
|
||||||
|
overriden: boolean;
|
||||||
|
reused: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CodeRequest = {
|
||||||
|
card_id: string; // UUIDv7
|
||||||
|
override?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PrinterRequest = {
|
||||||
|
printerURL: string;
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PrinterResponse = unknown;
|
||||||
|
|
||||||
export interface NfcAPI {
|
export interface NfcAPI {
|
||||||
onTag: (callback: (event: { uid: string }) => void) => void;
|
onTag: (callback: (event: { uid: string }) => void) => void;
|
||||||
onRemoved: (callback: (event: { uid: string }) => void) => void;
|
onRemoved: (callback: (event: { uid: string }) => void) => void;
|
||||||
onError: (callback: (event: { message: string }) => void) => void;
|
onError: (callback: (event: { message: string }) => void) => void;
|
||||||
ping: (url: string) => Promise<boolean>;
|
ping: (url: string) => Promise<boolean>;
|
||||||
removeAllListeners: () => void;
|
removeAllListeners: () => void;
|
||||||
|
labelReq: (args: {
|
||||||
|
serverURL: string;
|
||||||
|
card_id: string;
|
||||||
|
override?: boolean;
|
||||||
|
}) => Promise<CodeResponse>;
|
||||||
|
printReq: (args: {
|
||||||
|
printerURL: string;
|
||||||
|
label: string;
|
||||||
|
}) => Promise<PrinterResponse>;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { contextBridge, ipcRenderer } from "electron";
|
import { contextBridge, ipcRenderer } from "electron";
|
||||||
import { electronAPI } from "@electron-toolkit/preload";
|
import { electronAPI } from "@electron-toolkit/preload";
|
||||||
|
import { CodeRequest, PrinterRequest } from "./index.d.js";
|
||||||
|
|
||||||
// Custom APIs for renderer
|
// Custom APIs for renderer
|
||||||
const api = {
|
const api = {
|
||||||
@@ -13,9 +14,12 @@ const api = {
|
|||||||
onError: (callback: (event: { message: string }) => void): void => {
|
onError: (callback: (event: { message: string }) => void): void => {
|
||||||
ipcRenderer.on("nfc:error", (_event, value) => callback(value));
|
ipcRenderer.on("nfc:error", (_event, value) => callback(value));
|
||||||
},
|
},
|
||||||
labelReq: (data: unknown): Promise<void> => {
|
labelReq: (data: CodeRequest): Promise<unknown> => {
|
||||||
return ipcRenderer.invoke("nfc:labelReq", data);
|
return ipcRenderer.invoke("nfc:labelReq", data);
|
||||||
},
|
},
|
||||||
|
printReq: (data: PrinterRequest): Promise<void> => {
|
||||||
|
return ipcRenderer.invoke("nfc:printReq", data);
|
||||||
|
},
|
||||||
ping: (url: string): Promise<boolean> => {
|
ping: (url: string): Promise<boolean> => {
|
||||||
return ipcRenderer.invoke("ping:url", url);
|
return ipcRenderer.invoke("ping:url", url);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,3 +6,27 @@ body {
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flex-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-col {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grow {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-dots {
|
||||||
|
background-image: radial-gradient(
|
||||||
|
var(--te-gray-light) 1px,
|
||||||
|
transparent 1px
|
||||||
|
);
|
||||||
|
background-size: 20px 20px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
|
|
||||||
const printerUrl = ref(localStorage.getItem("printerUrl") || "192.168.1.100");
|
const printerUrl = ref(localStorage.getItem("printerUrl") || "192.168.1.254");
|
||||||
const serverUrl = ref(
|
const serverUrl = ref(
|
||||||
localStorage.getItem("serverUrl") || "http://localhost:3000",
|
localStorage.getItem("serverUrl") || "http://localhost:3000",
|
||||||
);
|
);
|
||||||
@@ -29,16 +29,6 @@ const handleSave = (): void => {
|
|||||||
errorMsg.value = "";
|
errorMsg.value = "";
|
||||||
isSuccess.value = false;
|
isSuccess.value = false;
|
||||||
|
|
||||||
if (!validateIpOrUrl(printerUrl.value)) {
|
|
||||||
errorMsg.value = "Invalid Printer IP or URL format";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validateIpOrUrl(serverUrl.value)) {
|
|
||||||
errorMsg.value = "Invalid Server IP or URL format";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
localStorage.setItem("printerUrl", printerUrl.value);
|
localStorage.setItem("printerUrl", printerUrl.value);
|
||||||
localStorage.setItem("serverUrl", serverUrl.value);
|
localStorage.setItem("serverUrl", serverUrl.value);
|
||||||
isSuccess.value = true;
|
isSuccess.value = true;
|
||||||
@@ -54,20 +44,9 @@ const performPing = async (type: "printer" | "server"): Promise<void> => {
|
|||||||
|
|
||||||
const url = type === "printer" ? printerUrl.value : serverUrl.value;
|
const url = type === "printer" ? printerUrl.value : serverUrl.value;
|
||||||
|
|
||||||
if (!validateIpOrUrl(url)) {
|
|
||||||
errorMsg.value = `Invalid ${type === "printer" ? "Printer" : "Server"} IP/URL format`;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure URL has protocol for the ping check
|
|
||||||
let pingUrl = url;
|
|
||||||
if (!url.startsWith("http://") && !url.startsWith("https://")) {
|
|
||||||
pingUrl = `http://${url}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
pingStatus.value[type] = "pinging";
|
pingStatus.value[type] = "pinging";
|
||||||
try {
|
try {
|
||||||
const ok = await window.api.nfc.ping(pingUrl);
|
const ok = await window.api.nfc.ping(url);
|
||||||
pingStatus.value[type] = ok ? "success" : "error";
|
pingStatus.value[type] = ok ? "success" : "error";
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
errorMsg.value = `${type === "printer" ? "Printer" : "Server"} is unreachable`;
|
errorMsg.value = `${type === "printer" ? "Printer" : "Server"} is unreachable`;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { type CodeResponse } from "src/preload/index.d.js";
|
||||||
import { ref, onMounted, onUnmounted } from "vue";
|
import { ref, onMounted, onUnmounted } from "vue";
|
||||||
import axios from "axios";
|
|
||||||
/**
|
/**
|
||||||
* waiting = esperando a leer una tarjeta (igual es mejor ready)
|
* waiting = esperando a leer una tarjeta (igual es mejor ready)
|
||||||
* reading = proceso de lectura
|
* reading = proceso de lectura
|
||||||
@@ -10,6 +10,8 @@ import axios from "axios";
|
|||||||
const readerState = ref<"waiting" | "reading" | "success" | "error">("waiting");
|
const readerState = ref<"waiting" | "reading" | "success" | "error">("waiting");
|
||||||
const uid = ref<string | null>(null);
|
const uid = ref<string | null>(null);
|
||||||
const errorMsg = ref<string | null>(null);
|
const errorMsg = ref<string | null>(null);
|
||||||
|
const labelOutput = ref<string>("");
|
||||||
|
const labelOutputStructured = ref<CodeResponse | null>(null);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
window.api.nfc.onTag((event) => {
|
window.api.nfc.onTag((event) => {
|
||||||
@@ -60,36 +62,40 @@ const mockReadCard = (): void => {
|
|||||||
}, 1000);
|
}, 1000);
|
||||||
};
|
};
|
||||||
|
|
||||||
type CodeResponse = {
|
|
||||||
error?: string,
|
|
||||||
data: {
|
|
||||||
label: string;
|
|
||||||
code: string;
|
|
||||||
overriden: boolean;
|
|
||||||
reused: boolean;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type CodeRequest = {
|
const readCard = async (): Promise<void> => {
|
||||||
card_id: string; // UUIDv7
|
console.log("test readcard");
|
||||||
override?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const readCard = async (): void => {
|
|
||||||
console.log("test readcard")
|
|
||||||
if (readerState.value !== "waiting") return;
|
if (readerState.value !== "waiting") return;
|
||||||
readerState.value = "reading";
|
readerState.value = "reading";
|
||||||
|
|
||||||
const serverURL =
|
const serverURL =
|
||||||
localStorage.getItem("serverUrl") || "http://localhost:3000";
|
localStorage.getItem("serverUrl") || "http://localhost:3000";
|
||||||
const endpoint = "/nfc/generate";
|
const printerURL =
|
||||||
|
localStorage.getItem("printerUrl") || "192.168.1.254";
|
||||||
|
|
||||||
// TODO: De momento está hardcodeado porque no se puede leer la tarjeta
|
// TODO: De momento está hardcodeado porque no se puede leer la tarjeta
|
||||||
const card_id = "019cdd39-fc08-7417-b16d-a78794a24c01";
|
const card_id = "019cdd39-fc08-7417-b16d-a78794a24c01";
|
||||||
const override = false;
|
const override = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
window.api.nfc.labelReq({ card_id, override });
|
const res: CodeResponse = await window.api.nfc.labelReq({
|
||||||
|
serverURL,
|
||||||
|
card_id,
|
||||||
|
override,
|
||||||
|
});
|
||||||
|
console.log("Res IPC", res);
|
||||||
|
labelOutput.value = res.data.label;
|
||||||
|
labelOutputStructured.value = res;
|
||||||
|
console.log("Imprimiendo", res);
|
||||||
|
|
||||||
|
const impRes = await window.api.nfc.printReq({
|
||||||
|
printerURL,
|
||||||
|
label: res.data.label,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Impreso", impRes);
|
||||||
|
|
||||||
|
readerState.value = "waiting";
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
readerState.value = "error";
|
readerState.value = "error";
|
||||||
@@ -102,11 +108,10 @@ const delayWaiting = (): void => {
|
|||||||
readerState.value = "waiting";
|
readerState.value = "waiting";
|
||||||
errorMsg.value = null;
|
errorMsg.value = null;
|
||||||
}, 3000);
|
}, 3000);
|
||||||
}
|
};
|
||||||
|
|
||||||
const triggerError = (): void => {
|
const triggerError = (): void => {
|
||||||
if (readerState.value !== "waiting") return;
|
if (readerState.value !== "waiting") return;
|
||||||
|
|
||||||
readerState.value = "error";
|
readerState.value = "error";
|
||||||
delayWaiting();
|
delayWaiting();
|
||||||
};
|
};
|
||||||
@@ -114,52 +119,74 @@ const triggerError = (): void => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="main-display">
|
<div class="main-display">
|
||||||
<div class="display-container">
|
<div class="flex-row grow">
|
||||||
<div class="display-grid"></div>
|
<div class="display-container bg-dots">
|
||||||
<div class="top-meta">
|
<div class="display-grid"></div>
|
||||||
<div class="meta-item">
|
|
||||||
<span class="label">MODULE</span>
|
|
||||||
<span class="value">NFC_RD_01</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="status-center">
|
<div class="top-meta">
|
||||||
<div v-if="readerState === 'waiting'" class="waiting-state">
|
<div class="meta-item">
|
||||||
<div class="pulse-ring"></div>
|
<span class="label">MODULE</span>
|
||||||
<div class="pulse-inner"></div>
|
<span class="value">NFC_RD_01</span>
|
||||||
<h1 class="status-msg">TAP CARD TO INITIALIZE</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-else-if="readerState === 'reading'"
|
|
||||||
class="reading-state"
|
|
||||||
>
|
|
||||||
<div class="reading-indicator"></div>
|
|
||||||
<h1 class="status-msg reading">PROCESSING_DATA...</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-else-if="readerState === 'success'"
|
|
||||||
class="success-state"
|
|
||||||
>
|
|
||||||
<div class="success-indicator"></div>
|
|
||||||
<h1 class="status-msg success">SESSION_START_OK</h1>
|
|
||||||
<div class="id-capture">
|
|
||||||
<span class="id-label">UID_IDENTIFIED:</span>
|
|
||||||
<span class="id-value">{{ uid }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="readerState === 'error'" class="error-state">
|
<div class="status-center">
|
||||||
<div class="error-indicator"></div>
|
<div v-if="readerState === 'waiting'" class="waiting-state">
|
||||||
<h1 class="status-msg error">INTERFACE_ERROR</h1>
|
<div class="pulse-ring"></div>
|
||||||
<p class="error-desc">
|
<div class="pulse-inner"></div>
|
||||||
{{ errorMsg || "UNKNOWN_FAILURE" }}
|
<h1 class="status-msg">TAP CARD TO INITIALIZE</h1>
|
||||||
</p>
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-else-if="readerState === 'reading'"
|
||||||
|
class="reading-state"
|
||||||
|
>
|
||||||
|
<div class="reading-indicator"></div>
|
||||||
|
<h1 class="status-msg reading">PROCESSING_DATA...</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-else-if="readerState === 'success'"
|
||||||
|
class="success-state"
|
||||||
|
>
|
||||||
|
<div class="success-indicator"></div>
|
||||||
|
<h1 class="status-msg success">SESSION_START_OK</h1>
|
||||||
|
<div class="id-capture">
|
||||||
|
<span class="id-label">UID_IDENTIFIED:</span>
|
||||||
|
<span class="id-value">{{ uid }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-else-if="readerState === 'error'"
|
||||||
|
class="error-state"
|
||||||
|
>
|
||||||
|
<div class="error-indicator"></div>
|
||||||
|
<h1 class="status-msg error">INTERFACE_ERROR</h1>
|
||||||
|
<p class="error-desc">
|
||||||
|
{{ errorMsg || "UNKNOWN_FAILURE" }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Salida por pantalla de las respuestas del servidor y la impresora -->
|
||||||
|
<div class="display-container bg-dots">
|
||||||
|
<div v-if="labelOutputStructured != undefined" class="flex-col">
|
||||||
|
<span> Código {{ labelOutputStructured.data.code }} </span>
|
||||||
|
<span>
|
||||||
|
Etiqueta {{ labelOutputStructured.data.label }}
|
||||||
|
</span>
|
||||||
|
<span v-if="labelOutputStructured.data.reused == true">
|
||||||
|
YA SE HA IMPRIMIDO
|
||||||
|
</span>
|
||||||
|
<span v-if="labelOutputStructured.data.overriden == true">
|
||||||
|
SOBREESCRITA
|
||||||
|
</span>
|
||||||
|
<!-- Caso de error -->
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Discrete mock controls for dev testing -->
|
<!-- Discrete mock controls for dev testing -->
|
||||||
<div class="dev-panel">
|
<div class="dev-panel">
|
||||||
<button
|
<button
|
||||||
@@ -221,11 +248,6 @@ const triggerError = (): void => {
|
|||||||
.display-grid {
|
.display-grid {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
background-image: radial-gradient(
|
|
||||||
var(--te-gray-light) 1px,
|
|
||||||
transparent 1px
|
|
||||||
);
|
|
||||||
background-size: 20px 20px;
|
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user