import React, { useState, useEffect } from 'react'; import { Mic, Users, Image } from 'lucide-react'; import { AvatarStage } from '../components/AvatarStage'; import { MedicationCard } from '../components/MedicationCard'; import { MoodBoard } from '../components/MoodBoard'; import { MemoryPlayer } from '../components/MemoryPlayer'; import { MediaPlayer } from '../components/MediaPlayer'; import { EmergencySheet } from '../components/EmergencySheet'; import { ScheduleList } from '../components/ScheduleList'; import { ScheduleReminderToast } from '../components/ScheduleReminderToast'; import { ToastMessage } from '../components/ToastMessage'; import { ConfirmDialog } from '../components/ConfirmDialog'; import { TransparentMediaOverlay } from '../components/TransparentMediaOverlay'; import { LogNotification } from '../components/LogNotification'; import * as scheduleService from '../services/scheduleService'; import * as mediaService from '../services/mediaService'; import * as messageService from '../services/messageService'; import * as alertService from '../services/alertService'; import * as moodService from '../../family/services/moodService'; import { useToastSSE } from '../hooks/useToastSSE'; /** * 老人端主页 * 优化为 9:16 竖屏使用(如平板竖屏) * 包含数字人、大按钮和各类卡片叠加层 */ export const HomePage: React.FC = () => { const [activeOverlay, setActiveOverlay] = useState< 'none' | 'medication' | 'mood' | 'memory' | 'emergency' | 'schedule' | 'media' >('none'); const [isAvatarActive, setIsAvatarActive] = useState(false); const [memoryMode, setMemoryMode] = useState<'pip' | 'fullscreen'>('fullscreen'); const [toastMessage, setToastMessage] = useState<{ type: 'success' | 'info' | 'calling'; message: string } | null>(null); const [confirmDialog, setConfirmDialog] = useState<{ message: string; onConfirm: () => void } | null>(null); const [currentMediaIndex, setCurrentMediaIndex] = useState(0); const [sdkStatus, setSDKStatus] = useState<'loading' | 'ready' | 'error' | 'config-missing'>('loading'); const [wsStatus, setWSStatus] = useState<'disconnected' | 'connecting' | 'connected'>('disconnected'); const [todaySchedules, setTodaySchedules] = useState([]); const [reminderSchedule, setReminderSchedule] = useState(null); const [shownReminders, setShownReminders] = useState>(new Set()); const [postponedReminders, setPostponedReminders] = useState>(new Map()); // 记录推迟的日程和推迟到的时间 const [recommendedMedia, setRecommendedMedia] = useState([]); const [loadingMedia, setLoadingMedia] = useState(false); const [playedMessages, setPlayedMessages] = useState>(new Set()); // 记录已播报的留言ID const [mediaOverlay, setMediaOverlay] = useState<{ filename: string; type: 'photo' | 'video'; text?: string; duration?: number; } | null>(null); // 透明窗口媒体展示状态 const [isMicrophoneEnabled, setIsMicrophoneEnabled] = useState(true); // 麦克风状态 const [currentMood, setCurrentMood] = useState(null); // 当前情绪 const [logMessage, setLogMessage] = useState(null); // WebSocket log消息 const familyId = 'family_001'; // 实际使用时从用户上下文获取 const elderlyId = 1; // 老人用户ID,实际使用时从用户上下文获取 // 轮询检查联系家人的alerts(与家属端相同的方案) useEffect(() => { let lastAlertId = 0; // 记录上次处理的alert ID let isInitialized = false; // 标记是否已初始化 const checkContactFamilyAlerts = async () => { try { const response = await fetch( `http://localhost:8000/api/family/alerts?family_id=${familyId}&handled=false&alert_type=contact_family&limit=1` ); if (!response.ok) { console.error('[HomePage] 查询alerts失败:', response.status); return; } const data = await response.json(); const alerts = data.alerts || []; if (alerts.length > 0) { const alert = alerts[0]; // 首次初始化:只记录ID,不显示Toast(避免刷新页面时重复显示旧alert) if (!isInitialized) { lastAlertId = alert.id; isInitialized = true; console.log('[HomePage] 初始化lastAlertId:', lastAlertId); return; } // 只处理新的alert(避免重复显示) if (alert.id > lastAlertId) { lastAlertId = alert.id; console.log('[HomePage] ✓ 收到联系家人alert:', alert); // 显示Toast const message = alert.metadata?.is_emergency ? "正在紧急通知家人..." : "正在通知家人..."; setToastMessage({ type: 'calling', message: message }); console.log('[HomePage] ✓ Toast已显示:', message); // 10秒后自动关闭 setTimeout(() => { console.log('[HomePage] 关闭Toast'); setToastMessage(null); }, 10000); } } else { // 没有未处理的alert时,标记为已初始化 if (!isInitialized) { isInitialized = true; console.log('[HomePage] 初始化完成,当前无未处理alert'); } } } catch (error) { console.error('[HomePage] 检查alerts失败:', error); } }; // 立即执行一次 checkContactFamilyAlerts(); // 每2秒检查一次新alerts const interval = setInterval(checkContactFamilyAlerts, 2000); return () => clearInterval(interval); }, [familyId]); // 加载今日日程 useEffect(() => { loadTodaySchedules(); // 每分钟刷新一次 const interval = setInterval(loadTodaySchedules, 60000); return () => clearInterval(interval); }, []); // 监听日程状态变化,自动关闭已完成/已忽略的弹窗 useEffect(() => { if (reminderSchedule && reminderSchedule.id) { // 在最新的日程列表中查找当前弹窗对应的日程 const updatedSchedule = todaySchedules.find(s => s.id === reminderSchedule.id); // 如果日程状态不是pending,关闭弹窗 if (updatedSchedule && updatedSchedule.status !== 'pending') { console.log(`日程 ${reminderSchedule.title} 状态已变更为 ${updatedSchedule.status},关闭弹窗`); setReminderSchedule(null); } } }, [todaySchedules, reminderSchedule]); // 加载当前情绪 const loadCurrentMood = async () => { try { const response = await moodService.getFamilyMoods(familyId, { limit: 1 }); if (response.records && response.records.length > 0) { setCurrentMood(response.records[0].mood_type); } } catch (error) { console.error('加载当前情绪失败:', error); } }; // 初始化加载当前情绪 useEffect(() => { loadCurrentMood(); }, []); // 加载推荐媒体 const loadRecommendedMedia = async () => { try { setLoadingMedia(true); const media = await mediaService.getRecommendedMedia(familyId, elderlyId); setRecommendedMedia(media); console.log('加载到推荐媒体:', media.length, '个'); } catch (error) { console.error('加载推荐媒体失败:', error); } finally { setLoadingMedia(false); } }; // 检测日程到达时间 useEffect(() => { const checkScheduleTime = () => { const now = new Date(); // 遍历今日日程,查找需要提醒的 todaySchedules.forEach((schedule) => { if (!schedule.id) return; // 只处理待执行状态的日程 if (schedule.status !== 'pending') return; // 已经提醒过的跳过 if (shownReminders.has(schedule.id)) return; // 检查是否被推迟,如果被推迟且未到推迟时间,则跳过 const postponedTime = postponedReminders.get(schedule.id); if (postponedTime && now < postponedTime) { console.log(`日程 ${schedule.title} 已推迟到 ${postponedTime.toLocaleTimeString()},暂不提醒`); return; } // 如果已过推迟时间,清除推迟记录 if (postponedTime && now >= postponedTime) { console.log(`日程 ${schedule.title} 推迟时间已到,现在提醒`); setPostponedReminders(prev => { const newMap = new Map(prev); newMap.delete(schedule.id!); return newMap; }); } const scheduleTime = new Date(schedule.schedule_time); const diffMinutes = (scheduleTime.getTime() - now.getTime()) / (1000 * 60); // 到达时间(允许 1 分钟误差) if (diffMinutes <= 1 && diffMinutes >= -1) { console.log('日程到达时间,显示提醒:', schedule.title); // 构建提醒内容 const timeStr = scheduleService.formatTime(schedule.schedule_time); const typeLabel = scheduleService.getScheduleTypeLabel(schedule.schedule_type || 'other'); let reminderText = `${timeStr},${typeLabel}提醒:${schedule.title}`; // 如果有描述,添加描述 if (schedule.description) { reminderText += `。${schedule.description}`; } // 添加操作询问 reminderText += `。请问您要标记完成、推迟执行,还是忽略行程?`; // 推送播报内容到数字人 sendToAvatar(reminderText); // 显示弹窗提醒 setReminderSchedule(schedule); setShownReminders(prev => new Set(prev).add(schedule.id!)); } }); }; // 每 10 秒检查一次 const interval = setInterval(checkScheduleTime, 10000); checkScheduleTime(); // 立即执行一次 return () => clearInterval(interval); }, [todaySchedules, shownReminders, postponedReminders]); const loadTodaySchedules = async () => { try { const now = new Date().toLocaleTimeString('zh-CN'); console.log(`[${now}] 自动检查日程更新...`); const data = await scheduleService.getTodaySchedules(familyId); setTodaySchedules(data); console.log(`[${now}] 加载到 ${data.length} 条日程`); } catch (error) { console.error('加载今日日程失败:', error); } }; // 检查并播报待播放的留言 const checkAndPlayMessages = async () => { try { const now = new Date().toLocaleTimeString('zh-CN'); console.log(`[${now}] 检查待播放留言...`); const pendingMessages = await messageService.getPendingMessages(familyId); if (pendingMessages.length > 0) { console.log(`发现 ${pendingMessages.length} 条待播放留言`); for (const message of pendingMessages) { // 检查是否已经播报过(避免重复播报) if (!playedMessages.has(message.id)) { console.log(`播报留言 ID: ${message.id} - 来自${message.sender_relation}${message.sender_name}`); // 推送到数字人播报 await messageService.playMessageOnAvatar(message); // 显示Toast字幕提示(30秒) const toastText = `来自${message.sender_relation}${message.sender_name}的留言:${message.content}`; setToastMessage({ type: 'info', message: toastText }); // 30秒后自动关闭Toast setTimeout(() => { setToastMessage(null); }, 30000); // 标记为已播放 await messageService.markAsPlayed(message.id); // 记录已播报 setPlayedMessages((prev) => new Set(prev).add(message.id)); console.log(`留言 ID: ${message.id} 播报完成`); } } } } catch (error) { console.error('检查并播报留言失败:', error); } }; // 定时检查待播放留言(每分钟检查一次) useEffect(() => { checkAndPlayMessages(); // 立即执行一次 const interval = setInterval(checkAndPlayMessages, 60000); // 每分钟检查 return () => clearInterval(interval); }, [playedMessages]); // 轮询媒体展示事件(每5秒检查一次) const pollMediaEvents = async () => { try { const response = await fetch( `http://localhost:8000/api/elderly/poll-media-events?family_id=${familyId}` ); if (!response.ok) { throw new Error(`轮询媒体事件失败: ${response.statusText}`); } const data = await response.json(); if (data.event && data.event.metadata) { const metadata = data.event.metadata; console.log('收到媒体展示事件:', metadata); // 推送播报内容到数字人(与日程模块相同的逻辑) if (metadata.avatar_text) { console.log('准备调用 sendToAvatar,文本内容:', metadata.avatar_text); // 确保麦克风已开启(媒体展示时需要数字人播报) await ensureMicrophoneEnabled(); await sendToAvatar(metadata.avatar_text); console.log('sendToAvatar 调用完成'); } else { console.warn('没有 avatar_text,跳过数字人播报'); } // 设置媒体展示状态,触发透明窗口弹出 setMediaOverlay({ filename: metadata.media_filename, type: metadata.media_type || 'photo', text: metadata.avatar_text, duration: metadata.duration || 30, }); } } catch (error) { console.error('轮询媒体事件错误:', error); } }; // 定时轮询媒体展示事件(每5秒检查一次) useEffect(() => { pollMediaEvents(); // 立即执行一次 const interval = setInterval(pollMediaEvents, 5000); // 每5秒检查 return () => clearInterval(interval); }, []); // 确保麦克风已开启 const ensureMicrophoneEnabled = async () => { try { console.log('[ensureMicrophoneEnabled] 确保麦克风已开启...'); const response = await fetch('http://127.0.0.1:5000/api/toggle-microphone', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ enabled: true }), }); if (!response.ok) { throw new Error(`开启麦克风失败: ${response.statusText}`); } const result = await response.json(); console.log('[ensureMicrophoneEnabled] 麦克风状态:', result); } catch (error) { console.error('[ensureMicrophoneEnabled] 开启麦克风错误:', error); } }; // 向数字人推送播报内容 const sendToAvatar = async (text: string) => { try { console.log('[sendToAvatar] 开始推送播报内容:', text); console.log('[sendToAvatar] 请求URL: http://127.0.0.1:5000/transparent-pass'); const requestBody = { user: 'User', text: text, }; console.log('[sendToAvatar] 请求体:', requestBody); const response = await fetch('http://127.0.0.1:5000/transparent-pass', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(requestBody), }); console.log('[sendToAvatar] 响应状态:', response.status, response.statusText); if (!response.ok) { const errorText = await response.text(); console.error('[sendToAvatar] 响应错误内容:', errorText); throw new Error(`推送播报失败: ${response.status} ${response.statusText}`); } const result = await response.json(); console.log('[sendToAvatar] 播报内容推送成功,响应:', result); } catch (error) { console.error('[sendToAvatar] 推送播报内容错误:', error); // 不抛出错误,让流程继续(即使播报失败,也要显示媒体) } }; // 模拟媒体库数据 const mediaLibrary = [ { id: '1', url: '/placeholder-photo.jpg', type: 'photo' as const, caption: '小米 2018 秋游' }, { id: '2', url: '/placeholder-photo-2.jpg', type: 'photo' as const, caption: '2019 春节团聚' }, { id: '3', url: '/placeholder-photo-3.jpg', type: 'photo' as const, caption: '奶奶80岁生日' }, { id: '4', url: '/placeholder-photo-4.jpg', type: 'photo' as const, caption: '家庭野餐' }, { id: '5', url: '/placeholder-photo-5.jpg', type: 'photo' as const, caption: '小孙子周岁' }, ]; // 获取当前时间和日期信息 const now = new Date(); const currentTime = now.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', }); const currentDate = now.toLocaleDateString('zh-CN', { month: 'long', day: 'numeric', }); const currentDay = now.toLocaleDateString('zh-CN', { weekday: 'long', }); // 模拟天气信息(实际使用时应从天气API获取) const weather = '晴 22°C'; // 切换麦克风开关 const handleMicClick = async () => { try { console.log('[handleMicClick] 切换麦克风状态,当前状态:', isMicrophoneEnabled); // 先显示视觉反馈 setIsAvatarActive(true); setTimeout(() => setIsAvatarActive(false), 1000); // 调用麦克风切换API(不传参数则自动切换) const response = await fetch('http://127.0.0.1:5000/api/toggle-microphone', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({}), // 不传参数,自动切换状态 }); if (!response.ok) { throw new Error(`切换麦克风失败: ${response.statusText}`); } const result = await response.json(); console.log('[handleMicClick] 麦克风切换结果:', result); // 更新UI状态 setIsMicrophoneEnabled(result.enabled); } catch (error) { console.error('[handleMicClick] 切换麦克风错误:', error); } }; const handleFamilyClick = async () => { setToastMessage({ type: 'calling', message: '正在呼叫家人...' }); // 推送普通消息到家属端 try { await alertService.sendContactFamilyAlert(familyId); console.log('已通知家人'); } catch (error) { console.error('通知家人失败:', error); } }; const handleEmergencyClick = () => { setActiveOverlay('emergency'); }; const handlePhotosClick = async () => { // 加载推荐媒体 await loadRecommendedMedia(); setActiveOverlay('media'); }; const handleNextMedia = () => { if (currentMediaIndex < mediaLibrary.length - 1) { setCurrentMediaIndex(currentMediaIndex + 1); } }; const handlePreviousMedia = () => { if (currentMediaIndex > 0) { setCurrentMediaIndex(currentMediaIndex - 1); } }; const handleSelectMedia = (index: number) => { setCurrentMediaIndex(index); }; // 获取临近日程(前后30分钟内) const getNextSchedule = () => { if (todaySchedules.length === 0) return null; const now = new Date(); const sorted = scheduleService.sortSchedulesByTime(todaySchedules); // 查找在时间窗口内的日程(前30分钟到后30分钟) for (const schedule of sorted) { const scheduleTime = new Date(schedule.schedule_time); const diffMinutes = (scheduleTime.getTime() - now.getTime()) / (1000 * 60); // 如果在前后30分钟范围内,显示这个日程 if (diffMinutes >= -30 && diffMinutes <= 30) { return schedule; } // 如果日程还在30分钟之后,也显示(即将到来) if (diffMinutes > 30) { return schedule; } } // 如果所有日程都已过期超过30分钟,不显示任何日程 return null; }; // 获取临近的药物提醒(前后30分钟内) const getNearbyMedicationReminder = () => { if (todaySchedules.length === 0) return null; const now = new Date(); // 查找前后30分钟内的药物提醒 for (const schedule of todaySchedules) { if (schedule.schedule_type !== 'medication') continue; const scheduleTime = new Date(schedule.schedule_time); const diffMinutes = (scheduleTime.getTime() - now.getTime()) / (1000 * 60); // 如果在前后30分钟范围内,返回这个药物提醒 if (diffMinutes >= -30 && diffMinutes <= 30) { return schedule; } } return null; }; const nextSchedule = getNextSchedule(); const nearbyMedication = getNearbyMedicationReminder(); return (
{/* 主内容区域 - 满屏显示 */}
{/* 数字人画面 - 全屏背景 */}
{/* PIP 模式的媒体播放器 */} {activeOverlay === 'memory' && memoryMode === 'pip' && ( console.log('Liked')} onDislike={() => console.log('Disliked')} onClose={() => setActiveOverlay('none')} onToggleMode={() => setMemoryMode('fullscreen')} onNext={handleNextMedia} onPrevious={handlePreviousMedia} onSelectMedia={handleSelectMedia} /> )} {/* 顶部状态栏 - 悬浮层 */}
{/* 左侧区域:状态指示器 + 时间日期 */}
{/* 连接状态指示器 - 竖排两个绿点 */}
{/* 时间日期信息 */}
{currentTime} {currentDate}
{currentDay} ☀️ {weather}
{/* 中间Logo区域 */}

KinEcho

亲情回声

Bring family moments to life. / 把家人的声音带到眼前。

{nextSchedule && ( )}
{/* 左侧按钮组 - 功能按钮垂直排列 */}
{/* 麦克风按钮容器 - 用于承载log通知 */}
{/* Log通知 - 从麦克风按钮向右延伸 */} {logMessage && ( setLogMessage(null)} /> )}
{/* 右下角 - 紧急求助按钮 */}
{/* 叠加层 - 用药提醒 */} {activeOverlay === 'medication' && ( { setActiveOverlay('none'); setToastMessage({ type: 'success', message: '已记录服药' }); }} onSnooze={(mins) => { setActiveOverlay('none'); setToastMessage({ type: 'info', message: `将在 ${mins} 分钟后再次提醒` }); }} onSkip={() => { setActiveOverlay('none'); setConfirmDialog({ message: '跳过服药可能影响健康,确定吗?', onConfirm: () => { setConfirmDialog(null); setToastMessage({ type: 'info', message: '已记录跳过' }); } }); }} onInfo={() => { setToastMessage({ type: 'info', message: '氯沙坦用于降血压,请随餐服用,避免空腹。' }); }} /> )} {/* 叠加层 - 心情选择 */} {activeOverlay === 'mood' && ( { console.log('Selected mood:', mood); // 更新当前情绪显示 setCurrentMood(mood as moodService.MoodType); // 根据心情触发不同的回忆内容 setActiveOverlay('memory'); }} onClose={() => setActiveOverlay('none')} /> )} {/* 叠加层 - 媒体播放(全屏) */} {activeOverlay === 'memory' && memoryMode === 'fullscreen' && ( console.log('Liked')} onDislike={() => console.log('Disliked')} onClose={() => setActiveOverlay('none')} onToggleMode={() => setMemoryMode('pip')} onNext={handleNextMedia} onPrevious={handlePreviousMedia} onSelectMedia={handleSelectMedia} /> )} {/* 叠加层 - 紧急求助 */} {activeOverlay === 'emergency' && ( { setActiveOverlay('none'); setToastMessage({ type: 'calling', message: '正在呼叫家人...' }); // 推送SOS紧急消息到家属端 try { await alertService.sendSOSAlert(familyId); console.log('已发送SOS紧急通知给家人'); } catch (error) { console.error('发送SOS紧急通知失败:', error); } }} onContactEmergency={async () => { setActiveOverlay('none'); setToastMessage({ type: 'info', message: '暂未对接应急中心,已通知家人' }); // 也推送SOS紧急消息到家属端 try { await alertService.sendSOSAlert(familyId); console.log('已发送SOS紧急通知给家人'); } catch (error) { console.error('发送SOS紧急通知失败:', error); } }} onClose={() => setActiveOverlay('none')} /> )} {/* 叠加层 - 智能媒体播放器 */} {activeOverlay === 'media' && recommendedMedia.length > 0 && ( setActiveOverlay('none')} /> )} {/* 叠加层 - 日程列表 */} {activeOverlay === 'schedule' && ( setActiveOverlay('none')} /> )} {/* Toast 消息提示 */} {toastMessage && ( setToastMessage(null)} /> )} {/* 确认对话框 */} {confirmDialog && ( setConfirmDialog(null)} /> )} {/* 透明窗口媒体展示 */} {mediaOverlay && ( setMediaOverlay(null)} /> )} {/* 日程提醒 Toast */} {reminderSchedule && ( { try { if (reminderSchedule.id) { // 更新日程状态为已完成 await scheduleService.updateScheduleStatus(reminderSchedule.id, 'completed'); } setReminderSchedule(null); // 重新加载日程列表 loadTodaySchedules(); } catch (error) { console.error('标记完成失败:', error); } }} onSkip={async () => { try { if (reminderSchedule.id) { // 更新日程状态为已忽略 await scheduleService.updateScheduleStatus(reminderSchedule.id, 'skipped'); } setReminderSchedule(null); // 重新加载日程列表 loadTodaySchedules(); } catch (error) { console.error('忽略行程失败:', error); } }} onDismiss={async () => { if (!reminderSchedule) return; console.log('推迟执行日程:', reminderSchedule); try { // 计算10分钟后的时间 const postponedTime = new Date(); postponedTime.setMinutes(postponedTime.getMinutes() + 10); // 如果是每日重复的日程,只推迟当前实例 if (reminderSchedule.repeat_type === 'daily') { // 记录推迟时间到 postponedReminders Map setPostponedReminders(prev => { const newMap = new Map(prev); newMap.set(reminderSchedule.id!, postponedTime); return newMap; }); // 从已显示列表中移除,允许再次提醒 setShownReminders(prev => { const newSet = new Set(prev); if (reminderSchedule.id) newSet.delete(reminderSchedule.id); return newSet; }); console.log(`每日重复日程已推迟到 ${postponedTime.toLocaleTimeString()},原日程保持不变`); setReminderSchedule(null); } else { // 如果是一次性日程,更新数据库中的 schedule_time const response = await fetch( `http://localhost:8000/api/family/schedules/${reminderSchedule.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ family_id: reminderSchedule.family_id, title: reminderSchedule.title, description: reminderSchedule.description, schedule_type: reminderSchedule.schedule_type, schedule_time: postponedTime.toISOString(), repeat_type: reminderSchedule.repeat_type, repeat_days: reminderSchedule.repeat_days, auto_remind: reminderSchedule.auto_remind, status: 'pending', // 推迟后重置为pending状态 }), } ); if (response.ok) { console.log('一次性日程时间已更新为10分钟后:', postponedTime.toISOString()); setReminderSchedule(null); // 从已显示列表中移除,允许10分钟后再次提醒 setShownReminders(prev => { const newSet = new Set(prev); if (reminderSchedule.id) newSet.delete(reminderSchedule.id); return newSet; }); // 重新加载日程列表 loadTodaySchedules(); } else { const errorText = await response.text(); console.error('更新失败响应:', errorText); throw new Error('更新日程时间失败'); } } } catch (error) { console.error('推迟执行失败:', error); } }} onMissed={async () => { try { if (reminderSchedule?.id) { console.log('30分钟无操作,自动标记为已错过:', reminderSchedule.title); // 更新日程状态为已错过 await scheduleService.updateScheduleStatus(reminderSchedule.id, 'missed'); } setReminderSchedule(null); // 重新加载日程列表 loadTodaySchedules(); } catch (error) { console.error('标记已错过失败:', error); setReminderSchedule(null); } }} onClose={() => { // 手动关闭提醒(不做任何操作,只是隐藏) setReminderSchedule(null); }} /> )} {/* 调试按钮 - 方便演示 */}
{/* 药物提醒按钮 - 只在前后30分钟有药物提醒时显示 */} {nearbyMedication && ( )}
); };