/** * 上传表单字段组件 * 包含 SPU、店铺、场景、平台、品类、渲染器等表单字段 */ 'use client' import { useState, useEffect, useRef } from 'react' import { requiresCategoryAndScene, requiresProductCategoryName, requiresRenderer, shouldAutoFillGeneral } from '@/lib/material-validation' import { fieldControlClass, fieldErrorClass, fieldHintClass, fieldLabelClass, inkButtonClass, outlineButtonClass, } from '@/components/upload/upload-field-styles' import { parseSPU, type SPUParseResult } from '@/lib/spu-parser' import { isVideoMaterialType } from '@/lib/video-material-types' import { getSpuProductModelValidationError, isPlausibleSpuProductModel, normalizeSpuProductModel, } from '@/lib/product-model' /** * 分类数据项 */ interface CategoryItem { id: string name: string } /** * 表单字段 Props */ export interface UploadFormProps { /** 素材类型 */ materialType: string /** SPU型号 */ productModel: string /** 店铺名称 */ categoryName: string /** 场景名称 */ sceneName: string /** 平台名称 */ platformName: string /** 品类名称 */ productCategoryName: string /** 渲染器 */ renderer: string /** SKU(视频类素材和任务-详情使用) */ sku?: string /** 店铺列表 */ categories: CategoryItem[] /** 场景列表 */ scenes: CategoryItem[] /** 平台列表 */ platforms: CategoryItem[] /** 品类列表 */ productCategories: CategoryItem[] /** 是否批量模式(批量模式下隐藏部分字段) */ isBatchMode?: boolean /** SPU变更回调 */ onProductModelChange: (value: string) => void /** 店铺变更回调 */ onCategoryChange: (value: string) => void /** 场景变更回调 */ onSceneChange: (value: string) => void /** 平台变更回调 */ onPlatformChange: (value: string) => void /** 品类变更回调 */ onProductCategoryChange: (value: string) => void /** 渲染器变更回调 */ onRendererChange: (value: string) => void /** SKU变更回调 */ onSkuChange?: (value: string) => void /** 添加店铺回调 */ onAddCategory: (name: string) => Promise /** 添加场景回调 */ onAddScene: (name: string) => Promise } /** * 上传表单字段组件 */ export default function UploadForm({ materialType, productModel, categoryName, sceneName, platformName, productCategoryName, renderer, sku = '', categories, scenes, platforms, productCategories, isBatchMode = false, onProductModelChange, onCategoryChange, onSceneChange, onPlatformChange, onProductCategoryChange, onRendererChange, onSkuChange, onAddCategory, onAddScene, }: UploadFormProps) { // 新建店铺/场景输入状态 const [showNewCategoryInput, setShowNewCategoryInput] = useState(false) const [newCategoryName, setNewCategoryName] = useState('') const [showNewSceneInput, setShowNewSceneInput] = useState(false) const [newSceneName, setNewSceneName] = useState('') // SPU 自动识别状态 const [spuParseResult, setSpuParseResult] = useState(null) const [shopAutoFilled, setShopAutoFilled] = useState(false) const [categoryAutoFilled, setCategoryAutoFilled] = useState(false) const lastParsedSPU = useRef('') const isAutoGeneralType = shouldAutoFillGeneral(materialType) // SPU 变化时自动解析并填充店铺和品类 useEffect(() => { // 避免重复解析相同的 SPU if (productModel === lastParsedSPU.current) { return } lastParsedSPU.current = productModel // 材质类型不需要解析 if (materialType === '材质' || !productModel) { setSpuParseResult(null) setShopAutoFilled(false) setCategoryAutoFilled(false) return } const result = parseSPU(productModel) setSpuParseResult(result) // 自动填充店铺(如果识别成功且店铺列表中存在该店铺) if (result.shopMatched && result.shopName) { const shopExists = categories.some(c => c.name === result.shopName) if (shopExists && categoryName !== result.shopName) { onCategoryChange(result.shopName) setShopAutoFilled(true) } else if (!shopExists) { // 店铺不在列表中,清除自动填充标记 setShopAutoFilled(false) } } else { setShopAutoFilled(false) } // 自动填充品类(如果识别成功且品类列表中存在该品类) if (result.categoryMatched && result.categoryName) { const categoryExists = productCategories.some(c => c.name === result.categoryName) if (categoryExists && productCategoryName !== result.categoryName) { onProductCategoryChange(result.categoryName) setCategoryAutoFilled(true) } else if (!categoryExists) { // 品类不在列表中,清除自动填充标记 setCategoryAutoFilled(false) } } else { setCategoryAutoFilled(false) } }, [productModel, materialType, categories, productCategories, categoryName, productCategoryName, onCategoryChange, onProductCategoryChange]) // 处理添加新店铺 const handleAddNewCategory = async () => { if (!newCategoryName.trim()) return await onAddCategory(newCategoryName.trim()) setNewCategoryName('') setShowNewCategoryInput(false) } // 处理添加新场景 const handleAddNewScene = async () => { if (!newSceneName.trim()) return await onAddScene(newSceneName.trim()) setNewSceneName('') setShowNewSceneInput(false) } // 批量模式下不显示这些字段 if (isBatchMode) { return null } // 品牌VI灵感类型:只显示SPU和品类 const isBrandVI = materialType === '品牌VI灵感' return ( <> {/* SPU 或材质名称 */}
{ // 材质类型支持中文,其他类型只允许英文、数字和常见符号 if (materialType === '材质') { onProductModelChange(e.target.value) } else { const value = normalizeSpuProductModel(e.target.value) onProductModelChange(value) } }} maxLength={100} aria-invalid={isVideoMaterialType(materialType) && productModel.length > 0 && !isPlausibleSpuProductModel(productModel)} aria-describedby={isVideoMaterialType(materialType) ? 'product-model-hint' : undefined} className={fieldControlClass} placeholder="例如:DF-A3DZ0008" /> {isVideoMaterialType(materialType) && (

{productModel ? getSpuProductModelValidationError(productModel) || '格式正确;英文字母已自动转成大写。' : '请填写真实 SPU;英文字母会自动转成大写,纯数字或文件尾号不能作为 SPU。'}

)}
{/* SKU字段(视频类素材使用) */} {isVideoMaterialType(materialType) && (
onSkuChange?.(e.target.value)} className={fieldControlClass} placeholder="例如:SKU001,SKU002,SKU003" />
)} {/* 品类分类(标准模型必填,其他类型可选) */} {productCategories.length > 0 && (
)} {/* 使用店铺(品牌VI灵感不显示,其他类型按需显示) */} {!isBrandVI && requiresCategoryAndScene(materialType) && (
{showNewCategoryInput ? (
setNewCategoryName(e.target.value)} className={`flex-1 ${fieldControlClass}`} placeholder="输入新店铺名称" />
) : (
)}
)} {/* 场景类别(品牌VI灵感、材质类型和标准模型不需要) */} {!isBrandVI && requiresCategoryAndScene(materialType) && (
{showNewSceneInput ? (
setNewSceneName(e.target.value)} className={`flex-1 ${fieldControlClass}`} placeholder="输入新场景类别" />
) : (
)}
)} {/* 平台分类(可选,品牌VI灵感不显示,其他类型按需显示) */} {!isBrandVI && platforms.length > 0 && !requiresProductCategoryName(materialType) && (
)} {/* 渲染器(仅3D场景显示) */} {requiresRenderer(materialType) && (
)} {/* 标准模型说明 */} {requiresProductCategoryName(materialType) && (

标准模型怎么传

  • 以产品型号(SPU)为基准创建模型合集
  • 上传时只需填写 SPU 型号和预览图
  • 创建后在详情页添加各个 SKU 的模型文件
  • 每个 SKU 需要单独的预览图和压缩包
)} ) }