diff --git a/components/scene-fusion-view.tsx b/components/scene-fusion-view.tsx index 249a458..77a2bc6 100644 --- a/components/scene-fusion-view.tsx +++ b/components/scene-fusion-view.tsx @@ -92,6 +92,7 @@ export function SceneFusionView({ onBack, initialProductImage, initialProductNam const [productName, setProductName] = useState(initialProductName || ""); // 产品名称(如"沙发") const [scenes, setScenes] = useState([]); const [isProcessing, setIsProcessing] = useState(false); + const [isProductUploading, setIsProductUploading] = useState(false); const [aspectRatio, setAspectRatio] = useState("auto"); // 图片比例 const [imageSize, setImageSize] = useState("2K"); // 图片分辨率 @@ -99,6 +100,7 @@ export function SceneFusionView({ onBack, initialProductImage, initialProductNam const productInputRef = useRef(null); const sceneInputRef = useRef(null); const productPreviewObjectUrlRef = useRef(null); + const productUploadIdRef = useRef(0); // 如果外部传入了初始产品图,则在首次挂载或传入值变化时自动带入 const updateProductPreview = (previewUrl: string | null, isObjectUrl = false) => { @@ -115,6 +117,14 @@ export function SceneFusionView({ onBack, initialProductImage, initialProductNam const clearProductImage = () => { setProductImage(null); updateProductPreview(null); + setIsProductUploading(false); + productUploadIdRef.current += 1; + }; + + const handleProductPreviewError = () => { + if (productImage && productImagePreview !== productImage) { + updateProductPreview(productImage); + } }; useEffect(() => { @@ -134,8 +144,36 @@ export function SceneFusionView({ onBack, initialProductImage, initialProductNam }, []); // 处理产品图上传 - const handleProductUpload = (e: React.ChangeEvent) => { - const file = e.target.files?.[0]; + const uploadProductImageFile = async (file: File): Promise => { + const formData = new FormData(); + formData.append("file", file); + const response = await fetch("/api/files/upload", { + method: "POST", + body: formData, + }); + + if (!response.ok) { + let errorMessage = "上传失败"; + try { + const errorData = await response.json(); + errorMessage = errorData?.error || errorData?.message || errorMessage; + } catch { + // ignore + } + throw new Error(errorMessage); + } + + const result = await response.json(); + if (!result?.url) { + throw new Error("上传失败"); + } + + return result.url as string; + }; + + const handleProductUpload = async (e: React.ChangeEvent) => { + const input = e.target; + const file = input.files?.[0]; if (!file) return; if (file.size > 10 * 1024 * 1024) { @@ -146,9 +184,12 @@ export function SceneFusionView({ onBack, initialProductImage, initialProductNam setProductImage(null); const previewUrl = URL.createObjectURL(file); updateProductPreview(previewUrl, true); + setIsProductUploading(true); + const uploadId = ++productUploadIdRef.current; const reader = new FileReader(); reader.onload = (e) => { + if (uploadId !== productUploadIdRef.current) return; const result = e.target?.result; if (typeof result === "string") { setProductImage(result); @@ -158,12 +199,29 @@ export function SceneFusionView({ onBack, initialProductImage, initialProductNam } }; reader.onerror = () => { + if (uploadId !== productUploadIdRef.current) return; toast.error("图片读取失败,请重试"); clearProductImage(); }; reader.readAsDataURL(file); + + try { + toast.info("正在上传产品图..."); + const uploadedUrl = await uploadProductImageFile(file); + if (uploadId === productUploadIdRef.current) { + setProductImage(uploadedUrl); + updateProductPreview(uploadedUrl); + toast.success("产品图上传成功"); + } + } catch (error) { + toast.error(error instanceof Error ? error.message : "产品图上传失败"); + } finally { + if (uploadId === productUploadIdRef.current) { + setIsProductUploading(false); + } + } // 重置 input,允许重复选择同一文件 - e.target.value = ""; + input.value = ""; }; // 处理场景图上传 @@ -632,11 +690,19 @@ export function SceneFusionView({ onBack, initialProductImage, initialProductNam
- {(productImagePreview || productImage) && ( - - )} +
+ {isProductUploading && ( + + + 上传中... + + )} + {(productImagePreview || productImage) && ( + + )} +
- 产品图 + 产品图