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 placeholder = actionData?.placeholder || '请选择';
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('错误', '无法选择图片,请稍后重试');
}
};
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('错误', '无法选择视频,请稍后重试');
}
};
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: 52,
},
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,
},
});