From 9cd3c0f23185e616fc9b08b0546b76d8f089c317 Mon Sep 17 00:00:00 2001 From: chenpei Date: Mon, 11 May 2026 10:01:08 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BB=BB=E5=8A=A1=E8=AF=A6=E6=83=85?= =?UTF-8?q?=E6=97=A0=E6=9D=A1=E7=A0=81=E5=BC=80=E5=85=B3=20+=20=E4=B8=8A?= =?UTF-8?q?=E4=BC=A0=E9=9D=A2=E6=9D=BF=E4=BF=A1=E6=81=AF=E5=AF=86=E5=BA=A6?= =?UTF-8?q?=E4=BC=98=E5=8C=96=20+=20=E5=8E=86=E5=8F=B2=E5=AF=BC=E5=85=A5/?= =?UTF-8?q?=E8=84=9A=E6=9C=AC=E8=A1=A5=E5=85=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 任务详情上传支持"本批素材无条码"开关,跳过条码必填校验 - SKU卡片/主图网格从4列改5列,图片高度压缩,去掉冗余标签和提示 - 详情图/PSD上传区去掉大占位框,改成紧凑按钮+标签 - 补充历史导入相关脚本和工具文件 Co-Authored-By: Claude Opus 4.6 --- app/api/materials/download/route.ts | 24 +- .../upload/product-package/complete/route.ts | 4 + components/upload/ProductPackageUploader.tsx | 204 ++- docs/生产存储桶接入信息.md | 46 +- lib/history-material-import.ts | 262 +++- lib/history-sku-csv-import.ts | 159 +- lib/product-package-completion.ts | 36 +- lib/remote-upload-sync.ts | 214 +++ lib/spu-parser.ts | 1 + lib/task-material-processor.ts | 69 +- lib/upload-paths.ts | 26 + scripts/README.md | 66 +- ..._import_records_from_package_candidates.py | 233 +++ ...iscover_needs_review_package_candidates.py | 340 +++++ scripts/export-history-import-scan-summary.js | 76 + scripts/export_sku_folder_db.py | 2 + scripts/export_spu_ledger.py | 259 ++++ .../filter_needs_review_dry_run_records.py | 148 ++ scripts/hard-delete-history-materials.ts | 214 +++ scripts/import-history-materials.ts | 101 ++ scripts/migrate-history-preview-assets.js | 337 +++++ scripts/review_needs_review_spu_groups.py | 315 ++++ scripts/run-tianlan-remaining-batches.ps1 | 202 +++ scripts/scan_sku_folders_to_sqlite.py | 169 ++- scripts/scan_spu_ledger.py | 1282 +++++++++++++++++ scripts/validate-history-import-result.ts | 274 ++++ 26 files changed, 4819 insertions(+), 244 deletions(-) create mode 100644 lib/remote-upload-sync.ts create mode 100644 lib/upload-paths.ts create mode 100644 scripts/build_import_records_from_package_candidates.py create mode 100644 scripts/discover_needs_review_package_candidates.py create mode 100644 scripts/export-history-import-scan-summary.js create mode 100644 scripts/export_spu_ledger.py create mode 100644 scripts/filter_needs_review_dry_run_records.py create mode 100644 scripts/hard-delete-history-materials.ts create mode 100644 scripts/migrate-history-preview-assets.js create mode 100644 scripts/review_needs_review_spu_groups.py create mode 100644 scripts/run-tianlan-remaining-batches.ps1 create mode 100644 scripts/scan_spu_ledger.py create mode 100644 scripts/validate-history-import-result.ts diff --git a/app/api/materials/download/route.ts b/app/api/materials/download/route.ts index bbecbca..e36004a 100644 --- a/app/api/materials/download/route.ts +++ b/app/api/materials/download/route.ts @@ -22,6 +22,11 @@ import { } from '@/lib/download-errors'; import { isProductPackageType } from '@/lib/material-type-mapper'; import { ensureMaterialDownloadLogsTable } from '@/lib/material-download-log'; +import { resolveStoredUploadPath } from '@/lib/upload-paths'; + +function normalizeStoredLocalPath(filePath: string) { + return String(filePath || '').replace(/\\/g, '/'); +} // GET - 下载素材 export async function GET(request: NextRequest) { @@ -156,10 +161,8 @@ export async function GET(request: NextRequest) { ); } - let filePath = material.zipFilePath; - if (!path.isAbsolute(filePath)) { - filePath = path.join(process.cwd(), filePath); - } + let filePath = normalizeStoredLocalPath(material.zipFilePath); + filePath = path.isAbsolute(filePath) ? filePath : resolveStoredUploadPath(filePath); return serveFile(filePath, material, downloadType || '源文件', validateOnly); } catch (error) { @@ -201,7 +204,10 @@ async function handleProductPackageDownload( where: { materialId: material.id, assetType, - fileUrl: { startsWith: 'uploads/' }, // 只查询本地 zip 文件 + OR: [ + { fileUrl: { startsWith: 'uploads/' } }, + { fileUrl: { startsWith: 'uploads\\' } }, + ], }, }); } @@ -214,10 +220,10 @@ async function handleProductPackageDownload( ); } - const filePath = asset.fileUrl; - const absolutePath = path.isAbsolute(filePath) - ? filePath - : path.join(process.cwd(), filePath); + const filePath = normalizeStoredLocalPath(asset.fileUrl); + const absolutePath = path.isAbsolute(filePath) + ? filePath + : resolveStoredUploadPath(filePath); console.log(`[Download] 文件路径: ${absolutePath}`); console.log(`[Download] 文件名: ${asset.fileName}`); diff --git a/app/api/materials/upload/product-package/complete/route.ts b/app/api/materials/upload/product-package/complete/route.ts index 3f3ed5e..6166339 100644 --- a/app/api/materials/upload/product-package/complete/route.ts +++ b/app/api/materials/upload/product-package/complete/route.ts @@ -33,6 +33,7 @@ interface PsdAsset { interface CompleteRequestBody { materialId: string taskNo?: string + noBarcodes?: boolean assets: { main?: AssetUpload[] sku?: AssetUpload[] @@ -79,6 +80,9 @@ export async function POST(request: NextRequest) { taskNo: body.taskNo, assets: body.assets, operatorUserId: (session.user as any).id, + options: { + allowSkuWithoutBarcodes: body.noBarcodes === true, + }, }) return NextResponse.json({ diff --git a/components/upload/ProductPackageUploader.tsx b/components/upload/ProductPackageUploader.tsx index 12d9908..326cd7a 100644 --- a/components/upload/ProductPackageUploader.tsx +++ b/components/upload/ProductPackageUploader.tsx @@ -114,6 +114,7 @@ export default function ProductPackageUploader({ const [isPsdDragging, setIsPsdDragging] = useState(false); // 上传状态 + const [noBarcodes, setNoBarcodes] = useState(false); const [uploading, setUploading] = useState(false); const [uploadProgress, setUploadProgress] = useState(0); const [processingStep, setProcessingStep] = useState(''); @@ -406,7 +407,7 @@ export default function ProductPackageUploader({ } const barcodes = parseBarcodeInput(image.barcodeInput || ''); - if (barcodes.length === 0) { + if (!noBarcodes && barcodes.length === 0) { throw new Error(`请为 SKU 图 ${image.file.name} 至少填写 1 个产品条码`); } @@ -560,6 +561,7 @@ export default function ProductPackageUploader({ body: JSON.stringify({ materialId, taskNo, // 传递taskNo,上传完成后写入数据库 + noBarcodes, assets: { main: mainAssets, sku: skuAssets, @@ -698,9 +700,9 @@ export default function ProductPackageUploader({ -
- 系统会按素材实际内容自动判定是否整套:同时拥有主图、SKU 图、详情图时,自动标记为整套,无需手工选择。 -
+

+ 同时上传主图 + SKU 图 + 详情图时自动标记为整套 +

{/* 主图上传 */}
@@ -729,15 +731,15 @@ export default function ProductPackageUploader({ className="hidden" disabled={uploading} /> -
+
{mainImages.map((img) => (
-
+
-

- 支持 JPG、PNG、WebP 等格式,可一次选择多张或拖拽上传。每张 SKU 图都要填写 SKU 编号,并录入至少 1 个产品条码。 -

{/* SKU图上传 */}
- +
+ + +
{/* biome-ignore lint/a11y/noStaticElementInteractions: 拖拽上传区域需要使用 drag/drop 事件 */}
-
+
{skuImages.map((img) => ( -
-
+
+
-
-
- - updateSkuImageMeta(img.id, { skuCode: e.target.value })} - placeholder="例如 SKU001" - disabled={uploading} - className="w-full rounded-apple border border-gray-300 px-3 py-2 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-white" - /> -
-
- -