fix: improve image editor upload fallback
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-03-06 19:13:37 +08:00
parent 82162f8936
commit 593a00cebc

View File

@ -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<string> => {
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<HTMLDivElement>) => {
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<HTMLDivElement>) => {
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<HTMLInputElement>) => {
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);