import React, { useState, useCallback, useMemo, useEffect } from 'react'; import { Drawer, Tabs, Tag, Space, Typography, Empty, Card, Tooltip, Button, Popconfirm, Table, Badge, Row, Col, Divider, } from 'antd'; import { ApiOutlined, CloudServerOutlined, EnvironmentOutlined, EditOutlined, PlusCircleOutlined, DeleteOutlined, FileTextOutlined, ToolOutlined, EyeOutlined, DesktopOutlined, FieldTimeOutlined, InfoCircleOutlined, LinkOutlined, } from '@ant-design/icons'; import NetworkCardPanel from './NetworkCardPanel'; import { deviceAPI } from '../api'; import dayjs from 'dayjs'; const { Text, Title } = Typography; const designTokens = { colors: { primary: '#1890ff', success: '#52c41a', error: '#f5222d', warning: '#faad14', info: '#13c2c2', gray: '#8c8c8c', bgLight: '#f6ffed', bgBlue: '#e6f7ff', bgGray: '#f5f5f5', bgOrange: '#fff7e6', }, }; 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 '-'; return dayjs(date).format('YYYY-MM-DD HH:mm'); }, }, { 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 || '未知设备'; }, []); if (!device) return null; // 解析自定义字段 const customFields = device.customFields || {}; const standardFields = ['deviceId', 'name', 'type', 'model', 'serialNumber', 'status', 'ipAddress', 'position', 'height', 'powerConsumption', 'purchaseDate', 'warrantyExpiry', 'description']; const customFieldEntries = Object.entries(customFields).filter(([key]) => !standardFields.includes(key)); 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" /> ), }, ]; return ( 设备详情 } placement="right" width={700} open={visible} onClose={onClose} extra={ } styles={{ body: { padding: '0', overflow: 'auto' } }} > {/* 设备头部信息 */}
{device.name}
{getDeviceTypeName(device.type)} | {device.deviceId} | {getStatusTag(device.status)} {/* 设备信息内容 */}
{/* 基本信息卡片 */}
设备型号
{device.model || '-'}
序列号
{device.serialNumber || '-'}
IP地址
{device.ipAddress || '-'}
位置
U{device.position || '-'}
高度
{device.height ? `${device.height}U` : '-'}
功耗
{device.powerConsumption ? `${device.powerConsumption}W` : '-'}
{/* 维保信息卡片 */}
购买日期
{device.purchaseDate ? dayjs(device.purchaseDate).format('YYYY-MM-DD') : '-'}
保修到期
{device.warrantyExpiry ? dayjs(device.warrantyExpiry).format('YYYY-MM-DD') : '-'}
{/* 描述信息 */} {device.description && (
{device.description}
)} {/* 自定义字段卡片 */} {customFieldEntries.length > 0 && ( {customFieldEntries.map(([key, value]) => (
{key}
{String(value)}
))} )} {/* 标签页 */} ); } export default React.memo(DeviceDetailDrawer);