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) => ( ), }, ], [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="取消" >
), }, { key: 'tickets', label: ( 工单记录 ({ticketsPagination.total}) ), children: (
fetchDeviceTickets(page, pageSize), }} size="small" /> ), }, ]; if (!device) return null; return ( 设备详情 - {device.name} } placement="right" width={600} open={visible} onClose={onClose} extra={ } styles={{ body: { padding: '16px 20px', overflow: 'auto' } }} >
基本信息
{displayFields.map(field => (
{field.label} {renderFieldValue(field, device)}
))}
); } export default React.memo(DeviceDetailDrawer);