- 修复 verbatimModuleSyntax 要求的 import type 声明 - 修复未使用导入(Badge/EditOutlined/Space/Input/Switch 等) - 修复 mock.calls 类型注解([string,unknown] → any[]) - 修复 vitest 全局超时和 poolTimeout 配置 - 修复 PageContainer 缺少 onBack prop、MenuInfo children 可选 - 修复 CopilotAlert Badge status info→processing、useCopilotRisk 二次解包 - 修复 articles/doctors 测试 delete 调用缺少 version 参数 - 添加排班管理/预约管理面包屑标题 fallback
107 lines
3.1 KiB
TypeScript
107 lines
3.1 KiB
TypeScript
import { useState, useEffect, useCallback } from 'react';
|
|
import { Alert, Badge, List, Button, Space, Typography, Spin } from 'antd';
|
|
import { CheckOutlined } from '@ant-design/icons';
|
|
import { listAlerts, dismissInsight } from '../../api/copilot';
|
|
import type { CopilotInsight } from '../../api/copilot';
|
|
|
|
const severityConfig: Record<string, { type: 'success' | 'processing' | 'warning' | 'error'; label: string }> = {
|
|
critical: { type: 'error', label: '危急' },
|
|
warning: { type: 'warning', label: '警告' },
|
|
info: { type: 'processing', label: '提示' },
|
|
};
|
|
|
|
export function CopilotAlert() {
|
|
const [alerts, setAlerts] = useState<CopilotInsight[]>([]);
|
|
const [loading, setLoading] = useState(false);
|
|
const [dismissing, setDismissing] = useState<string | null>(null);
|
|
|
|
const fetchAlerts = useCallback(async () => {
|
|
setLoading(true);
|
|
try {
|
|
const res = await listAlerts({ page_size: 50 });
|
|
const payload = (res.data as { data?: CopilotInsight[] }).data ?? [];
|
|
setAlerts(payload);
|
|
} catch {
|
|
// 静默
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
fetchAlerts();
|
|
const timer = setInterval(fetchAlerts, 30_000);
|
|
return () => clearInterval(timer);
|
|
}, [fetchAlerts]);
|
|
|
|
const handleDismiss = async (id: string) => {
|
|
setDismissing(id);
|
|
try {
|
|
await dismissInsight(id);
|
|
setAlerts((prev) => prev.filter((a) => a.id !== id));
|
|
} finally {
|
|
setDismissing(null);
|
|
}
|
|
};
|
|
|
|
if (!alerts.length && !loading) return null;
|
|
|
|
const criticalCount = alerts.filter((a) => a.severity === 'critical').length;
|
|
|
|
return (
|
|
<div>
|
|
{criticalCount > 0 && (
|
|
<Alert
|
|
type="error"
|
|
showIcon
|
|
message={`${criticalCount} 条危急告警`}
|
|
banner
|
|
style={{ marginBottom: 16 }}
|
|
/>
|
|
)}
|
|
{loading && alerts.length === 0 ? (
|
|
<Spin />
|
|
) : (
|
|
<List
|
|
size="small"
|
|
dataSource={alerts}
|
|
renderItem={(item) => {
|
|
const config = severityConfig[item.severity] ?? severityConfig.info;
|
|
return (
|
|
<List.Item
|
|
actions={[
|
|
<Button
|
|
key="dismiss"
|
|
size="small"
|
|
icon={<CheckOutlined />}
|
|
loading={dismissing === item.id}
|
|
onClick={() => handleDismiss(item.id)}
|
|
>
|
|
已知悉
|
|
</Button>,
|
|
]}
|
|
>
|
|
<List.Item.Meta
|
|
title={
|
|
<Space>
|
|
<Badge status={config.type} />
|
|
<Typography.Text>{item.title}</Typography.Text>
|
|
</Space>
|
|
}
|
|
description={
|
|
item.content?.suggestion ? (
|
|
<Typography.Text type="secondary">
|
|
{item.content.suggestion as string}
|
|
</Typography.Text>
|
|
) : undefined
|
|
}
|
|
/>
|
|
</List.Item>
|
|
);
|
|
}}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|