From 056514f167da1ea5bac9bf6b2a3589f06dc90fd0 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 19 Feb 2026 22:10:31 +0800 Subject: [PATCH] fix: switch canvas zip download to native form submission --- app/api/canvas/download-zip/route.ts | 16 +++++- components/canvas/CanvasWorkspace.tsx | 73 ++++++++------------------- 2 files changed, 36 insertions(+), 53 deletions(-) diff --git a/app/api/canvas/download-zip/route.ts b/app/api/canvas/download-zip/route.ts index 01007e6f1..5fc07c4c0 100644 --- a/app/api/canvas/download-zip/route.ts +++ b/app/api/canvas/download-zip/route.ts @@ -103,7 +103,21 @@ export async function POST(request: NextRequest) { return NextResponse.json({ success: false, error: "未登录" }, { status: 401 }); } - const body = await request.json().catch(() => ({})); + let body: Record = {}; + const contentType = request.headers.get("content-type") || ""; + if (contentType.includes("application/json")) { + body = (await request.json().catch(() => ({}))) as Record; + } else { + const formData = await request.formData().catch(() => null); + const payload = formData?.get("payload"); + if (typeof payload === "string" && payload.trim()) { + try { + body = (JSON.parse(payload) as Record) || {}; + } catch (_error) { + body = {}; + } + } + } const requestOrigin = getRequestOrigin(request); const files = toDownloadItems((body as any)?.files, requestOrigin); const folderName = sanitizeFolderName(String((body as any)?.folderName || "")); diff --git a/components/canvas/CanvasWorkspace.tsx b/components/canvas/CanvasWorkspace.tsx index c9ef93dc0..e52874a54 100644 --- a/components/canvas/CanvasWorkspace.tsx +++ b/components/canvas/CanvasWorkspace.tsx @@ -36,18 +36,6 @@ function isNodeInViewport( ); } -function downloadBlob(blob: Blob, filename: string): void { - const link = document.createElement("a"); - const objectUrl = URL.createObjectURL(blob); - link.href = objectUrl; - link.download = filename; - link.rel = "noopener noreferrer"; - document.body.appendChild(link); - link.click(); - link.remove(); - window.setTimeout(() => URL.revokeObjectURL(objectUrl), 1500); -} - export function CanvasWorkspace() { const containerRef = useRef(null); const [isPanning, setIsPanning] = useState(false); @@ -563,51 +551,32 @@ export function CanvasWorkspace() { } setIsDownloadSubmitting(true); - let timeoutId: number | null = null; try { - const controller = new AbortController(); - timeoutId = window.setTimeout(() => controller.abort(), 120000); - const response = await fetch("/api/canvas/download-zip", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - signal: controller.signal, - body: JSON.stringify({ - folderName, - files, - }), + const form = document.createElement("form"); + form.method = "POST"; + form.action = "/api/canvas/download-zip"; + form.style.display = "none"; + + const payloadInput = document.createElement("input"); + payloadInput.type = "hidden"; + payloadInput.name = "payload"; + payloadInput.value = JSON.stringify({ + folderName, + files, }); + form.appendChild(payloadInput); - if (!response.ok) { - const data = await response.json().catch(() => ({})); - toast.error(data?.error || "打包失败,请稍后重试"); - return; - } + document.body.appendChild(form); + form.submit(); + form.remove(); - const zipBlob = await response.blob(); - downloadBlob(zipBlob, `${folderName}.zip`); - - const successCount = Number(response.headers.get("X-Download-Success-Count") || "0"); - const failedCount = Number(response.headers.get("X-Download-Failed-Count") || "0"); - const totalCount = files.length; - - if (failedCount > 0) { - toast.warning(`已打包 ${successCount} 张,失败 ${failedCount} 张`); - } else { - toast.success(`已下载压缩包,包含 ${successCount || totalCount} 张图片`); - } - } catch (error) { - if (error instanceof DOMException && error.name === "AbortError") { - toast.error("打包超时,请减少图片数量后重试"); - } else { - toast.error("下载失败,请稍后重试"); - } + toast.success("已开始打包下载,请留意浏览器下载栏"); + } catch (_error) { + toast.error("下载失败,请稍后重试"); } finally { - if (timeoutId) { - window.clearTimeout(timeoutId); - } - setIsDownloadSubmitting(false); + window.setTimeout(() => { + setIsDownloadSubmitting(false); + }, 800); } }, [isDownloadSubmitting, selectedImageNodes]);