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:
MingNian
2026-05-20 16:18:56 +08:00
commit 435af55c4a
215 changed files with 18595 additions and 0 deletions

View File

@@ -0,0 +1,75 @@
import { useEffect, useState } from 'react';
import { PageHeader } from '@/components/layout/PageHeader';
import { Card } from '@/components/common/Card';
import { Empty } from '@/components/common/Empty';
import { useNotificationStore } from '@/stores/notification.store';
import { formatRelative } from '@/utils/format';
import type { NotificationType } from '@/types';
import styles from './NotificationListPage.module.css';
const TYPE_TABS: { key: NotificationType | 'all'; label: string }[] = [
{ key: 'all', label: '全部' },
{ key: 'medication', label: '用药提醒' },
{ key: 'followup', label: '复查提醒' },
{ key: 'consultation', label: '问诊消息' },
{ key: 'system', label: '系统' },
];
export function NotificationListPage() {
const { notifications, unreadCount, fetchNotifications, markRead, markAllRead } = useNotificationStore();
const [tab, setTab] = useState<NotificationType | 'all'>('all');
useEffect(() => {
fetchNotifications();
}, [fetchNotifications]);
const filtered = tab === 'all'
? notifications
: notifications.filter((n) => n.type === tab);
return (
<div className="page--no-tab">
<PageHeader
title="消息通知"
rightAction={
unreadCount > 0 ? (
<button className={styles.markAllBtn} onClick={markAllRead}>
</button>
) : undefined
}
/>
<div className={styles.tabs}>
{TYPE_TABS.map((t) => (
<button
key={t.key}
className={`${styles.tab} ${tab === t.key ? styles.tabActive : ''}`}
onClick={() => setTab(t.key)}
>
{t.label}
</button>
))}
</div>
{filtered.length === 0 ? (
<Empty icon="🔔" message="暂无通知" />
) : (
filtered.map((n) => (
<Card
key={n.id}
className={`${styles.notifCard} ${!n.isRead ? styles.unread : ''}`}
onClick={() => { if (!n.isRead) markRead(n.id); }}
>
<div className={styles.notifHeader}>
<span className={styles.notifTitle}>{n.title}</span>
{!n.isRead && <span className={styles.unreadDot} />}
</div>
<p className={styles.notifContent}>{n.content}</p>
<span className={styles.notifTime}>{formatRelative(n.createdAt)}</span>
</Card>
))
)}
</div>
);
}