Files
hms/apps/web/src/components/Copilot/CopilotAlert.tsx
iven ced1c0ad0c fix(web): 清零前端 TS 构建错误 — 31 文件类型修复 + 面包屑 + 超时配置
- 修复 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
2026-05-15 23:03:08 +08:00

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>
);
}