feat(设备管理): 实现本地搜索和筛选功能
- 在RackVisualization中增加对故障设备悬停效果的限制 - 在DeviceManagement中添加全量设备数据缓存和本地搜索功能 - 实现全字段搜索功能,支持自定义字段和关联对象搜索 - 优化筛选逻辑,减少不必要的API调用
This commit is contained in:
@@ -141,6 +141,7 @@ const ResizeableTitle = (props) => {
|
|||||||
|
|
||||||
function DeviceManagement() {
|
function DeviceManagement() {
|
||||||
const [devices, setDevices] = useState([]);
|
const [devices, setDevices] = useState([]);
|
||||||
|
const [allDevices, setAllDevices] = useState([]);
|
||||||
const [racks, setRacks] = useState([]);
|
const [racks, setRacks] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [modalVisible, setModalVisible] = useState(false);
|
const [modalVisible, setModalVisible] = useState(false);
|
||||||
@@ -184,22 +185,13 @@ function DeviceManagement() {
|
|||||||
// 列宽状态
|
// 列宽状态
|
||||||
const [columnWidths, setColumnWidths] = useState({});
|
const [columnWidths, setColumnWidths] = useState({});
|
||||||
|
|
||||||
// 获取所有设备(支持搜索、筛选和分页)
|
// 获取所有设备数据(不分页,用于本地搜索)
|
||||||
const fetchDevices = async (page = 1, pageSize = 10, searchParams = {}) => {
|
const fetchAllDevices = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
const response = await axios.get('/api/devices', {
|
||||||
|
params: { page: 1, pageSize: 99999 }
|
||||||
// 构建查询参数
|
});
|
||||||
const params = {
|
const { devices } = response.data;
|
||||||
page,
|
|
||||||
pageSize,
|
|
||||||
keyword: searchParams.keyword || keyword,
|
|
||||||
status: searchParams.status || status,
|
|
||||||
type: searchParams.type || type
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await axios.get('/api/devices', { params });
|
|
||||||
const { devices, total } = response.data;
|
|
||||||
|
|
||||||
// 将customFields中的字段值映射为设备对象的直接属性
|
// 将customFields中的字段值映射为设备对象的直接属性
|
||||||
const processedDevices = devices.map(device => {
|
const processedDevices = devices.map(device => {
|
||||||
@@ -215,8 +207,45 @@ function DeviceManagement() {
|
|||||||
return deviceWithFields;
|
return deviceWithFields;
|
||||||
});
|
});
|
||||||
|
|
||||||
setDevices(processedDevices);
|
return processedDevices;
|
||||||
setPagination(prev => ({ ...prev, current: page, pageSize, total }));
|
} catch (error) {
|
||||||
|
console.error('获取所有设备数据失败:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取所有设备(支持搜索、筛选和分页)
|
||||||
|
const fetchDevices = async (page = 1, pageSize = 10, searchParams = {}) => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
// 先获取所有设备数据
|
||||||
|
const allData = await fetchAllDevices();
|
||||||
|
setAllDevices(allData);
|
||||||
|
|
||||||
|
// 应用筛选条件
|
||||||
|
let filteredDevices = allData;
|
||||||
|
|
||||||
|
// 状态筛选
|
||||||
|
const searchStatus = searchParams.status || status;
|
||||||
|
if (searchStatus && searchStatus !== 'all') {
|
||||||
|
filteredDevices = filteredDevices.filter(device => device.status === searchStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 类型筛选
|
||||||
|
const searchType = searchParams.type || type;
|
||||||
|
if (searchType && searchType !== 'all') {
|
||||||
|
filteredDevices = filteredDevices.filter(device => device.type === searchType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关键词搜索
|
||||||
|
const searchKeyword = searchParams.keyword || keyword;
|
||||||
|
if (searchKeyword && searchKeyword.trim()) {
|
||||||
|
filteredDevices = searchDevices(filteredDevices, searchKeyword);
|
||||||
|
}
|
||||||
|
|
||||||
|
setDevices(filteredDevices);
|
||||||
|
setPagination(prev => ({ ...prev, current: page, pageSize, total: filteredDevices.length }));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('获取设备列表失败');
|
message.error('获取设备列表失败');
|
||||||
console.error('获取设备列表失败:', error);
|
console.error('获取设备列表失败:', error);
|
||||||
@@ -225,6 +254,41 @@ function DeviceManagement() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 全字段搜索函数
|
||||||
|
const searchDevices = (devices, keyword) => {
|
||||||
|
if (!keyword || !keyword.trim()) {
|
||||||
|
return devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchTerm = keyword.toLowerCase().trim();
|
||||||
|
|
||||||
|
return devices.filter(device => {
|
||||||
|
// 搜索设备对象的所有属性值(包括自定义字段)
|
||||||
|
const allFieldValues = Object.values(device).filter(value => {
|
||||||
|
if (value === null || value === undefined) return false;
|
||||||
|
if (typeof value === 'object') return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 搜索嵌套对象中的值(Rack、Room等)
|
||||||
|
if (device.Rack?.name) allFieldValues.push(device.Rack.name);
|
||||||
|
if (device.Rack?.Room?.name) allFieldValues.push(device.Rack.Room.name);
|
||||||
|
|
||||||
|
// 搜索自定义字段的值
|
||||||
|
if (device.customFields && typeof device.customFields === 'object') {
|
||||||
|
Object.values(device.customFields).forEach(value => {
|
||||||
|
if (value !== null && value !== undefined && typeof value !== 'object') {
|
||||||
|
allFieldValues.push(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return allFieldValues.some(value => {
|
||||||
|
return String(value).toLowerCase().includes(searchTerm);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// 获取设备字段配置
|
// 获取设备字段配置
|
||||||
const fetchDeviceFields = async () => {
|
const fetchDeviceFields = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -391,7 +455,26 @@ function DeviceManagement() {
|
|||||||
|
|
||||||
// 搜索处理函数
|
// 搜索处理函数
|
||||||
const handleSearch = (values) => {
|
const handleSearch = (values) => {
|
||||||
fetchDevices(1, 10, values);
|
setKeyword(values.keyword || '');
|
||||||
|
setStatus(values.status || 'all');
|
||||||
|
setType(values.type || 'all');
|
||||||
|
|
||||||
|
// 本地全字段搜索
|
||||||
|
const filteredDevices = searchDevices(allDevices, values.keyword || '');
|
||||||
|
|
||||||
|
// 状态筛选
|
||||||
|
let finalDevices = filteredDevices;
|
||||||
|
if (values.status && values.status !== 'all') {
|
||||||
|
finalDevices = finalDevices.filter(device => device.status === values.status);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 类型筛选
|
||||||
|
if (values.type && values.type !== 'all') {
|
||||||
|
finalDevices = finalDevices.filter(device => device.type === values.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
setDevices(finalDevices);
|
||||||
|
setPagination(prev => ({ ...prev, current: 1, total: finalDevices.length }));
|
||||||
};
|
};
|
||||||
|
|
||||||
// 重置筛选条件
|
// 重置筛选条件
|
||||||
@@ -400,7 +483,8 @@ function DeviceManagement() {
|
|||||||
setStatus('all');
|
setStatus('all');
|
||||||
setType('all');
|
setType('all');
|
||||||
searchForm.resetFields();
|
searchForm.resetFields();
|
||||||
fetchDevices(1, pagination.pageSize, { keyword: '', status: 'all', type: 'all' });
|
setDevices(allDevices);
|
||||||
|
setPagination(prev => ({ ...prev, current: 1, total: allDevices.length }));
|
||||||
};
|
};
|
||||||
|
|
||||||
// 表格分页变化处理
|
// 表格分页变化处理
|
||||||
@@ -1300,7 +1384,7 @@ function DeviceManagement() {
|
|||||||
format={() => `${importProgress}%`}
|
format={() => `${importProgress}%`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : importResult && importResult.statistics && (
|
) : importResult?.statistics ? (
|
||||||
<div>
|
<div>
|
||||||
<p style={{ marginBottom: '10px', fontWeight: '600' }}>导入完成:</p>
|
<p style={{ marginBottom: '10px', fontWeight: '600' }}>导入完成:</p>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '12px', marginBottom: '16px' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '12px', marginBottom: '16px' }}>
|
||||||
@@ -1361,7 +1445,7 @@ function DeviceManagement() {
|
|||||||
确定
|
确定
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
) : null}
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
|
|||||||
@@ -1746,7 +1746,8 @@ function RackVisualization() {
|
|||||||
}}
|
}}
|
||||||
onMouseEnter={(e) => {
|
onMouseEnter={(e) => {
|
||||||
const isOneU = (device?.height || 1) === 1;
|
const isOneU = (device?.height || 1) === 1;
|
||||||
if (isOneU) {
|
const isFaultStatus = device?.status === 'error' || device?.status === 'fault';
|
||||||
|
if (isOneU && !isFaultStatus) {
|
||||||
e.currentTarget.style.height = '33px';
|
e.currentTarget.style.height = '33px';
|
||||||
e.currentTarget.style.zIndex = '150';
|
e.currentTarget.style.zIndex = '150';
|
||||||
}
|
}
|
||||||
@@ -1764,7 +1765,8 @@ function RackVisualization() {
|
|||||||
}}
|
}}
|
||||||
onMouseLeave={(e) => {
|
onMouseLeave={(e) => {
|
||||||
const isOneU = (device?.height || 1) === 1;
|
const isOneU = (device?.height || 1) === 1;
|
||||||
if (isOneU) {
|
const isFaultStatus = device?.status === 'error' || device?.status === 'fault';
|
||||||
|
if (isOneU && !isFaultStatus) {
|
||||||
const originalHeight = (device?.height || 1) * 25;
|
const originalHeight = (device?.height || 1) * 25;
|
||||||
e.currentTarget.style.height = `${originalHeight}px`;
|
e.currentTarget.style.height = `${originalHeight}px`;
|
||||||
e.currentTarget.style.zIndex = '100';
|
e.currentTarget.style.zIndex = '100';
|
||||||
|
|||||||
Reference in New Issue
Block a user