Add File
This commit is contained in:
241
src/family/main.tsx
Normal file
241
src/family/main.tsx
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import ReactDOM from 'react-dom/client';
|
||||||
|
import { Dashboard } from './pages/Dashboard';
|
||||||
|
import { MediaLibrary } from './pages/MediaLibrary';
|
||||||
|
import { CarePlan } from './pages/CarePlan';
|
||||||
|
import { AlertsAndCare } from './pages/AlertsAndCare';
|
||||||
|
import { InteractionHistory } from './pages/InteractionHistory';
|
||||||
|
import { FamilyMessages } from './pages/FamilyMessages';
|
||||||
|
import { MoodHistory } from './pages/MoodHistory';
|
||||||
|
import { ElderProfile } from './components/ElderProfile';
|
||||||
|
import { EmergencyAlert } from './components/EmergencyAlert';
|
||||||
|
import * as messageService from './services/messageService';
|
||||||
|
import {
|
||||||
|
LayoutDashboard,
|
||||||
|
MessageSquare,
|
||||||
|
HeartPulse,
|
||||||
|
Bell,
|
||||||
|
MessageCircle,
|
||||||
|
Image,
|
||||||
|
Smile,
|
||||||
|
} from 'lucide-react';
|
||||||
|
import '../index.css';
|
||||||
|
|
||||||
|
type Page = 'dashboard' | 'messages' | 'care' | 'alerts' | 'interaction' | 'media' | 'mood';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 家属端应用
|
||||||
|
* 优化为手机浏览器使用 - 底部标签栏导航
|
||||||
|
*/
|
||||||
|
function FamilyApp() {
|
||||||
|
const [currentPage, setCurrentPage] = useState<Page>('dashboard');
|
||||||
|
const [showElderProfile, setShowElderProfile] = useState(false);
|
||||||
|
const [unhandledCount, setUnhandledCount] = useState(0);
|
||||||
|
const [emergencyAlert, setEmergencyAlert] = useState<{
|
||||||
|
id: number;
|
||||||
|
message: string;
|
||||||
|
timestamp: string;
|
||||||
|
} | null>(null);
|
||||||
|
const [shownEmergencyIds, setShownEmergencyIds] = useState<Set<number>>(new Set());
|
||||||
|
const familyId = 'family_001'; // 实际使用时从用户上下文获取
|
||||||
|
|
||||||
|
// 老人信息数据
|
||||||
|
const [elderInfo, setElderInfo] = useState({
|
||||||
|
name: '张奶奶',
|
||||||
|
age: 82,
|
||||||
|
cognitive_status: 'mild' as const,
|
||||||
|
hearing_vision: {
|
||||||
|
hearing: 'mild_loss' as const,
|
||||||
|
vision: 'ok' as const,
|
||||||
|
},
|
||||||
|
preferences: {
|
||||||
|
music: ['老歌', '评弹', '京剧'],
|
||||||
|
avoid_topics: ['医院', '去世的老伴'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 加载未处理通知数量
|
||||||
|
const loadUnhandledCount = async () => {
|
||||||
|
try {
|
||||||
|
const stats = await messageService.getAlertStats(familyId);
|
||||||
|
setUnhandledCount(stats.status_stats?.unhandled || 0);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载未处理通知数量失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 检查是否有新的SOS紧急通知
|
||||||
|
const checkEmergencyAlerts = async () => {
|
||||||
|
try {
|
||||||
|
const { alerts } = await messageService.getFamilyAlerts(familyId, {
|
||||||
|
alert_type: 'sos_emergency',
|
||||||
|
handled: false,
|
||||||
|
limit: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (alerts.length > 0) {
|
||||||
|
const latestAlert = alerts[0];
|
||||||
|
// 只显示未展示过的紧急通知
|
||||||
|
if (!shownEmergencyIds.has(latestAlert.id)) {
|
||||||
|
setEmergencyAlert({
|
||||||
|
id: latestAlert.id,
|
||||||
|
message: latestAlert.message,
|
||||||
|
timestamp: new Date(latestAlert.created_at).toLocaleString('zh-CN', {
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
// 记录已显示的通知ID
|
||||||
|
setShownEmergencyIds(prev => new Set(prev).add(latestAlert.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('检查紧急通知失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 定时检查未处理通知和紧急通知
|
||||||
|
useEffect(() => {
|
||||||
|
loadUnhandledCount();
|
||||||
|
checkEmergencyAlerts();
|
||||||
|
// 每5秒检查一次
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
loadUnhandledCount();
|
||||||
|
checkEmergencyAlerts();
|
||||||
|
}, 5000);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 保存老人信息
|
||||||
|
const handleSaveElderInfo = (updatedInfo: any) => {
|
||||||
|
setElderInfo(updatedInfo);
|
||||||
|
// TODO: 这里可以添加API调用,将数据保存到后端
|
||||||
|
console.log('保存老人信息:', updatedInfo);
|
||||||
|
};
|
||||||
|
|
||||||
|
const navigation = [
|
||||||
|
{ id: 'dashboard' as const, label: '概览', shortLabel: '概览', icon: LayoutDashboard },
|
||||||
|
{ id: 'messages' as const, label: '家属留言', shortLabel: '留言', icon: MessageSquare },
|
||||||
|
{ id: 'media' as const, label: '媒体库', shortLabel: '媒体', icon: Image },
|
||||||
|
{ id: 'care' as const, label: '护理计划', shortLabel: '护理', icon: HeartPulse },
|
||||||
|
{ id: 'alerts' as const, label: '通知', shortLabel: '通知', icon: Bell },
|
||||||
|
{ id: 'interaction' as const, label: '交互记录', shortLabel: '交互', icon: MessageCircle },
|
||||||
|
];
|
||||||
|
|
||||||
|
const renderPage = () => {
|
||||||
|
switch (currentPage) {
|
||||||
|
case 'dashboard':
|
||||||
|
return <Dashboard onNavigate={setCurrentPage} />;
|
||||||
|
case 'messages':
|
||||||
|
return <FamilyMessages />;
|
||||||
|
case 'media':
|
||||||
|
return <MediaLibrary />;
|
||||||
|
case 'mood':
|
||||||
|
return <MoodHistory />;
|
||||||
|
case 'care':
|
||||||
|
return <CarePlan />;
|
||||||
|
case 'alerts':
|
||||||
|
return <AlertsAndCare />;
|
||||||
|
case 'interaction':
|
||||||
|
return <InteractionHistory />;
|
||||||
|
default:
|
||||||
|
return <Dashboard onNavigate={setCurrentPage} />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col h-screen bg-gray-50">
|
||||||
|
{/* 顶部标题栏 - 手机优化 */}
|
||||||
|
<div className="bg-white border-b px-4 py-3 flex items-center justify-between flex-shrink-0 safe-area-top">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-baseline gap-2">
|
||||||
|
<h1 className="text-lg font-bold text-primary-600">KinEcho</h1>
|
||||||
|
<span className="text-xs text-gray-500 font-medium">亲情回声</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-gray-400 italic mt-0.5">
|
||||||
|
Bring family moments to life. / 把家人的声音带到眼前。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowElderProfile(true)}
|
||||||
|
className="px-3 py-1.5 bg-primary-50 hover:bg-primary-100 rounded-full transition-colors flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<span className="text-primary-700 text-sm font-bold">
|
||||||
|
{elderInfo.name}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 主要内容区 - 可滚动 */}
|
||||||
|
<div className="flex-1 overflow-y-auto pb-safe">{renderPage()}</div>
|
||||||
|
|
||||||
|
{/* 底部标签栏导航 - 手机原生风格 */}
|
||||||
|
<nav className="bg-white border-t flex items-center justify-around flex-shrink-0 safe-area-bottom">
|
||||||
|
{navigation.map((item) => {
|
||||||
|
const Icon = item.icon;
|
||||||
|
const isActive = currentPage === item.id;
|
||||||
|
const showBadge = item.id === 'alerts' && unhandledCount > 0;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={item.id}
|
||||||
|
onClick={() => setCurrentPage(item.id)}
|
||||||
|
className={`
|
||||||
|
flex-1 flex flex-col items-center justify-center py-2 px-1 transition-colors
|
||||||
|
${isActive ? 'text-primary-600' : 'text-gray-500'}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<div className="relative">
|
||||||
|
<Icon
|
||||||
|
size={24}
|
||||||
|
strokeWidth={isActive ? 2.5 : 2}
|
||||||
|
className="mb-1"
|
||||||
|
/>
|
||||||
|
{showBadge && (
|
||||||
|
<span className="absolute -top-1 -right-1 w-2.5 h-2.5 bg-red-500 rounded-full animate-pulse" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<span className={`text-xs ${isActive ? 'font-semibold' : 'font-normal'}`}>
|
||||||
|
{item.shortLabel}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{/* 老人信息弹窗 */}
|
||||||
|
{showElderProfile && (
|
||||||
|
<ElderProfile
|
||||||
|
elderInfo={elderInfo}
|
||||||
|
onClose={() => setShowElderProfile(false)}
|
||||||
|
onSave={handleSaveElderInfo}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 紧急通知弹窗 */}
|
||||||
|
{emergencyAlert && (
|
||||||
|
<EmergencyAlert
|
||||||
|
message={emergencyAlert.message}
|
||||||
|
timestamp={emergencyAlert.timestamp}
|
||||||
|
onHandle={async () => {
|
||||||
|
// 标记为已处理
|
||||||
|
await messageService.handleAlert(emergencyAlert.id);
|
||||||
|
// 关闭弹窗
|
||||||
|
setEmergencyAlert(null);
|
||||||
|
// 跳转到通知页面
|
||||||
|
setCurrentPage('alerts');
|
||||||
|
// 刷新未处理数量
|
||||||
|
loadUnhandledCount();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<FamilyApp />
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
Reference in New Issue
Block a user