From 56a29b8be2d57fe7b61343d3242d9e47ef096d89 Mon Sep 17 00:00:00 2001 From: WuYongJie Date: Thu, 16 Apr 2026 18:42:37 +0800 Subject: [PATCH] feat: polish home assembly and material workflows --- .../src/app/HomeClient.workspace.tsx | 131 +- .../__tests__/assisted-assembly-page.test.tsx | 73 +- .../app/__tests__/home-client-delete.test.tsx | 103 +- .../src/app/__tests__/page-nav.test.tsx | 16 + .../substrate-dictionaries/[id]/route.ts | 16 +- .../substrate-dictionaries/route.ts | 2 +- .../substrate-dictionaries/schemas.ts | 29 + .../api/asset-center/substrates/[id]/route.ts | 2 +- .../app/api/asset-center/substrates/route.ts | 2 +- .../api/asset-center/substrates/schemas.ts | 30 + .../src/app/api/products/route.test.ts | 107 + .../src/app/api/products/route.ts | 3 +- .../asset-center/AssetCenterClient.test.tsx | 270 ++- .../app/asset-center/AssetCenterClient.tsx | 309 ++- .../MaterialLibraryClient.test.tsx | 78 +- .../MaterialLibraryClient.tsx | 2136 +++++++++++++---- .../AssistedAssemblyClient.test.tsx | 69 + .../AssistedAssemblyClient.tsx | 54 +- .../src/app/assisted-assembly/page.tsx | 8 +- .../app/home/components/HomeModelsSection.tsx | 32 +- .../home/components/HomeWorkspaceSwitcher.tsx | 4 +- furniture-3d-system/src/app/page.tsx | 2 +- .../src/lib/asset-thumbnail-auth.ts | 7 + .../__tests__/assembly-workspace.test.ts | 75 + .../lib/open-product/assembly-workspace.ts | 85 +- .../src/lib/open-product/asset-variant-uv.ts | 38 + .../src/lib/open-product/platform-api.ts | 40 +- .../__tests__/material.repository.test.ts | 28 + .../__tests__/product.repository.test.ts | 66 + .../repositories/material.repository.ts | 12 +- .../server/repositories/product.repository.ts | 10 + .../__tests__/asset-center.service.test.ts | 282 ++- .../asset-center.substrate.service.test.ts | 13 + .../__tests__/material.service.test.ts | 32 + .../server/services/asset-center.service.ts | 179 +- .../services/asset-center.service.types.ts | 6 + .../src/server/services/material.service.ts | 39 +- 37 files changed, 3630 insertions(+), 758 deletions(-) create mode 100644 furniture-3d-system/src/app/api/asset-center/substrate-dictionaries/schemas.ts create mode 100644 furniture-3d-system/src/app/api/asset-center/substrates/schemas.ts create mode 100644 furniture-3d-system/src/app/api/products/route.test.ts create mode 100644 furniture-3d-system/src/lib/open-product/asset-variant-uv.ts create mode 100644 furniture-3d-system/src/server/repositories/__tests__/product.repository.test.ts diff --git a/furniture-3d-system/src/app/HomeClient.workspace.tsx b/furniture-3d-system/src/app/HomeClient.workspace.tsx index b3b01644..c7401f7b 100644 --- a/furniture-3d-system/src/app/HomeClient.workspace.tsx +++ b/furniture-3d-system/src/app/HomeClient.workspace.tsx @@ -83,7 +83,7 @@ export default function HomeClient({ products: initialProducts, initialHasMore, myDraftGroups, - user, + user: _user, initialTab = 'products', }: HomeClientProps) { const toast = useToast() @@ -100,18 +100,10 @@ export default function HomeClient({ const [isMounted, setIsMounted] = useState(false) const loadMoreRef = useRef(null) - const [isCreateModalOpen, setIsCreateModalOpen] = useState(false) const [editingProduct, setEditingProduct] = useState(null) const [categories, setCategories] = useState([]) const categoriesLoadedRef = useRef(false) - const [newModelNumber, setNewModelNumber] = useState('') - const [newName, setNewName] = useState('') - const [newCategoryId, setNewCategoryId] = useState('') - const [newDescription, setNewDescription] = useState('') - const [newVisibility, setNewVisibility] = useState('TEAM') - const [createSubmitting, setCreateSubmitting] = useState(false) - const [addSKUProductId, setAddSKUProductId] = useState(null) const [addSKUProductName, setAddSKUProductName] = useState('') const [editName, setEditName] = useState('') @@ -156,7 +148,7 @@ export default function HomeClient({ }, [isMounted, restoreScrollY, shouldRestoreFromReturn]) useEffect(() => { - if (!isCreateModalOpen && !editingProduct) return + if (!editingProduct) return if (categoriesLoadedRef.current) return let cancelled = false categoriesLoadedRef.current = true @@ -176,7 +168,7 @@ export default function HomeClient({ return () => { cancelled = true } - }, [editingProduct, isCreateModalOpen]) + }, [editingProduct]) const filteredProducts = useFilteredProducts(products, deferredSearchQuery) @@ -193,13 +185,17 @@ export default function HomeClient({ markExpandedProductForRestore() }, [activeTab, markExpandedProductForRestore]) + const buildFormalizedProductsUrl = useCallback((params?: Record) => { + const searchParams = new URLSearchParams({ formalizedOnly: 'true', ...params }) + return `/api/products?${searchParams.toString()}` + }, []) + const loadMoreProducts = useCallback(async () => { if (isLoadingMore || !hasMore) return setIsLoadingMore(true) try { const nextPage = currentPage + 1 - const params = new URLSearchParams({ page: String(nextPage), limit: '20' }) - const response = await fetch(`/api/products?${params}`) + const response = await fetch(buildFormalizedProductsUrl({ page: String(nextPage), limit: '20' })) const responseBody = await readJsonSafely(response) if (!response.ok || isApiFailure(responseBody)) { throw new Error(getApiErrorMessage(parseApiErrorPayload(responseBody), '加载失败')) @@ -215,7 +211,7 @@ export default function HomeClient({ } finally { setIsLoadingMore(false) } - }, [currentPage, hasMore, isLoadingMore]) + }, [buildFormalizedProductsUrl, currentPage, hasMore, isLoadingMore]) useEffect(() => { const observer = new IntersectionObserver((entries) => { @@ -227,7 +223,7 @@ export default function HomeClient({ const reloadProducts = useCallback(async () => { try { - const response = await fetch('/api/products') + const response = await fetch(buildFormalizedProductsUrl()) const responseBody = await readJsonSafely(response) if (!response.ok || isApiFailure(responseBody)) { throw new Error(getApiErrorMessage(parseApiErrorPayload(responseBody), '刷新产品列表失败')) @@ -241,7 +237,7 @@ export default function HomeClient({ console.error('刷新产品列表失败:', error) window.location.reload() } - }, []) + }, [buildFormalizedProductsUrl]) const handleAddSKU = useCallback((productId: string) => { const product = products.find((item) => item.id === productId) @@ -268,49 +264,6 @@ export default function HomeClient({ setDeletingSku({ sku, product }) }, []) - const handleCreateProduct = useCallback(async () => { - if (!newModelNumber.trim() || !newName.trim()) { - toast.error('型号和名称为必填项') - return - } - if (!newCategoryId) { - toast.error('请选择产品分类') - return - } - setCreateSubmitting(true) - try { - const res = await fetch('/api/products', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - modelNumber: newModelNumber.trim(), - name: newName.trim(), - description: newDescription.trim() || undefined, - categoryId: newCategoryId, - visibility: newVisibility, - }), - }) - const responseBody = await readJsonSafely(res) - if (!res.ok || isApiFailure(responseBody)) { - toast.error(getApiErrorMessage(parseApiErrorPayload(responseBody), '创建产品失败')) - return - } - await reloadProducts() - toast.success(`产品 ${newModelNumber.trim()} 创建成功`) - setIsCreateModalOpen(false) - setNewModelNumber('') - setNewName('') - setNewCategoryId('') - setNewDescription('') - setNewVisibility('TEAM') - } catch (error) { - console.error('创建产品失败:', error) - toast.error('创建产品失败,请稍后重试') - } finally { - setCreateSubmitting(false) - } - }, [newCategoryId, newDescription, newModelNumber, newName, newVisibility, reloadProducts, toast]) - const handleEditProduct = useCallback(async () => { if (!editingProduct) return if (!editName.trim()) { @@ -367,7 +320,7 @@ export default function HomeClient({
@@ -398,7 +351,6 @@ export default function HomeClient({ onAddSKU={handleAddSKU} onDeleteProduct={handleOpenDeleteProduct} onDeleteSKU={handleOpenDeleteSKU} - onOpenCreate={() => setIsCreateModalOpen(true)} isLoadingMore={isLoadingMore} hasMore={hasMore} isMounted={isMounted} @@ -430,63 +382,6 @@ export default function HomeClient({ productName={addSKUProductName} /> - { - if (!createSubmitting) setIsCreateModalOpen(false) - }} - title="新建产品型号" - size="medium" - > -
- setNewModelNumber(e.target.value)} required /> - setNewName(e.target.value)} required /> -