feat(diy): add cabinet workbench with saved versions and BOM exports

This commit is contained in:
WuYongJie 2026-10-12 18:12:57 +08:00
parent b8093f899e
commit 12de551328
134 changed files with 4913 additions and 2 deletions

View File

@ -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;

View File

@ -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")

Binary file not shown.

After

Width:  |  Height:  |  Size: 467 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 666 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg width="16" height="16" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M24 44C29.5228 44 34.5228 41.7614 38.1421 38.1421C41.7614 34.5228 44 29.5228 44 24C44 18.4772 41.7614 13.4772 38.1421 9.85786C34.5228 6.23858 29.5228 4 24 4C18.4772 4 13.4772 6.23858 9.85786 9.85786C6.23858 13.4772 4 18.4772 4 24C4 29.5228 6.23858 34.5228 9.85786 38.1421C13.4772 41.7614 18.4772 44 24 44Z" fill="#0064fa" stroke="#0064fa" stroke-width="4" stroke-linejoin="round"/><path fill-rule="evenodd" clip-rule="evenodd" d="M24 11C25.3807 11 26.5 12.1193 26.5 13.5C26.5 14.8807 25.3807 16 24 16C22.6193 16 21.5 14.8807 21.5 13.5C21.5 12.1193 22.6193 11 24 11Z" fill="#FFF"/><path d="M24.5 34V20H23.5H22.5" stroke="#FFF" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path d="M21 34H28" stroke="#FFF" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></svg>

After

Width:  |  Height:  |  Size: 938 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -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',

View File

@ -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 })
}

View File

@ -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 })
}

Some files were not shown because too many files have changed in this diff Show More