From 51762bb2c28f8aa509f36f6c9ce38300237048f7 Mon Sep 17 00:00:00 2001 From: OmniAI Developer Date: Thu, 4 Jun 2026 17:03:49 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=8B=96=E6=8B=BD=E4=B8=8A=E4=BC=A0?= =?UTF-8?q?=E3=80=81=E5=9B=BE=E7=89=87=E7=BC=A9=E6=94=BE=E9=A2=84=E8=A7=88?= =?UTF-8?q?=E5=8F=8A=E6=96=B0=E5=8A=9F=E8=83=BD=E8=84=9A=E6=89=8B=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - EcommercePage/WorkbenchPage 增加页面级拖拽文件上传支持 - 上传图片悬停缩放预览效果 - Workbench 参考素材增加图片/视频缩放预览 - CanvasPage 连接菜单位置微调 (-40) - script-tokens-v5 文本溢出省略号修复 - 新增: CookieConsentBanner, CompliancePage, 电商面板组件, generation store/hooks/service --- src/components/CookieConsentBanner.tsx | 4 + src/features/canvas/CanvasPage.tsx | 2 +- src/features/compliance/CompliancePage.tsx | 14 +++ src/features/ecommerce/EcommercePage.tsx | 81 ++++++++++++- .../ecommerce/ecommerceImageValidation.ts | 16 +++ .../ecommerce/panels/EcommerceClonePanel.tsx | 4 + .../ecommerce/panels/EcommerceDetailPanel.tsx | 4 + .../ecommerce/panels/EcommerceSetPanel.tsx | 4 + .../ecommerce/panels/EcommerceTryOnPanel.tsx | 4 + src/features/workbench/WorkbenchPage.tsx | 94 ++++++++++++--- src/hooks/useGenerationTasks.ts | 12 ++ src/services/backgroundTaskRunner.ts | 3 + src/stores/useGenerationStore.ts | 110 ++++++++++++++++++ src/styles/pages/ecommerce.css | 42 ++++++- src/styles/pages/script-tokens-v5.css | 4 + 15 files changed, 377 insertions(+), 21 deletions(-) create mode 100644 src/components/CookieConsentBanner.tsx create mode 100644 src/features/compliance/CompliancePage.tsx create mode 100644 src/features/ecommerce/ecommerceImageValidation.ts create mode 100644 src/features/ecommerce/panels/EcommerceClonePanel.tsx create mode 100644 src/features/ecommerce/panels/EcommerceDetailPanel.tsx create mode 100644 src/features/ecommerce/panels/EcommerceSetPanel.tsx create mode 100644 src/features/ecommerce/panels/EcommerceTryOnPanel.tsx create mode 100644 src/hooks/useGenerationTasks.ts create mode 100644 src/services/backgroundTaskRunner.ts create mode 100644 src/stores/useGenerationStore.ts diff --git a/src/components/CookieConsentBanner.tsx b/src/components/CookieConsentBanner.tsx new file mode 100644 index 0000000..f8cf683 --- /dev/null +++ b/src/components/CookieConsentBanner.tsx @@ -0,0 +1,4 @@ +function CookieConsentBanner() { + return null; // TODO: implement cookie consent UI +} +export default CookieConsentBanner; diff --git a/src/features/canvas/CanvasPage.tsx b/src/features/canvas/CanvasPage.tsx index 68c758c..2afe062 100644 --- a/src/features/canvas/CanvasPage.tsx +++ b/src/features/canvas/CanvasPage.tsx @@ -2809,7 +2809,7 @@ function CanvasPage({ if (targetPort) { connectCanvasPorts(connectorDrag.port, targetPort); } else { - const menuPosition = positionFloatingMenu(event.clientX, event.clientY, 200, 160, 0); + const menuPosition = positionFloatingMenu(event.clientX, event.clientY, 200, 160, -40); setConnectionDropMenu({ ...menuPosition, originLeft: event.clientX, diff --git a/src/features/compliance/CompliancePage.tsx b/src/features/compliance/CompliancePage.tsx new file mode 100644 index 0000000..c6ca348 --- /dev/null +++ b/src/features/compliance/CompliancePage.tsx @@ -0,0 +1,14 @@ +interface CompliancePageProps { + kind: "agreement" | "privacy"; +} + +function CompliancePage({ kind }: CompliancePageProps) { + return ( +
+

{kind === "agreement" ? "用户协议" : "隐私政策"}

+

内容加载中...

+
+ ); +} + +export default CompliancePage; diff --git a/src/features/ecommerce/EcommercePage.tsx b/src/features/ecommerce/EcommercePage.tsx index c3b6416..43a82a3 100644 --- a/src/features/ecommerce/EcommercePage.tsx +++ b/src/features/ecommerce/EcommercePage.tsx @@ -786,6 +786,8 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { const [showHostingModal, setShowHostingModal] = useState(false); const [productImages, setProductImages] = useState([]); const [isProductUploadDragging, setIsProductUploadDragging] = useState(false); + const [isPageDragging, setIsPageDragging] = useState(false); + const pageDragCounterRef = useRef(0); const [cloneOutput, setCloneOutput] = useState("detail"); const [openCloneBasicSelect, setOpenCloneBasicSelect] = useState(null); const [openCloneModelSelect, setOpenCloneModelSelect] = useState(null); @@ -1295,6 +1297,63 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { }; }, [openCloneModelSelect]); + useEffect(() => { + const handleDragEnter = (e: DragEvent) => { + e.preventDefault(); + pageDragCounterRef.current += 1; + if (pageDragCounterRef.current === 1) { + setIsPageDragging(true); + } + }; + const handleDragLeave = (e: DragEvent) => { + e.preventDefault(); + pageDragCounterRef.current -= 1; + if (pageDragCounterRef.current <= 0) { + pageDragCounterRef.current = 0; + setIsPageDragging(false); + } + }; + const handleDragOver = (e: DragEvent) => { + e.preventDefault(); + }; + const handleDrop = (e: DragEvent) => { + e.preventDefault(); + pageDragCounterRef.current = 0; + setIsPageDragging(false); + const files = Array.from(e.dataTransfer?.files || []); + if (!files.length) return; + if (activeTool === "clone") { + addProductImages(files); + } else if (activeTool === "set") { + addSetImages(files); + } else if (activeTool === "detail") { + setDetailProductImages((current) => { + const remaining = 3 - current.length; + if (remaining <= 0) return current; + const next = createObjectImageItems(files, remaining, "detail"); + return next.length ? [...current, ...next].slice(0, 3) : current; + }); + } else if (activeTool === "wear") { + setGarmentImages((current) => { + const remaining = 5 - current.length; + if (remaining <= 0) return current; + const next = createObjectImageItems(files, remaining, "garment"); + return next.length ? [...current, ...next].slice(0, 5) : current; + }); + } + }; + window.addEventListener("dragenter", handleDragEnter); + window.addEventListener("dragleave", handleDragLeave); + window.addEventListener("dragover", handleDragOver); + window.addEventListener("drop", handleDrop); + return () => { + window.removeEventListener("dragenter", handleDragEnter); + window.removeEventListener("dragleave", handleDragLeave); + window.removeEventListener("dragover", handleDragOver); + window.removeEventListener("drop", handleDrop); + }; + }, [activeTool, addProductImages, addSetImages]); + const handleGarmentUpload = (event: ChangeEvent) => { const files = event.target.files; if (!files?.length) return; @@ -2009,6 +2068,11 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { onClick={() => openProductSetPreview(setPreviewCards[0] ?? productSetPreviewCards[0])} > 商品原图 + {setImages[0]?.src ? ( + + ) : null} 原图素材