From 7964ccd062d77f74d88d6f7e447bff47cf630136 Mon Sep 17 00:00:00 2001 From: "hangyu.tao" Date: Mon, 2 Feb 2026 11:30:06 +0800 Subject: [PATCH] =?UTF-8?q?fix(upload):=20=E6=81=A2=E5=A4=8D=E5=B9=B6?= =?UTF-8?q?=E5=A2=9E=E5=BC=BABase64=E5=89=8D=E7=AB=AF=E9=80=BB=E8=BE=91?= =?UTF-8?q?=EF=BC=8C=E7=BB=95=E8=BF=87=E6=9C=8D=E5=8A=A1=E5=99=A8=E7=BD=91?= =?UTF-8?q?=E7=BB=9C=E8=AE=BF=E9=97=AE=E9=99=90=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/index.html | 2 +- frontend/js/main.js | 112 +++++++++++++++++++++++++++----------------- 2 files changed, 69 insertions(+), 45 deletions(-) diff --git a/frontend/index.html b/frontend/index.html index fd7a66a..dda039f 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1180,7 +1180,7 @@ - + diff --git a/frontend/js/main.js b/frontend/js/main.js index 388fae5..463ac2e 100644 --- a/frontend/js/main.js +++ b/frontend/js/main.js @@ -61,7 +61,7 @@ function canDelete() { } document.addEventListener("DOMContentLoaded", function () { - console.log("CRM System Main JS v4.5 Loaded - " + new Date().toLocaleString()); + console.log("CRM System Main JS v5.0 Loaded - " + new Date().toLocaleString()); // 登录守卫 const token = localStorage.getItem("crmToken"); if (!token && !window.location.pathname.endsWith("login.html")) { @@ -3233,54 +3233,78 @@ document.addEventListener("DOMContentLoaded", function () { const files = Array.from(this.files); if (files.length === 0) return; - const formData = new FormData(); - files.forEach(file => { - if (file.type.startsWith("image/")) { - formData.append("screenshots", file); - } else { - console.warn(`File is not an image: ${file.name}`); - } - }); + const newBase64Images = []; - if (formData.getAll("screenshots").length === 0) { - alert("请选择有效的图片文件"); - return; + // Helper to convert file to base64 with retry + const fileToBase64 = async (f, attempt = 1) => { + try { + return await new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => resolve(reader.result); + reader.onerror = () => reject(reader.error || new Error("FileReader error")); + reader.onabort = () => reject(new Error("File read aborted")); + reader.readAsDataURL(f); + }); + } catch (err) { + // If NotReadableError, retry up to 3 times + if (attempt < 3 && (err.name === "NotReadableError" || err.code === 1)) { + console.warn(`Retrying ${f.name} read due to error (attempt ${attempt})...`); + await new Promise(r => setTimeout(r, 200 * attempt)); + return fileToBase64(f, attempt + 1); + } + throw err; + } + }; + + for (const file of files) { + if (!file.type.startsWith("image/")) { + console.warn(`File is not an image: ${file.name}`); + continue; + } + + try { + console.log(`[v5.0] Converting ${file.name} (${file.size} bytes)...`); + const base64 = await fileToBase64(file); + newBase64Images.push(base64); + console.log(`Successfully converted ${file.name}`); + } catch (error) { + console.error(`Primary method failed for ${file.name}:`, error); + + // Fallback: arrayBuffer (usually works when FileReader fails) + try { + console.log(`Trying arrayBuffer fallback for ${file.name}...`); + const buffer = await file.arrayBuffer(); + const bytes = new Uint8Array(buffer); + let binary = ""; + const chunkSize = 8192; + for (let i = 0; i < bytes.length; i += chunkSize) { + const chunk = bytes.slice(i, i + chunkSize); + binary += String.fromCharCode.apply(null, chunk); + } + const b64 = `data:${file.type};base64,${btoa(binary)}`; + newBase64Images.push(b64); + console.log(`Fallback successful for ${file.name}`); + } catch (fallbackError) { + console.error(`Both methods failed for ${file.name}:`, fallbackError); + alert(`无法读取文件 "${file.name}"。\n这种情况通常是浏览器或系统文件锁定导致的。\n\n建议尝试:\n1. 刷新页面后再试\n2. 使用浏览器的“无痕模式”\n3. 确认文件没有被其他软件打开`); + } + } } - try { - console.log(`[v4.0] Uploading ${files.length} files to server...`); - const response = await authenticatedFetch("/api/upload", { - method: "POST", - body: formData, - // Note: When sending FormData, the browser automatically sets the correct Content-Type with boundary - }); - - if (response.ok) { - const result = await response.json(); - const newBase64Images = result.filePaths || []; - console.log(`Successfully uploaded ${newBase64Images.length} images`); - - if (type === "create") { - createUploadedScreenshots = [ - ...createUploadedScreenshots, - ...newBase64Images, - ]; - renderScreenshotPreviews("create", createUploadedScreenshots); - } else { - editUploadedScreenshots = [ - ...editUploadedScreenshots, - ...newBase64Images, - ]; - renderScreenshotPreviews("edit", editUploadedScreenshots); - } + if (newBase64Images.length > 0) { + if (type === "create") { + createUploadedScreenshots = [ + ...createUploadedScreenshots, + ...newBase64Images, + ]; + renderScreenshotPreviews("create", createUploadedScreenshots); } else { - const errorText = await response.text(); - console.error("Upload failed:", errorText); - alert("图片上传失败,请稍后重试。"); + editUploadedScreenshots = [ + ...editUploadedScreenshots, + ...newBase64Images, + ]; + renderScreenshotPreviews("edit", editUploadedScreenshots); } - } catch (error) { - console.error("Error during upload:", error); - alert("上传过程出错,可能是浏览器权限问题或网络连接中断。"); } // 清除 input,以便再次选择同一张图片