This commit is contained in:
D8D Developer
2025-06-27 01:56:30 +00:00
parent 0ef1dd1484
commit b9a3c991d0
354 changed files with 5 additions and 5 deletions

View File

@@ -0,0 +1,336 @@
import React, { useState, useEffect } from 'react';
import {
useNavigate,
useLocation,
useParams
} from 'react-router';
import {
Button, Space,
Form, Input, Select, message,
Card, Spin, Typography,
Switch, Divider, Descriptions,
Tag, List,
} from 'antd';
import {
FileImageOutlined,
FilePdfOutlined,
FileOutlined,
} from '@ant-design/icons';
import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';
// 从share/types.ts导入所有类型包括MapMode
import type {
Attachment,
} from '@/share/monitorTypes';
import {
AlertLevel, AlertStatus,
HandleType, ProblemType,
HandleTypeNameMap, ProblemTypeNameMap,
} from '@/share/monitorTypes';
import { getEnumOptions } from '../utils';
import { alertsClient, alertsHandleLogsClient } from '@/client/api';
import type { InferRequestType, InferResponseType } from 'hono/client';
import { Uploader } from "../components/components_uploader";
type AlertResponse = InferResponseType<typeof alertsClient.$get, 200>;
type AlertHandleLogRequest = InferRequestType<typeof alertsHandleLogsClient.$post>['json'];
type DeviceAlert = InferResponseType<typeof alertsClient.$get, 200>['data'][0];
type AlertHandleLog = InferResponseType<typeof alertsHandleLogsClient.$post, 200>;
const { Text } = Typography;
// 告警处理页面
export const AlertHandlePage = () => {
const { id } = useParams<{ id: string }>();
const [loading, setLoading] = useState(false);
const [submitting, setSubmitting] = useState(false);
const [alert, setAlert] = useState<DeviceAlert | null>(null);
const [form] = Form.useForm();
const navigate = useNavigate();
const [uploadedFiles, setUploadedFiles] = useState<Attachment[]>([]);
const location = useLocation();
const searchParams = new URLSearchParams(location.search);
const mode = searchParams.get('mode') || 'view'; // 默认为查看模式
// 判断是否可编辑
const isEditable = mode === 'edit' && alert &&
(alert.status === AlertStatus.PENDING || alert.status === AlertStatus.HANDLING);
useEffect(() => {
if (id) {
fetchAlertData(parseInt(id));
}
}, [id]);
const fetchAlertData = async (alertId: number) => {
setLoading(true);
try {
const res = await alertsClient[':id'].$get({
param: { id: alertId }
});
if (res.status === 200) {
const data = await res.json();
setAlert(data);
} else {
throw new Error(res.statusText);
}
} catch (error) {
console.error('获取告警数据失败:', error);
message.error('获取告警数据失败');
} finally {
setLoading(false);
}
};
const handleSubmit = async (values: any) => {
if (!id) return;
setSubmitting(true);
try {
const alertHandleLog: AlertHandleLogRequest = {
alertId: parseInt(id),
handleType: values.handleType,
problemType: values.problemType,
handleResult: values.handleResult,
notifyDisabled: values.notifyDisabled ? 1 : 0,
attachments: uploadedFiles
};
const res = await alertsHandleLogsClient.$post({
json: alertHandleLog
});
if (res.status === 200) {
message.success('告警处理成功');
navigate('/admin/alert-records');
} else {
const error = await res.json();
throw new Error(error.message || '处理失败');
}
} catch (error) {
console.error('告警处理失败:', error);
message.error('告警处理失败');
} finally {
setSubmitting(false);
}
};
// 文件上传成功回调
const handleFileUploadSuccess = (fileUrl: string, fileInfo: any) => {
// 添加上传成功的文件到列表
const newFile: Attachment = {
id: fileInfo.id || String(Date.now()),
name: fileInfo.file_name,
url: fileUrl,
type: fileInfo.file_type,
size: fileInfo.file_size,
upload_time: new Date().toISOString()
};
setUploadedFiles(prev => [...prev, newFile]);
};
// 删除已上传文件
const handleFileDelete = (fileId: string) => {
setUploadedFiles(prev => prev.filter(file => file.id !== fileId));
};
const handleTypeOptions = getEnumOptions(HandleType, HandleTypeNameMap);
const problemTypeOptions = getEnumOptions(ProblemType, ProblemTypeNameMap);
const getAlertLevelTag = (level: AlertLevel | null) => {
if (level === null) return <Tag></Tag>;
switch (level) {
case AlertLevel.MINOR:
return <Tag color="blue"></Tag>;
case AlertLevel.NORMAL:
return <Tag color="green"></Tag>;
case AlertLevel.IMPORTANT:
return <Tag color="orange"></Tag>;
case AlertLevel.URGENT:
return <Tag color="red"></Tag>;
default:
return <Tag></Tag>;
}
};
const getAlertStatusTag = (status?: AlertStatus) => {
if (status === undefined) return <Tag></Tag>;
switch (status) {
case AlertStatus.PENDING:
return <Tag color="red"></Tag>;
case AlertStatus.HANDLING:
return <Tag color="orange"></Tag>;
case AlertStatus.RESOLVED:
return <Tag color="green"></Tag>;
case AlertStatus.IGNORED:
return <Tag color="default"></Tag>;
default:
return <Tag></Tag>;
}
};
if (loading) {
return (
<div style={{ textAlign: 'center', padding: '50px' }}>
<Spin size="large" />
</div>
);
}
if (!alert) {
return (
<div style={{ textAlign: 'center', padding: '50px' }}>
<Text></Text>
</div>
);
}
return (
<div>
<Card title={isEditable ? "告警处理" : "告警查看"} style={{ marginBottom: 16 }}>
<Descriptions bordered column={2} style={{ marginBottom: 16 }}>
<Descriptions.Item label="告警ID">{alert.id}</Descriptions.Item>
<Descriptions.Item label="设备名称">{alert.deviceName}</Descriptions.Item>
<Descriptions.Item label="告警等级">{getAlertLevelTag(alert.alertLevel)}</Descriptions.Item>
<Descriptions.Item label="状态">{getAlertStatusTag(alert.status)}</Descriptions.Item>
<Descriptions.Item label="监控指标">{alert.metricType}</Descriptions.Item>
<Descriptions.Item label="触发值">{alert.metricValue}</Descriptions.Item>
<Descriptions.Item label="告警消息">{alert.alertMessage}</Descriptions.Item>
<Descriptions.Item label="告警时间">{dayjs(alert.createdAt).format('YYYY-MM-DD HH:mm:ss')}</Descriptions.Item>
</Descriptions>
{/* 只有可编辑模式或者已经有处理记录的情况下才显示表单 */}
{isEditable && (
<>
<Divider />
<Form
form={form}
layout="vertical"
onFinish={handleSubmit}
initialValues={{
handle_type: HandleType.CONFIRM,
notify_disabled: false,
}}
>
<Form.Item
name="handleType"
label="处理类型"
rules={[{ required: true, message: '请选择处理类型' }]}
>
<Select options={handleTypeOptions} />
</Form.Item>
<Form.Item
name="problemType"
label="问题类型"
rules={[{ required: true, message: '请选择问题类型' }]}
>
<Select options={problemTypeOptions} />
</Form.Item>
<Form.Item
name="handleResult"
label="处理结果"
rules={[{ required: true, message: '请输入处理结果' }]}
>
<Input.TextArea rows={4} />
</Form.Item>
<Form.Item
label="附件"
>
<div className="upload-attachments">
{/* 使用MinIOUploader代替原始Upload组件 */}
<Uploader
onSuccess={handleFileUploadSuccess}
onError={(error) => message.error(`上传失败: ${error.message}`)}
onProgress={(percent) => console.log(`上传进度: ${percent}%`)}
prefix="alerts/"
maxSize={20 * 1024 * 1024}
allowedTypes={['image/jpeg', 'image/png', 'application/pdf', 'text/plain', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document']}
/>
{/* 已上传文件列表 */}
{uploadedFiles.length > 0 && (
<div style={{ marginTop: 16 }}>
<h4></h4>
<List
size="small"
bordered
dataSource={uploadedFiles}
renderItem={file => (
<List.Item
actions={[
<Button
key="delete"
type="link"
danger
onClick={() => handleFileDelete(file.id)}
>
</Button>
]}
>
<Space>
{file.type.includes('image') ? <FileImageOutlined /> :
file.type.includes('pdf') ? <FilePdfOutlined /> :
<FileOutlined />}
<a href={file.url} target="_blank" rel="noopener noreferrer">
{file.name}
</a>
<Text type="secondary">
({file.size < 1024 * 1024
? `${(file.size / 1024).toFixed(2)} KB`
: `${(file.size / 1024 / 1024).toFixed(2)} MB`})
</Text>
</Space>
</List.Item>
)}
/>
</div>
)}
</div>
</Form.Item>
<Form.Item
name="notifyDisabled"
valuePropName="checked"
>
<Switch checkedChildren="禁用通知" unCheckedChildren="启用通知" />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" loading={submitting}>
</Button>
<Button style={{ marginLeft: 8 }} onClick={() => navigate('/admin/alert-records')}>
</Button>
</Form.Item>
</Form>
</>
)}
{/* 不可编辑模式时只显示返回按钮 */}
{!isEditable && (
<Form.Item style={{ marginTop: 16 }}>
<Button onClick={() => navigate('/admin/alert-records')}>
</Button>
</Form.Item>
)}
</Card>
</div>
);
};