229 lines
5.5 KiB
TypeScript
229 lines
5.5 KiB
TypeScript
import { View, StyleSheet, TouchableOpacity, Alert } from 'react-native';
|
|
import * as ImagePicker from 'expo-image-picker';
|
|
import { ThemedText } from '@/components/themed-text';
|
|
import { useThemeColor } from '@/hooks/use-theme-color';
|
|
import { FormFieldSchema } from '@/lib/types/template-run';
|
|
import { Image } from 'expo-image';
|
|
import { useState } from 'react';
|
|
|
|
interface ImageUploadFieldProps {
|
|
field: FormFieldSchema;
|
|
value: string;
|
|
onChange: (value: string) => void;
|
|
error?: string;
|
|
}
|
|
|
|
export function ImageUploadField({ field, value, onChange, error }: ImageUploadFieldProps) {
|
|
const borderColor = useThemeColor({}, 'border');
|
|
const errorColor = useThemeColor({}, 'error');
|
|
const backgroundColor = useThemeColor({}, 'background');
|
|
const placeholderColor = useThemeColor({}, 'textPlaceholder');
|
|
const [uploading, setUploading] = useState(false);
|
|
|
|
const pickImage = async () => {
|
|
try {
|
|
setUploading(true);
|
|
|
|
// 请求权限
|
|
const permissionResult = await ImagePicker.requestMediaLibraryPermissionsAsync();
|
|
if (!permissionResult.granted) {
|
|
Alert.alert('权限请求', '需要相册权限才能选择图片');
|
|
return;
|
|
}
|
|
|
|
// 选择图片
|
|
const result = await ImagePicker.launchImageLibraryAsync({
|
|
mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
|
allowsEditing: true,
|
|
aspect: [16, 9],
|
|
quality: 0.8,
|
|
});
|
|
|
|
if (!result.canceled && result.assets[0]) {
|
|
onChange(result.assets[0].uri);
|
|
}
|
|
} catch (error) {
|
|
console.error('选择图片失败:', error);
|
|
Alert.alert('错误', '选择图片失败,请重试');
|
|
} finally {
|
|
setUploading(false);
|
|
}
|
|
};
|
|
|
|
const takePhoto = async () => {
|
|
try {
|
|
setUploading(true);
|
|
|
|
// 请求相机权限
|
|
const permissionResult = await ImagePicker.requestCameraPermissionsAsync();
|
|
if (!permissionResult.granted) {
|
|
Alert.alert('权限请求', '需要相机权限才能拍照');
|
|
return;
|
|
}
|
|
|
|
// 拍照
|
|
const result = await ImagePicker.launchCameraAsync({
|
|
allowsEditing: true,
|
|
aspect: [16, 9],
|
|
quality: 0.8,
|
|
});
|
|
|
|
if (!result.canceled && result.assets[0]) {
|
|
onChange(result.assets[0].uri);
|
|
}
|
|
} catch (error) {
|
|
console.error('拍照失败:', error);
|
|
Alert.alert('错误', '拍照失败,请重试');
|
|
} finally {
|
|
setUploading(false);
|
|
}
|
|
};
|
|
|
|
const showOptions = () => {
|
|
Alert.alert(
|
|
'选择图片',
|
|
'',
|
|
[
|
|
{
|
|
text: '从相册选择',
|
|
onPress: pickImage,
|
|
},
|
|
{
|
|
text: '拍照',
|
|
onPress: takePhoto,
|
|
},
|
|
{
|
|
text: '取消',
|
|
style: 'cancel',
|
|
},
|
|
]
|
|
);
|
|
};
|
|
|
|
const removeImage = () => {
|
|
onChange('');
|
|
};
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
{field.label && (
|
|
<ThemedText style={styles.label}>
|
|
{field.label}
|
|
{field.required && <ThemedText style={[styles.required, { color: errorColor }]}> *</ThemedText>}
|
|
</ThemedText>
|
|
)}
|
|
|
|
{value ? (
|
|
<View style={styles.imageContainer}>
|
|
<Image
|
|
source={{ uri: value }}
|
|
style={styles.image}
|
|
contentFit="cover"
|
|
/>
|
|
<TouchableOpacity
|
|
style={[styles.removeButton, { backgroundColor: errorColor }]}
|
|
onPress={removeImage}
|
|
activeOpacity={0.8}
|
|
>
|
|
<ThemedText style={styles.removeButtonText}>✕</ThemedText>
|
|
</TouchableOpacity>
|
|
</View>
|
|
) : (
|
|
<TouchableOpacity
|
|
style={[
|
|
styles.uploadButton,
|
|
{
|
|
borderColor: error ? errorColor : borderColor,
|
|
backgroundColor,
|
|
}
|
|
]}
|
|
onPress={showOptions}
|
|
disabled={uploading}
|
|
activeOpacity={0.8}
|
|
>
|
|
<ThemedText style={[styles.uploadIcon, { color: placeholderColor }]}>
|
|
📷
|
|
</ThemedText>
|
|
<ThemedText style={[styles.uploadText, { color: placeholderColor }]}>
|
|
{uploading ? '上传中...' : (field.placeholder || '点击上传图片')}
|
|
</ThemedText>
|
|
</TouchableOpacity>
|
|
)}
|
|
|
|
{field.description && (
|
|
<ThemedText style={styles.description}>{field.description}</ThemedText>
|
|
)}
|
|
|
|
{error && (
|
|
<ThemedText style={[styles.errorText, { color: errorColor }]}>
|
|
{error}
|
|
</ThemedText>
|
|
)}
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
marginBottom: 16,
|
|
},
|
|
label: {
|
|
fontSize: 16,
|
|
fontWeight: '600',
|
|
marginBottom: 8,
|
|
},
|
|
required: {
|
|
fontSize: 16,
|
|
fontWeight: '600',
|
|
},
|
|
imageContainer: {
|
|
position: 'relative',
|
|
borderRadius: 8,
|
|
overflow: 'hidden',
|
|
},
|
|
image: {
|
|
width: '100%',
|
|
height: 200,
|
|
borderRadius: 8,
|
|
},
|
|
removeButton: {
|
|
position: 'absolute',
|
|
top: 8,
|
|
right: 8,
|
|
width: 24,
|
|
height: 24,
|
|
borderRadius: 12,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
},
|
|
removeButtonText: {
|
|
color: '#fff',
|
|
fontSize: 12,
|
|
fontWeight: '600',
|
|
},
|
|
uploadButton: {
|
|
borderWidth: 2,
|
|
borderStyle: 'dashed',
|
|
borderRadius: 8,
|
|
paddingVertical: 32,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
uploadIcon: {
|
|
fontSize: 24,
|
|
marginBottom: 8,
|
|
},
|
|
uploadText: {
|
|
fontSize: 16,
|
|
textAlign: 'center',
|
|
},
|
|
description: {
|
|
fontSize: 12,
|
|
opacity: 0.7,
|
|
marginTop: 4,
|
|
},
|
|
errorText: {
|
|
fontSize: 12,
|
|
marginTop: 4,
|
|
},
|
|
}); |