| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401 |
- const reader = new ReaderClient();
- const MAX_LOG_LINES = 300;
- const logLines = [];
- // 状态
- let isPolling = false;
- let pollingTimer = null;
- let readCount = 0;
- // 去重相关
- let seenUids = new Map(); // uid -> { protocol, time }
- const elements = {
- statusTag: document.getElementById("statusTag"),
- wsUrlText: document.getElementById("wsUrlText"),
- uidText: document.getElementById("nfch"), // ("uidText")。Lin
- readCountText: document.getElementById("readCountText"),
- lastProtocolText: document.getElementById("lastProtocolText"),
- // ISO14443A
- keyTypeInput: document.getElementById("keyTypeInput"),
- keyInput: document.getElementById("keyInput"),
- blockAddressInput: document.getElementById("blockAddressInput"),
- numberOfBlocksReadInput: document.getElementById("numberOfBlocksReadInput"),
- // ISO15693
- readDataTypeInput: document.getElementById("readDataTypeInput"),
- readSecurityStatusInput: document.getElementById("readSecurityStatusInput"),
- iso15693BlockAddressInput: document.getElementById("iso15693BlockAddressInput"),
- iso15693NumberOfBlocksInput: document.getElementById("iso15693NumberOfBlocksInput"),
- // 轮询
- pollingInterval: document.getElementById("pollingInterval"),
- dedupeMode: document.getElementById("dedupeMode"),
- dedupeTime: document.getElementById("dedupeTime"),
- dedupeTimeItem: document.getElementById("dedupeTimeItem"),
- // 按钮
- btnConnect: document.getElementById("btnConnect"),
- btnReadUid: document.getElementById("btnReadUid"),
- btnStartPolling: document.getElementById("btnStartPolling"),
- btnStopPolling: document.getElementById("btnStopPolling"),
- btnClearLog: document.getElementById("btnClearLog"),
- loadingHint: document.getElementById("loadingHint"),
- logArea: document.getElementById("logArea"),
- };
- function log(message, type = "info") {
- const time = new Date().toLocaleTimeString();
- const prefix = type === "success" ? "✓" : type === "error" ? "✗" : type === "warn" ? "⚠" : "•";
- const line = `[${time}] ${prefix} ${message}`;
- console.log(line);
- logLines.unshift(line);
- if (logLines.length > MAX_LOG_LINES) {
- logLines.length = MAX_LOG_LINES;
- }
- elements.logArea.value = logLines.join("\n");
- }
- function getErrorMessage(err) {
- if (err instanceof Error && err.message) {
- return err.message;
- }
- if (typeof err === "string") {
- return err;
- }
- try {
- return JSON.stringify(err);
- } catch {
- return "未知错误";
- }
- }
- function setLoading(text = "") {
- elements.loadingHint.textContent = text;
- }
- function setStatus(text, ok) {
- elements.statusTag.textContent = text;
- elements.statusTag.classList.toggle("ok", Boolean(ok));
- elements.statusTag.classList.toggle("bad", !ok);
- }
- function syncButtons() {
- const connected = reader.connected;
- elements.btnReadUid.disabled = !connected;
- elements.btnStartPolling.disabled = !connected || isPolling;
- elements.btnStopPolling.disabled = !connected || !isPolling;
- }
- function updateReadCount() {
- readCount++;
- elements.readCountText.textContent = readCount;
- }
- function normalizeHexKey(value) {
- return String(value || "")
- .trim()
- .toUpperCase()
- .replace(/[^0-9A-F]/g, "");
- }
- function applyReaderConfig() {
- // ISO14443A 参数
- const key = normalizeHexKey(elements.keyInput.value);
- const keyType = String(elements.keyTypeInput.value || "0");
- const blockAddress = String(elements.blockAddressInput.value || "1").trim();
- const numberOfBlocksRead = String(
- elements.numberOfBlocksReadInput.value || "1"
- ).trim();
- if (!/^[0-9A-F]{12}$/.test(key)) {
- throw new Error("密钥必须是 12 位十六进制字符");
- }
- if (!["0", "1"].includes(keyType)) {
- throw new Error("密钥类型必须是 Key A 或 Key B");
- }
- if (!/^\d+$/.test(blockAddress)) {
- throw new Error("块地址必须是非负整数");
- }
- if (!/^\d+$/.test(numberOfBlocksRead) || Number(numberOfBlocksRead) <= 0) {
- throw new Error("读取块数必须是大于 0 的整数");
- }
- reader.keyType = keyType;
- reader.key = key;
- reader.blockAddress = blockAddress;
- reader.numberOfBlocksRead = numberOfBlocksRead;
- elements.keyInput.value = key;
- // ISO15693 参数
- reader.readDataType = elements.readDataTypeInput.value;
- reader.readSecurityStatus = elements.readSecurityStatusInput.value;
- reader.iso15693BlockAddress = elements.iso15693BlockAddressInput.value;
- reader.iso15693NumberOfBlocks = elements.iso15693NumberOfBlocksInput.value;
- }
- async function connectReader() {
- applyReaderConfig();
- setLoading("连接中...");
- log("开始连接读卡器(双协议检测)");
- await reader.selectDevice();
- setStatus("已连接", true);
- setLoading("");
- syncButtons();
- log("读卡器连接成功");
- }
- // 检测 UID 是否已存在(去重)
- function checkDuplicate(uid) {
- const dedupeMode = elements.dedupeMode.value;
- const dedupeTimeMs = parseInt(elements.dedupeTime.value, 10);
- if (dedupeMode === "none") {
- return { isDup: false };
- }
- if (dedupeMode === "session") {
- if (seenUids.has(uid)) {
- const info = seenUids.get(uid);
- return { isDup: true, reason: `已在会话中读取过(${info.protocol})` };
- }
- return { isDup: false };
- }
- if (dedupeMode === "time") {
- const lastTime = seenUids.has(uid) ? seenUids.get(uid).time : null;
- if (lastTime && Date.now() - lastTime < dedupeTimeMs) {
- const info = seenUids.get(uid);
- const remain = Math.ceil((dedupeTimeMs - (Date.now() - lastTime)) / 1000);
- return { isDup: true, reason: `时间窗口内已读取(${info.protocol}),${remain}秒后可再次读取` };
- }
- return { isDup: false };
- }
- return { isDup: false };
- }
- function recordUid(uid, protocol) {
- seenUids.set(uid, { protocol, time: Date.now() });
- }
- function clearDedupeHistory() {
- seenUids.clear();
- log("去重历史已清空");
- }
- // 同步读取两种协议,返回成功读取的结果
- async function readUidBothProtocols() {
- if (!reader.connected) {
- throw new Error("读卡器未连接,请先连接读卡器");
- }
- applyReaderConfig();
- const results = [];
- // 先尝试 ISO14443A
- try {
- log("尝试读取 ISO14443A...");
- const uid14443 = await reader.readUid("ISO14443A");
- results.push({ protocol: "ISO14443A", uid: uid14443 });
- } catch (err) {
- if (!err.message?.includes("未读取到 UID")) {
- log(`ISO14443A 读取异常: ${getErrorMessage(err)}`, "warn");
- }
- }
- // 再尝试 ISO15693
- try {
- log("尝试读取 ISO15693...");
- const uid15693 = await reader.readUid("ISO15693");
- results.push({ protocol: "ISO15693", uid: uid15693 });
- } catch (err) {
- if (!err.message?.includes("未读取到 UID")) {
- log(`ISO15693 读取异常: ${getErrorMessage(err)}`, "warn");
- }
- }
- if (results.length === 0) {
- throw new Error("未读取到 UID,请确认卡片/标签已到读卡位");
- }
- // 处理读取结果(去重逻辑)
- const validResults = [];
- for (const result of results) {
- const dupCheck = checkDuplicate(result.uid);
- if (dupCheck.isDup) {
- log(`${result.protocol} 读取到 UID: ${result.uid}(${dupCheck.reason})`, "warn");
- } else {
- validResults.push(result);
- }
- }
- if (validResults.length === 0) {
- // 全都是重复的,取第一个显示但不记录
- return results[0];
- }
- // 返回第一个有效结果
- const firstResult = validResults[0];
- recordUid(firstResult.uid, firstResult.protocol);
- return firstResult;
- }
- async function readUid(showError = true) {
- setLoading("读取 UID 中...");
- const result = await readUidBothProtocols();
- elements.uidText.textContent = result.uid;
- elements.lastProtocolText.textContent = result.protocol;
- updateReadCount();
- setLoading("");
- log(`读取成功 [${result.protocol}] UID=${result.uid}`, "success");
- // Console 输出 UID
- console.log("%c[RFID] 读取到 UID:", "color: #10b981; font-weight: bold; font-size: 14px;", result.uid);
- console.log("%c[RFID] 协议:", "color: #64748b;", result.protocol);
- console.log("%c[RFID] 时间:", "color: #64748b;", new Date().toISOString());
- return result;
- }
- async function startPolling() {
- if (isPolling) return;
- if (!reader.connected) {
- log("读卡器未连接,无法启动轮询", "error");
- alert("请先连接读卡器");
- return;
- }
- isPolling = true;
- syncButtons();
- const interval = parseInt(elements.pollingInterval.value, 10);
- log(`开始定时轮询(双协议),间隔 ${interval}ms`);
- // 立即执行一次
- await doPoll();
- // 定时轮询
- pollingTimer = setInterval(async () => {
- if (!isPolling) return;
- await doPoll();
- }, interval);
- }
- async function doPoll() {
- try {
- await readUid(false);
- } catch (err) {
- // 轮询模式下,读不到卡不报错
- const msg = getErrorMessage(err);
- if (!msg.includes("未读取到 UID")) {
- log(`轮询错误: ${msg}`, "error");
- }
- }
- }
- function stopPolling() {
- if (!isPolling) return;
- isPolling = false;
- if (pollingTimer) {
- clearInterval(pollingTimer);
- pollingTimer = null;
- }
- syncButtons();
- setLoading("");
- log("已停止定时轮询");
- }
- function clearLog() {
- logLines.length = 0;
- elements.logArea.value = "";
- log("日志已清空");
- }
- function initView() {
- elements.wsUrlText.textContent = reader.wsUrl;
- elements.uidText.textContent = "-";
- elements.readCountText.textContent = "0";
- elements.lastProtocolText.textContent = "-";
- // ISO14443A 默认值
- elements.keyTypeInput.value = reader.keyType;
- elements.keyInput.value = reader.key;
- elements.blockAddressInput.value = reader.blockAddress;
- elements.numberOfBlocksReadInput.value = reader.numberOfBlocksRead;
- // ISO15693 默认值
- elements.readDataTypeInput.value = reader.readDataType;
- elements.readSecurityStatusInput.value = reader.readSecurityStatus;
- elements.iso15693BlockAddressInput.value = reader.iso15693BlockAddress;
- elements.iso15693NumberOfBlocksInput.value = reader.iso15693NumberOfBlocks;
- setStatus("未连接", false);
- syncButtons();
- log("页面已初始化,支持双协议自动检测(ISO14443A + ISO15693)");
- }
- function bindEvents() {
- // 连接读卡器
- elements.btnConnect.addEventListener("click", async () => {
- try {
- await connectReader();
- } catch (err) {
- const msg = getErrorMessage(err);
- setStatus("连接失败", false);
- setLoading("");
- syncButtons();
- log(`连接失败: ${msg}`, "error");
- alert(msg);
- }
- });
- // 读取 UID
- elements.btnReadUid.addEventListener("click", async () => {
- try {
- await readUid(true);
- } catch (err) {
- const msg = getErrorMessage(err);
- setLoading("");
- log(`读取失败: ${msg}`, "error");
- alert(msg);
- }
- });
- // 开始轮询
- elements.btnStartPolling.addEventListener("click", () => {
- startPolling();
- });
- // 停止轮询
- elements.btnStopPolling.addEventListener("click", () => {
- stopPolling();
- });
- // 清空日志
- elements.btnClearLog.addEventListener("click", () => {
- clearLog();
- });
- // 去重模式切换
- elements.dedupeMode.addEventListener("change", (e) => {
- if (e.target.value === "time") {
- elements.dedupeTimeItem.style.display = "block";
- } else {
- elements.dedupeTimeItem.style.display = "none";
- }
- // 切换去重模式时清空历史
- clearDedupeHistory();
- });
- }
- bindEvents();
- initView();
|