'use client' import { useEffect, useState } from 'react' import SearchableUserSelect from '@/components/SearchableUserSelect' import { canManuallyReplacePreview, getSelectableMaterialTypeOptions, supportsIsFullSet, supportsTaskNo, } from '@/lib/material-type-options' import { isCertificateReportMaterialType } from '@/lib/certificate-report' import { TASK_DETAIL_SOURCE_TYPE_OPTIONS } from '@/lib/task-detail-source-type' import { requiresCategoryAndScene, requiresProductCategoryName, requiresRenderer, } from '@/lib/material-validation' interface EditMaterialModalProps { material: { id: string previewUrl: string productModel: string reportTitle?: string | null categoryName: string sceneName: string platformName?: string | null productCategoryName?: string | null materialType: string renderer: string authorName: string userId: string taskNo?: string | null taskDetailSourceType?: string | null taskStatus?: string | null taskFetchedAt?: string | null isFullSet?: boolean } onClose: () => void onSuccess: () => void } export default function EditMaterialModal({ material, onClose, onSuccess }: EditMaterialModalProps) { const [previewImage, setPreviewImage] = useState(null) const [previewUrl, setPreviewUrl] = useState(material.previewUrl || '') const [productModel, setProductModel] = useState(material.productModel) const [reportTitle, setReportTitle] = useState(material.reportTitle || '') const [categoryName, setCategoryName] = useState(material.categoryName) const [sceneName, setSceneName] = useState(material.sceneName) const [platformName, setPlatformName] = useState(material.platformName || '') const [productCategoryName, setProductCategoryName] = useState(material.productCategoryName || '') const [materialType, setMaterialType] = useState(material.materialType || '3D场景') const [renderer, setRenderer] = useState(material.renderer || 'CR') const [authorName, setAuthorName] = useState(material.authorName) const [userId, setUserId] = useState(material.userId) const [taskNo, setTaskNo] = useState(material.taskNo || '') const [taskDetailSourceType, setTaskDetailSourceType] = useState(material.taskDetailSourceType || '') const [taskStatus] = useState(material.taskStatus || '') const [taskFetchedAt] = useState(material.taskFetchedAt || '') const [isFullSet] = useState(Boolean(material.isFullSet)) const [users, setUsers] = useState>([]) const [categories, setCategories] = useState>([]) const [scenes, setScenes] = useState>([]) const [platforms, setPlatforms] = useState>([]) const [productCategories, setProductCategories] = useState>([]) const [updating, setUpdating] = useState(false) const [error, setError] = useState('') const showCategoryScene = requiresCategoryAndScene(materialType) const showRenderer = requiresRenderer(materialType) const showTaskNo = supportsTaskNo(materialType) const showIsFullSet = supportsIsFullSet(materialType) const allowPreviewReplacement = canManuallyReplacePreview(materialType) const isCertificateReport = isCertificateReportMaterialType(materialType) const isFetchedTask = taskStatus === 'fetched' const materialTypeOptions = getSelectableMaterialTypeOptions(materialType) useEffect(() => { void fetchSelectOptions() }, []) const fetchSelectOptions = async () => { try { const [categoriesResponse, scenesResponse, platformsResponse, productCategoriesResponse, usersResponse] = await Promise.all([ fetch('/api/categories'), fetch('/api/scenes'), fetch('/api/platforms'), fetch('/api/product-categories'), fetch('/api/users/list'), ]) if (categoriesResponse.ok) { setCategories(await categoriesResponse.json()) } if (scenesResponse.ok) { setScenes(await scenesResponse.json()) } if (platformsResponse.ok) { setPlatforms(await platformsResponse.json()) } if (productCategoriesResponse.ok) { setProductCategories(await productCategoriesResponse.json()) } if (usersResponse.ok) { setUsers(await usersResponse.json()) } } catch (fetchError) { console.error('加载编辑弹窗数据失败:', fetchError) } } const handlePreviewChange = (event: React.ChangeEvent) => { const file = event.target.files?.[0] if (!file) { return } if (!file.type.startsWith('image/')) { setError('请选择图片文件') return } setPreviewImage(file) setPreviewUrl(URL.createObjectURL(file)) setError('') } const handleSubmit = async (event: React.FormEvent) => { event.preventDefault() setError('') if (!materialType || !authorName) { setError('请填写所有必填字段') return } if (isCertificateReport) { if (!productModel.trim() && !reportTitle.trim()) { setError('SPU 和 报告名称/检测对象 至少填写一个') return } } else if (!productModel) { setError('请填写所有必填字段') return } if (showCategoryScene && (!categoryName || !sceneName)) { setError('请填写店铺和场景') return } if (requiresProductCategoryName(materialType) && !productCategoryName) { setError('标准模型类型必须选择品类') return } if (showRenderer && !renderer) { setError('3D场景类型必须指定渲染器') return } if (isFetchedTask) { const confirmed = window.confirm('这条任务素材已经被外部系统获取过,继续保存可能影响对方使用,确认继续吗?') if (!confirmed) { return } } setUpdating(true) try { const formData = new FormData() formData.append('id', material.id) formData.append('productModel', productModel) formData.append('reportTitle', reportTitle) formData.append('categoryName', categoryName) formData.append('sceneName', sceneName) formData.append('platformName', platformName) formData.append('productCategoryName', productCategoryName) formData.append('materialType', materialType) formData.append('renderer', renderer) formData.append('authorName', authorName) formData.append('userId', userId) formData.append('taskNo', showTaskNo ? taskNo : '') formData.append('taskDetailSourceType', materialType === '任务-详情' ? taskDetailSourceType : '') if (previewImage && allowPreviewReplacement) { formData.append('previewImage', previewImage) } const response = await fetch('/api/materials/update', { method: 'PUT', body: formData, }) if (!response.ok) { const data = await response.json() setError(data.error || '更新失败') return } onSuccess() } catch (submitError) { console.error('更新失败:', submitError) setError('更新失败,请稍后再试') } finally { setUpdating(false) } } return (

编辑素材

{isFetchedTask && (
这条任务素材已被外部系统获取。 {taskFetchedAt ? ` 获取时间:${new Date(taskFetchedAt).toLocaleString('zh-CN')}` : ''}
)} {!allowPreviewReplacement && (
当前素材类型的预览以系统生成结果为准,这里不支持手工替换封面。
)} {error && (
{error}
)} {allowPreviewReplacement && (
{previewUrl ? (
预览图
) : (
当前没有预览图
)}
)}
setProductModel(event.target.value)} className="input-apple" required={!isCertificateReport} />
{isCertificateReport && (
setReportTitle(event.target.value)} className="input-apple" placeholder="SPU 和这个字段至少填写一个" />
)}
{showCategoryScene && ( <>
)}
{showRenderer && (
)} {showTaskNo && (
setTaskNo(event.target.value)} className="input-apple" placeholder="TASK + 12-18位数字,可带多级 -01" />
)} {materialType === '任务-详情' && (
)} {showIsFullSet && (

当前结果:{isFullSet ? '是' : '否'}

系统会根据是否同时存在主图、SKU 图、详情图自动判定,这里不允许手工修改。

)}
{ setUserId(nextUserId) setAuthorName(displayName) }} placeholder="请输入或搜索用户(支持拼音)" required />
{(showTaskNo || showIsFullSet) && (

任务状态

{taskStatus || '未记录'}

外部获取时间

{taskFetchedAt ? new Date(taskFetchedAt).toLocaleString('zh-CN') : '未记录'}

)}
) }