import { TemplateGraphNode } from '@/lib/types/template'; import { Feather } from '@expo/vector-icons'; import * as ImagePicker from 'expo-image-picker'; import { UploadCloud } from 'lucide-react'; import React from 'react'; import { Alert, Image, StyleSheet, Text, TextInput, TouchableOpacity, View, } from 'react-native'; interface DynamicFormFieldProps { node: TemplateGraphNode; value: any; onChange: (value: any) => void; error?: string; } export function DynamicFormField({ node, value, onChange, error }: DynamicFormFieldProps) { const { type, data } = node; const { label, description, actionData } = data; const renderField = () => { switch (type) { case 'select': return renderSelectField(); case 'text': return renderTextField(); case 'image': return renderImageField(); case 'video': return renderVideoField(); default: return null; } }; const renderSelectField = () => { const options = actionData?.options || []; const allowMultiple = actionData?.allowMultiple || false; const handleSelect = (optionValue: string) => { if (allowMultiple) { const currentValues = Array.isArray(value) ? value : []; const newValues = currentValues.includes(optionValue) ? currentValues.filter((v: string) => v !== optionValue) : [...currentValues, optionValue]; onChange(newValues); } else { onChange(optionValue); } }; const isSelected = (optionValue: string) => { if (allowMultiple) { return Array.isArray(value) && value.includes(optionValue); } return value === optionValue; }; return ( {label} {description && {description}} {options.map((option: any, index: number) => ( handleSelect(option.value)} > {option.label} {isSelected(option.value) && ( )} ))} {error && {error}} ); }; const renderTextField = () => { const placeholder = actionData?.placeholder || '请输入内容'; const multiline = actionData?.multiline || false; return ( {label} {description && {description}} {error && {error}} ); }; const renderImageField = () => { const handleImagePick = async () => { try { const permission = await ImagePicker.requestMediaLibraryPermissionsAsync(); if (!permission.granted) { Alert.alert( '需要权限', '请在设置中允许访问相册以上传图片', ); return; } const result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ImagePicker.MediaTypeOptions.Images, allowsEditing: true, quality: 0.9, }); if (!result.canceled && result.assets && result.assets.length > 0) { onChange(result.assets[0].uri); } } catch (error) { console.error('Failed to pick image:', error); Alert.alert('错误', '无法选择图片,请稍后重试'); } }; const handleRemove = () => { onChange(null); }; return ( {label} {description && {description}} {value ? ( <> ) : ( <> 上传图片 {actionData?.placeholder || 'PNG, JPG 格式'} )} {error && {error}} ); }; const renderVideoField = () => { const handleVideoPick = async () => { try { const permission = await ImagePicker.requestMediaLibraryPermissionsAsync(); if (!permission.granted) { Alert.alert( '需要权限', '请在设置中允许访问相册以上传视频', ); return; } const result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ImagePicker.MediaTypeOptions.Videos, allowsEditing: true, quality: 0.9, }); if (!result.canceled && result.assets && result.assets.length > 0) { onChange(result.assets[0].uri); } } catch (error) { console.error('Failed to pick video:', error); Alert.alert('错误', '无法选择视频,请稍后重试'); } }; const handleRemove = () => { onChange(null); }; return ( {label} {description && {description}} {value ? ( <> 视频已选择 ) : ( <> 上传视频 {actionData?.placeholder || 'MP4, MOV 格式'} )} {error && {error}} ); }; return renderField(); } const styles = StyleSheet.create({ fieldContainer: { marginBottom: 24, }, fieldLabel: { fontSize: 16, fontWeight: '700', letterSpacing: 0.4, color: '#FFFFFF', marginBottom: 8, textTransform: 'uppercase', }, fieldDescription: { fontSize: 13, lineHeight: 18, color: 'rgba(255, 255, 255, 0.6)', marginBottom: 12, }, selectOptions: { gap: 12, }, selectOption: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 20, paddingVertical: 16, borderRadius: 24, borderWidth: 1, borderColor: 'rgba(255, 255, 255, 0.08)', backgroundColor: 'rgba(17, 19, 24, 0.95)', }, selectOptionSelected: { backgroundColor: '#D1FF00', borderColor: '#D1FF00', }, selectOptionError: { borderColor: '#FF6B6B', }, selectOptionText: { fontSize: 15, fontWeight: '600', color: '#FFFFFF', }, selectOptionTextSelected: { color: '#050505', }, textInput: { borderRadius: 24, borderWidth: 1, borderColor: 'rgba(255, 255, 255, 0.08)', backgroundColor: 'rgba(17, 19, 24, 0.95)', paddingHorizontal: 20, paddingVertical: 16, fontSize: 15, color: '#FFFFFF', minHeight: 105, }, textInputMultiline: { minHeight: 140, paddingTop: 16, paddingBottom: 16, }, textInputError: { borderColor: '#FF6B6B', }, uploadCard: { borderRadius: 28, borderWidth: 1, borderColor: 'rgba(255, 255, 255, 0.08)', backgroundColor: 'rgba(17, 19, 24, 0.95)', paddingHorizontal: 20, paddingVertical: 28, alignItems: 'center', justifyContent: 'center', minHeight: 188, overflow: 'hidden', }, uploadCardFilled: { paddingHorizontal: 0, paddingVertical: 0, }, uploadCardError: { borderColor: '#FF6B6B', }, uploadIconWrap: { width: 60, height: 60, borderRadius: 22, backgroundColor: 'rgba(209, 255, 0, 0.12)', alignItems: 'center', justifyContent: 'center', marginBottom: 16, }, uploadLabel: { fontSize: 14, fontWeight: '700', letterSpacing: 0.6, color: '#FFFFFF', textAlign: 'center', textTransform: 'uppercase', marginBottom: 6, }, uploadDescription: { fontSize: 12, lineHeight: 16, color: 'rgba(255, 255, 255, 0.55)', textAlign: 'center', }, uploadImage: { width: '100%', height: '100%', resizeMode: 'cover', }, videoPreview: { alignItems: 'center', justifyContent: 'center', padding: 40, }, videoPreviewText: { marginTop: 12, fontSize: 14, color: '#D1FF00', fontWeight: '600', }, errorText: { marginTop: 8, fontSize: 12, color: '#FF6B6B', letterSpacing: 0.2, }, removeButton: { position: 'absolute', top: 12, right: 12, width: 32, height: 32, borderRadius: 16, backgroundColor: '#D1FF00', alignItems: 'center', justifyContent: 'center', shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.25, shadowRadius: 4, elevation: 5, }, });