import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
import {
Table,
Button,
Modal,
Form,
Input,
Select,
message,
Card,
Space,
InputNumber,
Progress,
Drawer,
Tag,
Tooltip,
Dropdown,
Badge,
Row,
Col,
Statistic,
Typography,
Empty,
Spin,
Alert,
Checkbox,
} from 'antd';
import {
PlusOutlined,
EditOutlined,
DeleteOutlined,
ReloadOutlined,
SearchOutlined,
FilterOutlined,
MoreOutlined,
EyeOutlined,
CloudOutlined,
EnvironmentOutlined,
DashboardOutlined,
ExpandOutlined,
CompressOutlined,
CheckCircleOutlined,
WarningOutlined,
SyncOutlined,
DeleteFilled,
} from '@ant-design/icons';
import axios from 'axios';
import { designTokens } from '../config/theme';
import CloseButton from '../components/CloseButton';
const { Option } = Select;
const { Title, Text } = Typography;
const containerStyle = {
minHeight: '100vh',
background: 'linear-gradient(180deg, #f5f7fa 0%, #e8ecf1 100%)',
padding: '24px',
};
const headerStyle = {
marginBottom: '24px',
padding: '24px',
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
borderRadius: '20px',
color: '#fff',
boxShadow: '0 8px 32px rgba(102, 126, 234, 0.3)',
};
const statCardStyle = color => ({
background: 'rgba(255, 255, 255, 0.15)',
borderRadius: designTokens.borderRadius.medium,
padding: '16px',
border: '1px solid rgba(255, 255, 255, 0.2)',
backdropFilter: 'blur(10px)',
});
const cardStyle = {
borderRadius: designTokens.borderRadius.large,
border: 'none',
boxShadow: designTokens.shadows.medium,
background: '#fff',
overflow: 'hidden',
};
const cardHeadStyle = {
borderBottom: '1px solid #f0f0f0',
padding: '16px 24px',
background: 'linear-gradient(135deg, #f8f9ff 0%, #ffffff 100%)',
};
const primaryButtonStyle = {
height: '42px',
borderRadius: '10px',
background: designTokens.colors.primary.gradient,
border: 'none',
boxShadow: '0 4px 16px rgba(102, 126, 234, 0.35)',
fontWeight: '500',
};
const actionButtonStyle = {
height: '36px',
borderRadius: '8px',
border: '1px solid #e8e8e8',
};
const searchInputStyle = {
borderRadius: '10px',
height: '42px',
border: '1px solid #e8e8e8',
};
const statusConfig = {
active: { text: '在用', color: 'success', icon: },
maintenance: { text: '维护中', color: 'warning', icon: },
inactive: { text: '停用', color: 'default', icon: },
};
const CapacityProgress = ({ used, capacity, color }) => {
const percentage = Math.min((used / capacity) * 100, 100);
const status = percentage >= 90 ? 'exception' : percentage >= 70 ? 'active' : 'success';
return (
{used} / {capacity}
{percentage.toFixed(1)}%
);
};
const RoomCard = ({ room, onEdit, onDelete, onView, selected, onSelect }) => {
const rackCount = room.Racks?.length || 0;
const capacityUsage = (rackCount / room.capacity) * 100;
const statusInfo = statusConfig[room.status];
return (
onSelect(room.roomId)}
onDoubleClick={() => onView(room)}
styles={{ body: { padding: '20px' } }}
>
{room.name}
{room.roomId}
{statusInfo.text}
{room.location}
= 90
? designTokens.colors.error.main
: capacityUsage >= 70
? designTokens.colors.warning.main
: designTokens.colors.success.main
}
/>
}
onClick={e => {
e.stopPropagation();
onView(room);
}}
style={{ color: designTokens.colors.text.secondary }}
/>
}
onClick={e => {
e.stopPropagation();
onEdit(room);
}}
style={{ color: designTokens.colors.primary.main }}
/>
}
danger
onClick={e => {
e.stopPropagation();
onDelete(room.roomId);
}}
/>
);
};
function RoomManagement() {
const [rooms, setRooms] = useState([]);
const [loading, setLoading] = useState(true);
const [modalVisible, setModalVisible] = useState(false);
const [drawerVisible, setDrawerVisible] = useState(false);
const [editingRoom, setEditingRoom] = useState(null);
const [viewingRoom, setViewingRoom] = useState(null);
const [selectedRoomIds, setSelectedRoomIds] = useState([]);
const [viewMode, setViewMode] = useState('table');
const [searchKeyword, setSearchKeyword] = useState('');
const [statusFilter, setStatusFilter] = useState('all');
const [form] = Form.useForm();
const fetchRooms = useCallback(async () => {
try {
setLoading(true);
const response = await axios.get('/api/rooms');
setRooms(response.data.rooms || []);
} catch (error) {
message.error('获取机房列表失败');
console.error('获取机房列表失败:', error);
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
fetchRooms();
}, [fetchRooms]);
const showModal = (room = null) => {
setEditingRoom(room);
if (room) {
form.setFieldsValue(room);
} else {
form.resetFields();
}
setModalVisible(true);
};
const handleCancel = () => {
setModalVisible(false);
setEditingRoom(null);
};
const handleSubmit = async values => {
try {
if (editingRoom) {
await axios.put(`/api/rooms/${editingRoom.roomId}`, values);
message.success('机房更新成功');
} else {
await axios.post('/api/rooms', values);
message.success('机房创建成功');
}
setModalVisible(false);
fetchRooms();
setEditingRoom(null);
} catch (error) {
message.error(editingRoom ? '机房更新失败' : '机房创建失败');
console.error(editingRoom ? '机房更新失败:' : '机房创建失败:', error);
}
};
const handleDelete = async roomId => {
Modal.confirm({
title: '确认删除',
content: '确定要删除这个机房吗?删除后无法恢复。',
okText: '删除',
okType: 'danger',
cancelText: '取消',
onOk: async () => {
try {
await axios.delete(`/api/rooms/${roomId}`);
message.success('机房删除成功');
fetchRooms();
} catch (error) {
message.error('机房删除失败');
console.error('机房删除失败:', error);
}
},
});
};
const handleBatchDelete = async () => {
if (selectedRoomIds.length === 0) {
message.warning('请先选择要删除的机房');
return;
}
Modal.confirm({
title: '批量删除',
content: `确定要删除选中的 ${selectedRoomIds.length} 个机房吗?`,
okText: '删除',
okType: 'danger',
cancelText: '取消',
onOk: async () => {
try {
await Promise.all(selectedRoomIds.map(id => axios.delete(`/api/rooms/${id}`)));
message.success(`成功删除 ${selectedRoomIds.length} 个机房`);
setSelectedRoomIds([]);
fetchRooms();
} catch (error) {
message.error('批量删除失败');
console.error('批量删除失败:', error);
}
},
});
};
const handleView = room => {
setViewingRoom(room);
setDrawerVisible(true);
};
const filteredRooms = useMemo(() => {
return rooms.filter(room => {
const matchKeyword =
!searchKeyword ||
room.name?.toLowerCase().includes(searchKeyword.toLowerCase()) ||
room.roomId?.toLowerCase().includes(searchKeyword.toLowerCase()) ||
room.location?.toLowerCase().includes(searchKeyword.toLowerCase());
const matchStatus = statusFilter === 'all' || room.status === statusFilter;
return matchKeyword && matchStatus;
});
}, [rooms, searchKeyword, statusFilter]);
const stats = useMemo(
() => ({
total: rooms.length,
active: rooms.filter(r => r.status === 'active').length,
maintenance: rooms.filter(r => r.status === 'maintenance').length,
inactive: rooms.filter(r => r.status === 'inactive').length,
totalRacks: rooms.reduce((sum, r) => sum + (r.Racks?.length || 0), 0),
totalCapacity: rooms.reduce((sum, r) => sum + (r.capacity || 0), 0),
}),
[rooms]
);
const tableColumns = [
{
title: '机房信息',
key: 'roomInfo',
render: (_, record) => (
{record.name}
{record.roomId}
),
},
{
title: '位置',
dataIndex: 'location',
key: 'location',
render: location => (
{location}
),
},
{
title: '面积/容量',
key: 'areaCapacity',
render: (_, record) => (
),
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
render: status => {
const config = statusConfig[status];
return (
{config.text}
);
},
filters: [
{ text: '在用', value: 'active' },
{ text: '维护中', value: 'maintenance' },
{ text: '停用', value: 'inactive' },
],
onFilter: (value, record) => record.status === value,
},
{
title: '机柜数',
key: 'rackCount',
render: (_, record) => (
{record.Racks?.length || 0}
),
sorter: (a, b) => (a.Racks?.length || 0) - (b.Racks?.length || 0),
},
{
title: '创建时间',
dataIndex: 'createdAt',
key: 'createdAt',
render: date => (date ? new Date(date).toLocaleString() : '-'),
sorter: (a, b) => new Date(a.createdAt || 0) - new Date(b.createdAt || 0),
},
{
title: '操作',
key: 'action',
fixed: 'right',
width: 160,
render: (_, record) => (
}
onClick={() => handleView(record)}
style={{ color: designTokens.colors.text.secondary }}
/>
}
onClick={() => showModal(record)}
style={{ color: designTokens.colors.primary.main }}
/>
}
danger
onClick={() => handleDelete(record.roomId)}
/>
),
},
];
const rowSelection = {
selectedRowKeys: selectedRoomIds,
onChange: selectedRowKeys => {
setSelectedRoomIds(selectedRowKeys);
},
};
return (
}
onClick={() => setViewMode('table')}
style={viewMode === 'table' ? primaryButtonStyle : actionButtonStyle}
>
表格视图
}
onClick={() => setViewMode('card')}
style={viewMode === 'card' ? primaryButtonStyle : actionButtonStyle}
>
卡片视图
{viewMode === 'table' ? (
`共 ${total} 条记录`,
}}
scroll={{ x: 1000 }}
rowClassName={() => 'table-row'}
/>
) : (
{filteredRooms.length > 0 ? (
filteredRooms.map(room => (
{
if (selectedRoomIds.includes(id)) {
setSelectedRoomIds(selectedRoomIds.filter(rid => rid !== id));
} else {
setSelectedRoomIds([...selectedRoomIds, id]);
}
}}
/>
))
) : (
)}
)}
{editingRoom ? '编辑机房' : '添加机房'}
}
open={modalVisible}
closeIcon={}
onCancel={handleCancel}
footer={null}
width={600}
destroyOnHidden
styles={{
body: { padding: '24px' },
header: {
borderBottom: '1px solid #f0f0f0',
padding: '16px 24px',
position: 'relative',
},
}}
style={{ borderRadius: '16px' }}
>
}
placeholder="请输入机房位置"
style={{ borderRadius: '8px' }}
/>
机房详情 - {viewingRoom?.name}
}
open={drawerVisible}
onClose={() => setDrawerVisible(false)}
width={480}
styles={{
header: { borderBottom: '1px solid #f0f0f0' },
body: { padding: '24px' },
}}
>
{viewingRoom && (
{viewingRoom.name}
{viewingRoom.roomId}
{statusConfig[viewingRoom.status].text}
位置
{viewingRoom.location}
容量
{viewingRoom.capacity} 机柜
{viewingRoom.description && (
描述
{viewingRoom.description}
)}
}
onClick={() => {
setDrawerVisible(false);
showModal(viewingRoom);
}}
style={primaryButtonStyle}
>
编辑机房
}
onClick={() => {
setDrawerVisible(false);
handleDelete(viewingRoom.roomId);
}}
style={{ borderRadius: '8px', height: '42px' }}
>
删除机房
)}
);
}
export default RoomManagement;