import React, { useState, useEffect, useCallback } from 'react'; import { Table, Button, Modal, Form, Input, Select, message, Card, Space, Tag, Tooltip, Typography, Pagination, Popconfirm, Row, Col, Badge, Avatar, Statistic, } from 'antd'; import { PlusOutlined, EditOutlined, DeleteOutlined, SearchOutlined, ReloadOutlined, InboxOutlined, ClockCircleOutlined, UploadOutlined, ExclamationCircleOutlined, } from '@ant-design/icons'; import axios from 'axios'; import { deviceAPI } from '../api'; const { Title, Text, Paragraph } = Typography; const { Option } = Select; const { TextArea } = Input; const IdleDeviceManagement = () => { const [idleDevices, setIdleDevices] = useState([]); const [loading, setLoading] = useState(false); const [pagination, setPagination] = useState({ current: 1, pageSize: 10, total: 0 }); const [searchKeyword, setSearchKeyword] = useState(''); const [sourceTypeFilter, setSourceTypeFilter] = useState('all'); const [isModalVisible, setIsModalVisible] = useState(false); const [isShelveModalVisible, setIsShelveModalVisible] = useState(false); const [editingDevice, setEditingDevice] = useState(null); const [shelvingDevice, setShelvingDevice] = useState(null); const [form] = Form.useForm(); const [shelveForm] = Form.useForm(); const [racks, setRacks] = useState([]); const [rooms, setRooms] = useState([]); const [selectedRoomId, setSelectedRoomId] = useState(null); const [selectedShelveRoomId, setSelectedShelveRoomId] = useState(null); const [shelvePositionConflict, setShelvePositionConflict] = useState(null); const [shelveSelectedRackId, setShelveSelectedRackId] = useState(null); const fetchIdleDevices = useCallback(async () => { setLoading(true); try { const params = { page: pagination.current, pageSize: pagination.pageSize, keyword: searchKeyword, sourceType: sourceTypeFilter, }; const response = await axios.get('/api/idle-devices', { params }); setIdleDevices(response.data.idleDevices || []); setPagination(prev => ({ ...prev, total: response.data.total || 0, })); } catch (error) { message.error('获取空闲设备列表失败'); } finally { setLoading(false); } }, [pagination.current, pagination.pageSize, searchKeyword, sourceTypeFilter]); const fetchRacks = async () => { try { const response = await axios.get('/api/racks/all'); setRacks(response.data.racks || []); } catch (error) { console.error('获取机柜列表失败', error); } }; const fetchRooms = async () => { try { const response = await axios.get('/api/rooms', { params: { pageSize: 100 } }); setRooms(response.data.rooms || []); } catch (error) { console.error('获取机房列表失败', error); } }; useEffect(() => { fetchIdleDevices(); }, [fetchIdleDevices]); useEffect(() => { fetchRacks(); fetchRooms(); }, []); const handleAdd = () => { setEditingDevice(null); setSelectedRoomId(null); form.resetFields(); setIsModalVisible(true); }; const handleEdit = record => { setEditingDevice(record); let roomId = null; if (record.rackId && record.Rack) { roomId = record.Rack.roomId; setSelectedRoomId(roomId); } form.setFieldsValue({ name: record.name, type: record.type, model: record.model, serialNumber: record.serialNumber, powerConsumption: record.powerConsumption, idleReason: record.idleReason, warehouseId: record.warehouseId, roomId: roomId, rackId: record.rackId, position: record.position, description: record.description, }); setIsModalVisible(true); }; const handleDelete = async deviceId => { try { await axios.delete(`/api/idle-devices/${deviceId}`); message.success('删除成功'); fetchIdleDevices(); } catch (error) { message.error(error.response?.data?.error || '删除失败'); } }; const handleShelve = record => { setShelvingDevice(record); setShelvePositionConflict(null); setShelveSelectedRackId(null); let roomId = null; if (record.rackId) { const rack = racks.find(r => r.rackId === record.rackId); if (rack) { roomId = rack.roomId; } } setSelectedShelveRoomId(roomId); shelveForm.setFieldsValue({ name: record.name, type: record.type, model: record.model, serialNumber: record.serialNumber, height: record.height || 1, powerConsumption: record.powerConsumption, roomId: roomId, rackId: record.rackId, position: null, description: record.description, }); if (record.rackId) { setShelveSelectedRackId(record.rackId); } setIsShelveModalVisible(true); }; const checkShelvePositionConflict = async (rackId, position, height) => { if (!rackId || !position) { setShelvePositionConflict(null); return; } try { console.log('检查U位冲突:', { rackId, position, height, deviceId: shelvingDevice?.deviceId }); const result = await deviceAPI.checkPosition(rackId, { position, height: height || 1 }); console.log('U位检查结果:', result); if (!result.available) { setShelvePositionConflict(result.reason); } else { setShelvePositionConflict(null); } } catch (error) { console.error('检查U位冲突失败:', error); setShelvePositionConflict(null); } }; const handleShelveRackChange = value => { setShelveSelectedRackId(value); const position = shelveForm.getFieldValue('position'); const height = shelveForm.getFieldValue('height'); if (position) { checkShelvePositionConflict(value, position, height); } else { setShelvePositionConflict(null); } }; const handleShelvePositionChange = e => { const value = e.target.value ? parseInt(e.target.value) : null; const height = shelveForm.getFieldValue('height'); if (shelveSelectedRackId && value) { checkShelvePositionConflict(shelveSelectedRackId, value, height); } else { setShelvePositionConflict(null); } }; const handleShelveHeightChange = e => { const value = e.target.value ? parseInt(e.target.value) : null; const position = shelveForm.getFieldValue('position'); if (shelveSelectedRackId && position) { checkShelvePositionConflict(shelveSelectedRackId, position, value); } else { setShelvePositionConflict(null); } }; const handleShelveSubmit = async () => { try { const values = await shelveForm.validateFields(); if (shelvePositionConflict) { message.error('存在U位冲突,请重新选择上架位置'); return; } const submitData = { name: values.name, type: values.type, model: values.model, serialNumber: values.serialNumber, height: values.height || 1, powerConsumption: values.powerConsumption || 0, rackId: values.rackId, position: values.position, description: values.description || '', }; await axios.put(`/api/idle-devices/${shelvingDevice.deviceId}/shelve`, submitData); message.success('设备上架成功'); setIsShelveModalVisible(false); fetchIdleDevices(); } catch (error) { message.error(error.response?.data?.error || '上架失败'); } }; const handleSubmit = async () => { try { const values = await form.validateFields(); const submitData = { ...values }; if (submitData.warehouseId) { submitData.rackId = null; submitData.position = null; submitData.sourceType = 'warehouse'; } else if (submitData.rackId) { submitData.warehouseId = null; submitData.sourceType = 'rack'; } if (editingDevice) { await axios.put(`/api/idle-devices/${editingDevice.deviceId}`, submitData); message.success('更新成功'); } else { const response = await axios.post('/api/idle-devices', submitData); message.success(`添加成功,设备ID:${response.data.deviceId}`); } setIsModalVisible(false); fetchIdleDevices(); } catch (error) { message.error(error.response?.data?.error || '操作失败'); } }; const getIdleDays = idleDate => { if (!idleDate) return 0; const diff = new Date() - new Date(idleDate); return Math.floor(diff / (1000 * 60 * 60 * 24)); }; const columns = [ { title: '序号', key: 'index', width: 60, align: 'center', render: (_, __, index) => ( ), }, { title: '设备信息', key: 'deviceInfo', width: 220, render: (_, record) => (
} />
{record.name || '-'} {record.type === 'server' ? '服务器' : record.type === 'switch' ? '交换机' : '其他'} {record.model || '-'}
), }, { title: '设备ID', dataIndex: 'deviceId', key: 'deviceId', width: 100, render: text => ( {text} ), }, { title: '位置', key: 'location', width: 160, render: (_, record) => { if (record.sourceType === 'warehouse' && record.warehouseId) { return ( {record.warehouseId} ); } if (record.sourceType === 'rack' && record.Rack) { const location = [ record.Rack.Room?.name, record.Rack.name, record.position ? `U${record.position}` : null, ] .filter(Boolean) .join(' / '); return ( {location || '-'} ); } return -; }, }, { title: '空闲天数', key: 'idleDays', width: 100, align: 'center', render: (_, record) => { const days = getIdleDays(record.idleDate); const color = days > 30 ? '#ef4444' : days > 7 ? '#f59e0b' : '#22c55e'; return (
{days}
); }, }, { title: '空闲原因', dataIndex: 'idleReason', key: 'idleReason', width: 140, ellipsis: true, render: text => ( {text || '-'} ), }, { title: '来源', dataIndex: 'sourceType', key: 'sourceType', width: 80, align: 'center', render: type => ( {type === 'warehouse' ? '库房' : '机架'} ), }, { title: '操作', key: 'action', width: 140, fixed: 'right', align: 'center', render: (_, record) => ( (index % 2 === 0 ? 'table-row-even' : 'table-row-odd')} style={{ borderRadius: '0 0 16px 16px' }} /> {pagination.total > 0 && (
{pagination.total} 条记录 setPagination(prev => ({ ...prev, current: page, pageSize })) } showSizeChanger showQuickJumper showTotal={total => `共 ${total} 条`} size="small" /> )}
{editingDevice ? '编辑空闲设备' : '添加空闲设备'}
} open={isModalVisible} onOk={handleSubmit} onCancel={() => setIsModalVisible(false)} okText="确定" cancelText="取消" width={680} destroyOnClose bodyStyle={{ padding: '24px' }} style={{ top: 100 }} >
设备基本信息
{editingDevice && ( )}
位置信息
— 或 —
{ if (form.getFieldValue('warehouseId')) { form.setFieldsValue({ roomId: null, rackId: null, position: null }); setSelectedRoomId(null); } }} style={{ borderRadius: '8px' }} prefix={} />
附加信息