| @@ -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<Error> { | |||||
| 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. | * Fetch job orders by plan date from GET /py/job-orders. | ||||
| * Client-side only; uses auth token from localStorage. | * Client-side only; uses auth token from localStorage. | ||||
| @@ -75,7 +98,7 @@ export async function downloadOnPackQrZip( | |||||
| }); | }); | ||||
| if (!res.ok) { | if (!res.ok) { | ||||
| throw new Error((await res.text()) || "Download failed"); | |||||
| throw await zipDownloadError(res); | |||||
| } | } | ||||
| return res.blob(); | return res.blob(); | ||||
| @@ -93,7 +116,7 @@ export async function downloadOnPackTextQrZip( | |||||
| }); | }); | ||||
| if (!res.ok) { | if (!res.ok) { | ||||
| throw new Error((await res.text()) || "Download failed"); | |||||
| throw await zipDownloadError(res); | |||||
| } | } | ||||
| return res.blob(); | return res.blob(); | ||||