diff --git a/furniture-3d-system/prisma/migrations/20261012173000_add_diy_schemes/migration.sql b/furniture-3d-system/prisma/migrations/20261012173000_add_diy_schemes/migration.sql new file mode 100644 index 00000000..c9ffa794 --- /dev/null +++ b/furniture-3d-system/prisma/migrations/20261012173000_add_diy_schemes/migration.sql @@ -0,0 +1,15 @@ +CREATE TABLE "DiyScheme" ( + "id" TEXT NOT NULL, + "code" TEXT NOT NULL, + "name" TEXT NOT NULL, + "parentCode" TEXT, + "createdBy" TEXT NOT NULL, + "snapshot" JSONB NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT "DiyScheme_pkey" PRIMARY KEY ("id"), + CONSTRAINT "DiyScheme_name_not_blank" CHECK (length(btrim("name")) BETWEEN 1 AND 80), + CONSTRAINT "DiyScheme_snapshot_object" CHECK (jsonb_typeof("snapshot") = 'object') +); +CREATE UNIQUE INDEX "DiyScheme_code_key" ON "DiyScheme"("code"); +CREATE INDEX "DiyScheme_createdBy_createdAt_id_idx" ON "DiyScheme"("createdBy", "createdAt", "id"); +ALTER TABLE "DiyScheme" ADD CONSTRAINT "DiyScheme_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/furniture-3d-system/prisma/schema.prisma b/furniture-3d-system/prisma/schema.prisma index 45e0c375..2bfbc6f7 100644 --- a/furniture-3d-system/prisma/schema.prisma +++ b/furniture-3d-system/prisma/schema.prisma @@ -2740,6 +2740,19 @@ model AssistedAssemblyAiRender { @@index([status, createdAt]) } +model DiyScheme { + id String @id @default(cuid()) + code String @unique + name String + parentCode String? + createdBy String + creator User @relation("DiySchemeCreator", fields: [createdBy], references: [id], onDelete: Cascade) + snapshot Json + createdAt DateTime @default(now()) + + @@index([createdBy, createdAt, id]) +} + model AiRenderWorkbenchSpu { id String @id @default(cuid()) @@ -3290,6 +3303,7 @@ model User { createdAssetSeries AssetSeries[] @relation("AssetSeriesCreator") createdDraftGroups DraftGroup[] @relation("DraftGroupCreator") createdAiRenderWorkbenchSpus AiRenderWorkbenchSpu[] @relation("AiRenderWorkbenchSpuCreator") + createdDiySchemes DiyScheme[] @relation("DiySchemeCreator") createdAiRenderWorkbenchSkus AiRenderWorkbenchSku[] @relation("AiRenderWorkbenchSkuCreator") assistedAssemblyAiRenders AssistedAssemblyAiRender[] @relation("AssistedAssemblyAiRenderCreator") createdIntakeProductModelAssets IntakeProductModelAsset[] @relation("IntakeProductModelAssetCreator") diff --git a/furniture-3d-system/public/diy/images/access-control.png b/furniture-3d-system/public/diy/images/access-control.png new file mode 100644 index 00000000..c20fb282 Binary files /dev/null and b/furniture-3d-system/public/diy/images/access-control.png differ diff --git a/furniture-3d-system/public/diy/images/cloud-call.png b/furniture-3d-system/public/diy/images/cloud-call.png new file mode 100644 index 00000000..f2d0a755 Binary files /dev/null and b/furniture-3d-system/public/diy/images/cloud-call.png differ diff --git a/furniture-3d-system/public/diy/images/cloud-function.png b/furniture-3d-system/public/diy/images/cloud-function.png new file mode 100644 index 00000000..06c3c825 Binary files /dev/null and b/furniture-3d-system/public/diy/images/cloud-function.png differ diff --git a/furniture-3d-system/public/diy/images/collection-todos.png b/furniture-3d-system/public/diy/images/collection-todos.png new file mode 100644 index 00000000..9b9858ed Binary files /dev/null and b/furniture-3d-system/public/diy/images/collection-todos.png differ diff --git a/furniture-3d-system/public/diy/images/function-deploy.png b/furniture-3d-system/public/diy/images/function-deploy.png new file mode 100644 index 00000000..5942a90d Binary files /dev/null and b/furniture-3d-system/public/diy/images/function-deploy.png differ diff --git a/furniture-3d-system/public/diy/images/icons/copy.png b/furniture-3d-system/public/diy/images/icons/copy.png new file mode 100644 index 00000000..d2d8f618 Binary files /dev/null and b/furniture-3d-system/public/diy/images/icons/copy.png differ diff --git a/furniture-3d-system/public/diy/images/icons/examples-active.png b/furniture-3d-system/public/diy/images/icons/examples-active.png new file mode 100644 index 00000000..b6ae531f Binary files /dev/null and b/furniture-3d-system/public/diy/images/icons/examples-active.png differ diff --git a/furniture-3d-system/public/diy/images/icons/examples.png b/furniture-3d-system/public/diy/images/icons/examples.png new file mode 100644 index 00000000..743f391b Binary files /dev/null and b/furniture-3d-system/public/diy/images/icons/examples.png differ diff --git a/furniture-3d-system/public/diy/images/icons/home-active.png b/furniture-3d-system/public/diy/images/icons/home-active.png new file mode 100644 index 00000000..74a8cd47 Binary files /dev/null and b/furniture-3d-system/public/diy/images/icons/home-active.png differ diff --git a/furniture-3d-system/public/diy/images/icons/home.png b/furniture-3d-system/public/diy/images/icons/home.png new file mode 100644 index 00000000..669f0e6c Binary files /dev/null and b/furniture-3d-system/public/diy/images/icons/home.png differ diff --git a/furniture-3d-system/public/diy/images/icons/info.svg b/furniture-3d-system/public/diy/images/icons/info.svg new file mode 100644 index 00000000..9de34771 --- /dev/null +++ b/furniture-3d-system/public/diy/images/icons/info.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/furniture-3d-system/public/diy/images/nodejs-function.png b/furniture-3d-system/public/diy/images/nodejs-function.png new file mode 100644 index 00000000..ca88e319 Binary files /dev/null and b/furniture-3d-system/public/diy/images/nodejs-function.png differ diff --git a/furniture-3d-system/public/diy/images/patterns-3d/SJ038043.jpg b/furniture-3d-system/public/diy/images/patterns-3d/SJ038043.jpg new file mode 100644 index 00000000..c78155ef Binary files /dev/null and b/furniture-3d-system/public/diy/images/patterns-3d/SJ038043.jpg differ diff --git a/furniture-3d-system/public/diy/images/patterns-3d/SJ038044.jpg b/furniture-3d-system/public/diy/images/patterns-3d/SJ038044.jpg new file mode 100644 index 00000000..d3e004cd Binary files /dev/null and b/furniture-3d-system/public/diy/images/patterns-3d/SJ038044.jpg differ diff --git a/furniture-3d-system/public/diy/images/patterns-3d/SJ038045.jpg b/furniture-3d-system/public/diy/images/patterns-3d/SJ038045.jpg new file mode 100644 index 00000000..0ff24fe1 Binary files /dev/null and b/furniture-3d-system/public/diy/images/patterns-3d/SJ038045.jpg differ diff --git a/furniture-3d-system/public/diy/images/patterns-3d/SJ038046.jpg b/furniture-3d-system/public/diy/images/patterns-3d/SJ038046.jpg new file mode 100644 index 00000000..2efe92a7 Binary files /dev/null and b/furniture-3d-system/public/diy/images/patterns-3d/SJ038046.jpg differ diff --git a/furniture-3d-system/public/diy/images/patterns-3d/SJ038047.jpg b/furniture-3d-system/public/diy/images/patterns-3d/SJ038047.jpg new file mode 100644 index 00000000..65781a5c Binary files /dev/null and b/furniture-3d-system/public/diy/images/patterns-3d/SJ038047.jpg differ diff --git a/furniture-3d-system/public/diy/images/patterns-3d/SJ038048.jpg b/furniture-3d-system/public/diy/images/patterns-3d/SJ038048.jpg new file mode 100644 index 00000000..66cedc4b Binary files /dev/null and b/furniture-3d-system/public/diy/images/patterns-3d/SJ038048.jpg differ diff --git a/furniture-3d-system/public/diy/images/patterns-3d/SJ038049.jpg b/furniture-3d-system/public/diy/images/patterns-3d/SJ038049.jpg new file mode 100644 index 00000000..f1f4c950 Binary files /dev/null and b/furniture-3d-system/public/diy/images/patterns-3d/SJ038049.jpg differ diff --git a/furniture-3d-system/public/diy/images/patterns-3d/SJ038050.jpg b/furniture-3d-system/public/diy/images/patterns-3d/SJ038050.jpg new file mode 100644 index 00000000..7ab9879c Binary files /dev/null and b/furniture-3d-system/public/diy/images/patterns-3d/SJ038050.jpg differ diff --git a/furniture-3d-system/public/diy/images/patterns-3d/SJ038051.jpg b/furniture-3d-system/public/diy/images/patterns-3d/SJ038051.jpg new file mode 100644 index 00000000..cf74464e Binary files /dev/null and b/furniture-3d-system/public/diy/images/patterns-3d/SJ038051.jpg differ diff --git a/furniture-3d-system/public/diy/images/patterns-3d/SJ038052.jpg b/furniture-3d-system/public/diy/images/patterns-3d/SJ038052.jpg new file mode 100644 index 00000000..39bb2d11 Binary files /dev/null and b/furniture-3d-system/public/diy/images/patterns-3d/SJ038052.jpg differ diff --git a/furniture-3d-system/public/diy/images/patterns-3d/SJ038053.jpg b/furniture-3d-system/public/diy/images/patterns-3d/SJ038053.jpg new file mode 100644 index 00000000..b7c9e854 Binary files /dev/null and b/furniture-3d-system/public/diy/images/patterns-3d/SJ038053.jpg differ diff --git a/furniture-3d-system/public/diy/images/patterns-3d/SJ038054.jpg b/furniture-3d-system/public/diy/images/patterns-3d/SJ038054.jpg new file mode 100644 index 00000000..8be7e599 Binary files /dev/null and b/furniture-3d-system/public/diy/images/patterns-3d/SJ038054.jpg differ diff --git a/furniture-3d-system/public/diy/images/patterns-3d/SJ038055.jpg b/furniture-3d-system/public/diy/images/patterns-3d/SJ038055.jpg new file mode 100644 index 00000000..dd77f594 Binary files /dev/null and b/furniture-3d-system/public/diy/images/patterns-3d/SJ038055.jpg differ diff --git a/furniture-3d-system/public/diy/images/patterns-3d/SJ038056.jpg b/furniture-3d-system/public/diy/images/patterns-3d/SJ038056.jpg new file mode 100644 index 00000000..86f6d7f1 Binary files /dev/null and b/furniture-3d-system/public/diy/images/patterns-3d/SJ038056.jpg differ diff --git a/furniture-3d-system/public/diy/images/patterns-3d/SJ038057.jpg b/furniture-3d-system/public/diy/images/patterns-3d/SJ038057.jpg new file mode 100644 index 00000000..01c7b75a Binary files /dev/null and b/furniture-3d-system/public/diy/images/patterns-3d/SJ038057.jpg differ diff --git a/furniture-3d-system/public/diy/images/patterns-3d/SJ038058.jpg b/furniture-3d-system/public/diy/images/patterns-3d/SJ038058.jpg new file mode 100644 index 00000000..8a1f4b69 Binary files /dev/null and b/furniture-3d-system/public/diy/images/patterns-3d/SJ038058.jpg differ diff --git a/furniture-3d-system/public/diy/images/patterns-3d/SJ038059.jpg b/furniture-3d-system/public/diy/images/patterns-3d/SJ038059.jpg new file mode 100644 index 00000000..4192d5c9 Binary files /dev/null and b/furniture-3d-system/public/diy/images/patterns-3d/SJ038059.jpg differ diff --git a/furniture-3d-system/public/diy/images/patterns-3d/SJ038060.jpg b/furniture-3d-system/public/diy/images/patterns-3d/SJ038060.jpg new file mode 100644 index 00000000..535174d0 Binary files /dev/null and b/furniture-3d-system/public/diy/images/patterns-3d/SJ038060.jpg differ diff --git a/furniture-3d-system/public/diy/images/patterns-3d/SJ038061.jpg b/furniture-3d-system/public/diy/images/patterns-3d/SJ038061.jpg new file mode 100644 index 00000000..3462b7db Binary files /dev/null and b/furniture-3d-system/public/diy/images/patterns-3d/SJ038061.jpg differ diff --git a/furniture-3d-system/public/diy/images/patterns-3d/SJ038062.jpg b/furniture-3d-system/public/diy/images/patterns-3d/SJ038062.jpg new file mode 100644 index 00000000..6603350f Binary files /dev/null and b/furniture-3d-system/public/diy/images/patterns-3d/SJ038062.jpg differ diff --git a/furniture-3d-system/public/diy/images/patterns-3d/SJ038063.jpg b/furniture-3d-system/public/diy/images/patterns-3d/SJ038063.jpg new file mode 100644 index 00000000..debce6e6 Binary files /dev/null and b/furniture-3d-system/public/diy/images/patterns-3d/SJ038063.jpg differ diff --git a/furniture-3d-system/public/diy/images/patterns-3d/SJ038064.jpg b/furniture-3d-system/public/diy/images/patterns-3d/SJ038064.jpg new file mode 100644 index 00000000..a2689cf1 Binary files /dev/null and b/furniture-3d-system/public/diy/images/patterns-3d/SJ038064.jpg differ diff --git a/furniture-3d-system/public/diy/images/patterns-3d/SJ038065.jpg b/furniture-3d-system/public/diy/images/patterns-3d/SJ038065.jpg new file mode 100644 index 00000000..c6544678 Binary files /dev/null and b/furniture-3d-system/public/diy/images/patterns-3d/SJ038065.jpg differ diff --git a/furniture-3d-system/public/diy/images/patterns-3d/SJ038066.jpg b/furniture-3d-system/public/diy/images/patterns-3d/SJ038066.jpg new file mode 100644 index 00000000..7ef83886 Binary files /dev/null and b/furniture-3d-system/public/diy/images/patterns-3d/SJ038066.jpg differ diff --git a/furniture-3d-system/public/diy/images/patterns-3d/SJ038067.jpg b/furniture-3d-system/public/diy/images/patterns-3d/SJ038067.jpg new file mode 100644 index 00000000..804663cc Binary files /dev/null and b/furniture-3d-system/public/diy/images/patterns-3d/SJ038067.jpg differ diff --git a/furniture-3d-system/public/diy/images/patterns-3d/SJ038068.jpg b/furniture-3d-system/public/diy/images/patterns-3d/SJ038068.jpg new file mode 100644 index 00000000..e39f91f0 Binary files /dev/null and b/furniture-3d-system/public/diy/images/patterns-3d/SJ038068.jpg differ diff --git a/furniture-3d-system/public/diy/images/patterns-3d/SJ038069.jpg b/furniture-3d-system/public/diy/images/patterns-3d/SJ038069.jpg new file mode 100644 index 00000000..3ae3035c Binary files /dev/null and b/furniture-3d-system/public/diy/images/patterns-3d/SJ038069.jpg differ diff --git a/furniture-3d-system/public/diy/images/patterns-3d/SJ038070.jpg b/furniture-3d-system/public/diy/images/patterns-3d/SJ038070.jpg new file mode 100644 index 00000000..ece5c7cd Binary files /dev/null and b/furniture-3d-system/public/diy/images/patterns-3d/SJ038070.jpg differ diff --git a/furniture-3d-system/public/diy/images/patterns-3d/SJ038071.jpg b/furniture-3d-system/public/diy/images/patterns-3d/SJ038071.jpg new file mode 100644 index 00000000..504adcc5 Binary files /dev/null and b/furniture-3d-system/public/diy/images/patterns-3d/SJ038071.jpg differ diff --git a/furniture-3d-system/public/diy/images/patterns-3d/SJ038072.jpg b/furniture-3d-system/public/diy/images/patterns-3d/SJ038072.jpg new file mode 100644 index 00000000..9a4fb626 Binary files /dev/null and b/furniture-3d-system/public/diy/images/patterns-3d/SJ038072.jpg differ diff --git a/furniture-3d-system/public/diy/images/patterns-3d/SJ038073.jpg b/furniture-3d-system/public/diy/images/patterns-3d/SJ038073.jpg new file mode 100644 index 00000000..821c2ba3 Binary files /dev/null and b/furniture-3d-system/public/diy/images/patterns-3d/SJ038073.jpg differ diff --git a/furniture-3d-system/public/diy/images/patterns-3d/SJ038074.jpg b/furniture-3d-system/public/diy/images/patterns-3d/SJ038074.jpg new file mode 100644 index 00000000..984bc461 Binary files /dev/null and b/furniture-3d-system/public/diy/images/patterns-3d/SJ038074.jpg differ diff --git a/furniture-3d-system/public/diy/images/quick-deploy.png b/furniture-3d-system/public/diy/images/quick-deploy.png new file mode 100644 index 00000000..076e9a7b Binary files /dev/null and b/furniture-3d-system/public/diy/images/quick-deploy.png differ diff --git a/furniture-3d-system/public/diy/images/styles/SJ0376K09.jpg b/furniture-3d-system/public/diy/images/styles/SJ0376K09.jpg new file mode 100644 index 00000000..b040b1f6 Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/SJ0376K09.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/SJ0380K01.jpg b/furniture-3d-system/public/diy/images/styles/SJ0380K01.jpg new file mode 100644 index 00000000..4586b148 Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/SJ0380K01.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/SJ0380K02.jpg b/furniture-3d-system/public/diy/images/styles/SJ0380K02.jpg new file mode 100644 index 00000000..95a174da Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/SJ0380K02.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/SJ0380K03.jpg b/furniture-3d-system/public/diy/images/styles/SJ0380K03.jpg new file mode 100644 index 00000000..6bf0f523 Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/SJ0380K03.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/SJ0380K04.jpg b/furniture-3d-system/public/diy/images/styles/SJ0380K04.jpg new file mode 100644 index 00000000..0b4c5776 Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/SJ0380K04.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/SJ0380K05.jpg b/furniture-3d-system/public/diy/images/styles/SJ0380K05.jpg new file mode 100644 index 00000000..137188d4 Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/SJ0380K05.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/SJ0380K06.jpg b/furniture-3d-system/public/diy/images/styles/SJ0380K06.jpg new file mode 100644 index 00000000..ae31a091 Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/SJ0380K06.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/SJ0380K07.jpg b/furniture-3d-system/public/diy/images/styles/SJ0380K07.jpg new file mode 100644 index 00000000..93474e65 Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/SJ0380K07.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/SJ0380K08.jpg b/furniture-3d-system/public/diy/images/styles/SJ0380K08.jpg new file mode 100644 index 00000000..16cde25a Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/SJ0380K08.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/SJ0380K09.jpg b/furniture-3d-system/public/diy/images/styles/SJ0380K09.jpg new file mode 100644 index 00000000..f24a2534 Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/SJ0380K09.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038001.jpg b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038001.jpg new file mode 100644 index 00000000..2a5ddd69 Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038001.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038002.jpg b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038002.jpg new file mode 100644 index 00000000..f6053123 Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038002.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038003.jpg b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038003.jpg new file mode 100644 index 00000000..0e59f00b Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038003.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038004.jpg b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038004.jpg new file mode 100644 index 00000000..10240d60 Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038004.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038005.jpg b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038005.jpg new file mode 100644 index 00000000..ff159b71 Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038005.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038006.jpg b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038006.jpg new file mode 100644 index 00000000..237c5c80 Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038006.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038007.jpg b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038007.jpg new file mode 100644 index 00000000..c541d634 Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038007.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038008.jpg b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038008.jpg new file mode 100644 index 00000000..9ccba1b8 Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038008.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038009.jpg b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038009.jpg new file mode 100644 index 00000000..3d08001d Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038009.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038010.jpg b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038010.jpg new file mode 100644 index 00000000..d31890e9 Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038010.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038011.jpg b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038011.jpg new file mode 100644 index 00000000..cd1dcf05 Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038011.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038012.jpg b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038012.jpg new file mode 100644 index 00000000..9ac031da Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038012.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038013.jpg b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038013.jpg new file mode 100644 index 00000000..c6588b91 Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038013.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038014.jpg b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038014.jpg new file mode 100644 index 00000000..4a02c12a Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038014.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038015.jpg b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038015.jpg new file mode 100644 index 00000000..a0366b63 Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038015.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038016.jpg b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038016.jpg new file mode 100644 index 00000000..7213af14 Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038016.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038017.jpg b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038017.jpg new file mode 100644 index 00000000..b61c61e0 Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038017.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038018.jpg b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038018.jpg new file mode 100644 index 00000000..24e89aff Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038018.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038019.jpg b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038019.jpg new file mode 100644 index 00000000..6499aa7c Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038019.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038020.jpg b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038020.jpg new file mode 100644 index 00000000..9b6ace5a Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038020.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038021.jpg b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038021.jpg new file mode 100644 index 00000000..c2de3601 Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038021.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038022.jpg b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038022.jpg new file mode 100644 index 00000000..b7b0e3ae Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038022.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038023.jpg b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038023.jpg new file mode 100644 index 00000000..126cc5ab Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038023.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038024.jpg b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038024.jpg new file mode 100644 index 00000000..2e38929f Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038024.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038025.jpg b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038025.jpg new file mode 100644 index 00000000..6f8747ad Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038025.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038026.jpg b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038026.jpg new file mode 100644 index 00000000..62240f7f Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038026.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038027.jpg b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038027.jpg new file mode 100644 index 00000000..61b75269 Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038027.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038028.jpg b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038028.jpg new file mode 100644 index 00000000..fb5b043d Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038028.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038029.jpg b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038029.jpg new file mode 100644 index 00000000..a51a5e91 Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038029.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038030.jpg b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038030.jpg new file mode 100644 index 00000000..a2c32958 Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038030.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038031.jpg b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038031.jpg new file mode 100644 index 00000000..b50daea4 Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038031.jpg differ diff --git a/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038032.jpg b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038032.jpg new file mode 100644 index 00000000..06164666 Binary files /dev/null and b/furniture-3d-system/public/diy/images/styles/color-thumbs/SJ038032.jpg differ diff --git a/furniture-3d-system/public/diy/images/textures/carcass/SJ038033-hd.jpg b/furniture-3d-system/public/diy/images/textures/carcass/SJ038033-hd.jpg new file mode 100644 index 00000000..b7bdc8bb Binary files /dev/null and b/furniture-3d-system/public/diy/images/textures/carcass/SJ038033-hd.jpg differ diff --git a/furniture-3d-system/public/diy/images/textures/carcass/SJ038033.jpg b/furniture-3d-system/public/diy/images/textures/carcass/SJ038033.jpg new file mode 100644 index 00000000..2de0c3c6 Binary files /dev/null and b/furniture-3d-system/public/diy/images/textures/carcass/SJ038033.jpg differ diff --git a/furniture-3d-system/public/diy/images/textures/carcass/SJ038034-hd.jpg b/furniture-3d-system/public/diy/images/textures/carcass/SJ038034-hd.jpg new file mode 100644 index 00000000..785d5acc Binary files /dev/null and b/furniture-3d-system/public/diy/images/textures/carcass/SJ038034-hd.jpg differ diff --git a/furniture-3d-system/public/diy/images/textures/carcass/SJ038034.jpg b/furniture-3d-system/public/diy/images/textures/carcass/SJ038034.jpg new file mode 100644 index 00000000..0a6c7535 Binary files /dev/null and b/furniture-3d-system/public/diy/images/textures/carcass/SJ038034.jpg differ diff --git a/furniture-3d-system/public/diy/images/textures/carcass/SJ038035-hd.jpg b/furniture-3d-system/public/diy/images/textures/carcass/SJ038035-hd.jpg new file mode 100644 index 00000000..82480b84 Binary files /dev/null and b/furniture-3d-system/public/diy/images/textures/carcass/SJ038035-hd.jpg differ diff --git a/furniture-3d-system/public/diy/images/textures/carcass/SJ038035.jpg b/furniture-3d-system/public/diy/images/textures/carcass/SJ038035.jpg new file mode 100644 index 00000000..e87e5e2e Binary files /dev/null and b/furniture-3d-system/public/diy/images/textures/carcass/SJ038035.jpg differ diff --git a/furniture-3d-system/public/diy/images/textures/carcass/SJ038036-hd.jpg b/furniture-3d-system/public/diy/images/textures/carcass/SJ038036-hd.jpg new file mode 100644 index 00000000..0eddf9b2 Binary files /dev/null and b/furniture-3d-system/public/diy/images/textures/carcass/SJ038036-hd.jpg differ diff --git a/furniture-3d-system/public/diy/images/textures/carcass/SJ038036.jpg b/furniture-3d-system/public/diy/images/textures/carcass/SJ038036.jpg new file mode 100644 index 00000000..8f609b2b Binary files /dev/null and b/furniture-3d-system/public/diy/images/textures/carcass/SJ038036.jpg differ diff --git a/furniture-3d-system/scripts/verify-master-data-code-boundaries.js b/furniture-3d-system/scripts/verify-master-data-code-boundaries.js index 3c164987..157860ff 100644 --- a/furniture-3d-system/scripts/verify-master-data-code-boundaries.js +++ b/furniture-3d-system/scripts/verify-master-data-code-boundaries.js @@ -211,6 +211,7 @@ const schemaJsonFieldClassifications = new Map( ['AssistedAssemblyAiRender.workspaceSnapshot', ['process-snapshot', '辅助装配工作区快照。']], ['AssistedAssemblyAiRender.assetSnapshot', ['process-snapshot', '辅助装配资产快照。']], ['AssistedAssemblyAiRender.attempts', ['process-snapshot', '辅助装配 AI 生图尝试记录。']], + ['DiyScheme.snapshot', ['process-snapshot', 'DIY 组合方案配置与几何恢复快照;不创建正式产品、SKU 或条码。']], ['PreSku.familySelection', ['compatibility-snapshot', '准 SKU 资产族选择快照;正式基础资料主读必须走 PreSkuBasicInfo。']], ['PreSku.variantSelection', ['process-snapshot', '准 SKU 规格款组合快照。']], ['PreSku.recommendedHardware', ['process-snapshot', '准 SKU 推荐五金快照。']], @@ -758,7 +759,9 @@ const rules = [ '应用层不能把本地 JSON 文件读写或导入当作主数据真相;正式资料必须进入数据库或明确的服务端契约,文件 JSON 只能作为导入载荷、迁移计划、备份或测试夹具。', pattern: /\b(?:fs\.)?(?:promises\.)?(?:readFileSync|writeFileSync|readFile|writeFile)\s*\([\s\S]{0,220}\.json['"`][\s\S]{0,180}\)|\bimport\s+[^;\n]+?\s+from\s+['"`][^'"`]+\.json['"`]|\brequire\s*\(\s*['"`][^'"`]+\.json['"`]\s*\)|\bimport\s*\(\s*['"`][^'"`]+\.json['"`]\s*\)/g, - isAllowed: ({ relativePath }) => isTestFile(relativePath), + // DIY 的两个固定样本仅提供几何预览/参考明细,方案存入 DiyScheme;禁止写正式产品、SKU 或订单。 + isAllowed: ({ relativePath, match }) => isTestFile(relativePath) || + (relativePath === 'src/lib/diy/catalog.ts' && /^import (spuData|catalogData) from '\.\/data\/(spu|catalog)-SJ0380\.json'$/.test(match[0])), }, { id: 'reference-library-item-key-id-fallback', diff --git a/furniture-3d-system/src/app/api/diy/schemes/[code]/route.ts b/furniture-3d-system/src/app/api/diy/schemes/[code]/route.ts new file mode 100644 index 00000000..995a55ee --- /dev/null +++ b/furniture-3d-system/src/app/api/diy/schemes/[code]/route.ts @@ -0,0 +1,8 @@ +import { NextRequest } from 'next/server' +import { handleRequest, jsonSuccess } from '@/server/core/response' +import { diySchemeService } from '@/server/services/diy-scheme.service' + +export async function GET(request: NextRequest, context: { params: Promise<{ code: string }> }) { + const { code } = await context.params + return handleRequest(request, async ctx => jsonSuccess(await diySchemeService.get(code, ctx.user), 200, ctx.requestId), { requireAuth: true }) +} diff --git a/furniture-3d-system/src/app/api/diy/schemes/route.ts b/furniture-3d-system/src/app/api/diy/schemes/route.ts new file mode 100644 index 00000000..300f0ba5 --- /dev/null +++ b/furniture-3d-system/src/app/api/diy/schemes/route.ts @@ -0,0 +1,19 @@ +import { NextRequest } from 'next/server' +import { handleRequest, jsonSuccess, jsonError } from '@/server/core/response' +import { ErrorCode } from '@/server/core/errors' +import { diySchemeService } from '@/server/services/diy-scheme.service' + +export async function GET(request: NextRequest) { + return handleRequest(request, async ctx => jsonSuccess( + await diySchemeService.list(ctx.user, request.nextUrl.searchParams.get('page'), request.nextUrl.searchParams.get('pageSize')), 200, ctx.requestId, + ), { requireAuth: true }) +} +export async function POST(request: NextRequest) { + return handleRequest(request, async ctx => { + const text = await request.text() + if (Buffer.byteLength(text) > 256 * 1024) return jsonError(ErrorCode.FILE_TOO_LARGE, '方案不能超过256KB', 413, ctx.requestId) + let input: unknown + try { input = JSON.parse(text) } catch { return jsonError(ErrorCode.VALIDATION_FAILED, '方案格式无效', 400, ctx.requestId) } + return jsonSuccess(await diySchemeService.create(input, ctx.user), 201, ctx.requestId) + }, { requireAuth: true }) +} diff --git a/furniture-3d-system/src/app/diy/DiyWorkbench.tsx b/furniture-3d-system/src/app/diy/DiyWorkbench.tsx new file mode 100644 index 00000000..59eaa857 --- /dev/null +++ b/furniture-3d-system/src/app/diy/DiyWorkbench.tsx @@ -0,0 +1,94 @@ +'use client' + +import { useRef, useState } from 'react' +import dynamic from 'next/dynamic' +import { History, Save, FileSpreadsheet, Camera, Plus, Move, PanelTop } from 'lucide-react' +import Button from '@/components/ui/Button' +import Modal from '@/components/ui/Modal' +import type { DiyAccess } from '@/lib/diy/types' +import { useDiyWorkbench } from './hooks/useDiyWorkbench' +import { saveDiyScheme } from './lib/api' +import { downloadDiyFile } from './lib/download' +import DiyCabinetLibrary from './components/DiyCabinetLibrary' +import DiyInspector from './components/DiyInspector' +import DiyHistoryDialog from './components/DiyHistoryDialog' +import DiyBomDialog from './components/DiyBomDialog' + +const DiyViewport=dynamic(()=>import('./components/DiyViewport'),{ssr:false,loading:()=>
正在加载 3D 工作台…
}) +export default function DiyWorkbench({ access }: {access:DiyAccess}) { + const controller=useDiyWorkbench(),stage=useRef(null) + const [historyOpen,setHistoryOpen]=useState(false),[bomOpen,setBomOpen]=useState(false),[saveOpen,setSaveOpen]=useState(false) + const [confirmAction,setConfirmAction]=useState<'new'|'history'|null>(null) + const [name,setName]=useState('SJ0380 组合方案'),[saving,setSaving]=useState(false) + const [mobilePanel,setMobilePanel]=useState<'library'|'inspector'|null>(null) + const disabled=!access.canConfigure||saving + const save=async()=>{ + if(saving)return;setSaving(true) + try{const record=await saveDiyScheme(controller.saveSnapshot(),name,controller.scheme?.code);controller.setSaved(record);setSaveOpen(false)} + catch(error){controller.setFeedback({tone:'error',text:error instanceof Error?error.message:'方案保存失败'})} + finally{setSaving(false)} + } + const requestAction=(action:'new'|'history')=>{ + if(controller.dirty){setConfirmAction(action);return} + if(action==='new')controller.reset();else setHistoryOpen(true) + } + const capture=()=>{ + try{const canvas=stage.current?.querySelector('canvas');if(!canvas)throw new Error('3D画面还未就绪') + controller.setFeedback({tone:'success',text:'正在生成预览截图…'}) + canvas.toBlob(blob=>{if(!blob){controller.setFeedback({tone:'error',text:'截图生成失败'});return} + downloadDiyFile(blob,'DIY-SJ0380-preview.png');controller.setFeedback({tone:'success',text:'预览截图已开始下载'})},'image/png') + }catch(error){controller.setFeedback({tone:'error',text:error instanceof Error?error.message:'截图失败'})} + } + return
+
+

DIY

+ {controller.scheme?.name||'SJ0380 组合柜'}{controller.dirty?' · 未保存':''} +
+ + + + + +
+
+
+ +
+ +
+ {(['layout','doors'] as const).map(mode=>)} +
+
+ {controller.snapshot.items.length} 柜体 · {controller.cells} 格口 · {Object.keys(controller.snapshot.doors).length} 柜门 +
+ {controller.feedback&&
{controller.feedback.text}
} +
+ +
+
+ + +
+ setHistoryOpen(false)} controller={controller}/> + setBomOpen(false)} controller={controller} canDownload={access.canDownload}/> + {if(!saving)setSaveOpen(false)}} title="保存 DIY 方案" size="small" + footer={}> + + setName(event.target.value)} maxLength={80} disabled={saving} + className="mt-2 h-10 w-full rounded-lg border border-zinc-300 px-3 text-sm focus:border-blue-500 focus:outline-none"/> +

每次保存生成新方案编码,保留原有历史版本。

+ {controller.feedback?.tone==='error'&&

{controller.feedback.text}

} +
+ setConfirmAction(null)} title="当前方案尚未保存" size="small" + footer={<> + }> +

继续操作会放弃当前未保存的配置。

+
+ setMobilePanel(null)} title={mobilePanel==='library'?'柜体款式':'配色与柜门'} size="medium"> +
{mobilePanel==='library'?:}
+
+
+} diff --git a/furniture-3d-system/src/app/diy/components/DiyBomDialog.tsx b/furniture-3d-system/src/app/diy/components/DiyBomDialog.tsx new file mode 100644 index 00000000..598f70c4 --- /dev/null +++ b/furniture-3d-system/src/app/diy/components/DiyBomDialog.tsx @@ -0,0 +1,36 @@ +'use client' + +import { useMemo, useState } from 'react' +import Modal from '@/components/ui/Modal' +import Button from '@/components/ui/Button' +import { buildDiyBom, diyBomCsv, DIY_BOM_COLUMNS } from '@/lib/diy/bom' +import { downloadDiyFile, renderDiyBomImage } from '../lib/download' +import type { DiyWorkbenchController } from '../hooks/useDiyWorkbench' + +export default function DiyBomDialog({ open,onClose,controller:c,canDownload }: {open:boolean;onClose:()=>void;controller:DiyWorkbenchController;canDownload:boolean}) { + const rows=useMemo(()=>buildDiyBom(c.snapshot),[c.snapshot]),[busy,setBusy]=useState(false),[message,setMessage]=useState('') + const exportFile=async(kind:'csv'|'png')=>{ + setBusy(true);setMessage('正在生成下载文件…') + try{ + await new Promise(resolve=>requestAnimationFrame(resolve)) + const blob=kind==='csv'?new Blob([diyBomCsv(rows)],{type:'text/csv;charset=utf-8'}): + await renderDiyBomImage(rows,c.scheme?.name||'SJ0380 DIY',c.scheme?.code||'') + downloadDiyFile(blob,`DIY-${c.scheme?.code||'SJ0380'}-BOM.${kind}`);setMessage('文件已生成,已开始下载') + }catch(error){setMessage(error instanceof Error?error.message:'下载失败')} + finally{setBusy(false)} + } + return + {message} + + + }> +

柜体 {c.snapshot.items.length} 件 · 柜门 {Object.keys(c.snapshot.doors).length} 扇 · 柜体与柜门分开下单、分开打包

+
+ + {DIY_BOM_COLUMNS.map(column=>)} + {rows.map((row,index)=> + {DIY_BOM_COLUMNS.map(column=>)} + )}
{column.title}
{row[column.key]}
+
+
+} diff --git a/furniture-3d-system/src/app/diy/components/DiyCabinetLibrary.tsx b/furniture-3d-system/src/app/diy/components/DiyCabinetLibrary.tsx new file mode 100644 index 00000000..4a173ea4 --- /dev/null +++ b/furniture-3d-system/src/app/diy/components/DiyCabinetLibrary.tsx @@ -0,0 +1,36 @@ +'use client' + +import { Plus } from 'lucide-react' +import { DIY_STYLES, DIY_MAIN_COLORS, DIY_TEXTURES, getCatalogStyle, styleImage } from '@/lib/diy/catalog' +import type { DiyWorkbenchController } from '../hooks/useDiyWorkbench' + +export default function DiyCabinetLibrary({ controller, disabled }: { controller: DiyWorkbenchController; disabled: boolean }) { + return
+
+

SJ0380 组合柜

+
+ {DIY_MAIN_COLORS.map(color => )} +
+

{controller.family}

+
+
+ {DIY_STYLES.map(style => { + const entry = getCatalogStyle(style.code), count = controller.snapshot.items.filter(item => item.code === style.code).length + return + })} +
+
+} diff --git a/furniture-3d-system/src/app/diy/components/DiyCabinetMesh.tsx b/furniture-3d-system/src/app/diy/components/DiyCabinetMesh.tsx new file mode 100644 index 00000000..0a523a0d --- /dev/null +++ b/furniture-3d-system/src/app/diy/components/DiyCabinetMesh.tsx @@ -0,0 +1,71 @@ +'use client' + +import { useEffect, useMemo } from 'react' +import { useTexture } from '@react-three/drei' +import type { ThreeEvent } from '@react-three/fiber' +import * as THREE from 'three' +import { buildCabinet, buildDoor, isInteriorShelf } from '@/lib/diy/geometry' +import { cellKey, DIY_COLOR_HEX, DIY_PATTERNS, DIY_TEXTURES, getStyle } from '@/lib/diy/catalog' +import { toThreeGeometry } from '../lib/threeGeometry' +import type { CabinetCell, DiyItem } from '@/lib/diy/types' + +type CabinetPointer = (event: ThreeEvent, item: DiyItem) => void +interface Props { + item: DiyItem; doors: Record; selected: boolean; mode: 'layout'|'doors'; disabled: boolean + onDown: CabinetPointer; onMove: CabinetPointer; onUp: CabinetPointer + onDoor: (id:string,cellId:string) => void +} +function DoorMesh({ item, cell, code, onClick }: { item:DiyItem; cell:CabinetCell; code:string; onClick:()=>void }) { + const pattern=DIY_PATTERNS.find(pattern=>pattern.itemCode===code)! + const loadedTexture=useTexture(pattern.image) + const texture=useMemo(()=>{const value=loadedTexture.clone();value.colorSpace=THREE.SRGBColorSpace;value.needsUpdate=true;return value},[loadedTexture]) + useEffect(()=>()=>texture.dispose(),[texture]) + const geometry=useMemo(()=>toThreeGeometry(buildDoor(cell,buildCabinet(getStyle(item.code)).center,2),item.quarterTurn,true),[cell,item.code,item.quarterTurn]) + useEffect(()=>()=>geometry.dispose(),[geometry]) + return {event.stopPropagation();onClick()}}> + + +} +export default function DiyCabinetMesh({ item, doors, selected, mode, disabled, onDown, onMove, onUp, onDoor }:Props) { + const style=getStyle(item.code), [main,shelf]=item.colorName.split('+') + const loadedTexture=useTexture(DIY_TEXTURES[main]) + const texture=useMemo(()=>{ + const value=loadedTexture.clone();value.colorSpace=THREE.SRGBColorSpace + value.wrapS=value.wrapT=THREE.RepeatWrapping;value.anisotropy=8 + if(main==='榆木黑色'||main==='柚木色'){value.center.set(.5,.5);value.rotation=Math.PI/2} + value.needsUpdate=true;return value + },[loadedTexture,main]) + const geometry=useMemo(()=>({ + body:toThreeGeometry(buildCabinet(style,panel=>!isInteriorShelf(panel)),item.quarterTurn), + shelf:toThreeGeometry(buildCabinet(style,isInteriorShelf),item.quarterTurn), + }),[style,item.quarterTurn]) + useEffect(()=>()=>{geometry.body.dispose();geometry.shelf.dispose()},[geometry]) + useEffect(()=>()=>texture.dispose(),[texture]) + const center=useMemo(()=>buildCabinet(style).center,[style]) + const bounds=useMemo(()=>{ + geometry.body.computeBoundingBox();const box=geometry.body.boundingBox! + return new THREE.EdgesGeometry(new THREE.BoxGeometry(...box.getSize(new THREE.Vector3()).toArray() as [number,number,number])) + },[geometry]) + useEffect(()=>()=>bounds.dispose(),[bounds]) + const angle=item.quarterTurn*Math.PI/2 + return onDown(event,item)} onPointerMove={event=>onMove(event,item)} + onPointerUp={event=>onUp(event,item)} onPointerCancel={event=>onUp(event,item)}> + + + + {geometry.shelf.index?.count ? + + : null} + {selected && } + {style.cells.map(cell=>{ + const key=cellKey(item.instanceId,cell.id), door=doors[key] + const y=(cell.center[0]-center[1])*.01, z=(cell.center[1]-center[2])*.01 + const ry=y*Math.cos(angle)-z*Math.sin(angle), rz=y*Math.sin(angle)+z*Math.cos(angle) + return door ? {if(mode==='doors'&&!disabled)onDoor(item.instanceId,cell.id)}}/> : mode==='doors' && !disabled ? + {event.stopPropagation();onDoor(item.instanceId,cell.id)}}> + + :null + })} + +} diff --git a/furniture-3d-system/src/app/diy/components/DiyHistoryDialog.tsx b/furniture-3d-system/src/app/diy/components/DiyHistoryDialog.tsx new file mode 100644 index 00000000..d47fd6e6 --- /dev/null +++ b/furniture-3d-system/src/app/diy/components/DiyHistoryDialog.tsx @@ -0,0 +1,58 @@ +'use client' + +import { useEffect, useRef, useState } from 'react' +import Modal from '@/components/ui/Modal' +import Button from '@/components/ui/Button' +import type { DiySchemeDTO } from '@/lib/diy/types' +import { listDiySchemes, readDiyScheme } from '../lib/api' +import type { DiyWorkbenchController } from '../hooks/useDiyWorkbench' + +export default function DiyHistoryDialog({ open, onClose, controller }: {open:boolean;onClose:()=>void;controller:DiyWorkbenchController}) { + const [records,setRecords]=useState([]), [page,setPage]=useState(1),[pageSize,setPageSize]=useState(20) + const [total,setTotal]=useState(0),[pageInput,setPageInput]=useState('1') + const pages=Math.max(1,Math.ceil(total/pageSize)) + const [busy,setBusy]=useState(false),[error,setError]=useState('') + const generation=useRef(0) + const load=async()=>{ + const token=++generation.current;setBusy(true);setError('') + try{const result=await listDiySchemes(page,pageSize);if(token!==generation.current)return + setRecords(result.schemes);setTotal(result.total) + }catch(error){if(token===generation.current)setError(error instanceof Error?error.message:'读取失败')} + finally{if(token===generation.current)setBusy(false)} + } + useEffect(()=>{if(open)void load();return()=>{generation.current++}},[open,page,pageSize]) // eslint-disable-line react-hooks/exhaustive-deps + useEffect(()=>setPageInput(String(page)),[page]) + const jump=()=>{const target=Math.min(pages,Math.max(1,Number(pageInput)||1));setPage(target);setPageInput(String(target))} + const select=async(record:DiySchemeDTO)=>{ + setBusy(true);setError('') + try{controller.load(await readDiyScheme(record.code));onClose()} + catch(error){setError(error instanceof Error?error.message:'读取失败')} + finally{setBusy(false)} + } + return + {error&&

{error}

} +
+
+ {records.map(record=>)} + {!busy&&!error&&!records.length&&

还没有保存的方案

} +
+
+ {busy?'正在读取…':`共 ${total} 条`} + + setPageInput(event.target.value.replace(/\D/g,''))} onBlur={jump} + onKeyDown={event=>{if(event.key==='Enter')jump()}} className="h-7 w-9 rounded border border-zinc-200 text-center"/> / {pages} 页 + + +
+
+} diff --git a/furniture-3d-system/src/app/diy/components/DiyInspector.tsx b/furniture-3d-system/src/app/diy/components/DiyInspector.tsx new file mode 100644 index 00000000..54aabf8b --- /dev/null +++ b/furniture-3d-system/src/app/diy/components/DiyInspector.tsx @@ -0,0 +1,42 @@ +'use client' + +import { RotateCw, Trash2, Layers } from 'lucide-react' +import Button from '@/components/ui/Button' +import { DIY_COLOR_HEX, DIY_PATTERNS, getCatalogStyle } from '@/lib/diy/catalog' +import type { DiyWorkbenchController } from '../hooks/useDiyWorkbench' + +export default function DiyInspector({ controller: c, disabled }: { controller: DiyWorkbenchController; disabled: boolean }) { + return
+
+

当前柜体

+ {c.selected ? <> +

{getCatalogStyle(c.selected.code).name} · {c.selected.code}

+
+ + + +
+
{getCatalogStyle(c.selected.code).colors.map(color => )}
+ :

点击画面中的柜体,查看颜色与摆放操作。

} +
+
+

柜门图案

+

选择图案后,切换贴门并点击格口。

+
+ {DIY_PATTERNS.map(pattern => )} +
+
+
+} diff --git a/furniture-3d-system/src/app/diy/components/DiyViewport.tsx b/furniture-3d-system/src/app/diy/components/DiyViewport.tsx new file mode 100644 index 00000000..d7214b93 --- /dev/null +++ b/furniture-3d-system/src/app/diy/components/DiyViewport.tsx @@ -0,0 +1,92 @@ +'use client' + +import { Suspense, useEffect, useMemo, useRef, useState } from 'react' +import { Canvas, useThree, type ThreeEvent } from '@react-three/fiber' +import { Grid, OrbitControls } from '@react-three/drei' +import type { OrbitControls as OrbitControlsImpl } from 'three-stdlib' +import * as THREE from 'three' +import { ViewportStageShell } from '@/components/platform/ViewportStageShell' +import { descendants, itemSize } from '@/lib/diy/layout' +import type { DiyItem, DiySnapshot } from '@/lib/diy/types' +import type { DiyWorkbenchController } from '../hooks/useDiyWorkbench' +import DiyCabinetMesh from './DiyCabinetMesh' + +function CameraRig({ snapshot, recordCode, enabled, onCamera }: { + snapshot:DiySnapshot; recordCode:string; enabled:boolean; onCamera:(view:DiySnapshot['camera'])=>void +}) { + const { camera, size:viewportSize }=useThree(), controls=useRef(null) + const countKey=snapshot.items.map(item=>item.instanceId+item.quarterTurn).join(',') + const bounds=useMemo(()=>{ + const min=Math.min(0,...snapshot.items.map(item=>-item.offsetY-itemSize(item).width/2)) + const max=Math.max(0,...snapshot.items.map(item=>-item.offsetY+itemSize(item).width/2)) + const height=Math.max(3.34,...snapshot.items.map(item=>item.offsetZ+itemSize(item).height/2)) + const depth=3, sphereRadius=Math.hypot(max-min,height,depth)/2 + const halfFov=THREE.MathUtils.degToRad(35/2) + const limitingAngle=Math.min(halfFov,Math.atan(Math.tan(halfFov)*viewportSize.width/Math.max(1,viewportSize.height))) + return {center:new THREE.Vector3((min+max)/2,height/2,0),radius:sphereRadius/Math.sin(limitingAngle)*1.15} + },[snapshot.items,viewportSize.width,viewportSize.height]) + const boundsRef=useRef(bounds);boundsRef.current=bounds + useEffect(()=>{ + const b=boundsRef.current, angle=snapshot.camera.yaw, distance=b.radius*snapshot.camera.zoom + camera.position.set(b.center.x+Math.sin(angle)*distance,b.center.y,b.center.z+Math.cos(angle)*distance) + camera.lookAt(b.center); controls.current?.target.copy(b.center);controls.current?.update() + // Reframe structural changes and restored versions; dragging keeps the user's view. + // eslint-disable-next-line react-hooks/exhaustive-deps + },[countKey,recordCode,camera,viewportSize.width,viewportSize.height]) + return {if(controls.current)onCamera({yaw:controls.current.getAzimuthalAngle(),zoom:Math.max(.2,Math.min(5,controls.current.getDistance()/bounds.radius))})}}/> +} +function Scene({ controller:c, disabled }: {controller:DiyWorkbenchController;disabled:boolean}) { + const [drag,setDrag]=useState<{id:string;dy:number;dz:number}|null>(null) + const active=useRef<{id:string;start:THREE.Vector3;item:DiyItem;point:THREE.Vector3;moved:boolean}|null>(null) + const plane=useMemo(()=>new THREE.Plane(new THREE.Vector3(0,0,1),0),[]) + const down=(event:ThreeEvent,item:DiyItem)=>{ + if(disabled || c.mode!=='layout' || event.button!==0)return + event.stopPropagation();c.setSelectedId(item.instanceId) + const point=event.ray.intersectPlane(plane,new THREE.Vector3());if(!point)return + ;(event.target as unknown as {setPointerCapture:(id:number)=>void}).setPointerCapture(event.pointerId) + active.current={id:item.instanceId,start:point.clone(),point:point.clone(),item,moved:false} + setDrag({id:item.instanceId,dy:0,dz:0}) + } + const move=(event:ThreeEvent)=>{ + const current=active.current;if(!current)return + event.stopPropagation();const point=event.ray.intersectPlane(plane,new THREE.Vector3());if(!point)return + current.point=point;const delta=point.clone().sub(current.start) + if(delta.length()>.08)current.moved=true + setDrag({id:current.id,dy:-delta.x,dz:delta.y}) + } + const up=(event:ThreeEvent)=>{ + const current=active.current;if(!current)return + event.stopPropagation();(event.target as unknown as {releasePointerCapture:(id:number)=>void}).releasePointerCapture(event.pointerId) + if(current.moved){const delta=current.point.clone().sub(current.start);c.move(current.id,current.item.offsetY-delta.x,current.item.offsetZ+delta.y)} + active.current=null;setDrag(null) + } + const movingIds=drag?descendants(c.snapshot.items,drag.id):new Set() + return <> + + + + {c.snapshot.items.map(item=>)} + + +} +export default function DiyViewport({ controller, disabled }: {controller:DiyWorkbenchController;disabled:boolean}) { + return + {gl.setClearColor('#f2f4f4')}}> + + + {!controller.snapshot.items.length &&
+
从左侧添加柜体

组合、配色,再选择柜门图案

+
} +
+ {controller.mode==='layout'?'拖动柜体摆放 · 拖动空白处旋转 · 滚轮缩放':'点击绿色格口贴门 · 再次点击同图案移除'} +
+
+} diff --git a/furniture-3d-system/src/app/diy/hooks/useDiyWorkbench.ts b/furniture-3d-system/src/app/diy/hooks/useDiyWorkbench.ts new file mode 100644 index 00000000..3b702cd3 --- /dev/null +++ b/furniture-3d-system/src/app/diy/hooks/useDiyWorkbench.ts @@ -0,0 +1,74 @@ +'use client' + +import { useRef, useState } from 'react' +import { cellKey, DIY_MAIN_COLORS, getCatalogStyle, getStyle } from '@/lib/diy/catalog' +import { descendants, itemSize, moveItem, placeOnFloor } from '@/lib/diy/layout' +import type { DiySnapshot, DiySchemeDTO } from '@/lib/diy/types' + +export const EMPTY_DIY_SNAPSHOT: DiySnapshot = { version: 1, spu: 'SJ0380', items: [], doors: {}, camera: { yaw: .35, zoom: 1 } } +export function useDiyWorkbench() { + const [snapshot, setSnapshot] = useState(EMPTY_DIY_SNAPSHOT) + const [scheme, setScheme] = useState(null) + const [dirty, setDirty] = useState(false) + const [selectedId, setSelectedId] = useState('') + const [family, setFamily] = useState(DIY_MAIN_COLORS[2]) + const [patternCode, setPatternCode] = useState('SJ038043') + const [mode, setMode] = useState<'layout' | 'doors'>('layout') + const [feedback, setFeedback] = useState<{ tone: 'success' | 'error'; text: string } | null>(null) + const camera = useRef(snapshot.camera) + const change = (next: DiySnapshot) => { setSnapshot(next); setDirty(true); setFeedback(null) } + const add = (code: string) => { + if (snapshot.items.length >= 100) { setFeedback({ tone: 'error', text: '最多添加100个柜体' }); return } + const style = getCatalogStyle(code) + const colorName = (style.colors.find(color => color.name === family || color.name.startsWith(`${family}+`)) || style.colors[0]).name + const item = placeOnFloor({ instanceId: `cabinet-${crypto.randomUUID()}`, code, colorName, + offsetY: 0, offsetZ: 0, quarterTurn: 0, supportId: 'floor' }, snapshot.items) + change({ ...snapshot, items: [...snapshot.items, item] }); setSelectedId(item.instanceId) + } + const remove = (id: string) => { + const ids = descendants(snapshot.items, id) + if (ids.size > 1) { setFeedback({ tone: 'error', text: '请先移走上层柜体,再删除承托柜体' }); return } + const doors = Object.fromEntries(Object.entries(snapshot.doors).filter(([key]) => !key.startsWith(`${id}#`))) + change({ ...snapshot, items: snapshot.items.filter(item => item.instanceId !== id), doors }); setSelectedId('') + } + const rotate = (id: string) => { + const item = snapshot.items.find(item => item.instanceId === id) + if (!item) return + if (descendants(snapshot.items, id).size > 1) { setFeedback({ tone: 'error', text: '请先移走上层柜体,再切换横竖方向' }); return } + const turned = { ...item, quarterTurn: (item.quarterTurn+1)%4 } + const atFloor = placeOnFloor(turned, snapshot.items.filter(item => item.instanceId !== id)) + change({ ...snapshot, items: snapshot.items.map(item => item.instanceId === id ? atFloor : item) }) + } + const move = (id: string, y: number, z: number) => { + const items = moveItem(snapshot.items, id, y, z) + if (items) { change({ ...snapshot, items }); setFeedback({ tone: 'success', text: '柜体已吸附' }) } + else setFeedback({ tone: 'error', text: '当前位置无法承托柜体,请靠近地面或柜顶完整放置' }) + } + const setColor = (id: string, colorName: string) => change({ ...snapshot, + items: snapshot.items.map(item => item.instanceId === id ? { ...item, colorName } : item) }) + const toggleDoor = (id: string, cellId: string) => { + const key = cellKey(id, cellId), doors = { ...snapshot.doors } + if (doors[key] === patternCode) delete doors[key]; else doors[key] = patternCode + change({ ...snapshot, doors }) + } + const load = (record: DiySchemeDTO) => { + setSnapshot(record.snapshot); setScheme(record); setDirty(false); setSelectedId('') + camera.current = record.snapshot.camera; setFeedback({ tone: 'success', text: '方案已载入' }) + } + const reset = () => { change(EMPTY_DIY_SNAPSHOT); setScheme(null); setSelectedId(''); camera.current = EMPTY_DIY_SNAPSHOT.camera } + const saveSnapshot = (): DiySnapshot => ({ ...snapshot, camera: camera.current }) + const setSaved = (record: DiySchemeDTO) => { setScheme(record); setDirty(false); setFeedback({ tone: 'success', text: '方案已保存,历史版本已保留' }) } + const selected = snapshot.items.find(item => item.instanceId === selectedId) + const cells = snapshot.items.reduce((sum,item) => sum+getStyle(item.code).cellCount,0) + const stackSelected = () => { + if (!selected) return + const support = snapshot.items.find(item => item.instanceId !== selectedId && + !descendants(snapshot.items,selectedId).has(item.instanceId) && itemSize(item).width >= itemSize(selected).width) + if (!support) { setFeedback({tone:'error',text:'请先添加可完整承托的柜体'}); return } + move(selectedId,support.offsetY,support.offsetZ+itemSize(support).height/2+itemSize(selected).height/2) + } + return { snapshot, scheme, dirty, selected, selectedId, setSelectedId, family, setFamily, patternCode, setPatternCode, + mode, setMode, feedback, setFeedback, add, remove, rotate, move, setColor, toggleDoor, load, reset, saveSnapshot, setSaved, cells, stackSelected, + setCamera: (view: DiySnapshot['camera']) => { camera.current = view } } +} +export type DiyWorkbenchController = ReturnType diff --git a/furniture-3d-system/src/app/diy/lib/api.ts b/furniture-3d-system/src/app/diy/lib/api.ts new file mode 100644 index 00000000..e84b13e4 --- /dev/null +++ b/furniture-3d-system/src/app/diy/lib/api.ts @@ -0,0 +1,12 @@ +import type { DiySchemeDTO, DiySchemePage, DiySnapshot } from '@/lib/diy/types' +async function request(path: string, init?: RequestInit): Promise { + const response = await fetch(path, { cache: 'no-store', ...init }) + const body = await response.json().catch(() => null) + if (!response.ok || !body?.success) throw new Error(body?.error?.message || '请求失败,请稍后重试') + return body.data as T +} +export const listDiySchemes = (page = 1, pageSize = 20) => request(`/api/diy/schemes?page=${page}&pageSize=${pageSize}`) +export const saveDiyScheme = (snapshot: DiySnapshot, name: string, parentCode?: string) => request('/api/diy/schemes', { + method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ snapshot, name, parentCode }), +}) +export const readDiyScheme = (code: string) => request(`/api/diy/schemes/${encodeURIComponent(code)}`) diff --git a/furniture-3d-system/src/app/diy/lib/download.ts b/furniture-3d-system/src/app/diy/lib/download.ts new file mode 100644 index 00000000..8c40d5e7 --- /dev/null +++ b/furniture-3d-system/src/app/diy/lib/download.ts @@ -0,0 +1,34 @@ +import type { DiyBomRow } from '@/lib/diy/types' +import { DIY_BOM_COLUMNS } from '@/lib/diy/bom' + +export function downloadDiyFile(blob:Blob,name:string) { + const url=URL.createObjectURL(blob),link=document.createElement('a') + link.href=url;link.download=name;document.body.appendChild(link);link.click();link.remove() + setTimeout(()=>URL.revokeObjectURL(url),1000) +} +export async function renderDiyBomImage(rows:DiyBomRow[],name:string,code:string) { + const widths=[80,145,235,150,185,220,215,170,70] + const canvas=document.createElement('canvas'),scale=2,rowHeight=60 + const width=widths.reduce((sum,value)=>sum+value,0)+48,height=160+(rows.length+1)*rowHeight+55 + canvas.width=width*scale;canvas.height=height*scale + const ctx=canvas.getContext('2d');if(!ctx)throw new Error('无法生成明细图片') + ctx.scale(scale,scale);ctx.fillStyle='#fff';ctx.fillRect(0,0,width,height) + ctx.fillStyle='#2d3335';ctx.font='bold 26px Microsoft YaHei, sans-serif';ctx.fillText(`${name} · 下单明细`,24,42) + ctx.font='14px Microsoft YaHei, sans-serif';ctx.fillStyle='#64716e';ctx.fillText(code||'当前配置',24,72) + ctx.fillText('柜体与柜门分开下单、分开打包',24,100) + let x=24 + DIY_BOM_COLUMNS.forEach((column,index)=>{ctx.fillStyle='#eef2f1';ctx.fillRect(x,130,widths[index],rowHeight);ctx.fillStyle='#2d3335';ctx.fillText(column.title,x+8,166);x+=widths[index]}) + rows.forEach((row,index)=>{ + const y=130+(index+1)*rowHeight;x=24 + DIY_BOM_COLUMNS.forEach((column,i)=>{ + ctx.fillStyle=row.group==='柜体'?'#f5f8fc':'#fff9ef';ctx.fillRect(x,y,widths[i],rowHeight) + ctx.strokeStyle='#dfe5e3';ctx.strokeRect(x,y,widths[i],rowHeight) + ctx.fillStyle='#2d3335';const text=String(row[column.key]??'');let line='',lines:string[]=[] + for(const char of text){if(ctx.measureText(line+char).width>widths[i]-16){lines.push(line);line=char}else line+=char} + lines.push(line);if(lines.length>2){lines=lines.slice(0,2);lines[1]=lines[1].slice(0,-1)+'…'} + lines.forEach((line,j)=>ctx.fillText(line,x+8,y+24+j*20));x+=widths[i] + }) + }) + ctx.fillStyle='#64716e';ctx.fillText('辅助定位标随柜门打包,数量与门板一致。',24,height-20) + return new Promise((resolve,reject)=>canvas.toBlob(blob=>blob?resolve(blob):reject(new Error('明细图片生成失败')),'image/png')) +} diff --git a/furniture-3d-system/src/app/diy/lib/threeGeometry.ts b/furniture-3d-system/src/app/diy/lib/threeGeometry.ts new file mode 100644 index 00000000..1d806cf2 --- /dev/null +++ b/furniture-3d-system/src/app/diy/lib/threeGeometry.ts @@ -0,0 +1,28 @@ +import * as THREE from 'three' +import { rotateQuarterGeometry } from '@/lib/diy/geometry' +import type { GeometryData } from '@/lib/diy/types' + +export function toThreeGeometry(data: GeometryData, turns = 0, door = false) { + const rotated = rotateQuarterGeometry(data,turns) + const positions = new Float32Array(rotated.positions.length), normals = new Float32Array(rotated.normals.length) + for (let i=0;i +} diff --git a/furniture-3d-system/src/components/layout/AppShell.tsx b/furniture-3d-system/src/components/layout/AppShell.tsx index b8d42386..5356549b 100644 --- a/furniture-3d-system/src/components/layout/AppShell.tsx +++ b/furniture-3d-system/src/components/layout/AppShell.tsx @@ -34,6 +34,7 @@ export const APP_SHELL_ROOTS = [ '/warehouse-quality', '/ai-render-workbench', '/installation-demo-workbench', + '/diy', '/supply-chain', ] as const @@ -106,6 +107,7 @@ function isProductWorkbenchRoute(pathname: string) { '/warehouse-quality', '/ai-render-workbench', '/installation-demo-workbench', + '/diy', ].some((prefix) => pathname === prefix || pathname.startsWith(`${prefix}/`)) } @@ -146,6 +148,7 @@ function AppShellContent({ children, user, localOaBoardPreview = false }: AppShe pathname.startsWith('/installation-demo-workbench/') const isSupplyChainRoute = pathname === '/supply-chain' || pathname.startsWith('/supply-chain/') + const isDiyRoute = pathname === '/diy' || pathname.startsWith('/diy/') const isProfileRoute = pathname === '/profile' || pathname.startsWith('/profile/') const isUsersRoute = pathname === '/users' || pathname.startsWith('/users/') const isAdminRoute = pathname === '/admin' || pathname.startsWith('/admin/') @@ -166,6 +169,7 @@ function AppShellContent({ children, user, localOaBoardPreview = false }: AppShe isWarehouseQualityRoute || isAiRenderWorkbenchRoute || isInstallationDemoWorkbenchRoute || + isDiyRoute || isSupplyChainRoute // 手机/平板不渲染侧栏,内容占满整屏,切换依赖顶部下拉;桌面保持完整侧栏 const showProductWorkbenchSidebar = @@ -320,7 +324,7 @@ function AppShellContent({ children, user, localOaBoardPreview = false }: AppShe // 移动端:workbench 路由内容占满整屏,切换依赖顶部下拉;桌面端:保持原有布局 if (isWorkbenchShellRoute) { return ( -
+
{/* 桌面端显示完整顶部导航;移动端仅显示品牌栏 */} {isMobileOrTablet ? (