import { MedicalRecordCard } from '@/components/health/MedicalRecordCard'; import { palette } from '@/constants/Colors'; import { useAppDispatch, useAppSelector } from '@/hooks/redux'; import { useCosUpload } from '@/hooks/useCosUpload'; import { MedicalRecordItem, MedicalRecordType } from '@/services/healthProfile'; import { addNewMedicalRecord, deleteMedicalRecordItem, fetchMedicalRecords, selectHealthLoading, selectMedicalRecords, } from '@/store/healthSlice'; import { Ionicons } from '@expo/vector-icons'; import dayjs from 'dayjs'; import * as DocumentPicker from 'expo-document-picker'; import { Image } from 'expo-image'; import * as ImagePicker from 'expo-image-picker'; import { LinearGradient } from 'expo-linear-gradient'; import React, { useEffect, useState } from 'react'; import { ActivityIndicator, Alert, FlatList, Modal, Platform, StyleSheet, Text, TextInput, TouchableOpacity, View, } from 'react-native'; import ImageViewing from 'react-native-image-viewing'; import DateTimePickerModal from 'react-native-modal-datetime-picker'; export function MedicalRecordsTab() { const dispatch = useAppDispatch(); const medicalRecords = useAppSelector(selectMedicalRecords); const records = medicalRecords?.records || []; const prescriptions = medicalRecords?.prescriptions || []; const isLoading = useAppSelector(selectHealthLoading); // COS 上传 const { upload: uploadToCos, uploading: isUploading } = useCosUpload({ prefix: 'images/health/medical-records' }); const [activeTab, setActiveTab] = useState('medical_record'); const [isModalVisible, setModalVisible] = useState(false); const [isDatePickerVisible, setDatePickerVisibility] = useState(false); // Form State const [title, setTitle] = useState(''); const [date, setDate] = useState(new Date()); const [images, setImages] = useState([]); const [note, setNote] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); // Image Viewer State const [viewerVisible, setViewerVisible] = useState(false); const [currentViewerImages, setCurrentViewerImages] = useState<{ uri: string }[]>([]); useEffect(() => { dispatch(fetchMedicalRecords()); }, [dispatch]); const currentList = activeTab === 'medical_record' ? records : prescriptions; const handleTabPress = (tab: MedicalRecordType) => { setActiveTab(tab); }; const resetForm = () => { setTitle(''); setDate(new Date()); setImages([]); setNote(''); }; const openAddModal = () => { resetForm(); setModalVisible(true); }; const handlePickImage = async () => { const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync(); if (status !== 'granted') { Alert.alert('需要权限', '请允许访问相册以上传图片'); return; } const result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ImagePicker.MediaTypeOptions.Images, allowsEditing: true, quality: 0.8, }); if (!result.canceled && result.assets && result.assets.length > 0) { setImages([...images, result.assets[0].uri]); } }; const handleTakePhoto = async () => { const { status } = await ImagePicker.requestCameraPermissionsAsync(); if (status !== 'granted') { Alert.alert('需要权限', '请允许访问相机以拍摄照片'); return; } const result = await ImagePicker.launchCameraAsync({ allowsEditing: true, quality: 0.8, }); if (!result.canceled && result.assets && result.assets.length > 0) { setImages([...images, result.assets[0].uri]); } }; const handlePickDocument = async () => { try { const result = await DocumentPicker.getDocumentAsync({ type: ['application/pdf', 'image/*'], copyToCacheDirectory: true, multiple: false, }); if (!result.canceled && result.assets && result.assets.length > 0) { setImages([...images, result.assets[0].uri]); } } catch (error) { console.error('Error picking document:', error); Alert.alert('错误', '选择文件失败'); } }; const handleSubmit = async () => { if (!title.trim()) { Alert.alert('提示', '请输入标题'); return; } if (images.length === 0) { Alert.alert('提示', '请至少上传一张图片'); return; } setIsSubmitting(true); try { // 1. 上传所有图片到 COS const uploadPromises = images.map(async (uri) => { const result = await uploadToCos({ uri }); return result.url; }); const uploadedUrls = await Promise.all(uploadPromises); // 2. 创建就医资料记录 await dispatch(addNewMedicalRecord({ type: activeTab, title: title.trim(), date: dayjs(date).format('YYYY-MM-DD'), images: uploadedUrls, note: note.trim() || undefined, })).unwrap(); setModalVisible(false); resetForm(); } catch (error: any) { console.error('保存失败:', error); const errorMessage = error?.message || '保存失败,请重试'; Alert.alert('错误', errorMessage); } finally { setIsSubmitting(false); } }; const handleDelete = (item: MedicalRecordItem) => { Alert.alert( '确认删除', '确定要删除这条记录吗?', [ { text: '取消', style: 'cancel' }, { text: '删除', style: 'destructive', onPress: () => dispatch(deleteMedicalRecordItem({ id: item.id, type: item.type })), }, ] ); }; const handleViewImages = (item: MedicalRecordItem) => { if (item.images && item.images.length > 0) { setCurrentViewerImages(item.images.map(uri => ({ uri }))); setViewerVisible(true); } }; const renderItem = ({ item }: { item: MedicalRecordItem }) => ( ); return ( {/* Segmented Control */} handleTabPress('medical_record')} activeOpacity={0.8} > 病历资料 handleTabPress('prescription')} activeOpacity={0.8} > 处方单据 {/* Content List */} {isLoading && records.length === 0 && prescriptions.length === 0 ? ( ) : currentList.length > 0 ? ( item.id} showsVerticalScrollIndicator={false} contentContainerStyle={styles.listContent} scrollEnabled={false} // Since it's inside a parent ScrollView /> ) : ( {activeTab === 'medical_record' ? '暂无病历资料' : '暂无处方单据'} {activeTab === 'medical_record' ? '上传您的检查报告、诊断证明等' : '上传您的处方单、用药清单等'} )} {/* Add Button */} {/* Add/Edit Modal */} setModalVisible(false)} > setModalVisible(false)} style={styles.modalCloseButton}> 取消 {activeTab === 'medical_record' ? '添加病历' : '添加处方'} {(isSubmitting || isUploading) ? ( ) : ( 保存 )} {/* Title Input */} 标题 * {/* Date Picker */} 日期 setDatePickerVisibility(true)} > {dayjs(date).format('YYYY年MM月DD日')} {/* Images */} 图片资料 * {images.map((uri, index) => { const isPdf = uri.toLowerCase().endsWith('.pdf'); return ( {isPdf ? ( PDF ) : ( )} setImages(images.filter((_, i) => i !== index))} > ); })} {images.length < 9 && ( { Alert.alert( '上传文件', '请选择上传方式', [ { text: '拍照', onPress: handleTakePhoto }, { text: '从相册选择', onPress: handlePickImage }, { text: '选择文档 (PDF)', onPress: handlePickDocument }, { text: '取消', style: 'cancel' }, ] ); }}> 上传 )} {/* Note */} 备注 { setDate(d); setDatePickerVisibility(false); }} onCancel={() => setDatePickerVisibility(false)} maximumDate={new Date()} locale="zh_CN" confirmTextIOS="确定" cancelTextIOS="取消" /> setViewerVisible(false)} /> ); } const styles = StyleSheet.create({ container: { flex: 1, }, segmentContainer: { flexDirection: 'row', backgroundColor: '#F3F4F6', borderRadius: 12, padding: 4, marginBottom: 16, }, segmentButton: { flex: 1, paddingVertical: 10, alignItems: 'center', borderRadius: 10, }, segmentButtonActive: { backgroundColor: '#FFFFFF', shadowColor: '#000', shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.05, shadowRadius: 2, elevation: 1, }, segmentText: { fontSize: 14, fontWeight: '500', color: '#6B7280', fontFamily: 'AliRegular', }, segmentTextActive: { color: palette.purple[600], fontWeight: '600', fontFamily: 'AliBold', }, contentContainer: { minHeight: 300, }, loadingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingVertical: 40, }, listContent: { paddingBottom: 80, }, emptyState: { alignItems: 'center', justifyContent: 'center', marginTop: 40, paddingHorizontal: 40, }, emptyIconContainer: { width: 80, height: 80, borderRadius: 40, backgroundColor: '#F9FAFB', alignItems: 'center', justifyContent: 'center', marginBottom: 16, }, emptyText: { fontSize: 16, fontWeight: '600', color: '#374151', marginBottom: 8, fontFamily: 'AliBold', }, emptySubtext: { fontSize: 13, color: '#9CA3AF', textAlign: 'center', lineHeight: 20, fontFamily: 'AliRegular', }, fab: { position: 'absolute', right: 16, bottom: 16, width: 56, height: 56, borderRadius: 28, shadowColor: palette.purple[500], shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.3, shadowRadius: 8, elevation: 6, }, fabGradient: { width: '100%', height: '100%', borderRadius: 28, alignItems: 'center', justifyContent: 'center', }, // Modal Styles modalContainer: { flex: 1, backgroundColor: '#F9FAFB', }, modalHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: 16, paddingVertical: 12, backgroundColor: '#FFFFFF', borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: '#E5E7EB', paddingTop: Platform.OS === 'ios' ? 12 : 12, }, modalCloseButton: { padding: 8, }, modalCloseText: { fontSize: 16, color: '#6B7280', fontFamily: 'AliRegular', }, modalTitle: { fontSize: 17, fontWeight: '600', color: '#111827', fontFamily: 'AliBold', }, modalSaveButton: { paddingHorizontal: 12, paddingVertical: 6, backgroundColor: palette.purple[600], borderRadius: 6, }, modalSaveButtonDisabled: { opacity: 0.6, }, modalSaveText: { fontSize: 14, fontWeight: '600', color: '#FFFFFF', fontFamily: 'AliBold', }, formContainer: { padding: 16, }, inputGroup: { marginBottom: 20, }, label: { fontSize: 14, fontWeight: '500', color: '#374151', marginBottom: 8, fontFamily: 'AliRegular', }, required: { color: palette.error[500], }, input: { backgroundColor: '#FFFFFF', borderRadius: 12, paddingHorizontal: 12, paddingVertical: 12, fontSize: 16, color: '#111827', borderWidth: 1, borderColor: '#E5E7EB', fontFamily: 'AliRegular', }, textArea: { height: 100, textAlignVertical: 'top', }, dateInput: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', backgroundColor: '#FFFFFF', borderRadius: 12, paddingHorizontal: 12, paddingVertical: 12, borderWidth: 1, borderColor: '#E5E7EB', }, dateText: { fontSize: 16, color: '#111827', fontFamily: 'AliRegular', }, imageGrid: { flexDirection: 'row', flexWrap: 'wrap', gap: 12, }, imagePreviewContainer: { width: 80, height: 80, borderRadius: 8, overflow: 'hidden', position: 'relative', }, imagePreview: { width: '100%', height: '100%', }, pdfPreview: { alignItems: 'center', justifyContent: 'center', backgroundColor: '#F3F4F6', }, pdfText: { fontSize: 10, marginTop: 4, color: '#EF4444', fontWeight: '600', }, removeImageButton: { position: 'absolute', top: 2, right: 2, backgroundColor: 'rgba(255,255,255,0.8)', borderRadius: 10, }, addImageButton: { width: 80, height: 80, borderRadius: 8, borderWidth: 1, borderColor: palette.purple[200], borderStyle: 'dashed', backgroundColor: palette.purple[50], alignItems: 'center', justifyContent: 'center', }, addImageText: { fontSize: 12, color: palette.purple[600], marginTop: 4, fontFamily: 'AliRegular', }, });