feat: 添加设备位置冲突检查功能并优化操作日志

feat(api): 在设备API中添加checkPosition接口用于检查U位冲突
feat(frontend): 在设备表单和空闲设备管理中实现U位冲突检查
refactor(backend): 重构操作日志功能,添加设备描述生成和元数据构建工具
fix(backend): 修复批量导入机柜时的ID验证规则
feat(backend): 为机柜导入添加创建和跳过机柜的详细返回信息
fix(backend): 修复设备删除时未检查关联接线的问题
feat(backend): 添加机柜导入模板生成脚本
perf(frontend): 优化机柜管理页面的导入结果展示
fix(frontend): 修复设备端口删除时的关联接线检查
This commit is contained in:
zhang1106
2026-03-26 14:35:08 +08:00
parent 0b7732e427
commit d9c46245df
14 changed files with 2152 additions and 384 deletions
+104 -9
View File
@@ -417,7 +417,8 @@ function RackManagement() {
message.success('机柜删除成功');
fetchRacks();
} catch (error) {
message.error('机柜删除失败');
const errorMsg = error.response?.data?.error || '机柜删除失败';
message.error(errorMsg);
console.error('机柜删除失败:', error);
}
},
@@ -438,8 +439,16 @@ function RackManagement() {
cancelText: '取消',
onOk: async () => {
try {
await Promise.all(selectedRackIds.map(id => axios.delete(`/api/racks/${id}`)));
message.success(`成功删除 ${selectedRackIds.length} 个机柜`);
const results = await Promise.allSettled(selectedRackIds.map(id => axios.delete(`/api/racks/${id}`)));
const succeeded = results.filter(r => r.status === 'fulfilled').length;
const failed = results.filter(r => r.status === 'rejected');
if (succeeded > 0) {
message.success(`成功删除 ${succeeded} 个机柜`);
}
if (failed.length > 0) {
const firstError = failed[0].reason.response?.data?.error || '部分机柜删除失败';
message.error(`${firstError}${failed.length} 个失败)`);
}
setSelectedRackIds([]);
fetchRacks();
} catch (error) {
@@ -519,13 +528,33 @@ function RackManagement() {
setImportProgress(100);
setImportPhase('导入完成');
setImportResult(response.data);
const resData = response.data;
const importResult = {
total: resData.total || 0,
successCount: resData.imported || 0,
duplicates: resData.duplicates || 0,
failedCount: 0,
errors: [],
createdRacks: resData.createdRacks || [],
skippedRacks: resData.skippedRacks || []
};
if (resData.details && Array.isArray(resData.details)) {
importResult.failedCount = resData.details.length;
importResult.errors = resData.details.map(item => ({
row: item.row,
error: item.errors.join('')
}));
}
setImportResult(importResult);
setIsImporting(false);
if (response.data.success) {
if (resData.success) {
message.success('机柜导入成功');
} else {
message.warning(response.data.message || '部分记录导入失败');
message.warning(resData.message || '部分记录导入失败');
}
fetchRacks();
@@ -533,8 +562,24 @@ function RackManagement() {
} catch (error) {
setIsImporting(false);
setImportProgress(0);
message.error('机柜导入失败');
console.error('机柜导入失败:', error);
const errorData = error.response?.data;
if (errorData?.details && Array.isArray(errorData.details)) {
const importResult = {
total: errorData.total || 0,
successCount: 0,
failedCount: errorData.details.length,
errors: errorData.details.map(item => ({
row: item.row,
error: item.errors.join('')
}))
};
setImportResult(importResult);
setImportPhase('导入失败');
} else {
message.error(errorData?.error || errorData?.message || '机柜导入失败');
console.error('机柜导入失败:', error);
}
return false;
}
},
@@ -1380,10 +1425,60 @@ function RackManagement() {
导入失败{importResult.failedCount}
</p>
)}
{importResult.duplicates > 0 && (
<p style={{ margin: '8px 0', color: '#faad14' }}>
跳过已存在{importResult.duplicates}
</p>
)}
</div>
{importResult.createdRacks && importResult.createdRacks.length > 0 && (
<div style={{ marginBottom: '20px' }}>
<p style={{ fontWeight: '600', marginBottom: '8px', color: '#52c41a' }}>
本次新增机柜{importResult.createdRacks.length}
</p>
<div style={{
maxHeight: '150px',
overflow: 'auto',
background: '#f6ffed',
border: '1px solid #b7eb8f',
borderRadius: '8px',
padding: '12px'
}}>
{importResult.createdRacks.map((rack, idx) => (
<div key={idx} style={{ fontSize: '13px', marginBottom: '4px' }}>
{rack.rackId} - {rack.name}
</div>
))}
</div>
</div>
)}
{importResult.skippedRacks && importResult.skippedRacks.length > 0 && (
<div style={{ marginBottom: '20px' }}>
<p style={{ fontWeight: '600', marginBottom: '8px', color: '#faad14' }}>
已跳过机柜{importResult.skippedRacks.length}
</p>
<div style={{
maxHeight: '150px',
overflow: 'auto',
background: '#fffbe6',
border: '1px solid #ffe58f',
borderRadius: '8px',
padding: '12px'
}}>
{importResult.skippedRacks.map((rack, idx) => (
<div key={idx} style={{ fontSize: '13px', marginBottom: '4px' }}>
{rack.rackId} - {rack.name}
</div>
))}
</div>
</div>
)}
{importResult.errors && importResult.errors.length > 0 && (
<div style={{ marginBottom: '20px' }}>
<p style={{ fontWeight: '600', marginBottom: '8px' }}>错误详情</p>
<p style={{ fontWeight: '600', marginBottom: '8px', color: '#ff4d4f' }}>错误详情</p>
{importResult.errors.slice(0, 5).map((err, idx) => (
<div
key={idx}