diff --git a/app/api/materials/upload/complete/route.ts b/app/api/materials/upload/complete/route.ts index 6f4a88b..e5140b0 100644 --- a/app/api/materials/upload/complete/route.ts +++ b/app/api/materials/upload/complete/route.ts @@ -9,6 +9,7 @@ import fs from 'fs/promises' import { createWriteStream, createReadStream } from 'fs' import { pipeline } from 'stream/promises' import { getUploadSession, deleteUploadSession } from '@/lib/upload-sessions' +import { sanitizeFileName } from '@/lib/image-processor' export const runtime = 'nodejs' export const dynamic = 'force-dynamic' @@ -62,7 +63,10 @@ export async function POST(req: NextRequest) { // 合并文件 await fs.mkdir(tempDir, { recursive: true }) - const mergedFileName = originalFileName || uploadSession.fileName || `upload-${uploadId}` + // 处理文件名:提取纯文件名(去除路径),并清理特殊字符,避免中文路径或特殊字符导致的问题 + const rawFileName = originalFileName || uploadSession.fileName || `upload-${uploadId}` + const baseFileName = path.basename(rawFileName) // 提取文件名部分,去除路径 + const mergedFileName = sanitizeFileName(baseFileName) || `upload-${uploadId}` // 清理特殊字符 const mergedPath = path.join(tempDir, `merged-${mergedFileName}`) const mergedStream = createWriteStream(mergedPath) diff --git a/components/UploadModal.tsx b/components/UploadModal.tsx index e2dd176..531aab5 100644 --- a/components/UploadModal.tsx +++ b/components/UploadModal.tsx @@ -75,7 +75,7 @@ export default function UploadModal({ onClose, onSuccess }: UploadModalProps) { // 音乐:最多100个 const [batchMusicFiles, setBatchMusicFiles] = useState>([]) // 种草视频:最多8个 - const [batchVideoFiles, setBatchVideoFiles] = useState>([]) + const [batchVideoFiles, setBatchVideoFiles] = useState>([]) // 3D场景:最多5个,每个需要预览图 const [batch3DFiles, setBatch3DFiles] = useState>([]) // 品牌VI:最多50个PDF @@ -1036,7 +1036,7 @@ export default function UploadModal({ onClose, onSuccess }: UploadModalProps) { setError('') try { - await uploadBatchVideo(batchVideoFiles, platformName, renderer, (progress) => { + await uploadBatchVideo(batchVideoFiles, platformName, renderer, productCategoryName, (progress) => { setUploadProgress(progress) }) @@ -1758,11 +1758,17 @@ export default function UploadModal({ onClose, onSuccess }: UploadModalProps) { {(materialType === '种草视频' && batchVideoFiles.length > 0) && ( { const newFiles = [...batchVideoFiles] newFiles[index].productModel = value setBatchVideoFiles(newFiles) }} + onUpdateProductCategory={(index, value) => { + const newFiles = [...batchVideoFiles] + newFiles[index].productCategoryName = value + setBatchVideoFiles(newFiles) + }} onDelete={(index) => { setBatchVideoFiles(batchVideoFiles.filter((_, i) => i !== index)) }} diff --git a/components/upload/SpecialBatchUploadUI.tsx b/components/upload/SpecialBatchUploadUI.tsx index ec03496..dc326bc 100644 --- a/components/upload/SpecialBatchUploadUI.tsx +++ b/components/upload/SpecialBatchUploadUI.tsx @@ -78,41 +78,75 @@ export function BatchMusicList({ files, onUpdateProductModel, onDelete }: BatchM // ========== 视频批量上传列表 ========== +interface BatchVideoFile extends BatchFileWithSpu { + productCategoryName?: string +} + interface BatchVideoListProps { - files: BatchFileWithSpu[] + files: BatchVideoFile[] + productCategories: Array<{ id: string; name: string }> onUpdateProductModel: (index: number, value: string) => void + onUpdateProductCategory?: (index: number, value: string) => void onDelete: (index: number) => void } -export function BatchVideoList({ files, onUpdateProductModel, onDelete }: BatchVideoListProps) { +export function BatchVideoList({ + files, + productCategories, + onUpdateProductModel, + onUpdateProductCategory, + onDelete +}: BatchVideoListProps) { if (files.length === 0) return null return (
{files.map((item, index) => ( -
-
-

- {item.file.name} -

-

- {formatBytes(item.file.size)} -

+
+
+
+

+ {item.file.name} +

+

+ {formatBytes(item.file.size)} +

+
+ onUpdateProductModel(index, e.target.value)} + className="flex-1 px-3 py-1.5 text-sm rounded-apple border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white" + placeholder="SPU型号" + /> +
- onUpdateProductModel(index, e.target.value)} - className="flex-1 px-3 py-1.5 text-sm rounded-apple border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white" - placeholder="SPU型号" - /> - + {/* 品类字段 */} + {productCategories.length > 0 && onUpdateProductCategory && ( +
+ + +
+ )}
))}
diff --git a/components/upload/special-batch-uploaders.ts b/components/upload/special-batch-uploaders.ts index 85506b3..45bbb3c 100644 --- a/components/upload/special-batch-uploaders.ts +++ b/components/upload/special-batch-uploaders.ts @@ -184,9 +184,10 @@ export async function uploadBatchMusic( * 批量上传种草视频 */ export async function uploadBatchVideo( - files: Array<{ file: File; productModel: string }>, + files: Array<{ file: File; productModel: string; productCategoryName?: string }>, platformName: string, renderer: string, + productCategoryName: string, onProgress: (progress: number) => void ) { const baseMetadata = { @@ -195,6 +196,7 @@ export async function uploadBatchVideo( sceneName: '通用', platformName: platformName || '', renderer: renderer, + productCategoryName: productCategoryName || '', } const results = await Promise.all( @@ -203,6 +205,7 @@ export async function uploadBatchVideo( const metadata = { ...baseMetadata, productModel: item.productModel, + productCategoryName: item.productCategoryName || productCategoryName || '', fileName: item.file.name, } diff --git a/scripts/README.md b/scripts/README.md index b94c36d..12ecece 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -2,35 +2,54 @@ ## 核心部署脚本 -### `deploy-local-build.ps1` ⭐ -**主部署脚本** - 本地构建镜像并部署到服务器 -- 支持影子系统和正式系统 -- 本地构建 Docker 镜像 -- 支持清理旧镜像 +### `deploy-git-build.ps1` ⭐ +**主部署脚本** - Git 工作流部署(推荐使用) +- 本地提交代码到 Git +- 服务器从 Git 拉取代码 +- 服务器端构建 Docker 镜像 +- 自动部署新容器 +- 支持清理旧镜像(保留最新4个) **重要提示:** -- ⚠️ **文件位置**:此脚本位于**项目根目录**(`d:\gongxiangp2\deploy-local-build.ps1`),**不在** `scripts` 目录 -- ⚠️ **错误记录**:之前误判文件不存在,实际文件存在于项目根目录,请勿重复此错误 +- ⚠️ **文件位置**:此脚本位于**项目根目录**(`d:\gongxiangp2\deploy-git-build.ps1`),**不在** `scripts` 目录 +- ⚠️ **部署流程**:本地提交 → Git推送 → 服务器拉取 → 服务器构建 → 部署 **使用方式:** ```powershell # 在项目根目录执行(d:\gongxiangp2\) +# 部署到正式系统(端口 4320,自动提交并推送代码) +.\deploy-git-build.ps1 -Target production + # 部署到影子系统(端口 5320) -.\deploy-local-build.ps1 -Target shadow +.\deploy-git-build.ps1 -Target shadow -# 部署到正式系统(端口 4320) -.\deploy-local-build.ps1 -Target production +# 指定提交信息 +.\deploy-git-build.ps1 -Target production -CommitMessage "修复BUG" -# 部署并清理旧镜像 -.\deploy-local-build.ps1 -Target production -CleanImages +# 仅提交不推送(用于本地测试) +.\deploy-git-build.ps1 -Target shadow -SkipPush -# 跳过构建,直接部署 -.\deploy-local-build.ps1 -Target shadow -SkipBuild +# 跳过提交,直接推送已有提交 +.\deploy-git-build.ps1 -Target production -SkipCommit + +# 构建镜像时禁用缓存 +.\deploy-git-build.ps1 -Target production -NoCache + +# 跳过构建产物清理 +.\deploy-git-build.ps1 -Target production -SkipCleanup # 显示帮助 -.\deploy-local-build.ps1 -Help +.\deploy-git-build.ps1 -Help ``` +**参数说明:** +- `-Target`: 部署目标,`shadow`(影子系统,端口5320)或 `production`(正式系统,端口4320) +- `-CommitMessage`: 提交信息(如果为空,将自动生成) +- `-SkipCommit`: 跳过Git提交(直接推送到远程) +- `-SkipPush`: 跳过Git推送(仅提交,不推送) +- `-NoCache`: 构建镜像时禁用缓存 +- `-SkipCleanup`: 跳过构建产物清理 + ## 基础镜像管理 ### `save-base-images.ps1`