175 lines
4.1 KiB
TypeScript
175 lines
4.1 KiB
TypeScript
import React from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
|
import Icon from 'react-native-vector-icons/MaterialIcons';
|
|
import { FileStatus } from './file-uploader';
|
|
|
|
interface SingleFileUploaderProps {
|
|
file: FileStatus;
|
|
onReplace: () => void;
|
|
disabled?: boolean;
|
|
formatFileSize?: (bytes: number) => string;
|
|
}
|
|
|
|
export default function SingleFileUploader({
|
|
file,
|
|
onReplace,
|
|
disabled = false,
|
|
formatFileSize = (bytes) => `${bytes} B`
|
|
}: SingleFileUploaderProps) {
|
|
const { t } = useTranslation();
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
{/* 缩略图容器 */}
|
|
<View style={styles.thumbnailContainer}>
|
|
{file.thumbnailUrl ? (
|
|
<>
|
|
<Image
|
|
source={{ uri: file.thumbnailUrl }}
|
|
style={styles.thumbnailImage}
|
|
resizeMode="cover"
|
|
/>
|
|
{/* 错误信息显示 */}
|
|
{file.error && (
|
|
<View style={styles.errorContainer}>
|
|
<Text style={styles.errorText}>
|
|
{file.error}
|
|
</Text>
|
|
</View>
|
|
)}
|
|
</>
|
|
) : (
|
|
<View style={styles.placeholderContainer}>
|
|
<Icon name="videocam" size={40} color="#9CA3AF" />
|
|
</View>
|
|
)}
|
|
|
|
{/* 显示替换按钮 */}
|
|
{file.thumbnailUrl && !disabled && (
|
|
<TouchableOpacity
|
|
style={styles.replaceButton}
|
|
onPress={(e) => {
|
|
e.stopPropagation();
|
|
onReplace();
|
|
}}
|
|
disabled={disabled}
|
|
>
|
|
<View style={styles.replaceButtonContent}>
|
|
<Icon name="upload" size={24} color="white" />
|
|
<Text style={styles.replaceButtonText}>
|
|
{t('common.replace')}
|
|
</Text>
|
|
</View>
|
|
</TouchableOpacity>
|
|
)}
|
|
</View>
|
|
|
|
{/* 文件信息 */}
|
|
<View style={styles.fileInfo}>
|
|
<View style={styles.fileInfoText}>
|
|
<Text style={styles.fileName} numberOfLines={1}>
|
|
{file.file.name}
|
|
</Text>
|
|
<Text style={styles.fileSize}>
|
|
{formatFileSize(file.file.size)} • {file.status}
|
|
</Text>
|
|
</View>
|
|
{file.status === 'uploading' && (
|
|
<View style={styles.progressContainer}>
|
|
<View style={[styles.progressBar, { width: `${file.progress}%` }]} />
|
|
</View>
|
|
)}
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
width: '100%',
|
|
height: '100%',
|
|
},
|
|
thumbnailContainer: {
|
|
width: '100%',
|
|
aspectRatio: 16 / 9,
|
|
backgroundColor: '#F3F4F6',
|
|
borderRadius: 6,
|
|
overflow: 'hidden',
|
|
position: 'relative',
|
|
},
|
|
thumbnailImage: {
|
|
width: '100%',
|
|
height: '100%',
|
|
},
|
|
errorContainer: {
|
|
position: 'absolute',
|
|
bottom: 0,
|
|
left: 0,
|
|
right: 0,
|
|
backgroundColor: 'rgba(220, 38, 38, 0.8)',
|
|
padding: 4,
|
|
},
|
|
errorText: {
|
|
color: 'white',
|
|
fontSize: 12,
|
|
},
|
|
placeholderContainer: {
|
|
width: '100%',
|
|
height: '100%',
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
},
|
|
replaceButton: {
|
|
position: 'absolute',
|
|
top: 0,
|
|
left: 0,
|
|
right: 0,
|
|
bottom: 0,
|
|
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
opacity: 0,
|
|
},
|
|
replaceButtonContent: {
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
},
|
|
replaceButtonText: {
|
|
color: 'white',
|
|
fontSize: 14,
|
|
fontWeight: '500',
|
|
},
|
|
fileInfo: {
|
|
marginTop: 8,
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
},
|
|
fileInfoText: {
|
|
flex: 1,
|
|
minWidth: 0,
|
|
},
|
|
fileName: {
|
|
fontSize: 14,
|
|
fontWeight: '500',
|
|
color: '#111827',
|
|
},
|
|
fileSize: {
|
|
fontSize: 12,
|
|
color: '#6B7280',
|
|
},
|
|
progressContainer: {
|
|
width: 96,
|
|
height: 8,
|
|
backgroundColor: '#E5E7EB',
|
|
borderRadius: 4,
|
|
marginLeft: 8,
|
|
overflow: 'hidden',
|
|
},
|
|
progressBar: {
|
|
height: '100%',
|
|
backgroundColor: '#3B82F6',
|
|
},
|
|
});
|