import { ExpiryDatePickerModal } from '@/components/medications/ExpiryDatePickerModal'; import { ThemedText } from '@/components/ThemedText'; import { ConfirmationSheet } from '@/components/ui/ConfirmationSheet'; import { HeaderBar } from '@/components/ui/HeaderBar'; import InfoCard from '@/components/ui/InfoCard'; import { Colors } from '@/constants/Colors'; import { DOSAGE_UNITS, DOSAGE_VALUES, FORM_OPTIONS } from '@/constants/Medication'; import { ROUTES } from '@/constants/Routes'; import { useMembershipModal } from '@/contexts/MembershipModalContext'; import { useAppDispatch, useAppSelector } from '@/hooks/redux'; import { useAuthGuard } from '@/hooks/useAuthGuard'; import { useColorScheme } from '@/hooks/useColorScheme'; import { useCosUpload } from '@/hooks/useCosUpload'; import { useI18n } from '@/hooks/useI18n'; import { useVipService } from '@/hooks/useVipService'; import { analyzeMedicationV2, confirmMedicationRecognition, getMedicationById, getMedicationRecognitionStatus, getMedicationRecords, } from '@/services/medications'; import { deleteMedicationAction, fetchMedications, selectMedications, updateMedicationAction, } from '@/store/medicationsSlice'; import type { Medication, MedicationAiAnalysisV2, MedicationAiRecognitionResult, MedicationForm, } from '@/types/medication'; import { Ionicons } from '@expo/vector-icons'; import { Picker } from '@react-native-picker/picker'; import Voice from '@react-native-voice/voice'; import dayjs from 'dayjs'; import { GlassView, isLiquidGlassAvailable } from 'expo-glass-effect'; import { Image } from 'expo-image'; import * as ImagePicker from 'expo-image-picker'; import { LinearGradient } from 'expo-linear-gradient'; import { useLocalSearchParams, useRouter } from 'expo-router'; import LottieView from 'lottie-react-native'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { ActivityIndicator, Alert, Keyboard, Modal, Platform, Pressable, ScrollView, StyleSheet, Switch, Text, TextInput, TouchableOpacity, View, } from 'react-native'; import ImageViewing from 'react-native-image-viewing'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; const DEFAULT_IMAGE = require('@/assets/images/medicine/image-medicine.png'); type RecordsSummary = { takenCount: number; startedDays: number | null; }; export default function MedicationDetailScreen() { const { t } = useI18n(); const params = useLocalSearchParams<{ medicationId?: string; aiTaskId?: string; cover?: string }>(); const medicationId = Array.isArray(params.medicationId) ? params.medicationId[0] : params.medicationId; const aiTaskId = Array.isArray(params.aiTaskId) ? params.aiTaskId[0] : params.aiTaskId; const coverFromParams = Array.isArray(params.cover) ? params.cover[0] : params.cover; const dispatch = useAppDispatch(); const scheme = (useColorScheme() ?? 'light') as keyof typeof Colors; const colors = Colors[scheme]; const insets = useSafeAreaInsets(); const router = useRouter(); const { ensureLoggedIn } = useAuthGuard(); const { openMembershipModal } = useMembershipModal(); const { checkServiceAccess } = useVipService(); const { upload, uploading } = useCosUpload({ prefix: 'images/medications' }); const medications = useAppSelector(selectMedications); const medicationFromStore = medications.find((item) => item.id === medicationId); const isAiDraft = Boolean(aiTaskId); const [medication, setMedication] = useState(medicationFromStore ?? null); const [loading, setLoading] = useState(isAiDraft ? true : !medicationFromStore); const [summary, setSummary] = useState({ takenCount: 0, startedDays: null, }); const [summaryLoading, setSummaryLoading] = useState(true); const [updatePending, setUpdatePending] = useState(false); const [error, setError] = useState(null); const [noteModalVisible, setNoteModalVisible] = useState(false); const [noteDraft, setNoteDraft] = useState(medication?.note ?? ''); const [noteSaving, setNoteSaving] = useState(false); const [nameModalVisible, setNameModalVisible] = useState(false); const [nameDraft, setNameDraft] = useState(medicationFromStore?.name ?? ''); const [nameSaving, setNameSaving] = useState(false); const [dictationActive, setDictationActive] = useState(false); const [dictationLoading, setDictationLoading] = useState(false); const isDictationSupported = Platform.OS === 'ios'; const [keyboardHeight, setKeyboardHeight] = useState(0); const [deleteSheetVisible, setDeleteSheetVisible] = useState(false); const [deleteLoading, setDeleteLoading] = useState(false); const [deactivateSheetVisible, setDeactivateSheetVisible] = useState(false); const [deactivateLoading, setDeactivateLoading] = useState(false); const [showImagePreview, setShowImagePreview] = useState(false); const [photoPreview, setPhotoPreview] = useState(null); const buildAiDraftMedication = useCallback( (result: MedicationAiRecognitionResult): Medication => { const timeList = result.medicationTimes && result.medicationTimes.length ? result.medicationTimes : Array.from({ length: result.timesPerDay ?? 1 }, (_, idx) => { const base = ['08:00', '12:30', '18:30', '22:00']; return base[idx] ?? base[0]; }); return { id: 'ai-draft', userId: '', name: result.name || 'AI 识别药物', photoUrl: result.photoUrl || coverFromParams || undefined, form: result.form || 'other', dosageValue: result.dosageValue ?? 1, dosageUnit: result.dosageUnit || '次', timesPerDay: result.timesPerDay ?? Math.max(timeList.length, 1), medicationTimes: timeList, startDate: result.startDate || new Date().toISOString(), endDate: result.endDate ?? null, repeatPattern: 'daily', note: result.note || '', aiAnalysis: result ? JSON.stringify(result) : undefined, isActive: true, deleted: false, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }; }, [coverFromParams] ); // AI 分析相关状态 const [aiAnalysisLoading, setAiAnalysisLoading] = useState(false); const [aiAnalysisResult, setAiAnalysisResult] = useState(null); const [aiAnalysisError, setAiAnalysisError] = useState(null); const [aiAnalysisLocked, setAiAnalysisLocked] = useState(false); const [aiDraftSaving, setAiDraftSaving] = useState(false); // 剂量选择相关状态 const [dosagePickerVisible, setDosagePickerVisible] = useState(false); const [dosageValuePicker, setDosageValuePicker] = useState( medicationFromStore?.dosageValue ?? 1 ); const [dosageUnitPicker, setDosageUnitPicker] = useState( medicationFromStore?.dosageUnit ?? DOSAGE_UNITS[0] ); // 剂型选择相关状态 const [formPickerVisible, setFormPickerVisible] = useState(false); const [formPicker, setFormPicker] = useState( medicationFromStore?.form ?? 'capsule' ); // 有效期选择相关状态 const [expiryDatePickerVisible, setExpiryDatePickerVisible] = useState(false); const [expiryDatePickerValue, setExpiryDatePickerValue] = useState(new Date()); // ScrollView 引用,用于滚动到底部 const scrollViewRef = React.useRef(null); useEffect(() => { if (!medicationFromStore) { dispatch(fetchMedications()); } }, [dispatch, medicationFromStore]); useEffect(() => { setAiAnalysisError(null); }, [medicationId]); useEffect(() => { if (medicationFromStore) { setMedication(medicationFromStore); setLoading(false); setAiAnalysisResult(null); // 如果服务端返回了 AI 分析结果,自动展示 if (medicationFromStore.aiAnalysis) { try { const parsed = JSON.parse(medicationFromStore.aiAnalysis); if (parsed && typeof parsed === 'object') { setAiAnalysisResult(parsed as MedicationAiAnalysisV2); } } catch { // ignore legacy markdown } } } }, [medicationFromStore]); useEffect(() => { // 同步剂量选择器和剂型选择器的默认值 if (medication) { setDosageValuePicker(medication.dosageValue); setDosageUnitPicker(medication.dosageUnit); setFormPicker(medication.form); } }, [medication?.dosageValue, medication?.dosageUnit, medication?.form]); useEffect(() => { setNoteDraft(medication?.note ?? ''); }, [medication?.note]); useEffect(() => { setNameDraft(medication?.name ?? ''); }, [medication?.name]); useEffect(() => { let isMounted = true; let abortController = new AbortController(); console.log('[MEDICATION_DETAIL] useEffect triggered', { medicationId, hasMedicationFromStore: !!medicationFromStore, deleteLoading }); if (isAiDraft) { return () => { isMounted = false; abortController.abort(); }; } // 如果正在删除操作中,不执行任何操作 if (deleteLoading) { console.log('[MEDICATION_DETAIL] Delete operation in progress, skipping useEffect'); return () => { isMounted = false; abortController.abort(); }; } if (!medicationId || medicationFromStore) { console.log('[MEDICATION_DETAIL] Early return from useEffect', { hasMedicationId: !!medicationId, hasMedicationFromStore: !!medicationFromStore }); return () => { isMounted = false; abortController.abort(); }; } console.log('[MEDICATION_DETAIL] Starting API call for medication', medicationId); setLoading(true); getMedicationById(medicationId) .then((data) => { if (!isMounted || abortController.signal.aborted) return; console.log('[MEDICATION_DETAIL] API call successful', data); setMedication(data); setError(null); // 如果服务端返回了 AI 分析结果,自动展示 setAiAnalysisResult(null); if (data.aiAnalysis) { try { const parsed = JSON.parse(data.aiAnalysis); if (parsed && typeof parsed === 'object') { setAiAnalysisResult(parsed as MedicationAiAnalysisV2); } } catch { // ignore legacy markdown } } }) .catch((err) => { if (abortController.signal.aborted) return; console.error('加载药品详情失败', err); console.log('[MEDICATION_DETAIL] API call failed for medication', medicationId, err); if (isMounted) { setError(t('medications.detail.error.title')); } }) .finally(() => { if (isMounted && !abortController.signal.aborted) { setLoading(false); } }); return () => { isMounted = false; abortController.abort(); }; }, [medicationId, medicationFromStore, deleteLoading]); useEffect(() => { let cancelled = false; if (!aiTaskId) return; const hydrateFromAi = async () => { try { setLoading(true); const data = await getMedicationRecognitionStatus(aiTaskId); if (cancelled) return; if (data.status !== 'completed' || !data.result) { setError('AI 识别结果暂不可用'); return; } const draft = buildAiDraftMedication(data.result); setMedication(draft); setAiAnalysisResult({ suitableFor: data.result.suitableFor ?? [], unsuitableFor: data.result.unsuitableFor ?? [], mainIngredients: data.result.mainIngredients ?? [], mainUsage: data.result.mainUsage ?? '', sideEffects: data.result.sideEffects ?? [], storageAdvice: data.result.storageAdvice ?? [], healthAdvice: data.result.healthAdvice ?? [], }); setSummary({ takenCount: 0, startedDays: null }); setSummaryLoading(false); setError(null); setAiAnalysisLocked(false); } catch (err) { if (cancelled) return; console.error('[MEDICATION_DETAIL] 加载 AI 草稿失败', err); setError('识别结果加载失败,请返回重试'); } finally { if (!cancelled) { setLoading(false); } } }; hydrateFromAi(); return () => { cancelled = true; }; }, [aiTaskId, buildAiDraftMedication]); useEffect(() => { let isMounted = true; if (!medicationId || isAiDraft) { return () => { isMounted = false; }; } setSummaryLoading(true); getMedicationRecords({ medicationId }) .then((records) => { if (!isMounted) return; const takenCount = records.filter((record) => record.status === 'taken').length; const earliestRecord = records.reduce((earliest, record) => { const current = new Date(record.scheduledTime); if (!earliest || current < earliest) { return current; } return earliest; }, null); const startedDaysRaw = earliestRecord ? dayjs().diff(dayjs(earliestRecord), 'day') : medication ? dayjs().diff(dayjs(medication.startDate), 'day') : null; const startedDays = typeof startedDaysRaw === 'number' ? Math.max(startedDaysRaw, 0) : null; setSummary({ takenCount, startedDays: startedDays ?? null, }); }) .catch((err) => { console.error('加载服药记录失败', err); }) .finally(() => { if (isMounted) { setSummaryLoading(false); } }); return () => { isMounted = false; }; }, [medicationId, medication]); const appendDictationResult = useCallback((text: string) => { const clean = text.trim(); if (!clean) return; setNoteDraft((prev) => { if (!prev) return clean; return `${prev}${prev.endsWith('\n') ? '' : '\n'}${clean}`; }); }, []); useEffect(() => { if (!isDictationSupported || !noteModalVisible) { return; } Voice.onSpeechStart = () => { setDictationActive(true); setDictationLoading(false); }; Voice.onSpeechEnd = () => { setDictationActive(false); setDictationLoading(false); }; Voice.onSpeechResults = (event: any) => { const recognized = event?.value?.[0]; if (recognized) { appendDictationResult(recognized); } }; Voice.onSpeechError = (error: any) => { console.log('[MEDICATION_DETAIL] voice error', error); setDictationActive(false); setDictationLoading(false); Alert.alert(t('medications.add.note.voiceError'), t('medications.add.note.voiceErrorMessage')); }; return () => { Voice.destroy() .then(() => { Voice.removeAllListeners(); }) .catch(() => {}); }; }, [appendDictationResult, isDictationSupported, noteModalVisible]); useEffect(() => { // 统一处理名字编辑弹窗和备注弹窗的键盘监听 if (!noteModalVisible && !nameModalVisible) { setKeyboardHeight(0); return; } const showEvent = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow'; const hideEvent = Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide'; const handleShow = (event: any) => { const height = event?.endCoordinates?.height ?? 0; setKeyboardHeight(height); }; const handleHide = () => setKeyboardHeight(0); const showSub = Keyboard.addListener(showEvent, handleShow); const hideSub = Keyboard.addListener(hideEvent, handleHide); return () => { showSub.remove(); hideSub.remove(); }; }, [noteModalVisible, nameModalVisible]); const handleDictationPress = useCallback(async () => { if (!isDictationSupported || dictationLoading) { return; } try { if (dictationActive) { setDictationLoading(true); await Voice.stop(); setDictationLoading(false); return; } setDictationLoading(true); try { await Voice.stop(); } catch { // ignore if not recording } await Voice.start('zh-CN'); } catch (error) { console.log('[MEDICATION_DETAIL] unable to start dictation', error); setDictationLoading(false); Alert.alert(t('medications.add.note.voiceStartError'), t('medications.add.note.voiceStartErrorMessage')); } }, [dictationActive, dictationLoading, isDictationSupported]); const closeNoteModal = useCallback(() => { setNoteModalVisible(false); if (dictationActive) { Voice.stop().catch(() => {}); } setDictationActive(false); setDictationLoading(false); setKeyboardHeight(0); }, [dictationActive]); const handleToggleMedication = async (nextValue: boolean) => { if (!medication || updatePending || isAiDraft) return; // 如果是关闭激活状态,显示确认弹窗 if (!nextValue) { setDeactivateSheetVisible(true); return; } // 如果是开启激活状态,直接执行 try { setUpdatePending(true); const updated = await dispatch( updateMedicationAction({ id: medication.id, isActive: nextValue, }) ).unwrap(); setMedication(updated); } catch (err) { console.error('切换药品状态失败', err); Alert.alert(t('medications.detail.toggleError.title'), t('medications.detail.toggleError.message')); } finally { setUpdatePending(false); } }; const handleDeactivateMedication = useCallback(async () => { if (!medication || deactivateLoading || isAiDraft) return; try { setDeactivateLoading(true); setDeactivateSheetVisible(false); // 立即关闭确认对话框 const updated = await dispatch( updateMedicationAction({ id: medication.id, isActive: false, }) ).unwrap(); setMedication(updated); } catch (error) { console.error('停用药物失败', error); Alert.alert(t('medications.detail.deactivate.error.title'), t('medications.detail.deactivate.error.message')); } finally { setDeactivateLoading(false); } }, [dispatch, medication, deactivateLoading]); const formLabel = medication ? t(`medications.manage.formLabels.${medication.form}`) : ''; const dosageLabel = medication ? `${medication.dosageValue} ${medication.dosageUnit}` : '--'; const startDateLabel = medication ? dayjs(medication.startDate).format('YYYY年M月D日') : '--'; // 计算服药周期显示 const medicationPeriodLabel = useMemo(() => { if (!medication) return '--'; const startDate = dayjs(medication.startDate).format('YYYY年M月D日'); if (medication.endDate) { // 有结束日期,显示开始日期到结束日期 const endDate = dayjs(medication.endDate).format('YYYY年M月D日'); return `${startDate} - ${endDate}`; } else { // 没有结束日期,显示长期 return `${startDate} - ${t('medications.detail.plan.longTerm')}`; } }, [medication, t]); // 计算有效期显示 const expiryDateLabel = useMemo(() => { if (!medication?.expiryDate) return '未设置'; const expiry = dayjs(medication.expiryDate); const today = dayjs(); const daysUntilExpiry = expiry.diff(today, 'day'); if (daysUntilExpiry < 0) { return `${expiry.format('YYYY年M月D日')} (已过期)`; } else if (daysUntilExpiry === 0) { return `${expiry.format('YYYY年M月D日')} (今天到期)`; } else if (daysUntilExpiry <= 30) { return `${expiry.format('YYYY年M月D日')} (${daysUntilExpiry}天后到期)`; } else { return expiry.format('YYYY年M月D日'); } }, [medication?.expiryDate]); const reminderTimes = medication?.medicationTimes?.length ? medication.medicationTimes.join('、') : t('medications.manage.reminderNotSet'); const frequencyLabel = useMemo(() => { if (!medication) return '--'; switch (medication.repeatPattern) { case 'daily': return `${t('medications.manage.frequency.daily')} ${medication.timesPerDay} ${t('medications.add.frequency.value', { count: medication.timesPerDay }).split(' ')[1]}`; case 'weekly': return `${t('medications.manage.frequency.weekly')} ${medication.timesPerDay} ${t('medications.add.frequency.value', { count: medication.timesPerDay }).split(' ')[1]}`; default: return `${t('medications.manage.frequency.custom')} · ${medication.timesPerDay} ${t('medications.add.frequency.value', { count: medication.timesPerDay }).split(' ')[1]}`; } }, [medication, t]); const serviceInfo = useMemo(() => checkServiceAccess(), [checkServiceAccess]); const hasAiAnalysis = Boolean(aiAnalysisResult); const aiActionLabel = aiAnalysisLoading ? t('medications.detail.aiAnalysis.analyzingButton') : hasAiAnalysis ? '重新分析' : '获取 AI 分析'; const handleOpenNoteModal = useCallback(() => { setNoteDraft(medication?.note ?? ''); setNoteModalVisible(true); }, [medication?.note]); const handleOpenNameModal = useCallback(() => { setNameDraft(medication?.name ?? ''); setNameModalVisible(true); }, [medication?.name]); const handleCloseNameModal = useCallback(() => { setNameModalVisible(false); }, []); const handleNameChange = useCallback((value: string) => { const sanitized = value.replace(/\n/g, ''); const normalized = Array.from(sanitized).slice(0, 10).join(''); setNameDraft(normalized); }, []); const handleSaveName = useCallback(async () => { if (!medication || nameSaving) return; const trimmed = nameDraft.trim(); if (!trimmed) { Alert.alert( '提示', '药物名称不能为空' ); return; } if (Array.from(trimmed).length > 10) { Alert.alert( '提示', '药物名称不能超过10个字' ); return; } if (isAiDraft) { setMedication((prev) => (prev ? { ...prev, name: trimmed } : prev)); setNameModalVisible(false); return; } setNameSaving(true); try { const updated = await dispatch( updateMedicationAction({ id: medication.id, name: trimmed, }) ).unwrap(); setMedication(updated); setNameModalVisible(false); } catch (err) { console.error('更新药物名称失败', err); Alert.alert( '提示', '名称更新失败,请稍后再试' ); } finally { setNameSaving(false); } }, [dispatch, isAiDraft, medication, nameDraft, nameSaving, t]); const handleSaveNote = useCallback(async () => { if (!medication) return; const trimmed = noteDraft.trim(); if (isAiDraft) { setMedication((prev) => (prev ? { ...prev, note: trimmed } : prev)); closeNoteModal(); return; } setNoteSaving(true); try { const updated = await dispatch( updateMedicationAction({ id: medication.id, note: trimmed || undefined, }) ).unwrap(); setMedication(updated); closeNoteModal(); } catch (err) { console.error('保存备注失败', err); Alert.alert(t('medications.detail.note.saveError.title'), t('medications.detail.note.saveError.message')); } finally { setNoteSaving(false); } }, [closeNoteModal, dispatch, isAiDraft, medication, noteDraft]); useEffect(() => { if (serviceInfo.canUseService) { setAiAnalysisLocked(false); } }, [serviceInfo.canUseService]); const noteText = medication?.note?.trim() ? medication.note : t('medications.detail.note.noNote'); const dayStreakText = typeof summary.startedDays === 'number' ? t('medications.detail.overview.startedDays', { days: summary.startedDays }) : medication ? t('medications.detail.overview.startDate', { date: dayjs(medication.startDate).format('YYYY年M月D日') }) : t('medications.detail.overview.noStartDate'); const handleDeleteMedication = useCallback(async () => { if (!medication || deleteLoading) { console.log('[MEDICATION_DETAIL] Delete aborted', { hasMedication: !!medication, deleteLoading }); return; } console.log('[MEDICATION_DETAIL] Starting delete operation for medication', medication.id); try { setDeleteLoading(true); setDeleteSheetVisible(false); // 立即关闭确认对话框 await dispatch(deleteMedicationAction(medication.id)).unwrap(); console.log('[MEDICATION_DETAIL] Delete operation successful, navigating back'); router.back(); } catch (err) { console.error('删除药品失败', err); Alert.alert(t('medications.detail.delete.error.title'), t('medications.detail.delete.error.message')); } finally { setDeleteLoading(false); } }, [deleteLoading, dispatch, medication, router]); const handleImagePreview = useCallback(() => { if (medication?.photoUrl) { setShowImagePreview(true); } }, [medication?.photoUrl]); // 处理图片选择(拍照或相册) const handleSelectPhoto = useCallback(async () => { if (!medication || uploading) return; Alert.alert( t('medications.add.photo.selectTitle'), t('medications.add.photo.selectMessage'), [ { text: t('medications.add.photo.camera'), onPress: async () => { try { const permission = await ImagePicker.requestCameraPermissionsAsync(); if (permission.status !== 'granted') { Alert.alert( t('medications.detail.photo.permissionDenied', { defaultValue: '权限不足' }), t('medications.detail.photo.cameraPermissionMessage', { defaultValue: '需要相机权限以拍摄药品照片' }) ); return; } const result = await ImagePicker.launchCameraAsync({ allowsEditing: true, quality: 0.3, aspect: [9, 16], }); if (result.canceled || !result.assets?.length) { return; } const asset = result.assets[0]; setPhotoPreview(asset.uri); try { const { url } = await upload( { uri: asset.uri, name: asset.fileName ?? `medication-${Date.now()}.jpg`, type: asset.mimeType ?? 'image/jpeg', }, { prefix: 'images/medications' } ); if (isAiDraft) { setMedication((prev) => (prev ? { ...prev, photoUrl: url } : prev)); setPhotoPreview(null); return; } // 上传成功后更新药物信息 const updated = await dispatch( updateMedicationAction({ id: medication.id, photoUrl: url, }) ).unwrap(); setMedication(updated); setPhotoPreview(null); } catch (uploadError) { console.error('[MEDICATION] 图片上传失败', uploadError); Alert.alert( t('medications.detail.photo.uploadFailed', { defaultValue: '上传失败' }), t('medications.detail.photo.uploadFailedMessage', { defaultValue: '图片上传失败,请稍后重试' }) ); setPhotoPreview(null); } } catch (error) { console.error('[MEDICATION] 拍照失败', error); Alert.alert( t('medications.detail.photo.cameraFailed', { defaultValue: '拍照失败' }), t('medications.detail.photo.cameraFailedMessage', { defaultValue: '无法打开相机,请稍后再试' }) ); } }, }, { text: t('medications.add.photo.album'), onPress: async () => { try { const permission = await ImagePicker.requestMediaLibraryPermissionsAsync(); if (permission.status !== 'granted') { Alert.alert( t('medications.detail.photo.permissionDenied', { defaultValue: '权限不足' }), t('medications.detail.photo.libraryPermissionMessage', { defaultValue: '需要相册权限以选择药品照片' }) ); return; } const result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ['images'], quality: 0.9, }); if (result.canceled || !result.assets?.length) { return; } const asset = result.assets[0]; setPhotoPreview(asset.uri); try { const { url } = await upload( { uri: asset.uri, name: asset.fileName ?? `medication-${Date.now()}.jpg`, type: asset.mimeType ?? 'image/jpeg', }, { prefix: 'images/medications' } ); if (isAiDraft) { setMedication((prev) => (prev ? { ...prev, photoUrl: url } : prev)); setPhotoPreview(null); return; } // 上传成功后更新药物信息 const updated = await dispatch( updateMedicationAction({ id: medication.id, photoUrl: url, }) ).unwrap(); setMedication(updated); setPhotoPreview(null); } catch (uploadError) { console.error('[MEDICATION] 图片上传失败', uploadError); Alert.alert( t('medications.detail.photo.uploadFailed', { defaultValue: '上传失败' }), t('medications.detail.photo.uploadFailedMessage', { defaultValue: '图片上传失败,请稍后重试' }) ); setPhotoPreview(null); } } catch (error) { console.error('[MEDICATION] 从相册选择失败', error); Alert.alert( t('medications.detail.photo.libraryFailed', { defaultValue: '选择失败' }), t('medications.detail.photo.libraryFailedMessage', { defaultValue: '无法打开相册,请稍后再试' }) ); } }, }, { text: t('medications.add.photo.cancel'), style: 'cancel', }, ], { cancelable: true } ); }, [dispatch, isAiDraft, medication, t, upload, uploading]); const handleStartDatePress = useCallback(() => { if (!medication) return; const startDate = dayjs(medication.startDate).format('YYYY年M月D日'); let message; if (medication.endDate) { const endDate = dayjs(medication.endDate).format('YYYY年M月D日'); message = `从 ${startDate} 至 ${endDate}`; } else { message = `从 ${startDate} 至长期`; } Alert.alert('服药周期', message); }, [medication, t]); const handleTimePress = useCallback(() => { Alert.alert('服药时间', `每日提醒时间:${reminderTimes}`); }, [reminderTimes, t]); const handleDosagePress = useCallback(() => { if (!medication) return; setDosagePickerVisible(true); }, [medication]); const handleFormPress = useCallback(() => { if (!medication) return; setFormPickerVisible(true); }, [medication]); const handleFrequencyPress = useCallback(() => { if (!medication) return; // AI 草稿模式:显示提示,暂不支持编辑频率 if (isAiDraft) { Alert.alert( '提示', '请先保存药物信息后,再编辑服药频率', [{ text: '知道了', style: 'default' }] ); return; } // 正常模式:跳转到独立的频率编辑页面 router.push({ pathname: ROUTES.MEDICATION_EDIT_FREQUENCY, params: { medicationId: medication.id, medicationName: medication.name, repeatPattern: medication.repeatPattern, timesPerDay: medication.timesPerDay.toString(), medicationTimes: medication.medicationTimes.join(','), }, }); }, [medication, router, isAiDraft]); const handleExpiryDatePress = useCallback(() => { if (!medication) return; setExpiryDatePickerValue(medication.expiryDate ? new Date(medication.expiryDate) : new Date()); setExpiryDatePickerVisible(true); }, [medication]); const handleExpiryDateConfirm = useCallback(async (date: Date) => { if (!medication || updatePending) return; if (isAiDraft) { setMedication((prev) => prev ? { ...prev, expiryDate: dayjs(date).endOf('day').toISOString() } : prev ); return; } try { setUpdatePending(true); const updated = await dispatch( updateMedicationAction({ id: medication.id, expiryDate: dayjs(date).endOf('day').toISOString(), }) ).unwrap(); setMedication(updated); } catch (err) { console.error('更新有效期失败', err); Alert.alert('更新失败', '有效期更新失败,请稍后重试'); } finally { setUpdatePending(false); } }, [dispatch, isAiDraft, medication, updatePending]); const renderAdviceCard = useCallback( ( title: string, items: string[] | undefined, icon: keyof typeof Ionicons.glyphMap, accentColor: string, backgroundColor: string ) => { if (!items?.length) return null; return ( {title} {items.length} {items.map((item, index) => ( {item} ))} ); }, [colors.text] ); const confirmDosagePicker = useCallback(async () => { if (!medication || updatePending) return; setDosagePickerVisible(false); // 如果值没有变化,不需要更新 if (dosageValuePicker === medication.dosageValue && dosageUnitPicker === medication.dosageUnit) { return; } if (isAiDraft) { setMedication((prev) => prev ? { ...prev, dosageValue: dosageValuePicker, dosageUnit: dosageUnitPicker } : prev ); return; } try { setUpdatePending(true); const updated = await dispatch( updateMedicationAction({ id: medication.id, dosageValue: dosageValuePicker, dosageUnit: dosageUnitPicker, }) ).unwrap(); setMedication(updated); } catch (err) { console.error('更新剂量失败', err); Alert.alert(t('medications.detail.updateErrors.dosage'), t('medications.detail.updateErrors.dosageMessage')); } finally { setUpdatePending(false); } }, [dispatch, dosageUnitPicker, dosageValuePicker, isAiDraft, medication, updatePending]); const confirmFormPicker = useCallback(async () => { if (!medication || updatePending) return; setFormPickerVisible(false); // 如果值没有变化,不需要更新 if (formPicker === medication.form) { return; } if (isAiDraft) { setMedication((prev) => (prev ? { ...prev, form: formPicker } : prev)); return; } try { setUpdatePending(true); const updated = await dispatch( updateMedicationAction({ id: medication.id, form: formPicker, }) ).unwrap(); setMedication(updated); } catch (err) { console.error('更新剂型失败', err); Alert.alert(t('medications.detail.updateErrors.form'), t('medications.detail.updateErrors.formMessage')); } finally { setUpdatePending(false); } }, [dispatch, formPicker, isAiDraft, medication, updatePending]); // AI 分析处理函数 const handleAiAnalysis = useCallback(async () => { if (!medication || aiAnalysisLoading || isAiDraft) return; // 1. 先验证用户是否登录 const isLoggedIn = await ensureLoggedIn(); if (!isLoggedIn) { return; // 如果未登录,ensureLoggedIn 会自动跳转到登录页 } // 2. 检查用户是否是 VIP 或有剩余免费次数 const serviceAccess = checkServiceAccess(); if (!serviceAccess.canUseService) { setAiAnalysisLocked(true); openMembershipModal({ onPurchaseSuccess: () => { handleAiAnalysis(); }, }); return; } // 3. 通过验证,执行 AI 分析 setAiAnalysisLoading(true); setAiAnalysisError(null); setAiAnalysisLocked(false); // 滚动到底部,让用户看到分析内容 setTimeout(() => { scrollViewRef.current?.scrollToEnd({ animated: true }); }, 100); try { const result = await analyzeMedicationV2(medication.id); setAiAnalysisResult(result); // 本地保存一份,便于下次打开快速展示 setMedication((prev) => (prev ? { ...prev, aiAnalysis: JSON.stringify(result) } : prev)); } catch (error: any) { console.error('[MEDICATION] AI 分析失败:', error); const status = error?.status; const message = status === 403 ? '免费使用次数已用完,请开通会员获取更多分析次数' : status === 404 ? '药品未找到' : t('medications.detail.aiAnalysis.error.message'); setAiAnalysisError(message); if (status === 403) { setAiAnalysisLocked(true); openMembershipModal({ onPurchaseSuccess: () => { handleAiAnalysis(); }, }); } else { Alert.alert(t('medications.detail.aiAnalysis.error.title'), message); } } finally { setAiAnalysisLoading(false); } }, [aiAnalysisLoading, checkServiceAccess, ensureLoggedIn, isAiDraft, medication, openMembershipModal, t]); const handleAiDraftSave = useCallback(async () => { if (!aiTaskId || !medication || aiDraftSaving) return; try { setAiDraftSaving(true); const created = await confirmMedicationRecognition(aiTaskId, { name: medication.name, timesPerDay: medication.timesPerDay, medicationTimes: medication.medicationTimes, startDate: medication.startDate, endDate: medication.endDate ?? undefined, note: medication.note, }); await dispatch(fetchMedications()); router.replace({ pathname: '/medications/[medicationId]', params: { medicationId: created.id }, }); } catch (err: any) { console.error('[MEDICATION_DETAIL] AI 草稿保存失败', err); Alert.alert('保存失败', err?.message || '请稍后再试'); } finally { setAiDraftSaving(false); } }, [aiDraftSaving, aiTaskId, dispatch, medication, router]); if (!medicationId) { return ( {/* 背景渐变 */} {/* 装饰性圆圈 */} {t('medications.detail.notFound.title')} {t('medications.detail.notFound.subtitle')} ); } const isLoadingState = loading && !medication; const contentBottomPadding = Math.max(insets.bottom, 16) + 140; return ( {/* 背景渐变 */} {/* 装饰性圆圈 */} {isLoadingState ? ( {t('medications.detail.loading')} ) : error ? ( {error} {t('medications.detail.error.subtitle')} ) : medication ? ( {/* 点击图片区域 - 触发上传 */} {/* 点击预览图标 - 触发预览(只在有照片且不在上传中时显示) */} {medication.photoUrl && !uploading && ( )} {/* 上传中提示 */} {uploading && ( 上传中... )} {medication.name} {dosageLabel} · {formLabel}
{t('medications.detail.note.label')} {noteText}
分析结果 {hasAiAnalysis ? '已生成' : aiAnalysisLocked ? '会员专享' : '待生成'} {hasAiAnalysis && ( AI 推荐 )} {medication.name} {aiAnalysisResult?.mainUsage || '获取 AI 分析,快速了解适用人群、成分安全与使用建议。'} {dosageLabel} {formLabel} {aiAnalysisLoading && ( {t('medications.detail.aiAnalysis.analyzing')} )} {aiAnalysisResult ? ( <> {!!aiAnalysisResult.mainIngredients?.length && ( {aiAnalysisResult.mainIngredients.map((item) => ( {item} ))} )} {aiAnalysisResult.mainUsage} 适合人群 {aiAnalysisResult.suitableFor.map((item, idx) => ( {item} ))} 不适合人群 {aiAnalysisResult.unsuitableFor.map((item, idx) => ( {item} ))} {renderAdviceCard('可能的副作用', aiAnalysisResult.sideEffects, 'warning-outline', '#f59e0b', '#FFFBEB')} {renderAdviceCard('储存建议', aiAnalysisResult.storageAdvice, 'cube-outline', '#10b981', '#ECFDF3')} {renderAdviceCard('健康/使用建议', aiAnalysisResult.healthAdvice, 'sparkles-outline', '#6366f1', '#EEF2FF')} ) : null} {aiAnalysisError && ( {aiAnalysisError} )} {(aiAnalysisLocked || !serviceInfo.canUseService) && ( openMembershipModal({ onPurchaseSuccess: () => { handleAiAnalysis(); }, }) } style={styles.aiMembershipCard} > 会员专享 AI 深度解读 解锁完整药品分析与无限次使用 )}
{summaryLoading ? t('medications.detail.overview.calculating') : t('medications.detail.overview.takenCount', { count: summary.takenCount })} {summaryLoading ? t('medications.detail.overview.calculatingDays') : dayStreakText}
) : null} {medication ? ( {isAiDraft ? ( router.replace('/medications/ai-camera')} > 重新拍摄 {aiDraftSaving ? ( ) : ( 保存并创建 )} ) : ( {/* AI 分析按钮 */} {!hasAiAnalysis && ( {isLiquidGlassAvailable() ? ( {aiAnalysisLoading ? ( ) : ( )} {aiActionLabel} ) : ( {aiAnalysisLoading ? ( ) : ( )} {aiActionLabel} )} )} {/* 删除按钮 */} setDeleteSheetVisible(true)} > {isLiquidGlassAvailable() ? ( ) : ( )} )} ) : null} 编辑药物名称 {`${Array.from(nameDraft).length}/10`} {nameSaving ? ( ) : ( 保存 )} {t('medications.detail.note.edit')} {isDictationSupported && ( {dictationLoading ? ( ) : ( )} )} {!isDictationSupported && ( {t('medications.detail.note.voiceNotSupported')} )} {noteSaving ? ( ) : ( {t('medications.detail.note.save')} )} setDosagePickerVisible(false)} > setDosagePickerVisible(false)} /> {t('medications.detail.dosage.selectDosage')} {t('medications.detail.dosage.dosageValue')} setDosageValuePicker(Number(value))} itemStyle={styles.pickerItem} style={styles.picker} > {DOSAGE_VALUES.map((value) => ( ))} {t('medications.detail.dosage.unit')} setDosageUnitPicker(String(value))} itemStyle={styles.pickerItem} style={styles.picker} > {DOSAGE_UNITS.map((unit) => ( ))} setDosagePickerVisible(false)} style={[styles.pickerBtn, { borderColor: colors.border }]} > {t('medications.detail.pickers.cancel')} {t('medications.detail.pickers.confirm')} setFormPickerVisible(false)} > setFormPickerVisible(false)} /> {t('medications.detail.dosage.selectForm')} setFormPicker(value as MedicationForm)} itemStyle={styles.pickerItem} style={styles.picker} > {FORM_OPTIONS.map((option) => ( ))} setFormPickerVisible(false)} style={[styles.pickerBtn, { borderColor: colors.border }]} > {t('medications.detail.pickers.cancel')} {t('medications.detail.pickers.confirm')} {/* 有效期选择器 */} setExpiryDatePickerVisible(false)} onConfirm={handleExpiryDateConfirm} isAiDraft={isAiDraft} /> {medication && !isAiDraft ? ( setDeleteSheetVisible(false)} onConfirm={handleDeleteMedication} title={t('medications.detail.delete.title', { name: medication.name })} description={t('medications.detail.delete.description')} confirmText={t('medications.detail.delete.confirm')} cancelText={t('medications.detail.delete.cancel')} destructive loading={deleteLoading} /> ) : null} {medication && !isAiDraft ? ( setDeactivateSheetVisible(false)} onConfirm={handleDeactivateMedication} title={t('medications.detail.deactivate.title', { name: medication.name })} description={t('medications.detail.deactivate.description')} confirmText={t('medications.detail.deactivate.confirm')} cancelText={t('medications.detail.deactivate.cancel')} destructive loading={deactivateLoading} /> ) : null} {/* 图片预览 */} {medication?.photoUrl && ( setShowImagePreview(false)} swipeToCloseEnabled={true} doubleTapToZoomEnabled={true} HeaderComponent={() => ( {medication.name} )} FooterComponent={() => ( setShowImagePreview(false)} > {t('medications.detail.imageViewer.close')} )} /> )}
); } const Section = ({ title, children, color, }: { title: string; children: React.ReactNode; color: string; }) => { return ( {title} {children} ); }; const styles = StyleSheet.create({ container: { flex: 1, position: 'relative', }, gradientBackground: { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0, }, decorativeCircle1: { position: 'absolute', top: 40, right: 20, width: 60, height: 60, borderRadius: 30, backgroundColor: '#0EA5E9', opacity: 0.1, }, decorativeCircle2: { position: 'absolute', bottom: -15, left: -15, width: 40, height: 40, borderRadius: 20, backgroundColor: '#0EA5E9', opacity: 0.05, }, content: { paddingHorizontal: 20, gap: 24, }, centered: { flex: 1, alignItems: 'center', justifyContent: 'center', gap: 12, }, loadingText: { marginTop: 8, fontSize: 14, }, emptyTitle: { fontSize: 18, fontWeight: '600', textAlign: 'center', }, emptySubtitle: { fontSize: 14, textAlign: 'center', }, heroCard: { borderRadius: 28, padding: 20, flexDirection: 'row', justifyContent: 'space-between', backgroundColor: '#FFFFFF', alignItems: 'center', gap: 12, }, heroInfo: { flexDirection: 'row', alignItems: 'center', gap: 14, flex: 1, }, heroImageWrapper: { width: 64, height: 64, borderRadius: 20, backgroundColor: '#F2F2F2', alignItems: 'center', justifyContent: 'center', position: 'relative', overflow: 'hidden', }, heroImageTouchable: { width: '100%', height: '100%', alignItems: 'center', justifyContent: 'center', }, heroImage: { width: '60%', height: '60%', borderRadius: '20%' }, heroTitleWrapper: { flex: 1, minWidth: 0, }, heroTitleRow: { flexDirection: 'row', alignItems: 'center', gap: 6, }, heroTitle: { fontSize: 20, fontWeight: '700', }, heroEditButton: { padding: 4, borderRadius: 12, }, heroMeta: { marginTop: 4, fontSize: 13, fontWeight: '500', }, heroToggle: { alignItems: 'flex-end', gap: 6, }, section: { gap: 12, }, sectionTitle: { fontSize: 18, fontWeight: '700', }, row: { flexDirection: 'row', gap: 12, }, fullCard: { borderRadius: 22, padding: 18, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', }, fullCardLeading: { flexDirection: 'row', alignItems: 'center', gap: 8, }, fullCardLabel: { fontSize: 15, fontWeight: '600', }, fullCardTrailing: { flexDirection: 'row', alignItems: 'center', gap: 6, }, fullCardValue: { fontSize: 16, fontWeight: '600', }, noteCard: { flexDirection: 'row', alignItems: 'center', gap: 14, borderRadius: 24, paddingHorizontal: 18, paddingVertical: 16 }, noteBody: { flex: 1, gap: 4, }, noteLabel: { fontSize: 14, fontWeight: '600', }, noteValue: { fontSize: 14, lineHeight: 20, }, summaryCard: { flexDirection: 'row', alignItems: 'center', borderRadius: 24, padding: 18, gap: 16, shadowColor: '#000', shadowOpacity: 0.06, shadowRadius: 12, shadowOffset: { width: 0, height: 3 }, elevation: 3, borderWidth: 1, borderColor: 'rgba(0, 0, 0, 0.04)', }, summaryIcon: { width: 44, height: 44, borderRadius: 22, backgroundColor: '#EEF1FF', alignItems: 'center', justifyContent: 'center', }, summaryBody: { flex: 1, gap: 4, }, summaryHighlight: { fontSize: 16, fontWeight: '700', }, summaryMeta: { fontSize: 14, }, modalOverlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.35)', justifyContent: 'flex-end', }, modalBackdrop: { flex: 1, }, modalContainer: { width: '100%', paddingHorizontal: 20, }, modalCard: { borderTopLeftRadius: 28, borderTopRightRadius: 28, paddingHorizontal: 20, paddingTop: 20, paddingBottom: 32, gap: 16, }, modalHandle: { width: 60, height: 5, borderRadius: 2.5, backgroundColor: 'rgba(0,0,0,0.12)', alignSelf: 'center', marginBottom: 12, }, modalHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', }, modalTitle: { fontSize: 18, fontWeight: '700', }, nameInputWrapper: { borderRadius: 18, paddingHorizontal: 18, paddingVertical: 14, }, nameInput: { fontSize: 18, fontWeight: '600', }, nameInputCounterWrapper: { alignItems: 'flex-end', }, nameInputCounter: { fontSize: 12, fontWeight: '500', }, noteEditorWrapper: { borderWidth: 1, borderRadius: 24, paddingHorizontal: 18, paddingVertical: 24, minHeight: 120, paddingRight: 70, shadowColor: '#000', shadowOpacity: 0.06, shadowRadius: 12, shadowOffset: { width: 0, height: 6 }, elevation: 3, }, noteEditorInput: { minHeight: 50, fontSize: 15, lineHeight: 22, }, voiceButton: { position: 'absolute', right: 16, top: 16, width: 40, height: 40, borderRadius: 20, borderWidth: 1, alignItems: 'center', justifyContent: 'center', }, voiceHint: { fontSize: 12, }, modalActionGhostText: { fontSize: 16, fontWeight: '600', }, modalActionPrimary: { borderRadius: 22, height: 52, alignItems: 'center', justifyContent: 'center', marginHorizontal: 8, shadowOpacity: 0.25, shadowRadius: 12, shadowOffset: { width: 0, height: 8 }, elevation: 4, }, modalActionPrimaryText: { fontSize: 17, fontWeight: '700', }, modalActionContainer: { marginTop: 8, }, footerBar: { position: 'absolute', left: 0, right: 0, bottom: 0, paddingHorizontal: 20, paddingTop: 16, borderTopWidth: StyleSheet.hairlineWidth, borderTopColor: 'rgba(15,23,42,0.06)', }, deleteButton: { width: 56, height: 56, borderRadius: 28, alignItems: 'center', justifyContent: 'center', overflow: 'hidden', // 保证玻璃边界圆角效果 }, fallbackDeleteButton: { backgroundColor: '#EF4444', shadowColor: 'rgba(239,68,68,0.4)', shadowOffset: { width: 0, height: 10 }, shadowOpacity: 1, shadowRadius: 20, elevation: 6, }, // 底部按钮容器样式 footerButtonContainer: { flexDirection: 'row', gap: 12, justifyContent: 'flex-end', }, aiAnalysisButtonWrapper: { flex: 1, }, deleteButtonWrapper: { // auto width }, aiAnalysisButton: { height: 56, borderRadius: 24, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 8, overflow: 'hidden', }, fallbackAiButton: { backgroundColor: '#3B82F6', shadowColor: 'rgba(59,130,246,0.4)', shadowOffset: { width: 0, height: 10 }, shadowOpacity: 1, shadowRadius: 20, elevation: 6, }, aiAnalysisButtonText: { fontSize: 17, fontWeight: '700', color: '#fff', }, primaryFooterBtn: { flex: 1, height: 56, borderRadius: 18, alignItems: 'center', justifyContent: 'center', shadowColor: '#0f172a', shadowOpacity: 0.15, shadowRadius: 10, shadowOffset: { width: 0, height: 8 }, }, primaryFooterText: { fontSize: 17, fontWeight: '700', }, secondaryFooterBtn: { height: 56, paddingHorizontal: 18, borderRadius: 16, borderWidth: 1, borderColor: '#E2E8F0', alignItems: 'center', justifyContent: 'center', }, secondaryFooterText: { fontSize: 16, fontWeight: '700', color: '#0f172a', }, // AI 分析卡片样式 aiCardContainer: { borderRadius: 26, padding: 20, gap: 14, shadowColor: '#000', shadowOpacity: 0.06, shadowRadius: 12, shadowOffset: { width: 0, height: 3 }, elevation: 3, borderWidth: 1, borderColor: 'rgba(0, 0, 0, 0.04)', }, aiHeaderRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', }, aiHeaderLeft: { flexDirection: 'row', alignItems: 'center', gap: 8, }, aiHeaderTitle: { fontSize: 17, fontWeight: '700', }, aiStatusPill: { flexDirection: 'row', alignItems: 'center', gap: 6, paddingHorizontal: 10, paddingVertical: 6, borderRadius: 14, }, aiStatusText: { fontSize: 12, fontWeight: '700', }, aiLoadingContainer: { alignItems: 'center', justifyContent: 'center', paddingVertical: 24, gap: 12, }, aiLoadingAnimation: { width: 120, height: 120, }, aiLoadingText: { fontSize: 14, fontWeight: '500', }, aiHeroRow: { flexDirection: 'row', gap: 14, alignItems: 'center', }, aiHeroImageWrapper: { width: 110, height: 110, borderRadius: 28, backgroundColor: '#F8FAFC', alignItems: 'center', justifyContent: 'center', position: 'relative', }, aiHeroImageShadow: { width: 90, height: 90, borderRadius: 22, overflow: 'hidden', backgroundColor: '#fff', shadowColor: '#000', shadowOpacity: 0.08, shadowRadius: 8, shadowOffset: { width: 0, height: 4 }, elevation: 3, }, aiHeroImage: { width: '100%', height: '100%', borderRadius: 22, }, aiScoreBadge: { position: 'absolute', bottom: -6, right: -4, paddingHorizontal: 10, paddingVertical: 6, borderRadius: 14, flexDirection: 'row', alignItems: 'center', gap: 6, shadowColor: '#22c55e', shadowOpacity: 0.25, shadowRadius: 8, shadowOffset: { width: 0, height: 4 }, elevation: 4, }, aiScoreBadgeText: { fontSize: 12, fontWeight: '700', color: '#fff', }, aiHeroText: { flex: 1, gap: 8, }, aiHeroTitle: { fontSize: 20, fontWeight: '800', }, aiHeroSubtitle: { fontSize: 14, lineHeight: 20, }, aiChipRow: { flexDirection: 'row', gap: 8, flexWrap: 'wrap', }, aiChip: { flexDirection: 'row', alignItems: 'center', gap: 6, paddingHorizontal: 10, paddingVertical: 8, backgroundColor: '#F3F4F6', borderRadius: 12, }, aiChipText: { fontSize: 13, fontWeight: '600', }, aiTagRow: { flexDirection: 'row', flexWrap: 'wrap', gap: 8, }, aiTag: { paddingHorizontal: 12, paddingVertical: 8, borderRadius: 14, backgroundColor: '#F2FCE2', }, aiTagText: { fontSize: 13, fontWeight: '600', }, aiUsageCard: { flexDirection: 'row', alignItems: 'center', gap: 10, padding: 14, borderRadius: 18, backgroundColor: '#F8FAFC', borderWidth: 1, borderColor: 'rgba(15,23,42,0.05)', }, aiUsageText: { fontSize: 15, lineHeight: 22, flex: 1, }, aiColumns: { flexDirection: 'row', gap: 12, flexWrap: 'wrap', }, aiBubbleCard: { flex: 1, borderWidth: 1, borderRadius: 18, padding: 14, gap: 8, }, aiBubbleHeader: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 4, }, aiBubbleTitle: { fontSize: 15, fontWeight: '700', }, aiListCard: { borderWidth: 1, borderRadius: 18, padding: 14, gap: 12, }, aiListHeader: { flexDirection: 'row', alignItems: 'center', gap: 10, }, aiListIcon: { width: 28, height: 28, borderRadius: 14, alignItems: 'center', justifyContent: 'center', }, aiListTitle: { fontSize: 15, fontWeight: '700', flex: 1, }, aiListCountBadge: { borderRadius: 10, borderWidth: 1, paddingHorizontal: 8, paddingVertical: 2, }, aiListCount: { fontSize: 12, fontWeight: '700', }, aiListContent: { gap: 8, }, aiBulletRow: { flexDirection: 'row', alignItems: 'flex-start', gap: 8, }, aiBulletDot: { width: 8, height: 8, borderRadius: 4, marginTop: 6, }, aiBulletText: { fontSize: 14, lineHeight: 22, flex: 1, }, aiEmptyBox: { borderRadius: 18, padding: 16, backgroundColor: '#F8FAFF', borderWidth: 1, borderColor: '#E0EAFF', gap: 8, alignItems: 'center', }, aiEmptyTitle: { fontSize: 16, fontWeight: '700', }, aiEmptyDesc: { fontSize: 14, lineHeight: 22, textAlign: 'center', }, aiFreeBadge: { flexDirection: 'row', alignItems: 'center', gap: 6, paddingHorizontal: 12, paddingVertical: 8, backgroundColor: '#EEF2FF', borderRadius: 12, }, aiFreeBadgeText: { fontSize: 12, fontWeight: '700', }, aiErrorBox: { flexDirection: 'row', alignItems: 'center', gap: 10, padding: 12, borderRadius: 14, backgroundColor: '#FEF2F2', borderWidth: 1, borderColor: '#FEE2E2', }, aiErrorText: { fontSize: 14, lineHeight: 20, flex: 1, }, aiMembershipCard: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', padding: 14, borderRadius: 18, backgroundColor: '#FFFBEB', borderWidth: 1, borderColor: '#FDE68A', }, aiMembershipLeft: { flexDirection: 'row', alignItems: 'center', gap: 10, flex: 1, }, aiMembershipTitle: { fontSize: 15, fontWeight: '700', color: '#92400e', }, aiMembershipSub: { fontSize: 13, color: '#b45309', marginTop: 2, }, // Picker 相关样式 pickerBackdrop: { flex: 1, backgroundColor: 'rgba(15, 23, 42, 0.4)', }, pickerSheet: { position: 'absolute', left: 20, right: 20, bottom: 40, borderRadius: 24, padding: 20, shadowColor: '#000', shadowOffset: { width: 0, height: 10 }, shadowOpacity: 0.2, shadowRadius: 20, elevation: 8, }, pickerTitle: { fontSize: 20, fontWeight: '700', marginBottom: 20, textAlign: 'center', }, pickerRow: { flexDirection: 'row', gap: 16, marginBottom: 20, }, pickerColumn: { flex: 1, gap: 8, }, pickerLabel: { fontSize: 14, fontWeight: '600', textAlign: 'center', }, picker: { width: '100%', height: 150, }, pickerItem: { fontSize: 18, height: 150, }, pickerActions: { flexDirection: 'row', gap: 12, marginTop: 16, }, pickerBtn: { flex: 1, paddingVertical: 14, borderRadius: 16, alignItems: 'center', borderWidth: 1, }, pickerBtnPrimary: { borderWidth: 0, }, pickerBtnText: { fontSize: 16, fontWeight: '600', }, imagePreviewHint: { position: 'absolute', top: 4, right: 4, backgroundColor: 'rgba(0, 0, 0, 0.5)', borderRadius: 10, padding: 4, zIndex: 10, }, photoUploadingIndicator: { position: 'absolute', bottom: 8, left: 8, right: 8, flexDirection: 'row', alignItems: 'center', gap: 6, paddingHorizontal: 8, paddingVertical: 6, backgroundColor: 'rgba(0, 0, 0, 0.6)', borderRadius: 12, }, uploadingText: { fontSize: 11, fontWeight: '600', color: '#FFF', }, // ImageViewing 组件样式 imageViewerHeader: { position: 'absolute', top: 60, left: 20, right: 20, backgroundColor: 'rgba(0, 0, 0, 0.7)', borderRadius: 12, paddingHorizontal: 16, paddingVertical: 12, zIndex: 1, }, imageViewerHeaderText: { color: '#FFF', fontSize: 14, fontWeight: '500', textAlign: 'center', }, imageViewerFooter: { position: 'absolute', bottom: 60, left: 20, right: 20, alignItems: 'center', zIndex: 1, }, imageViewerFooterButton: { backgroundColor: 'rgba(0, 0, 0, 0.7)', paddingHorizontal: 24, paddingVertical: 12, borderRadius: 20, }, imageViewerFooterButtonText: { color: '#FFF', fontSize: 16, fontWeight: '500', }, });