import { View, StyleSheet, ScrollView, ActivityIndicator } from 'react-native'; import { ThemedView } from '@/components/themed-view'; import { ThemedText } from '@/components/themed-text'; import { RunFormSchema, RunTemplateData, FormFieldSchema } from '@/lib/types/template-run'; import { TextInputField } from './form-fields/text-input'; import { NumberInputField } from './form-fields/number-input'; import { SelectInputField } from './form-fields/select-input'; import { ImageUploadField } from './form-fields/image-upload'; import { ColorInputField } from './form-fields/color-input'; import { useState, useEffect, useCallback, useMemo } from 'react'; import { validateFormSchema } from '@/lib/utils/form-schema-transformer'; interface DynamicFormProps { schema: RunFormSchema; initialData?: RunTemplateData; onDataChange?: (data: RunTemplateData, isValid: boolean) => void; onSubmit?: () => void; } export function DynamicForm({ schema, initialData = {}, onDataChange, onSubmit }: DynamicFormProps) { const [formData, setFormData] = useState(initialData); const [errors, setErrors] = useState>({}); const [isValidating, setIsValidating] = useState(false); const [schemaError, setSchemaError] = useState(null); // 验证schema并初始化默认值 useEffect(() => { setIsValidating(true); setSchemaError(null); try { if (!validateFormSchema(schema)) { setSchemaError('表单配置格式不正确'); return; } const defaultData: RunTemplateData = {}; schema.fields.forEach(field => { if (field.defaultValue !== undefined) { defaultData[field.name] = field.defaultValue; } }); const finalInitialData = { ...defaultData, ...initialData }; setFormData(finalInitialData); if (onDataChange) { onDataChange(finalInitialData, true); } } catch (error) { console.error('表单初始化失败:', error); setSchemaError('表单初始化失败'); } finally { setIsValidating(false); } }, [schema, initialData]); // 验证单个字段 const validateField = useCallback((field: FormFieldSchema, value: any): string | null => { // 检查必填 if (field.required && (value === undefined || value === null || value === '')) { return `${field.label} 是必填项`; } // 如果值为空且不是必填,则跳过其他验证 if (value === undefined || value === null || value === '') { return null; } // 根据类型验证 switch (field.type) { case 'text': case 'textarea': if (typeof value !== 'string') { return `${field.label} 必须是文本`; } if (field.min && value.length < field.min) { return `${field.label} 至少需要 ${field.min} 个字符`; } if (field.max && value.length > field.max) { return `${field.label} 最多 ${field.max} 个字符`; } break; case 'number': const numValue = Number(value); if (isNaN(numValue)) { return `${field.label} 必须是数字`; } if (field.min !== undefined && numValue < field.min) { return `${field.label} 不能小于 ${field.min}`; } if (field.max !== undefined && numValue > field.max) { return `${field.label} 不能大于 ${field.max}`; } break; case 'select': const isValidOption = field.options?.some(option => option.value === value); if (!isValidOption) { return `${field.label} 选择了无效的选项`; } break; case 'image': if (typeof value !== 'string') { return `${field.label} 必须是图片URL`; } if (value && !value.startsWith('http') && !value.startsWith('file://')) { return `${field.label} 必须是有效的图片地址`; } break; case 'color': if (typeof value !== 'string') { return `${field.label} 必须是颜色值`; } if (value && !/^#[0-9A-F]{6}$/i.test(value)) { return `${field.label} 必须是有效的颜色值 (如 #FF0000)`; } break; } return null; }, []); // 更新表单数据 const updateField = useCallback((fieldName: string, value: any) => { const newFormData = { ...formData, [fieldName]: value }; setFormData(newFormData); // 只验证当前字段 const field = schema.fields.find(f => f.name === fieldName); if (!field) return; const fieldError = validateField(field, value); const newErrors = { ...errors }; if (fieldError) { newErrors[fieldName] = fieldError; } else { delete newErrors[fieldName]; } setErrors(newErrors); // 通知父组件数据变化 if (onDataChange) { const isValid = Object.keys(newErrors).length === 0; onDataChange(newFormData, isValid); } }, [formData, schema.fields, errors, validateField, onDataChange]); // 渲染表单字段 const renderField = useCallback((field: FormFieldSchema) => { const value = formData[field.name]; const error = errors[field.name]; const onChange = (newValue: any) => updateField(field.name, newValue); switch (field.type) { case 'text': case 'textarea': return ; case 'number': return ; case 'select': return ; case 'image': return ; case 'color': return ; default: console.warn(`不支持的表单字段类型: ${field.type}`); return null; } }, [formData, errors, updateField]); // 显示加载状态 if (isValidating) { return ( 正在加载表单... ); } // 显示schema错误 if (schemaError) { return ( 表单加载失败 {schemaError} 请稍后重试,或联系技术支持。 ); } return ( {schema.fields.length === 0 ? ( 📋 无需配置参数 此模板使用默认参数,可以直接开始生成 ) : ( <> 请填写以下信息 完善配置信息以获得最佳的生成效果 {schema.fields.map(field => renderField(field))} {Object.keys(errors).length > 0 && ( ⚠️ 请修正 {Object.keys(errors).length} 个错误后再提交 {Object.entries(errors).map(([fieldName, error]) => ( • {error} ))} )} {Object.keys(errors).length === 0 && formData && Object.keys(formData).length > 0 && ( ✓ 配置信息完整,可以开始生成 )} )} ); } const styles = StyleSheet.create({ container: { flex: 1, }, content: { padding: 16, }, // 加载状态样式 loadingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingVertical: 60, }, loadingText: { fontSize: 16, marginTop: 16, opacity: 0.7, }, // 错误状态样式 errorContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 24, }, errorTitle: { fontSize: 18, fontWeight: '600', color: '#FF3B30', marginBottom: 8, }, errorText: { fontSize: 16, textAlign: 'center', opacity: 0.8, marginBottom: 12, }, errorHint: { fontSize: 14, textAlign: 'center', opacity: 0.6, }, // 空状态样式 emptyContainer: { alignItems: 'center', paddingVertical: 40, }, emptyIcon: { fontSize: 48, marginBottom: 16, }, emptyText: { fontSize: 18, fontWeight: '500', marginBottom: 8, opacity: 0.8, }, emptyDescription: { fontSize: 14, textAlign: 'center', opacity: 0.6, lineHeight: 20, }, // 表单头部样式 formHeader: { marginBottom: 20, }, formTitle: { fontSize: 18, fontWeight: '600', marginBottom: 4, }, formDescription: { fontSize: 14, opacity: 0.7, lineHeight: 20, }, // 错误总结样式 errorSummary: { backgroundColor: 'rgba(255, 59, 48, 0.1)', borderRadius: 12, padding: 16, marginTop: 20, borderLeftWidth: 4, borderLeftColor: '#FF3B30', }, errorSummaryText: { fontSize: 16, fontWeight: '600', color: '#FF3B30', marginBottom: 8, }, errorItem: { fontSize: 14, color: '#FF3B30', marginBottom: 4, opacity: 0.9, }, // 成功状态样式 successSummary: { backgroundColor: 'rgba(52, 199, 89, 0.1)', borderRadius: 12, padding: 16, marginTop: 20, borderLeftWidth: 4, borderLeftColor: '#34C759', }, successText: { fontSize: 16, fontWeight: '600', color: '#34C759', }, });