diff --git a/src/components/admin/ImageField.astro b/src/components/admin/ImageField.astro index 781eb2a..52f53a7 100644 --- a/src/components/admin/ImageField.astro +++ b/src/components/admin/ImageField.astro @@ -21,8 +21,8 @@ const { name, label, value = "", scope, id = name } = Astro.props; class="image-url" />
- - + +
diff --git a/src/layouts/AdminLayout.astro b/src/layouts/AdminLayout.astro index afa2013..4a302e8 100644 --- a/src/layouts/AdminLayout.astro +++ b/src/layouts/AdminLayout.astro @@ -64,7 +64,10 @@ const isActive = (href: string) => canvas.width = width; canvas.height = height; const ctx = canvas.getContext("2d"); - if (!ctx) return file; + if (!ctx) { + if (typeof bitmap.close === "function") bitmap.close(); + return file; + } ctx.drawImage(bitmap, 0, 0, width, height); if (typeof bitmap.close === "function") bitmap.close(); const blob = await new Promise((resolve) => @@ -84,6 +87,7 @@ const isActive = (href: string) => if (!input || !status) return; status.textContent = "上傳中…"; + status.classList.remove("error"); const blob = await toOptimised(file); const body = new FormData(); body.append("file", new File([blob], file.name, { type: blob.type })); @@ -91,9 +95,13 @@ const isActive = (href: string) => try { const res = await fetch("/admin/upload", { method: "POST", body }); - const data = (await res.json()) as { ok: boolean; url?: string; error?: string }; + const isJson = (res.headers.get("content-type") ?? "").includes("application/json"); + const data = isJson + ? ((await res.json()) as { ok: boolean; url?: string; error?: string }) + : { ok: false, error: `上傳失敗(${res.status})。` }; if (!res.ok || !data.ok || !data.url) { status.textContent = data.error ?? "上傳失敗。"; + status.classList.add("error"); return; } input.value = data.url; @@ -104,15 +112,22 @@ const isActive = (href: string) => status.textContent = "✓ 已上傳"; } catch { status.textContent = "上傳失敗,請檢查網絡。"; + status.classList.add("error"); } } document.querySelectorAll("[data-image-field]").forEach((field) => { const fileInput = field.querySelector("[data-image-input]"); if (!fileInput) return; + let busy = false; fileInput.addEventListener("change", () => { const file = fileInput.files?.[0]; - if (file) void upload(field, file); + fileInput.value = ""; + if (!file || busy) return; + busy = true; + void upload(field, file).finally(() => { + busy = false; + }); }); }); @@ -185,6 +200,7 @@ const isActive = (href: string) => .image-actions { display: flex; align-items: center; gap: 10px; margin-top: 8px; flex-wrap: wrap; } .image-actions input[type="file"] { font: inherit; font-size: 13px; max-width: 100%; } .image-status { font-size: 12.5px; font-weight: 600; color: #0b8a5e; } + .image-status.error { color: #a32d2d; } .image-preview { display: block; margin-top: 10px; max-width: 220px; border-radius: 10px; } .image-preview[hidden] { display: none; } .row { display: grid; gap: 12px; grid-template-columns: 1fr; }