This commit is contained in:
134
static/app.js
Normal file
134
static/app.js
Normal file
@@ -0,0 +1,134 @@
|
||||
const searchInput = document.getElementById("searchInput");
|
||||
const memberSelect = document.getElementById("memberSelect");
|
||||
const generateBtn = document.getElementById("generateBtn");
|
||||
const statusText = document.getElementById("statusText");
|
||||
const resultCard = document.getElementById("resultCard");
|
||||
const qrImage = document.getElementById("qrImage");
|
||||
const downloadLink = document.getElementById("downloadLink");
|
||||
const rawJson = document.getElementById("rawJson");
|
||||
|
||||
const metaMemberId = document.getElementById("metaMemberId");
|
||||
const metaVillage = document.getElementById("metaVillage");
|
||||
const metaUuid = document.getElementById("metaUuid");
|
||||
const metaStatus = document.getElementById("metaStatus");
|
||||
const metaMsg = document.getElementById("metaMsg");
|
||||
const metaQrPrefix = document.getElementById("metaQrPrefix");
|
||||
const metaPayloadLen = document.getElementById("metaPayloadLen");
|
||||
const metaPayloadPrefix = document.getElementById("metaPayloadPrefix");
|
||||
|
||||
let allItems = [];
|
||||
|
||||
function setStatus(message, isError = false) {
|
||||
statusText.textContent = message;
|
||||
statusText.classList.toggle("error", isError);
|
||||
}
|
||||
|
||||
function renderOptions(items) {
|
||||
memberSelect.innerHTML = "";
|
||||
|
||||
if (!items.length) {
|
||||
const option = document.createElement("option");
|
||||
option.value = "";
|
||||
option.textContent = "没有可选 ID";
|
||||
memberSelect.appendChild(option);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const item of items) {
|
||||
const option = document.createElement("option");
|
||||
option.value = String(item.member_id);
|
||||
option.textContent = item.label;
|
||||
memberSelect.appendChild(option);
|
||||
}
|
||||
|
||||
if (!memberSelect.value) {
|
||||
memberSelect.selectedIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function filterItems() {
|
||||
const keyword = searchInput.value.trim().toLowerCase();
|
||||
if (!keyword) {
|
||||
renderOptions(allItems);
|
||||
setStatus(`已加载 ${allItems.length} 个 ID`);
|
||||
return;
|
||||
}
|
||||
|
||||
const filtered = allItems.filter((item) => {
|
||||
const idText = String(item.member_id);
|
||||
const village = String(item.village_name || "").toLowerCase();
|
||||
return idText.includes(keyword) || village.includes(keyword);
|
||||
});
|
||||
|
||||
renderOptions(filtered);
|
||||
setStatus(`筛选后剩余 ${filtered.length} 个 ID`);
|
||||
}
|
||||
|
||||
async function loadFoundIds() {
|
||||
setStatus("正在加载 ID 列表...");
|
||||
try {
|
||||
const response = await fetch("/api/found-ids");
|
||||
const data = await response.json();
|
||||
if (!response.ok || !data.ok) {
|
||||
throw new Error(data.error || "加载 ID 列表失败");
|
||||
}
|
||||
allItems = data.items || [];
|
||||
renderOptions(allItems);
|
||||
setStatus(`已加载 ${allItems.length} 个 ID`);
|
||||
} catch (error) {
|
||||
setStatus(error.message || "加载失败", true);
|
||||
}
|
||||
}
|
||||
|
||||
function showResult(data) {
|
||||
resultCard.classList.remove("hidden");
|
||||
qrImage.src = data.data_url;
|
||||
downloadLink.href = data.data_url;
|
||||
downloadLink.download = `member_${data.member_id}.gif`;
|
||||
metaMemberId.textContent = data.member_id ?? "-";
|
||||
metaVillage.textContent = data.village_name ?? "-";
|
||||
metaUuid.textContent = data.uuid ?? "-";
|
||||
metaStatus.textContent = data.status ?? "-";
|
||||
metaMsg.textContent = data.msg ?? "-";
|
||||
metaQrPrefix.textContent = data.qr_prefix ?? "-";
|
||||
metaPayloadLen.textContent = data.payload_len ?? "-";
|
||||
metaPayloadPrefix.textContent = data.payload_hex_prefix ?? "-";
|
||||
rawJson.textContent = JSON.stringify(data, null, 2);
|
||||
}
|
||||
|
||||
async function generateQr() {
|
||||
const memberId = memberSelect.value;
|
||||
if (!memberId) {
|
||||
setStatus("请先选择一个 member_id", true);
|
||||
return;
|
||||
}
|
||||
|
||||
generateBtn.disabled = true;
|
||||
setStatus(`正在为 ${memberId} 生成二维码...`);
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/generate", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ member_id: Number(memberId) }),
|
||||
});
|
||||
const data = await response.json();
|
||||
if (!response.ok || !data.ok) {
|
||||
throw new Error(data.error || "生成二维码失败");
|
||||
}
|
||||
showResult(data);
|
||||
setStatus(`member_id=${memberId} 生成成功`);
|
||||
} catch (error) {
|
||||
setStatus(error.message || "生成失败", true);
|
||||
} finally {
|
||||
generateBtn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
searchInput.addEventListener("input", filterItems);
|
||||
generateBtn.addEventListener("click", generateQr);
|
||||
memberSelect.addEventListener("dblclick", generateQr);
|
||||
|
||||
loadFoundIds();
|
||||
161
static/styles.css
Normal file
161
static/styles.css
Normal file
@@ -0,0 +1,161 @@
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: #0f172a;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1080px;
|
||||
margin: 0 auto;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: #111827;
|
||||
border: 1px solid #334155;
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.25);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: #94a3b8;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin: 12px 0 8px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
input,
|
||||
select,
|
||||
button {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #475569;
|
||||
background: #0f172a;
|
||||
color: #e2e8f0;
|
||||
padding: 10px 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
select {
|
||||
min-height: 320px;
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
background: #2563eb;
|
||||
border: none;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.actions button {
|
||||
width: auto;
|
||||
min-width: 160px;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
color: #93c5fd;
|
||||
}
|
||||
|
||||
.status-text.error {
|
||||
color: #fca5a5;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.result-layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(260px, 340px) 1fr;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.qr-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.qr-panel img {
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.qr-panel a {
|
||||
color: #93c5fd;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.meta-list {
|
||||
margin: 0 0 16px;
|
||||
}
|
||||
|
||||
.meta-list > div {
|
||||
display: grid;
|
||||
grid-template-columns: 120px 1fr;
|
||||
gap: 8px;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #334155;
|
||||
}
|
||||
|
||||
.meta-list dt {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.meta-list dd {
|
||||
margin: 0;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
background: #020617;
|
||||
border-radius: 12px;
|
||||
padding: 12px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.result-layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.actions {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.actions button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user