15 KiB
15 KiB
标准模型素材类型逻辑梳理报告
📋 概述
标准模型是一种特殊的素材类型,用于以产品型号(SPU)为基准创建模型合集。上传时只需填写SPU和预览图,创建后可以在详情页添加各个SKU的模型文件。
🗂️ 数据库结构
Material 表(标准模型相关字段)
model Material {
id String @id @default(uuid())
previewUrl String // S3 URL(预览图)- 必填
productModel String // SPU型号 - 必填
categoryName String // 使用店铺 - 自动填充为"通用"
sceneName String // 场景类别 - 自动填充为"通用"
productCategoryName String // 品类分类 - 必填
materialType String // 素材类型 = "标准模型"
zipFileName String @default("") // 可选,标准模型主文件可选
zipFilePath String @default("") // 可选
zipFileSize BigInt @default(0) // 可选
// ... 其他字段
skus MaterialSku[] // 关联的SKU列表
}
MaterialSku 表
model MaterialSku {
id String @id @default(uuid())
materialId String @map("material_id")
skuName String @map("sku_name") // SKU名称(如:白色款)
filePath String @map("file_path") // 压缩包相对路径(如:skus/{materialId}/{fileName})
fileSize BigInt @default(0) @map("file_size")
sortOrder Int @default(0) @map("sort_order") // 排序字段
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
material Material @relation(fields: [materialId], references: [id], onDelete: Cascade)
@@index([materialId])
@@index([sortOrder])
@@map("material_skus")
}
⚠️ 发现的问题:
- MaterialSku表缺少
previewUrl字段,但前端组件(SkuList、SkuEditModal)都在使用该字段 - 前端上传预览图到S3后,在创建SKU时传递了
previewUrl,但后端API未接收和存储
📝 字段验证规则
上传时必填字段
| 字段 | 是否必填 | 说明 |
|---|---|---|
productModel |
✅ 必填 | SPU型号 |
productCategoryName |
✅ 必填 | 品类分类 |
previewImages |
✅ 必填 | 预览图(至少1张) |
categoryName |
❌ 自动填充 | 自动填充为"通用" |
sceneName |
❌ 自动填充 | 自动填充为"通用" |
zipFile |
❌ 可选 | 主文件可选(与模特类型相同) |
SKU添加时必填字段
| 字段 | 是否必填 | 说明 |
|---|---|---|
skuName |
✅ 必填 | SKU名称 |
previewUrl |
✅ 必填 | 预览图URL(前端上传到S3) |
zipFile |
✅ 必填 | 压缩包文件 |
🔄 完整流程
1. 标准模型上传流程
用户选择"标准模型"类型
↓
填写SPU型号(必填)
↓
选择品类(必填)
↓
上传预览图(必填,至少1张)
↓
上传主文件(可选)
↓
前端验证:
- requiresProductCategoryName() → 检查品类
- requiresCategoryAndScene() → 返回false(不需要店铺和场景)
- shouldAutoFillGeneral() → 返回true(自动填充"通用")
↓
提交到 /api/materials/upload
↓
后端处理:
- 验证productModel、productCategoryName
- 验证previewImages(必填)
- zipFile可选(不验证)
- 自动填充categoryName="通用", sceneName="通用"
- 上传预览图到S3
- 如果上传了zipFile,保存到本地uploads/
- 创建Material记录
↓
返回成功,跳转到详情页
关键代码位置:
- 前端验证:
components/UploadModal.tsx(lines 1292-1329) - 后端验证:
app/api/materials/upload/route.ts(lines 80-102) - 自动填充逻辑:
lib/material-validation.ts(lines 18-34)
2. SKU添加流程
用户在详情页点击"编辑SKU"
↓
打开SkuEditModal弹窗
↓
点击"添加SKU"
↓
填写SKU名称
↓
上传预览图 → /api/upload/preview → 返回S3 URL
↓
上传压缩包 → /api/materials/{id}/skus/upload → 保存到uploads/skus/{materialId}/
↓
点击"添加"按钮
↓
调用 POST /api/materials/{id}/skus
Body: {
skuName: string,
previewUrl: string, // ⚠️ 前端传递了,但后端未接收
zipFileName: string,
zipFilePath: string,
zipFileSize: number
}
↓
后端创建SKU记录(⚠️ 缺少previewUrl字段)
↓
刷新SKU列表
关键代码位置:
- SKU上传:
components/SkuEditModal.tsx(lines 78-211) - 预览图上传:
components/SkuEditModal.tsx(lines 108-131) - 后端API:
app/api/materials/[id]/skus/route.ts(lines 48-110) - 文件上传API:
app/api/materials/[id]/skus/upload/route.ts
3. SKU列表展示流程
用户访问标准模型详情页
↓
检查materialType === '标准模型'
↓
渲染SkuList组件
↓
调用 GET /api/materials/{id}/skus
↓
后端返回SKU列表(按sortOrder排序)
↓
前端渲染SKU卡片:
- 显示预览图(⚠️ 使用sku.previewUrl,但数据库中没有该字段)
- 显示SKU名称
- 显示文件大小
- 显示下载按钮
↓
用户可以选择SKU并下载
关键代码位置:
- 详情页:
app/share/[id]/page.tsx(lines 519-528) - SKU列表组件:
components/SkuList.tsx - SKU列表API:
app/api/materials/[id]/skus/route.ts(lines 8-46)
4. SKU下载流程
用户点击SKU的"下载"按钮
↓
调用 GET /api/materials/{id}/skus/{skuId}/download
↓
后端验证:
- 检查用户登录状态
- 检查用户下载权限(canDownload)
- 获取SKU信息
- 构建文件路径:uploads/{sku.filePath}
- 读取文件并返回
↓
前端接收文件流并触发下载
关键代码位置:
- 下载API:
app/api/materials/[id]/skus/[skuId]/download/route.ts - 前端下载:
components/SkuList.tsx(lines 100-129)
🔧 API接口清单
标准模型上传
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/materials/upload |
上传标准模型素材 |
请求参数:
productModel: string (必填)productCategoryName: string (必填)previewImage: File (必填,第一张预览图)previewImages: File[] (可选,其他预览图)zipFile: File (可选,主文件)materialType: "标准模型"categoryName: 自动填充为"通用"sceneName: 自动填充为"通用"
SKU管理
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/materials/{id}/skus |
获取SKU列表 |
| POST | /api/materials/{id}/skus |
添加SKU |
| GET | /api/materials/{id}/skus/{skuId} |
获取SKU详情 |
| PUT | /api/materials/{id}/skus/{skuId} |
更新SKU |
| DELETE | /api/materials/{id}/skus/{skuId} |
删除SKU |
| PUT | /api/materials/{id}/skus/reorder |
批量更新SKU排序 |
| POST | /api/materials/{id}/skus/upload |
上传SKU文件 |
权限控制:
- 只有素材上传者或管理员可以添加/编辑/删除SKU
- 所有登录用户都可以查看和下载SKU
🎨 前端组件
1. UploadModal(上传弹窗)
文件位置: components/UploadModal.tsx
标准模型相关逻辑:
- 选择"标准模型"类型时,隐藏店铺和场景字段
- 显示品类选择字段(必填)
- 显示标准模型说明提示框
- 验证:SPU、品类、预览图必填,主文件可选
关键代码:
// 验证品类(line 1292-1295)
if (requiresProductCategoryName(materialType) && !productCategoryName) {
setError('标准模型类型必须选择品类')
return
}
// 验证主文件(line 1302-1306)
if (materialType !== '模特' && materialType !== '标准模型' && !zipFile) {
setError('请上传素材文件')
return
}
// 验证预览图(line 1326-1329)
if (materialType === '标准模型' && previewImages.length === 0) {
setError('标准模型必须上传预览图')
return
}
2. UploadForm(上传表单字段)
文件位置: components/upload/UploadForm.tsx
标准模型相关逻辑:
- 标准模型不显示店铺和场景字段(line 144-210, 212-278)
- 标准模型显示品类字段(必填)(line 280-300)
- 标准模型不显示平台字段(line 302-322)
- 显示标准模型说明提示(line 343-356)
3. SkuList(SKU列表组件)
文件位置: components/SkuList.tsx
功能:
- 显示SKU列表(按sortOrder排序)
- 支持全选/单选SKU
- 支持单个/批量下载SKU
- 支持预览图点击放大
- 显示SKU名称、文件大小
⚠️ 问题:
- 使用
sku.previewUrl字段(line 231),但数据库中没有该字段 - API返回的SKU数据中没有
previewUrl
4. SkuEditModal(SKU编辑弹窗)
文件位置: components/SkuEditModal.tsx
功能:
- 显示SKU列表
- 添加新SKU(名称、预览图、压缩包)
- 删除SKU
- 调整SKU排序(上移/下移)
- 支持拖拽上传预览图和压缩包
⚠️ 问题:
- 上传预览图到S3后,在创建SKU时传递了
previewUrl(line 186) - 但后端API未接收和存储
previewUrl字段
⚠️ 发现的问题
1. MaterialSku表缺少previewUrl字段
问题描述:
- 前端组件(SkuList、SkuEditModal)都在使用
sku.previewUrl字段 - 但MaterialSku表结构中没有
previewUrl字段 - 前端上传预览图后,在创建SKU时传递了
previewUrl,但后端未接收和存储
影响:
- SKU列表无法显示预览图
- SKU编辑弹窗中无法显示已上传的预览图
解决方案:
- 在MaterialSku表中添加
previewUrl字段 - 修改后端API接收和存储
previewUrl - 更新Prisma schema并执行迁移
2. SKU上传API的字段不一致
问题描述:
SkuEditModal中上传压缩包后,调用POST /api/materials/{id}/skus时传递了:skuNamepreviewUrlzipFileNamezipFilePathzipFileSize
- 但后端API只接收了:
skuNamefilePath(对应前端的zipFilePath)fileSize(对应前端的zipFileSize)
- 缺少
previewUrl和zipFileName的接收
解决方案:
- 修改后端API接收
previewUrl和zipFileName - 在MaterialSku表中添加
previewUrl和zipFileName字段(如果需要)
3. SKU文件路径存储方式不一致
问题描述:
POST /api/materials/{id}/skus/upload保存文件到uploads/skus/{materialId}/,返回相对路径skus/{materialId}/{fileName}POST /api/materials/{id}/skus接收filePath,直接存储到数据库- 但前端传递的是
zipFilePath,需要确认字段名是否一致
解决方案:
- 统一字段命名:使用
filePath存储相对路径,使用zipFileName存储文件名
📂 文件存储结构
标准模型主文件(可选)
uploads/
{timestamp}-{random}.zip # 如果上传了主文件
SKU文件
uploads/
skus/
{materialId}/
{skuName}_{timestamp}.zip
{skuName}_{timestamp}.zip
...
预览图(S3存储)
- Material预览图:
s3://bucket/preview-{timestamp}-{random}.{ext} - SKU预览图:
s3://bucket/preview-{timestamp}-{random}.{ext}(如果添加了previewUrl字段)
🔍 验证逻辑代码位置
前端验证
文件: lib/material-validation.ts
// 标准模型不需要店铺和场景
export function requiresCategoryAndScene(materialType: string): boolean {
return !AUTO_GENERAL_TYPES.includes(materialType) && materialType !== '标准模型';
}
// 标准模型需要品类
export function requiresProductCategoryName(materialType: string): boolean {
return materialType === '标准模型';
}
// 标准模型自动填充"通用"
export function shouldAutoFillGeneral(materialType: string): boolean {
return AUTO_GENERAL_TYPES.includes(materialType) || materialType === '标准模型';
}
后端验证
文件: app/api/materials/upload/route.ts
// 验证主文件(line 94-102)
const requiresMainFile = !['模特', '标准模型', '买家秀图片', '小红书种草'].includes(materialType);
const hasMainFile = !!zipFile || !!existingFilePath;
if (requiresMainFile && !hasMainFile) {
return NextResponse.json({ error: '请上传素材文件' }, { status: 400 });
}
📊 数据流图
┌─────────────────┐
│ 用户上传 │
│ 标准模型 │
└────────┬────────┘
│
▼
┌─────────────────┐
│ UploadModal │
│ - 验证字段 │
│ - 上传预览图 │
│ - 上传主文件 │
└────────┬────────┘
│
▼
┌─────────────────┐
│ /api/materials │
│ /upload │
│ - 创建Material │
└────────┬────────┘
│
▼
┌─────────────────┐
│ 详情页展示 │
│ - 显示预览图 │
│ - 显示SKU列表 │
└────────┬────────┘
│
▼
┌─────────────────┐
│ 用户添加SKU │
│ - 上传预览图 │
│ - 上传压缩包 │
└────────┬────────┘
│
▼
┌─────────────────┐
│ /api/materials │
│ /{id}/skus │
│ - 创建SKU │
└────────┬────────┘
│
▼
┌─────────────────┐
│ SKU列表更新 │
│ - 显示预览图 │
│ - 支持下载 │
└─────────────────┘
🎯 待完成工作
高优先级
-
修复MaterialSku表缺少previewUrl字段
- 在Prisma schema中添加
previewUrl字段 - 执行数据库迁移
- 修改后端API接收和存储
previewUrl - 更新前端组件使用新的字段
- 在Prisma schema中添加
-
统一SKU字段命名
- 确认
zipFileName是否需要存储 - 统一使用
filePath存储相对路径 - 更新前端传递的字段名
- 确认
-
完善SKU上传流程
- 确保预览图上传后URL正确传递
- 确保压缩包上传后路径正确传递
- 添加错误处理和用户提示
中优先级
-
优化SKU列表展示
- 添加加载状态
- 添加空状态提示
- 优化预览图显示(如果修复了previewUrl字段)
-
添加SKU编辑功能
- 支持修改SKU名称
- 支持更换预览图
- 支持更换压缩包
低优先级
- 批量操作优化
- 支持批量删除SKU
- 支持批量下载SKU(已实现,但可以优化)
- 支持拖拽排序
📚 相关文档
项目文档.md- 项目整体架构和配置docs/upload-spec-from-decompiled.md- 上传规范说明批量上传流程完整检查报告.md- 批量上传逻辑检查
✅ 总结
标准模型素材类型的基本逻辑已经实现,包括:
- ✅ 上传时的字段验证和自动填充
- ✅ SKU的添加、删除、排序功能
- ✅ SKU的下载功能
- ✅ 详情页SKU列表展示
但存在以下问题需要修复:
- ❌ MaterialSku表缺少
previewUrl字段 - ❌ 后端API未接收和存储
previewUrl - ❌ 字段命名不一致(
zipFilePathvsfilePath)
修复这些问题后,标准模型功能将完全可用。