import React, { useState, useCallback, useMemo, useEffect } from 'react';
import {
Drawer,
Tabs,
Tag,
Space,
Typography,
Empty,
Card,
Tooltip,
Button,
Popconfirm,
Table,
Badge,
} from 'antd';
import {
ApiOutlined,
CloudServerOutlined,
EnvironmentOutlined,
EditOutlined,
PlusCircleOutlined,
DeleteOutlined,
FileTextOutlined,
ToolOutlined,
EyeOutlined,
} from '@ant-design/icons';
import NetworkCardPanel from './NetworkCardPanel';
import { deviceAPI } from '../api';
const { Text, Title } = Typography;
const designTokens = {
colors: {
primary: '#667eea',
success: '#10b981',
error: '#ef4444',
warning: '#f59e0b',
},
spacing: {
sm: 8,
md: 16,
},
};
function DeviceDetailDrawer({
device,
visible,
onClose,
cables,
onRefreshCables,
onEdit,
onAddNic,
onAddPort,
onAddCable,
onDeleteCable,
tooltipFields,
refreshTrigger,
onViewTicket,
onCreateTicket,
}) {
const [activeTab, setActiveTab] = useState('ports');
const [tickets, setTickets] = useState([]);
const [ticketsLoading, setTicketsLoading] = useState(false);
const [ticketsPagination, setTicketsPagination] = useState({
current: 1,
pageSize: 5,
total: 0,
});
// 获取设备关联的工单列表
const fetchDeviceTickets = useCallback(async (page = 1, pageSize = 5) => {
if (!device?.deviceId) return;
setTicketsLoading(true);
try {
const response = await deviceAPI.getTickets(device.deviceId, {
page,
pageSize,
});
setTickets(response.data || []);
setTicketsPagination({
current: response.page || 1,
pageSize: response.pageSize || 5,
total: response.total || 0,
});
} catch (error) {
console.error('获取设备工单失败:', error);
} finally {
setTicketsLoading(false);
}
}, [device?.deviceId]);
// 当设备变化或标签页切换到工单时,加载工单数据
useEffect(() => {
if (visible && device?.deviceId && activeTab === 'tickets') {
fetchDeviceTickets(1, 5);
}
}, [visible, device?.deviceId, activeTab, fetchDeviceTickets]);
// 工单表格列定义
const ticketColumns = useMemo(() => [
{
title: '工单编号',
dataIndex: 'ticketId',
key: 'ticketId',
width: 120,
render: (text) => {text},
},
{
title: '标题',
dataIndex: 'title',
key: 'title',
ellipsis: true,
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 90,
render: (status) => {
const statusConfig = {
pending: { color: 'warning', text: '待处理' },
processing: { color: 'processing', text: '处理中' },
completed: { color: 'success', text: '已完成' },
closed: { color: 'default', text: '已关闭' },
};
const config = statusConfig[status] || { color: 'default', text: status };
return ;
},
},
{
title: '优先级',
dataIndex: 'priority',
key: 'priority',
width: 80,
render: (priority) => {
const priorityConfig = {
low: { color: 'success', text: '低' },
medium: { color: 'warning', text: '中' },
high: { color: 'error', text: '高' },
critical: { color: 'purple', text: '紧急' },
};
const config = priorityConfig[priority] || { color: 'default', text: priority };
return {config.text};
},
},
{
title: '创建时间',
dataIndex: 'createdAt',
key: 'createdAt',
width: 150,
render: (date) => {
if (!date) return '-';
const d = new Date(date);
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
},
},
{
title: '操作',
key: 'action',
width: 80,
render: (_, record) => (
}
onClick={() => onViewTicket?.(record)}
>
查看
),
},
], [onViewTicket]);
const deviceCables = useMemo(() => {
if (!device || !cables) return [];
return cables.filter(
c => c.sourceDeviceId === device.deviceId || c.targetDeviceId === device.deviceId
);
}, [device, cables]);
const getStatusTag = useCallback(status => {
const config = {
running: { color: 'success', text: '运行中' },
normal: { color: 'success', text: '正常' },
warning: { color: 'warning', text: '警告' },
error: { color: 'error', text: '故障' },
fault: { color: 'error', text: '故障' },
offline: { color: 'default', text: '离线' },
maintenance: { color: 'processing', text: '维护中' },
};
const { color, text } = config[status] || { color: 'default', text: status };
return {text};
}, []);
const getDeviceTypeName = useCallback(type => {
const typeMap = {
server: '服务器',
switch: '交换机',
router: '路由器',
storage: '存储设备',
firewall: '防火墙',
ups: 'UPS',
pdu: 'PDU',
};
return typeMap[type?.toLowerCase()] || type || '未知设备';
}, []);
const renderFieldValue = useCallback(
(field, device) => {
const fieldKey = field.field;
if (fieldKey === 'status') return getStatusTag(device.status);
// 优先从device对象获取值,如果没有则从customFields中获取
let value = device[fieldKey];
if (
(value === undefined || value === null) &&
device.customFields &&
typeof device.customFields === 'object'
) {
value = device.customFields[fieldKey];
}
if (fieldKey === 'type') value = getDeviceTypeName(value);
else if (fieldKey === 'position')
value = `U${device.position} ${device.height ? `(${device.height}U)` : ''}`;
return (
{value !== undefined && value !== null ? value : '-'}
);
},
[getStatusTag, getDeviceTypeName]
);
const displayFields = useMemo(() => {
if (tooltipFields && Object.keys(tooltipFields).length > 0) {
return Object.values(tooltipFields).filter(f => f.enabled);
}
// Default fallback fields if no config
return [
{ field: 'deviceId', label: '设备ID' },
{ field: 'type', label: '设备类型' },
{ field: 'status', label: '设备状态' },
{ field: 'position', label: '位置' },
{ field: 'ipAddress', label: 'IP地址' },
{ field: 'brand', label: '品牌' },
];
}, [tooltipFields]);
const tabItems = [
{
key: 'ports',
label: (
端口与网卡
),
children: (
),
},
{
key: 'cables',
label: (
接线 ({deviceCables.length})
),
children: (
{deviceCables.length === 0 ? (
) : (
{deviceCables.map(cable => (
onDeleteCable?.(cable.cableId)}
okText="确定"
cancelText="取消"
>
} size="small" />
}
>
源设备:
{cable.sourceDevice?.name || '-'}
{cable.sourcePort}
目标设备:
{cable.targetDevice?.name || '-'}
{cable.targetPort}
{cable.status === 'normal'
? '正常'
: cable.status === 'fault'
? '故障'
: '未连接'}
{cable.cableType === 'ethernet'
? '网线'
: cable.cableType === 'fiber'
? '光纤'
: '铜缆'}
{cable.cableLength && {cable.cableLength}m}
{cable.description && (
{cable.description}
)}
))}
)}
),
},
{
key: 'tickets',
label: (
工单记录 ({ticketsPagination.total})
),
children: (
}
onClick={() => onCreateTicket?.(device)}
>
创建工单
fetchDeviceTickets(page, pageSize),
}}
size="small"
/>
),
},
];
if (!device) return null;
return (
设备详情 - {device.name}
}
placement="right"
width={600}
open={visible}
onClose={onClose}
extra={
} onClick={() => onEdit?.(device)} />
} onClick={() => onAddNic?.(device)}>
加网卡
} onClick={() => onAddPort?.(device)}>
加端口
} onClick={() => onAddCable?.(device)}>
加接线
}
styles={{ body: { padding: '16px 20px', overflow: 'auto' } }}
>
基本信息
{displayFields.map(field => (
{field.label}
{renderFieldValue(field, device)}
))}
);
}
export default React.memo(DeviceDetailDrawer);