diff --git a/src/app/api/bagPrint/actions.ts b/src/app/api/bagPrint/actions.ts index e9ac655..60794ec 100644 --- a/src/app/api/bagPrint/actions.ts +++ b/src/app/api/bagPrint/actions.ts @@ -33,6 +33,29 @@ export interface OnPackQrDownloadRequest { }[]; } +/** Readable message when ZIP download returns non-OK (plain text, JSON error body, or generic). */ +async function zipDownloadError(res: Response): Promise { + const text = await res.text(); + const ct = res.headers.get("content-type") ?? ""; + if (ct.includes("application/json")) { + try { + const j = JSON.parse(text) as { message?: string; error?: string }; + if (typeof j.message === "string" && j.message.length > 0) { + return new Error(j.message); + } + if (typeof j.error === "string" && j.error.length > 0) { + return new Error(j.error); + } + } catch { + /* ignore parse */ + } + } + if (text && text.length > 0 && text.length < 800 && !text.trim().startsWith("{")) { + return new Error(text); + } + return new Error(`下載失敗(HTTP ${res.status})。請查看後端日誌或確認資料庫已執行 Liquibase 更新。`); +} + /** * Fetch job orders by plan date from GET /py/job-orders. * Client-side only; uses auth token from localStorage. @@ -75,7 +98,7 @@ export async function downloadOnPackQrZip( }); if (!res.ok) { - throw new Error((await res.text()) || "Download failed"); + throw await zipDownloadError(res); } return res.blob(); @@ -93,7 +116,7 @@ export async function downloadOnPackTextQrZip( }); if (!res.ok) { - throw new Error((await res.text()) || "Download failed"); + throw await zipDownloadError(res); } return res.blob();