feat(web): DoctorDashboard 集成告警摘要卡片
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled

在医生工作台新增待处理告警摘要区域,展示最近 5 条
pending 状态告警,点击「查看全部」跳转告警仪表盘。
This commit is contained in:
iven
2026-04-28 20:01:11 +08:00
parent 27c32e5561
commit 493b479373

View File

@@ -1,18 +1,38 @@
import { Row, Col, Card, Statistic, List, Tag, Spin, Typography, Flex } from 'antd';
import { Row, Col, Card, Statistic, List, Tag, Spin, Typography, Flex, Space, Button } from 'antd';
import {
TeamOutlined,
MessageOutlined,
SafetyCertificateOutlined,
MedicineBoxOutlined,
ArrowUpOutlined,
AlertOutlined,
RightOutlined,
} from '@ant-design/icons';
import { useEffect, useState, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import { pointsApi, type PersonalStats } from '../../../api/health/points';
import { alertApi, type Alert } from '../../../api/health/alerts';
import { useStatsData } from './useStatsData';
import { useCountUp } from '../../../hooks/useCountUp';
const SEVERITY_COLOR: Record<string, string> = {
info: 'default',
warning: 'orange',
critical: 'red',
urgent: 'magenta',
};
const SEVERITY_LABEL: Record<string, string> = {
info: '提示',
warning: '警告',
critical: '严重',
urgent: '紧急',
};
export function DoctorDashboard() {
const navigate = useNavigate();
const [personal, setPersonal] = useState<PersonalStats | null>(null);
const [recentAlerts, setRecentAlerts] = useState<Alert[]>([]);
const [loading, setLoading] = useState(true);
const { consultationStats } = useStatsData();
const myPatientsCount = useCountUp(personal?.my_patients ?? 0);
@@ -29,11 +49,21 @@ export function DoctorDashboard() {
}
}, []);
useEffect(() => { fetchPersonal(); }, [fetchPersonal]);
const fetchRecentAlerts = useCallback(async () => {
try {
const result = await alertApi.list({ status: 'pending', page: 1, page_size: 5 });
setRecentAlerts(result.data);
} catch {
// 静默降级
}
}, []);
useEffect(() => { fetchPersonal(); fetchRecentAlerts(); }, [fetchPersonal, fetchRecentAlerts]);
if (loading && !personal) return <Spin size="large" style={{ display: 'block', margin: '80px auto' }} />;
const p = personal;
const pendingAlertCount = recentAlerts.length;
return (
<div>
@@ -63,6 +93,50 @@ export function DoctorDashboard() {
</Col>
)}
{/* 告警摘要卡片 */}
{pendingAlertCount > 0 && (
<Col span={24}>
<Card
size="small"
style={{ borderLeft: '4px solid #fa8c16' }}
title={
<Space>
<AlertOutlined style={{ color: '#fa8c16' }} />
<span>{pendingAlertCount} </span>
</Space>
}
extra={
<Button
type="link"
size="small"
icon={<RightOutlined />}
onClick={() => navigate('/health/alert-dashboard')}
>
</Button>
}
>
<List
size="small"
dataSource={recentAlerts}
renderItem={(alert) => (
<List.Item style={{ padding: '4px 0' }}>
<Space>
<Tag color={SEVERITY_COLOR[alert.severity]} style={{ margin: 0 }}>
{SEVERITY_LABEL[alert.severity] ?? alert.severity}
</Tag>
<Typography.Text style={{ fontSize: 13 }}>{alert.title}</Typography.Text>
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
{new Date(alert.created_at).toLocaleString('zh-CN')}
</Typography.Text>
</Space>
</List.Item>
)}
/>
</Card>
</Col>
)}
{/* 统计卡片 */}
<Col xs={12} sm={6}>
<Card size="small">