From e363553e90c85b4415d4f4038e98d9b6823c3cac Mon Sep 17 00:00:00 2001
From: zhang1106 <849185023@qq.com>
Date: Fri, 19 Dec 2025 13:21:39 +0800
Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BA=86=E8=AE=BE=E5=A4=87?=
=?UTF-8?q?=E7=AE=A1=E7=90=86=E7=9A=84=E5=8A=9F=E8=83=BD=EF=BC=8C=E4=B8=80?=
=?UTF-8?q?=E9=94=AE=E5=88=A0=E9=99=A4=EF=BC=8C=E4=BF=AE=E5=A4=8D=E4=BA=86?=
=?UTF-8?q?=E7=BC=96=E8=BE=91=E5=AD=97=E6=AE=B5=E7=9A=84bug,=E4=BF=AE?=
=?UTF-8?q?=E5=A4=8D=E4=BA=86=E5=AF=BC=E5=85=A5=E8=AE=BE=E5=A4=87=E7=9A=84?=
=?UTF-8?q?=E5=8A=9F=E8=83=BD=E7=AD=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
backend/routes/devices.js | 119 +++++----
frontend/src/pages/DeviceManagement.jsx | 306 ++++++++++++++----------
2 files changed, 265 insertions(+), 160 deletions(-)
diff --git a/backend/routes/devices.js b/backend/routes/devices.js
index 08ac8ec..0a58855 100644
--- a/backend/routes/devices.js
+++ b/backend/routes/devices.js
@@ -401,47 +401,86 @@ router.post('/import', async (req, res) => {
});
try {
- // 验证必填字段
- if (!fieldValueMap['设备ID'] || !fieldValueMap['设备名称'] || !fieldValueMap['设备类型'] || !fieldValueMap['型号'] || !fieldValueMap['序列号'] || !fieldValueMap['所在机柜ID'] || !fieldValueMap['位置(U)'] || !fieldValueMap['高度(U)'] || !fieldValueMap['功率(W)'] || !fieldValueMap['状态'] || !fieldValueMap['购买日期'] || !fieldValueMap['保修到期']) {
- throw new Error('缺少必填字段');
- }
-
- // 验证设备ID是否已存在
- const existingDevice = await Device.findByPk(fieldValueMap['设备ID']);
- if (existingDevice) {
- throw new Error('设备ID已存在');
- }
-
- // 验证机柜是否存在,如果不存在则自动创建
- if (!rackIds.includes(fieldValueMap['所在机柜ID'])) {
- // 自动创建机柜
- const newRack = await Rack.create({
- rackId: fieldValueMap['所在机柜ID'],
- name: fieldValueMap['所在机柜ID'], // 使用机柜ID作为默认名称
- height: 42, // 默认42U高度
- maxPower: 10000, // 默认最大功耗10000W
- currentPower: 0,
- status: 'active',
- roomId: defaultRoomId
- });
+ // 验证必填字段
+ const requiredFields = {
+ '设备ID': fieldValueMap['设备ID'],
+ '设备名称': fieldValueMap['设备名称'],
+ '设备类型': fieldValueMap['设备类型'],
+ '型号': fieldValueMap['型号'],
+ '序列号': fieldValueMap['序列号'],
+ '所在机柜ID': fieldValueMap['所在机柜ID'],
+ '位置(U)': fieldValueMap['位置(U)'],
+ '高度(U)': fieldValueMap['高度(U)'],
+ '功率(W)': fieldValueMap['功率(W)'],
+ '状态': fieldValueMap['状态'],
+ '购买日期': fieldValueMap['购买日期'],
+ '保修到期': fieldValueMap['保修到期']
+ };
- // 更新机柜列表和ID列表
- racks.push(newRack);
- rackIds.push(newRack.rackId);
- }
-
- // 验证状态值
- const validStatus = ['running', 'maintenance', 'offline', 'fault'];
- if (!validStatus.includes(fieldValueMap['状态'])) {
- throw new Error('状态值无效,有效值为:running, maintenance, offline, fault');
- }
-
- // 验证日期格式并解析日期
- const purchaseDate = new Date(fieldValueMap['购买日期']);
- const warrantyExpiry = new Date(fieldValueMap['保修到期']);
- if (isNaN(purchaseDate.getTime()) || isNaN(warrantyExpiry.getTime())) {
- throw new Error('日期格式无效,请使用有效的日期格式');
- }
+ const missingFields = Object.keys(requiredFields).filter(fieldName => !requiredFields[fieldName]);
+ if (missingFields.length > 0) {
+ throw new Error(`缺少必填字段:${missingFields.join('、')}`);
+ }
+
+ // 验证序列号是否已存在
+ const existingDevice = await Device.findOne({
+ where: { serialNumber: fieldValueMap['序列号'] }
+ });
+ if (existingDevice) {
+ throw new Error(`序列号已存在:${fieldValueMap['序列号']}`);
+ }
+
+ // 验证机柜是否存在,如果不存在则自动创建
+ if (!rackIds.includes(fieldValueMap['所在机柜ID'])) {
+ // 自动创建机柜
+ const newRack = await Rack.create({
+ rackId: fieldValueMap['所在机柜ID'],
+ name: fieldValueMap['所在机柜ID'], // 使用机柜ID作为默认名称
+ height: 42, // 默认42U高度
+ maxPower: 10000, // 默认最大功耗10000W
+ currentPower: 0,
+ status: 'active',
+ roomId: defaultRoomId
+ });
+
+ // 更新机柜列表和ID列表
+ racks.push(newRack);
+ rackIds.push(newRack.rackId);
+ }
+
+ // 验证状态值
+ const validStatus = ['running', 'maintenance', 'offline', 'fault'];
+ if (!validStatus.includes(fieldValueMap['状态'])) {
+ throw new Error(`状态值无效,有效值为:${validStatus.join('、')},当前值:${fieldValueMap['状态']}`);
+ }
+
+ // 验证数字字段
+ const numericFields = {
+ '位置(U)': fieldValueMap['位置(U)'],
+ '高度(U)': fieldValueMap['高度(U)'],
+ '功率(W)': fieldValueMap['功率(W)']
+ };
+
+ for (const [fieldName, value] of Object.entries(numericFields)) {
+ if (isNaN(Number(value))) {
+ throw new Error(`${fieldName}必须是数字,当前值:${value}`);
+ }
+ }
+
+ // 验证日期格式并解析日期
+ const purchaseDate = new Date(fieldValueMap['购买日期']);
+ const warrantyExpiry = new Date(fieldValueMap['保修到期']);
+ if (isNaN(purchaseDate.getTime())) {
+ throw new Error(`购买日期格式无效:${fieldValueMap['购买日期']},请使用YYYY-MM-DD格式`);
+ }
+ if (isNaN(warrantyExpiry.getTime())) {
+ throw new Error(`保修到期日期格式无效:${fieldValueMap['保修到期']},请使用YYYY-MM-DD格式`);
+ }
+
+ // 验证日期逻辑
+ if (warrantyExpiry <= purchaseDate) {
+ throw new Error(`保修到期日期必须晚于购买日期:购买日期${fieldValueMap['购买日期']},保修到期${fieldValueMap['保修到期']}`);
+ }
// 构建设备数据
const deviceData = {
diff --git a/frontend/src/pages/DeviceManagement.jsx b/frontend/src/pages/DeviceManagement.jsx
index 9072131..6ac08ed 100644
--- a/frontend/src/pages/DeviceManagement.jsx
+++ b/frontend/src/pages/DeviceManagement.jsx
@@ -28,9 +28,7 @@ function DeviceManagement() {
showSizeChanger: true,
showTotal: (total) => `共 ${total} 条记录`
});
- // 自定义字段状态
- const [customFieldName, setCustomFieldName] = useState('');
- const [customFieldValue, setCustomFieldValue] = useState('');
+
// 设备字段配置
const [deviceFields, setDeviceFields] = useState([]);
const [loadingFields, setLoadingFields] = useState(true);
@@ -41,6 +39,10 @@ function DeviceManagement() {
const [importResult, setImportResult] = useState(null);
const [selectedDevices, setSelectedDevices] = useState([]);
const [selectAll, setSelectAll] = useState(false);
+ // 自定义字段状态
+ const [customFieldName, setCustomFieldName] = useState('');
+ const [customFieldValue, setCustomFieldValue] = useState('');
+
// 字段配置模态框
const [fieldConfigModalVisible, setFieldConfigModalVisible] = useState(false);
@@ -149,14 +151,47 @@ function DeviceManagement() {
const deviceData = { ...device };
if (deviceData.purchaseDate) deviceData.purchaseDate = dayjs(deviceData.purchaseDate);
if (deviceData.warrantyExpiry) deviceData.warrantyExpiry = dayjs(deviceData.warrantyExpiry);
- if (!deviceData.customFields) deviceData.customFields = {};
- // 将customFields中的字段值合并到deviceData中,以便动态表单控件能正确显示
- Object.entries(deviceData.customFields).forEach(([fieldName, value]) => {
- deviceData[fieldName] = value;
+ // 定义设备模型的固定字段
+ const fixedFields = [
+ 'deviceId', 'name', 'type', 'model', 'serialNumber', 'rackId',
+ 'position', 'height', 'powerConsumption', 'status', 'purchaseDate',
+ 'warrantyExpiry', 'ipAddress', 'description'
+ ];
+
+ // 定义需要排除的系统字段
+ const systemFields = ['createdAt', 'updatedAt', 'Rack', 'Room', 'customFields'];
+
+ // 创建一个干净的设备数据对象
+ const cleanDeviceData = {};
+
+ // 复制固定字段
+ fixedFields.forEach(field => {
+ if (deviceData[field] !== undefined) {
+ cleanDeviceData[field] = deviceData[field];
+ }
});
- form.setFieldsValue(deviceData);
+ // 将自定义字段直接添加到表单数据中(不在customFields对象内)
+ Object.entries(deviceData).forEach(([key, value]) => {
+ // 排除固定字段、系统字段和非基本类型的值
+ if (!fixedFields.includes(key) &&
+ !systemFields.includes(key) &&
+ key !== 'deviceId' &&
+ typeof value !== 'object' &&
+ value !== null) {
+ cleanDeviceData[key] = value;
+ }
+ });
+
+ // 如果有原始customFields对象,将其字段也添加到表单数据中
+ if (deviceData.customFields && typeof deviceData.customFields === 'object') {
+ Object.entries(deviceData.customFields).forEach(([key, value]) => {
+ cleanDeviceData[key] = value;
+ });
+ }
+
+ form.setFieldsValue(cleanDeviceData);
} else {
form.resetFields();
}
@@ -179,31 +214,22 @@ function DeviceManagement() {
'warrantyExpiry', 'ipAddress', 'description'
];
- // 分离固定字段和动态字段
- const fixedFieldValues = {};
- const dynamicFieldValues = {};
+ // 构建最终的设备数据,包含固定字段和自定义字段
+ const deviceData = {
+ ...values,
+ purchaseDate: values.purchaseDate ? values.purchaseDate.format('YYYY-MM-DD') : null,
+ warrantyExpiry: values.warrantyExpiry ? values.warrantyExpiry.format('YYYY-MM-DD') : null,
+ customFields: {} // 用于存储自定义字段
+ };
- Object.entries(values).forEach(([key, value]) => {
- if (fixedFields.includes(key)) {
- fixedFieldValues[key] = value;
- } else if (key !== 'customFields') {
- dynamicFieldValues[key] = value;
+ // 分离固定字段和自定义字段
+ Object.keys(deviceData).forEach(key => {
+ if (!fixedFields.includes(key) && key !== 'customFields') {
+ // 将非固定字段移动到customFields对象中
+ deviceData.customFields[key] = deviceData[key];
+ delete deviceData[key];
}
});
-
- // 合并原有的自定义字段和新的动态字段
- const allCustomFields = {
- ...(values.customFields || {}),
- ...dynamicFieldValues
- };
-
- // 构建最终的设备数据
- const deviceData = {
- ...fixedFieldValues,
- purchaseDate: fixedFieldValues.purchaseDate ? fixedFieldValues.purchaseDate.format('YYYY-MM-DD') : null,
- warrantyExpiry: fixedFieldValues.warrantyExpiry ? fixedFieldValues.warrantyExpiry.format('YYYY-MM-DD') : null,
- customFields: allCustomFields
- };
if (editingDevice) {
// 更新设备
@@ -397,7 +423,14 @@ function DeviceManagement() {
setImportProgress(100);
setImportResult(response.data);
setIsImporting(false);
- message.success('导入完成');
+
+ // 根据导入结果显示不同的消息
+ const { success, failed } = response.data.statistics;
+ if (failed > 0) {
+ message.warning(`导入完成,但有 ${failed} 条记录导入失败`);
+ } else {
+ message.success('所有记录导入成功');
+ }
// 重新加载设备列表
setTimeout(() => {
@@ -461,7 +494,18 @@ function DeviceManagement() {
title: field.displayName,
dataIndex: field.fieldName,
key: field.fieldName,
- render: (type) => typeMap[type],
+ render: (type) => {
+ if (Array.isArray(type)) {
+ return (
+
请上传CSV格式的设备数据文件
支持的编码格式:GBK
@@ -888,24 +928,50 @@ function DeviceManagement() {正在导入数据...
- {importResult && ( + {importResult && importResult.statistics && (导入完成:
-总记录数:{importResult.statistics.total}
-成功:{importResult.statistics.success}
-失败:{importResult.statistics.failed}
- {importResult.statistics.errors.length > 0 && ( -总记录数:{importResult.statistics.total || 0}
+成功:{importResult.statistics.success || 0}
+失败:{importResult.statistics.failed || 0}
+ {/* 显示导入失败记录详情表格 */} + {(() => { + // 检查是否有错误记录 + const errors = importResult.statistics?.errors; + const hasErrors = Array.isArray(errors) && errors.length > 0; + + // 即使没有errors字段,如果failed数量大于0,也应该提示用户 + const hasFailedRecords = importResult.statistics?.failed > 0; + + return (hasErrors || hasFailedRecords) && ( +| 行号 | +失败原因 | +
|---|---|
| {item.row || index + 1} | +{item.error || '未知错误'} | +