From 593a00cebccc339c743414b79a98b509d52158bd Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 6 Mar 2026 19:13:37 +0800 Subject: [PATCH] fix: improve image editor upload fallback --- components/image-annotator.tsx | 178 ++++++++++++++++----------------- 1 file changed, 89 insertions(+), 89 deletions(-) diff --git a/components/image-annotator.tsx b/components/image-annotator.tsx index 2831d2abd..1766b4397 100644 --- a/components/image-annotator.tsx +++ b/components/image-annotator.tsx @@ -190,18 +190,92 @@ export function ImageAnnotator({ } }; + const readUploadErrorMessage = useCallback(async (response: Response) => { + try { + const payload = await response.clone().json(); + return String(payload?.details || payload?.error || "").trim() || "上传失败,请重试"; + } catch { + return `上传失败(${response.status})`; + } + }, []); + + const resetEditorStateForNewImage = useCallback(() => { + setProcessedImageUrl(null); + setProcessedImageBase64(null); + setProcessType(null); + setActions([]); + setCurrentAction(null); + setTextInput(""); + setTextPosition(null); + setIsResizeMode(false); + }, []); + + const loadImageFromFile = useCallback( + async (file: File) => { + revokeObjectUrl(); + const objectUrl = URL.createObjectURL(file); + objectUrlRef.current = objectUrl; + return await createImageElement(objectUrl, false); + }, + [createImageElement, revokeObjectUrl] + ); + // 上传图片到服务器获取 URL 的辅助函数 - const uploadImageFile = async (file: File): Promise => { + const uploadImageFile = async (file: File): Promise<{ url: string; assetId?: string | null }> => { const formData = new FormData(); formData.append("file", file); const response = await fetchUploadWithRetry(formData); if (!response.ok) { - throw new Error("上传失败"); + throw new Error(await readUploadErrorMessage(response)); } const result = await response.json(); - return result.url; + if (!result?.url) { + throw new Error("上传成功,但未返回可用图片地址"); + } + return result; }; + const applyUploadedFile = useCallback( + async (file: File) => { + if (!file.type.startsWith("image/")) { + throw new Error("请上传图片文件"); + } + if (file.size > 50 * 1024 * 1024) { + throw new Error("图片大小不能超过 50MB"); + } + + setIsLoading(true); + toast.info("正在上传图片..."); + + try { + const localPreviewPromise = loadImageFromFile(file); + const uploadResult = await uploadImageFile(file); + + resetEditorStateForNewImage(); + setOriginalImageUrl(uploadResult.url); + + try { + const localImage = await localPreviewPromise; + setImage(localImage); + } catch (previewError) { + console.warn("[ImageAnnotator] Local preview failed, fallback to uploaded url:", previewError); + const uploadedImage = await loadImageFromSource(uploadResult.url); + setImage(uploadedImage); + } + + toast.success("图片上传成功"); + } catch (error) { + console.error("[ImageAnnotator] 上传图片失败:", error); + const message = error instanceof Error ? error.message : "上传失败,请重试"; + toast.error(message); + throw error; + } finally { + setIsLoading(false); + } + }, + [loadImageFromFile, loadImageFromSource, resetEditorStateForNewImage] + ); + // 处理拖拽上传 - 先上传到服务器获取URL const handleDrop = useCallback(async (e: React.DragEvent) => { e.preventDefault(); @@ -209,39 +283,11 @@ export function ImageAnnotator({ const files = e.dataTransfer.files; if (files.length > 0) { - const file = files[0]; - if (!file.type.startsWith('image/')) { - toast.error("请上传图片文件"); - return; - } - if (file.size > 50 * 1024 * 1024) { - toast.error("图片大小不能超过 50MB"); - return; - } - try { - setIsLoading(true); - toast.info("正在上传图片..."); - const imageUrl = await uploadImageFile(file); - - setOriginalImageUrl(imageUrl); - setProcessedImageUrl(null); - setProcessedImageBase64(null); - setProcessType(null); - setActions([]); - setCurrentAction(null); - - const img = await loadImageFromSource(imageUrl); - setImage(img); - setIsLoading(false); - toast.success("图片上传成功"); - } catch (error) { - console.error("上传图片失败:", error); - toast.error("上传失败,请重试"); - setIsLoading(false); - } + await applyUploadedFile(files[0]); + } catch {} } - }, [loadImageFromSource]); + }, [applyUploadedFile]); const handleDragOver = useCallback((e: React.DragEvent) => { e.preventDefault(); @@ -260,33 +306,9 @@ export function ImageAnnotator({ if (item.type.startsWith('image/')) { const file = item.getAsFile(); if (!file) continue; - - if (file.size > 50 * 1024 * 1024) { - toast.error("图片大小不能超过 50MB"); - return; - } - try { - setIsLoading(true); - toast.info("正在上传图片..."); - const imageUrl = await uploadImageFile(file); - - setOriginalImageUrl(imageUrl); - setProcessedImageUrl(null); - setProcessedImageBase64(null); - setProcessType(null); - setActions([]); - setCurrentAction(null); - - const img = await loadImageFromSource(imageUrl); - setImage(img); - setIsLoading(false); - toast.success("图片上传成功"); - } catch (error) { - console.error("上传图片失败:", error); - toast.error("上传失败,请重试"); - setIsLoading(false); - } + await applyUploadedFile(file); + } catch {} break; } } @@ -294,7 +316,7 @@ export function ImageAnnotator({ document.addEventListener('paste', handlePaste); return () => document.removeEventListener('paste', handlePaste); - }, [loadImageFromSource, mounted, originalImageUrl]); + }, [applyUploadedFile, mounted, originalImageUrl]); // 尺寸调整相关状态 const [isResizeMode, setIsResizeMode] = useState(initialResizeMode); @@ -398,34 +420,9 @@ export function ImageAnnotator({ const handleImageUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { - if (file.size > 50 * 1024 * 1024) { - toast.error("图片大小不能超过 50MB"); - e.target.value = ""; - return; - } - try { - setIsLoading(true); - toast.info("正在上传图片..."); - const imageUrl = await uploadImageFile(file); - - setOriginalImageUrl(imageUrl); - // 重置状态 - setProcessedImageUrl(null); - setProcessedImageBase64(null); - setProcessType(null); - setActions([]); - setCurrentAction(null); - - const img = await loadImageFromSource(imageUrl); - setImage(img); - setIsLoading(false); - toast.success("图片上传成功"); - } catch (error) { - console.error("上传图片失败:", error); - toast.error("上传失败,请重试"); - setIsLoading(false); - } + await applyUploadedFile(file); + } catch {} } // 重置 input e.target.value = ""; @@ -1369,10 +1366,13 @@ export function ImageAnnotator({ const uploadResponse = await fetchUploadWithRetry(formData); if (!uploadResponse.ok) { - throw new Error("上传失败"); + throw new Error(await readUploadErrorMessage(uploadResponse)); } const uploadResult = await uploadResponse.json(); + if (!uploadResult?.url) { + throw new Error("上传成功,但未返回可用图片地址"); + } return uploadResult.url; } catch (error) { console.error("上传图片失败:", error);