From 6eb624f8e4f8bdfe6731c214c8c4b016020f487c Mon Sep 17 00:00:00 2001 From: WuYongJie Date: Tue, 16 Jun 2026 13:53:32 +0800 Subject: [PATCH] fix(intake): simplify AI white product photo controls --- ...nIntegratedAiWhiteBackgroundPanel.test.tsx | 26 +++- .../NonIntegratedDetailClient.test.tsx | 23 ++-- .../NonIntegratedDetailClient.tsx | 7 -- .../NonIntegratedAiWhiteBackgroundPanel.tsx | 111 +++++++++++------- ...onIntegratedAiWhiteBackgroundWorkspace.tsx | 11 -- .../documents/ProductPhotoHeaderActions.tsx | 13 -- .../documents/ProductPhotoWorkspace.tsx | 4 +- .../documents/documentWorkspaceHelpers.ts | 12 +- .../documents/useProductPhotoTargetSync.ts | 10 +- .../DocumentWorkspaceHeaderActionsSlot.tsx | 6 - 10 files changed, 117 insertions(+), 106 deletions(-) diff --git a/furniture-3d-system/src/app/intake/non-integrated/NonIntegratedAiWhiteBackgroundPanel.test.tsx b/furniture-3d-system/src/app/intake/non-integrated/NonIntegratedAiWhiteBackgroundPanel.test.tsx index 1aa507ab..b28b8c86 100644 --- a/furniture-3d-system/src/app/intake/non-integrated/NonIntegratedAiWhiteBackgroundPanel.test.tsx +++ b/furniture-3d-system/src/app/intake/non-integrated/NonIntegratedAiWhiteBackgroundPanel.test.tsx @@ -95,7 +95,6 @@ describe("NonIntegratedAiWhiteBackgroundPanel", () => { ]} statusText="待生成" sessionStatus="draft" - missingSourceSlotLabels={[]} availableSourceViewCount={1} canUpload={false} canUploadToMaterialLibrary @@ -224,7 +223,6 @@ describe("NonIntegratedAiWhiteBackgroundPanel", () => { ]} statusText="生成中" sessionStatus="draft" - missingSourceSlotLabels={[]} availableSourceViewCount={1} canUpload={false} canUploadToMaterialLibrary @@ -314,12 +312,32 @@ describe("NonIntegratedAiWhiteBackgroundPanel", () => { expect( screen.getByText("所选花色未配置贴图,请先在基础资料库补充贴图。"), ).toBeInTheDocument(); - expect(screen.getByText("缺贴图")).toBeInTheDocument(); expect( - screen.getByRole("button", { name: "生成已上传角度" }), + screen.getByTestId("ai-white-background-target-finish-swatch"), + ).toHaveTextContent("缺贴图"); + expect( + screen + .getByTestId("ai-white-background-target-finish-swatch") + .querySelector("img"), + ).toBeNull(); + expect( + screen.getByRole("button", { name: "缺少花色贴图" }), ).toBeDisabled(); }); + it("labels selected target finish generation as a new finish output", () => { + renderPanel({ targetFinishId: "finish-black-walnut" }); + + expect( + screen.getByRole("button", { name: "生成新花色白底图" }), + ).toBeEnabled(); + expect( + screen + .getByTestId("ai-white-background-target-finish-swatch") + .querySelector("img"), + ).toBeInTheDocument(); + }); + it("explains read-only product photo permission before blocking operations", () => { renderPanel({ isReadOnly: true }); diff --git a/furniture-3d-system/src/app/intake/non-integrated/NonIntegratedDetailClient.test.tsx b/furniture-3d-system/src/app/intake/non-integrated/NonIntegratedDetailClient.test.tsx index 4fdde492..4e716786 100644 --- a/furniture-3d-system/src/app/intake/non-integrated/NonIntegratedDetailClient.test.tsx +++ b/furniture-3d-system/src/app/intake/non-integrated/NonIntegratedDetailClient.test.tsx @@ -10586,15 +10586,12 @@ describe("NonIntegratedDetailClient", () => { expect( within(detailList).queryByText("front-ai-white.png"), ).not.toBeInTheDocument(); + expect( + screen.queryByTestId("product-photo-panel-tab-ai-white-background"), + ).not.toBeInTheDocument(); await actor.click( - screen.getByTestId("product-photo-panel-tab-ai-white-background"), - ); - const aiArchiveList = await screen.findByTestId("product-photo-detail-list"); - expect(within(aiArchiveList).getByText("front-ai-white.png")).toBeInTheDocument(); - - await actor.click( - screen.getByTestId("product-photo-open-ai-white-background"), + screen.getByTestId("product-photo-workspace-mode-ai-white-background"), ); expect( screen.getByTestId("product-photo-ai-white-background-workspace"), @@ -10607,7 +10604,7 @@ describe("NonIntegratedDetailClient", () => { "BAR-001", ); expect( - screen.getByText("可生成 1/8"), + screen.getByText("源图 1/8"), ).toBeInTheDocument(); expect(screen.getByText("已生成 1/8")).toBeInTheDocument(); expect( @@ -10681,7 +10678,7 @@ describe("NonIntegratedDetailClient", () => { await actor.click(screen.getByTestId("document-workspace-trigger-产品实拍图")); await actor.click( - screen.getByTestId("product-photo-open-ai-white-background"), + screen.getByTestId("product-photo-workspace-mode-ai-white-background"), ); await actor.click(await screen.findByAltText("front-ai-white.png")); @@ -10689,9 +10686,9 @@ describe("NonIntegratedDetailClient", () => { expect( screen.getByRole("dialog", { name: "正视图原图" }), ).toBeInTheDocument(); - expect(screen.getByRole("button", { name: "关闭预览" })).toBeInTheDocument(); + expect(screen.getByRole("button", { name: "收起原图" })).toBeInTheDocument(); - await actor.click(screen.getByRole("button", { name: "关闭预览" })); + await actor.click(screen.getByRole("button", { name: "收起原图" })); expect( screen.queryByRole("dialog", { name: "正视图原图" }), @@ -10774,7 +10771,7 @@ describe("NonIntegratedDetailClient", () => { await actor.click(screen.getByTestId("document-workspace-trigger-产品实拍图")); await actor.click( - screen.getByTestId("product-photo-open-ai-white-background"), + screen.getByTestId("product-photo-workspace-mode-ai-white-background"), ); const aiWhiteBackgroundWorkspace = await screen.findByTestId( @@ -10845,7 +10842,7 @@ describe("NonIntegratedDetailClient", () => { await actor.click(screen.getByTestId("document-workspace-trigger-产品实拍图")); await actor.click( - screen.getByTestId("product-photo-open-ai-white-background"), + screen.getByTestId("product-photo-workspace-mode-ai-white-background"), ); await actor.click( screen.getByRole("button", { name: "生成已上传角度" }), diff --git a/furniture-3d-system/src/app/intake/non-integrated/NonIntegratedDetailClient.tsx b/furniture-3d-system/src/app/intake/non-integrated/NonIntegratedDetailClient.tsx index 94cb8f73..bb0ac674 100644 --- a/furniture-3d-system/src/app/intake/non-integrated/NonIntegratedDetailClient.tsx +++ b/furniture-3d-system/src/app/intake/non-integrated/NonIntegratedDetailClient.tsx @@ -4806,7 +4806,6 @@ export default function NonIntegratedDetailClient({ activeProductPhotoPanel, canDeleteSelectedReviewRound, canDownloadWorkspaceFiles, - canUseAiWhiteBackground, canUseProductPricingPushControls, confirmedClassName: DOCUMENT_WORKSPACE_CONFIRMED_CLASS, currentProductPhotoGroupUploadLabel, @@ -4905,12 +4904,6 @@ export default function NonIntegratedDetailClient({ handleFinishedSampleShipmentStatusClick, onImportProductLimitWorkbookChange: handleImportProductLimitWorkbookChange, - onOpenAiWhiteBackground: () => - setActiveProductPhotoMode((current) => - current === "ai-white-background" - ? "source" - : "ai-white-background", - ), onOpenProductPhotoCurrentUpload: openProductPhotoCurrentUpload, onOpenProductPhotoPanelUpload: openProductPhotoPanelUpload, diff --git a/furniture-3d-system/src/app/intake/non-integrated/workspaces/ai-white-background/NonIntegratedAiWhiteBackgroundPanel.tsx b/furniture-3d-system/src/app/intake/non-integrated/workspaces/ai-white-background/NonIntegratedAiWhiteBackgroundPanel.tsx index b40aa2c9..e65f3b85 100644 --- a/furniture-3d-system/src/app/intake/non-integrated/workspaces/ai-white-background/NonIntegratedAiWhiteBackgroundPanel.tsx +++ b/furniture-3d-system/src/app/intake/non-integrated/workspaces/ai-white-background/NonIntegratedAiWhiteBackgroundPanel.tsx @@ -12,7 +12,6 @@ import { } from "react"; import { createPortal } from "react-dom"; import { - AlertCircle, Check, ChevronDown, ImageIcon, @@ -54,7 +53,6 @@ type NonIntegratedAiWhiteBackgroundPanelProps = { barcodeOptions: AiWhiteBackgroundBarcodeOption[]; statusText: string; sessionStatus: AiWhiteBackgroundSessionStatus | null; - missingSourceSlotLabels: string[]; availableSourceViewCount: number; canUpload: boolean; canUploadToMaterialLibrary: boolean; @@ -788,6 +786,54 @@ function AiWhiteBackgroundCardImage({ ); } +function AiWhiteBackgroundTargetFinishSwatch({ + imageUrl, + fallbackColor, + missingTexture, +}: { + imageUrl: string; + fallbackColor: string; + missingTexture: boolean; +}) { + const [failedImageUrl, setFailedImageUrl] = useState(null); + const normalizedFallbackColor = fallbackColor.trim(); + const canUseFallbackColor = isHexColor(normalizedFallbackColor); + const canShowImage = + Boolean(imageUrl) && !missingTexture && failedImageUrl !== imageUrl; + + return ( + + {canShowImage ? ( + // eslint-disable-next-line @next/next/no-img-element + setFailedImageUrl(imageUrl)} + /> + ) : missingTexture ? ( + + + ) : null} + + ); +} + export default function NonIntegratedAiWhiteBackgroundPanel({ styleLabel, styleKey, @@ -798,7 +844,6 @@ export default function NonIntegratedAiWhiteBackgroundPanel({ targetFinishOptions, barcodeOptions, sessionStatus, - missingSourceSlotLabels, availableSourceViewCount, canUpload, canUploadToMaterialLibrary, @@ -846,6 +891,11 @@ export default function NonIntegratedAiWhiteBackgroundPanel({ const targetFinishDisabledReason = targetFinishTextureMissing ? "所选花色未配置贴图,请先在基础资料库补充贴图。" : null; + const bulkGenerateLabel = targetFinishId + ? targetFinishTextureMissing + ? "缺少花色贴图" + : "生成新花色白底图" + : "生成已上传角度"; const canOperateAiWhiteBackground = !isReadOnly && canUseAiWhiteBackground; const operationDisabledReason = isReadOnly ? "当前账号没有产品实拍写权限,无法生成或上传 AI 白底图。" @@ -1119,26 +1169,13 @@ export default function NonIntegratedAiWhiteBackgroundPanel({ options={targetFinishOptions} className="w-0 min-w-0 flex-1" /> - + {targetFinishDisabledReason ? (

@@ -1153,27 +1190,6 @@ export default function NonIntegratedAiWhiteBackgroundPanel({ ) : null} - {missingSourceSlotLabels.length > 0 ? ( -

-
- -
-

待补角度

-
- {missingSourceSlotLabels.map((label) => ( - - {label} - - ))} -
-
-
-
- ) : null} - {!canUploadToMaterialLibrary && uploadDisabledReason ? (
{uploadDisabledReason} @@ -1195,10 +1211,15 @@ export default function NonIntegratedAiWhiteBackgroundPanel({ hasAnyGenerationInFlight } className="h-10 !min-h-10 rounded-[6px] text-[12px] font-black" + title={ + targetFinishId && !targetFinishTextureMissing + ? "生成的是同规格新花色白底图,不覆盖当前产品。" + : undefined + } onClick={() => void onGenerate()} > - 生成已上传角度 + {bulkGenerateLabel} {isReadOnly || activeMode === "ai-white-background" ? null : ( <>