Initial commit: HealthManager full-stack health management platform
Backend: .NET 10 + PostgreSQL + EF Core + JWT + SignalR Frontend patient: React 19 + TypeScript + Vite (mobile H5) Frontend doctor: React 19 + TypeScript + Vite (desktop web)
This commit is contained in:
118
frontend-patient/src/pages/home/HomePage.tsx
Normal file
118
frontend-patient/src/pages/home/HomePage.tsx
Normal file
@@ -0,0 +1,118 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Card } from '@/components/common/Card';
|
||||
import { Empty } from '@/components/common/Empty';
|
||||
import { Badge } from '@/components/common/Badge';
|
||||
import { PageHeader } from '@/components/layout/PageHeader';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
import { useNotificationStore } from '@/stores/notification.store';
|
||||
import * as healthService from '@/services/health.service';
|
||||
import { MEASUREMENT_TYPES, HEALTH_TIPS } from '@/utils/constants';
|
||||
import { getBPRiskLevel } from '@/utils/format';
|
||||
import type { HealthStats } from '@/types';
|
||||
import styles from './HomePage.module.css';
|
||||
|
||||
const QUICK_ACTIONS = [
|
||||
{ label: '测血压', icon: '💓', path: '/health/records?type=blood_pressure' },
|
||||
{ label: '记用药', icon: '💊', path: '/health/medications' },
|
||||
{ label: '在线问诊', icon: '👨⚕️', path: '/services/consultation' },
|
||||
{ label: '报告解读', icon: '📋', path: '/services/reports' },
|
||||
{ label: '健康日历', icon: '📅', path: '/health/calendar' },
|
||||
{ label: '运动饮食', icon: '🏃', path: '/health/exercise-diet' },
|
||||
];
|
||||
|
||||
export function HomePage() {
|
||||
const navigate = useNavigate();
|
||||
const { user } = useAuth();
|
||||
const { unreadCount, fetchNotifications } = useNotificationStore();
|
||||
const [stats, setStats] = useState<HealthStats[]>([]);
|
||||
const [tipIndex, setTipIndex] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
healthService.getLatestStats().then(setStats);
|
||||
fetchNotifications();
|
||||
}, [fetchNotifications]);
|
||||
|
||||
const bpStats = stats.find((s) => s.type === 'blood_pressure');
|
||||
const hrStats = stats.find((s) => s.type === 'heart_rate');
|
||||
|
||||
const bpValue = bpStats?.latest?.value;
|
||||
const systolic = typeof bpValue === 'object' ? bpValue.systolic : null;
|
||||
const diastolic = typeof bpValue === 'object' ? bpValue.diastolic : null;
|
||||
const riskLevel = systolic && diastolic ? getBPRiskLevel(systolic, diastolic) : null;
|
||||
|
||||
return (
|
||||
<div className="page">
|
||||
<PageHeader
|
||||
title={`你好,${user?.nickname || '用户'}`}
|
||||
showBack={false}
|
||||
rightAction={
|
||||
<button className={styles.notifyBtn} onClick={() => navigate('/notifications')}>
|
||||
🔔
|
||||
{unreadCount > 0 && <Badge count={unreadCount} />}
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Health Overview */}
|
||||
{bpStats?.latest && hrStats?.latest ? (
|
||||
<Card className={styles.overviewCard}>
|
||||
<div className={styles.overviewHeader}>
|
||||
<span className={styles.overviewTitle}>健康概览</span>
|
||||
<span className={styles.overviewTime}>最新记录</span>
|
||||
</div>
|
||||
<div className={styles.overviewData}>
|
||||
<div className={styles.bpSection}>
|
||||
<span className={styles.dataLabel}>血压</span>
|
||||
<div className={styles.bpValues}>
|
||||
<span className={`${styles.bpNum} ${styles[`risk_${riskLevel}`] || ''}`}>
|
||||
{systolic}
|
||||
</span>
|
||||
<span className={styles.bpSep}>/</span>
|
||||
<span className={`${styles.bpNum} ${styles[`risk_${riskLevel}`] || ''}`}>
|
||||
{diastolic}
|
||||
</span>
|
||||
</div>
|
||||
<span className={styles.unit}>mmHg</span>
|
||||
</div>
|
||||
<div className={styles.divider} />
|
||||
<div className={styles.hrSection}>
|
||||
<span className={styles.dataLabel}>心率</span>
|
||||
<span className={styles.hrNum}>{Number(hrStats.latest.value)}</span>
|
||||
<span className={styles.unit}>bpm</span>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
) : (
|
||||
<Empty icon="💓" message="暂无健康数据" />
|
||||
)}
|
||||
|
||||
{/* Quick Actions */}
|
||||
<div className={styles.quickActions}>
|
||||
{QUICK_ACTIONS.map((action) => (
|
||||
<button
|
||||
key={action.label}
|
||||
className={styles.quickAction}
|
||||
onClick={() => navigate(action.path)}
|
||||
>
|
||||
<span className={styles.quickIcon}>{action.icon}</span>
|
||||
<span className={styles.quickLabel}>{action.label}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Health Tip */}
|
||||
<Card
|
||||
className={styles.tipCard}
|
||||
onClick={() => setTipIndex((prev) => (prev + 1) % HEALTH_TIPS.length)}
|
||||
>
|
||||
<div className={styles.tipHeader}>
|
||||
<span>💡</span>
|
||||
<span className={styles.tipTitle}>健康小贴士</span>
|
||||
<span className={styles.tipHint}>点击换一条</span>
|
||||
</div>
|
||||
<p className={styles.tipContent}>{HEALTH_TIPS[tipIndex]}</p>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user