bw-expo-app/components/forms/form-fields/image-upload.tsx

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,
},
});