memowake-front/components/file-upload/single-file-uploader.tsx
2025-06-26 15:12:34 +08:00

175 lines
4.1 KiB
TypeScript

import React from 'react';
import { View, Text, TouchableOpacity, Image, StyleSheet } from 'react-native';
import { useTranslation } from 'react-i18next';
import { FileStatus } from './file-uploader';
import Icon from 'react-native-vector-icons/MaterialIcons';
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',
},
});