'use client' import { useState, useRef, useEffect, useCallback, useMemo } from 'react' import Image from 'next/image' import Modal from '@/components/ui/Modal' import { useThemeStyles, useTheme, cn } from '@/lib/ui-styles' import { filterVisibleFieldValues, getVisibleTemplateFields, hasFieldValue } from '@/lib/field-conditions' interface EditMaterialModalProps { material: { id: string previewUrl: string productModel: string categoryName: string sceneName: string materialType: string renderer: string authorName: string brandName?: string | null seriesName?: string | null skuCode?: string | null materialTexture?: string | null renderTime?: string | null imageResolution?: string | null colorMode?: string | null templateId?: string | null platformName?: string | null templateFields?: Record // 音频相关字段 audioDuration?: string | null audioCategory?: string | null audioScene?: string | null } onClose: () => void onSuccess: () => void isOpen?: boolean } type UploadTemplateOption = { id: string name: string description: string | null materialType?: string fields: any[] isBuiltin?: boolean } export default function EditMaterialModal({ material, onClose, onSuccess, isOpen = true }: EditMaterialModalProps) { const styles = useThemeStyles() const theme = useTheme() const controlClass = 'w-full px-4 py-2.5 text-sm rounded-lg outline-none transition-colors border bg-white dark:bg-slate-800 text-slate-900 dark:text-white border-slate-300 dark:border-slate-700 focus-visible:ring-2 focus-visible:ring-slate-200 dark:focus-visible:ring-slate-700 focus-visible:border-slate-400 dark:focus-visible:border-slate-500 placeholder:text-slate-400 dark:placeholder:text-slate-500' const requiredControlClass = 'bg-slate-50 dark:bg-slate-800 border-slate-300 dark:border-slate-600' const requiredBadgeClass = 'inline-flex items-center px-2 py-0.5 rounded-md border border-slate-200 dark:border-slate-700 bg-slate-100 dark:bg-slate-800 text-[10px] font-medium text-slate-600 dark:text-slate-300' const [previewImage, setPreviewImage] = useState(null) const [previewUrl, setPreviewUrl] = useState(material.previewUrl) const [templateFieldValues, setTemplateFieldValues] = useState>({}) const [selectedTemplate, setSelectedTemplate] = useState(null) const [templateCategoryOptions, setTemplateCategoryOptions] = useState>({}) const [optionsLoading, setOptionsLoading] = useState(true) const [updating, setUpdating] = useState(false) const [error, setError] = useState('') const [success, setSuccess] = useState('') const previewInputRef = useRef(null) const visibleTemplateFields = useMemo(() => { if (!selectedTemplate?.fields?.length) return [] return getVisibleTemplateFields(selectedTemplate.fields, templateFieldValues) }, [selectedTemplate, templateFieldValues]) // 初始化字段值 useEffect(() => { const initialValues: Record = { ...(material.templateFields || {}), productModel: material.productModel, brandName: material.brandName || '', seriesName: material.seriesName || '', skuCode: material.skuCode || '', materialTexture: material.materialTexture || '', categoryName: material.categoryName || '', sceneName: material.sceneName || '', platformName: material.platformName || '', materialType: material.materialType || '3DMAX', renderer: material.renderer || 'CR', renderTime: material.renderTime || '', imageResolution: material.imageResolution || '', colorMode: material.colorMode || '', authorName: material.authorName || '', // 音频相关字段 audioDuration: material.audioDuration || '', audioCategory: material.audioCategory || '', audioScene: material.audioScene || '', } setTemplateFieldValues(initialValues) }, [material]) // 加载模板和分类选项 useEffect(() => { const loadTemplateAndOptions = async () => { setOptionsLoading(true) try { // 加载模板分类选项 const categoriesResponse = await fetch('/api/template-categories', { cache: 'no-store' }) if (categoriesResponse.ok) { const categoriesData = await categoriesResponse.json() const map = Array.isArray(categoriesData) ? categoriesData.reduce>((acc, field: any) => { if (field?.fieldKey && Array.isArray(field.options)) { acc[field.fieldKey] = field.options } return acc }, {}) : {} setTemplateCategoryOptions(map) } const setTemplateFromData = (templateData: any) => { setSelectedTemplate({ id: templateData.id, name: templateData.name, description: templateData.description, materialType: templateData.materialType, fields: templateData.fields || [], isBuiltin: templateData.isBuiltin, }) } const loadPublicTemplates = async () => { const templatesResponse = await fetch('/api/upload-templates') if (!templatesResponse.ok) { return [] } const templatesData = await templatesResponse.json() return Array.isArray(templatesData) ? templatesData : [] } // 如果有templateId,优先从普通用户可访问的模板列表加载 if (material.templateId) { const templates = await loadPublicTemplates() const matchingTemplate = templates.find((t: any) => t.id === material.templateId) if (matchingTemplate) { setTemplateFromData(matchingTemplate) } else { const templateResponse = await fetch(`/api/admin/upload-templates/${material.templateId}`) if (templateResponse.ok) { const templateData = await templateResponse.json() setTemplateFromData(templateData) } } } else { // 如果没有templateId,尝试根据materialType查找匹配的模板 const templates = await loadPublicTemplates() const matchingTemplate = templates.find( (t: any) => t.materialType === material.materialType && t.status === 'active' ) if (matchingTemplate) { setTemplateFromData(matchingTemplate) } } } catch (error) { console.error('[EditMaterialModal] Failed to load template:', error) } finally { setOptionsLoading(false) } } loadTemplateAndOptions() }, [material.templateId, material.materialType]) const handlePreviewChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0] if (file) { if (!file.type.startsWith('image/')) { setError('请选择图片文件') return } setPreviewImage(file) setPreviewUrl(URL.createObjectURL(file)) setError('') } } const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() setError('') setSuccess('') // 验证必填字段 const requiredFields = visibleTemplateFields.filter((f: any) => f.required) for (const field of requiredFields) { const value = templateFieldValues[field.key] if (!hasFieldValue(value, field.type)) { setError(`请填写必填字段:${field.label}`) return } } setUpdating(true) try { const formData = new FormData() formData.append('id', material.id) if (selectedTemplate?.id) { formData.append('templateId', selectedTemplate.id) } // 添加所有模板字段值 const visibleTemplateFieldValues = selectedTemplate?.fields?.length ? filterVisibleFieldValues(selectedTemplate.fields, templateFieldValues) : templateFieldValues Object.entries(visibleTemplateFieldValues).forEach(([key, value]) => { if (value !== null && value !== undefined) { formData.append(key, String(value)) } }) formData.append('templateFields', JSON.stringify(visibleTemplateFieldValues)) if (previewImage) { formData.append('previewImage', previewImage) } const response = await fetch('/api/materials/update', { method: 'PUT', body: formData, }) if (response.ok) { setSuccess('素材信息已更新') setTimeout(() => { onSuccess() onClose() }, 1000) } else { const data = await response.json() setError(data.error || '更新失败') } } catch (error) { console.error('Update error:', error) setError('更新失败,请稍后再试') } finally { setUpdating(false) } } // 渲染模板字段(和UploadModal相同的逻辑) const renderTemplateField = (field: any) => { const fieldValue = templateFieldValues[field.key] || '' const isHalfWidth = field.layoutWidth === 'half' const fieldInputId = `edit-template-field-${field.id || field.key}` return (
{field.required && ( 必填 )}
{field.type === 'text' || field.type === 'uppercase' ? ( { const value = field.type === 'uppercase' ? e.target.value.toUpperCase() : e.target.value setTemplateFieldValues({ ...templateFieldValues, [field.key]: value, }) }} required={field.required} placeholder={ field.key === 'imageResolution' ? '例如:5000 * 5000' : `请输入${field.label || field.key}` } className={cn( controlClass, field.required ? requiredControlClass : null, field.type === 'uppercase' && 'uppercase' )} /> ) : field.type === 'textarea' ? (