feat(设备字段): 添加系统字段保护机制
为设备字段添加isSystem属性标记系统字段,防止误删核心字段 后端添加字段删除前的系统字段检查 前端在管理界面标记并禁用系统字段的删除操作 更新导入模板生成逻辑以适配动态字段配置 优化设备导入功能,支持字段名称变更
This commit is contained in:
+35
-16
@@ -2,6 +2,7 @@ const DeviceField = require('./models/DeviceField');
|
||||
const { sequelize } = require('./db');
|
||||
|
||||
// 默认设备字段配置
|
||||
// 系统字段(isSystem: true)是核心字段,不可删除
|
||||
const defaultDeviceFields = [
|
||||
{
|
||||
fieldName: 'deviceId',
|
||||
@@ -9,7 +10,8 @@ const defaultDeviceFields = [
|
||||
fieldType: 'string',
|
||||
required: true,
|
||||
order: 1,
|
||||
visible: true
|
||||
visible: true,
|
||||
isSystem: true
|
||||
},
|
||||
{
|
||||
fieldName: 'name',
|
||||
@@ -17,7 +19,8 @@ const defaultDeviceFields = [
|
||||
fieldType: 'string',
|
||||
required: true,
|
||||
order: 2,
|
||||
visible: true
|
||||
visible: true,
|
||||
isSystem: true
|
||||
},
|
||||
{
|
||||
fieldName: 'type',
|
||||
@@ -26,6 +29,7 @@ const defaultDeviceFields = [
|
||||
required: true,
|
||||
order: 3,
|
||||
visible: true,
|
||||
isSystem: true,
|
||||
options: [
|
||||
{ value: 'server', label: '服务器' },
|
||||
{ value: 'switch', label: '交换机' },
|
||||
@@ -40,7 +44,8 @@ const defaultDeviceFields = [
|
||||
fieldType: 'string',
|
||||
required: true,
|
||||
order: 4,
|
||||
visible: true
|
||||
visible: true,
|
||||
isSystem: true
|
||||
},
|
||||
{
|
||||
fieldName: 'serialNumber',
|
||||
@@ -48,7 +53,8 @@ const defaultDeviceFields = [
|
||||
fieldType: 'string',
|
||||
required: true,
|
||||
order: 5,
|
||||
visible: true
|
||||
visible: true,
|
||||
isSystem: true
|
||||
},
|
||||
{
|
||||
fieldName: 'rackId',
|
||||
@@ -56,7 +62,8 @@ const defaultDeviceFields = [
|
||||
fieldType: 'select',
|
||||
required: true,
|
||||
order: 6,
|
||||
visible: true
|
||||
visible: true,
|
||||
isSystem: true
|
||||
},
|
||||
{
|
||||
fieldName: 'position',
|
||||
@@ -64,7 +71,8 @@ const defaultDeviceFields = [
|
||||
fieldType: 'number',
|
||||
required: true,
|
||||
order: 7,
|
||||
visible: true
|
||||
visible: true,
|
||||
isSystem: true
|
||||
},
|
||||
{
|
||||
fieldName: 'height',
|
||||
@@ -72,7 +80,8 @@ const defaultDeviceFields = [
|
||||
fieldType: 'number',
|
||||
required: true,
|
||||
order: 8,
|
||||
visible: true
|
||||
visible: true,
|
||||
isSystem: true
|
||||
},
|
||||
{
|
||||
fieldName: 'powerConsumption',
|
||||
@@ -80,7 +89,8 @@ const defaultDeviceFields = [
|
||||
fieldType: 'number',
|
||||
required: true,
|
||||
order: 9,
|
||||
visible: true
|
||||
visible: true,
|
||||
isSystem: true
|
||||
},
|
||||
{
|
||||
fieldName: 'status',
|
||||
@@ -89,6 +99,7 @@ const defaultDeviceFields = [
|
||||
required: true,
|
||||
order: 10,
|
||||
visible: true,
|
||||
isSystem: true,
|
||||
options: [
|
||||
{ value: 'running', label: '运行中' },
|
||||
{ value: 'maintenance', label: '维护中' },
|
||||
@@ -102,7 +113,8 @@ const defaultDeviceFields = [
|
||||
fieldType: 'date',
|
||||
required: true,
|
||||
order: 11,
|
||||
visible: true
|
||||
visible: true,
|
||||
isSystem: true
|
||||
},
|
||||
{
|
||||
fieldName: 'warrantyExpiry',
|
||||
@@ -110,7 +122,8 @@ const defaultDeviceFields = [
|
||||
fieldType: 'date',
|
||||
required: true,
|
||||
order: 12,
|
||||
visible: true
|
||||
visible: true,
|
||||
isSystem: true
|
||||
},
|
||||
{
|
||||
fieldName: 'ipAddress',
|
||||
@@ -118,7 +131,8 @@ const defaultDeviceFields = [
|
||||
fieldType: 'string',
|
||||
required: false,
|
||||
order: 13,
|
||||
visible: true
|
||||
visible: true,
|
||||
isSystem: false
|
||||
},
|
||||
{
|
||||
fieldName: 'description',
|
||||
@@ -126,7 +140,8 @@ const defaultDeviceFields = [
|
||||
fieldType: 'textarea',
|
||||
required: false,
|
||||
order: 14,
|
||||
visible: true
|
||||
visible: true,
|
||||
isSystem: false
|
||||
},
|
||||
{
|
||||
fieldName: 'owner',
|
||||
@@ -134,7 +149,8 @@ const defaultDeviceFields = [
|
||||
fieldType: 'string',
|
||||
required: false,
|
||||
order: 15,
|
||||
visible: true
|
||||
visible: true,
|
||||
isSystem: false
|
||||
},
|
||||
{
|
||||
fieldName: 'department',
|
||||
@@ -142,7 +158,8 @@ const defaultDeviceFields = [
|
||||
fieldType: 'string',
|
||||
required: false,
|
||||
order: 16,
|
||||
visible: true
|
||||
visible: true,
|
||||
isSystem: false
|
||||
},
|
||||
{
|
||||
fieldName: 'assetId',
|
||||
@@ -150,7 +167,8 @@ const defaultDeviceFields = [
|
||||
fieldType: 'string',
|
||||
required: false,
|
||||
order: 17,
|
||||
visible: true
|
||||
visible: true,
|
||||
isSystem: false
|
||||
},
|
||||
{
|
||||
fieldName: 'brand',
|
||||
@@ -158,7 +176,8 @@ const defaultDeviceFields = [
|
||||
fieldType: 'string',
|
||||
required: false,
|
||||
order: 18,
|
||||
visible: true
|
||||
visible: true,
|
||||
isSystem: false
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -40,6 +40,12 @@ const DeviceField = sequelize.define('DeviceField', {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true
|
||||
},
|
||||
isSystem: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
comment: '是否为系统字段,系统字段不可删除'
|
||||
}
|
||||
}, {
|
||||
tableName: 'deviceFields',
|
||||
|
||||
@@ -57,9 +57,22 @@ router.put('/:fieldId', async (req, res) => {
|
||||
// 删除字段配置
|
||||
router.delete('/:fieldId', async (req, res) => {
|
||||
try {
|
||||
// 先查询字段信息
|
||||
const field = await DeviceField.findByPk(req.params.fieldId);
|
||||
|
||||
if (!field) {
|
||||
return res.status(404).json({ error: '字段不存在' });
|
||||
}
|
||||
|
||||
// 检查是否为系统字段
|
||||
if (field.isSystem) {
|
||||
return res.status(403).json({ error: '系统字段不可删除' });
|
||||
}
|
||||
|
||||
const deleted = await DeviceField.destroy({
|
||||
where: { fieldId: req.params.fieldId }
|
||||
});
|
||||
|
||||
if (deleted) {
|
||||
res.status(204).json();
|
||||
} else {
|
||||
|
||||
+246
-160
@@ -115,73 +115,131 @@ router.get('/import-template', async (req, res) => {
|
||||
order: [['order', 'ASC']]
|
||||
});
|
||||
|
||||
// 准备模板数据
|
||||
const templateData = [
|
||||
{
|
||||
'设备ID': 'DEV001',
|
||||
'设备名称': '测试服务器001',
|
||||
'设备类型': 'server',
|
||||
'型号': 'DELL R740',
|
||||
'序列号': 'SN1234567890',
|
||||
'所在机柜ID': 'A01',
|
||||
'位置(U)': '1',
|
||||
'高度(U)': '2',
|
||||
'功率(W)': '500',
|
||||
'状态': 'running',
|
||||
'购买日期': '2023-01-01',
|
||||
'保修到期': '2026-12-31',
|
||||
'IP地址': '192.168.1.100',
|
||||
'描述': '测试用服务器'
|
||||
}
|
||||
];
|
||||
// 字段类型提示映射
|
||||
const fieldTypeHints = {
|
||||
'设备类型': 'server/switch/router/storage/other',
|
||||
'状态': 'running/maintenance/offline/fault'
|
||||
};
|
||||
|
||||
// 设置基础CSV标题(包含格式说明)
|
||||
const baseHeaders = [
|
||||
{ id: '设备ID', title: '设备ID' },
|
||||
{ id: '设备名称', title: '设备名称' },
|
||||
{ id: '设备类型', title: '设备类型(server/switch/router/storage)' },
|
||||
{ id: '型号', title: '型号' },
|
||||
{ id: '序列号', title: '序列号' },
|
||||
{ id: '所在机柜ID', title: '所在机柜ID' },
|
||||
{ id: '位置(U)', title: '位置(U)' },
|
||||
{ id: '高度(U)', title: '高度(U)' },
|
||||
{ id: '功率(W)', title: '功率(W)' },
|
||||
{ id: '状态', title: '状态(running/maintenance/offline/fault)' },
|
||||
{ id: '购买日期', title: '购买日期(YYYY-MM-DD)' },
|
||||
{ id: '保修到期', title: '保修到期(YYYY-MM-DD)' },
|
||||
{ id: 'IP地址', title: 'IP地址(可选)' },
|
||||
{ id: '描述', title: '描述(可选)' }
|
||||
];
|
||||
// 日期字段提示
|
||||
const dateFields = ['购买日期', '保修到期'];
|
||||
|
||||
// 添加自定义字段标题(排除与基础字段重复的字段)
|
||||
const baseFieldTitles = baseHeaders.map(header => header.id);
|
||||
const customHeaders = deviceFields
|
||||
.filter(field => field.visible && !baseFieldTitles.includes(field.displayName))
|
||||
.map(field => ({
|
||||
id: field.displayName,
|
||||
title: `${field.displayName}(${field.required ? '必填' : '可选'})`
|
||||
}));
|
||||
// 根据字段配置动态生成CSV标题
|
||||
const headers = deviceFields
|
||||
.filter(field => field.visible)
|
||||
.map(field => {
|
||||
let title = field.displayName;
|
||||
|
||||
// 添加字段类型提示
|
||||
if (fieldTypeHints[field.displayName]) {
|
||||
title = `${field.displayName}(${fieldTypeHints[field.displayName]})`;
|
||||
} else if (dateFields.includes(field.displayName)) {
|
||||
title = `${field.displayName}(YYYY-MM-DD)`;
|
||||
} else if (field.fieldType === 'select' && field.options && field.options.length > 0) {
|
||||
// 下拉选择字段,显示可选值
|
||||
const optionValues = field.options.map(opt => opt.value).join('/');
|
||||
title = `${field.displayName}(${optionValues})`;
|
||||
} else {
|
||||
// 显示必填/可选标识
|
||||
title = `${field.displayName}(${field.required ? '必填' : '可选'})`;
|
||||
}
|
||||
|
||||
return { id: field.displayName, title };
|
||||
});
|
||||
|
||||
// 准备示例数据(根据字段配置生成)
|
||||
const exampleData = {};
|
||||
deviceFields
|
||||
.filter(field => field.visible)
|
||||
.forEach(field => {
|
||||
switch (field.fieldName) {
|
||||
case 'deviceId':
|
||||
exampleData[field.displayName] = 'DEV001';
|
||||
break;
|
||||
case 'name':
|
||||
exampleData[field.displayName] = '测试服务器001';
|
||||
break;
|
||||
case 'type':
|
||||
exampleData[field.displayName] = 'server';
|
||||
break;
|
||||
case 'model':
|
||||
exampleData[field.displayName] = 'DELL R740';
|
||||
break;
|
||||
case 'serialNumber':
|
||||
exampleData[field.displayName] = 'SN1234567890';
|
||||
break;
|
||||
case 'rackId':
|
||||
exampleData[field.displayName] = 'A01';
|
||||
break;
|
||||
case 'position':
|
||||
exampleData[field.displayName] = '1';
|
||||
break;
|
||||
case 'height':
|
||||
exampleData[field.displayName] = '2';
|
||||
break;
|
||||
case 'powerConsumption':
|
||||
exampleData[field.displayName] = '500';
|
||||
break;
|
||||
case 'status':
|
||||
exampleData[field.displayName] = 'running';
|
||||
break;
|
||||
case 'purchaseDate':
|
||||
exampleData[field.displayName] = '2023-01-01';
|
||||
break;
|
||||
case 'warrantyExpiry':
|
||||
exampleData[field.displayName] = '2026-12-31';
|
||||
break;
|
||||
case 'ipAddress':
|
||||
exampleData[field.displayName] = '192.168.1.100';
|
||||
break;
|
||||
case 'description':
|
||||
exampleData[field.displayName] = '测试用服务器';
|
||||
break;
|
||||
default:
|
||||
// 自定义字段根据类型生成示例值
|
||||
if (field.fieldType === 'number') {
|
||||
exampleData[field.displayName] = '100';
|
||||
} else if (field.fieldType === 'boolean') {
|
||||
exampleData[field.displayName] = 'true';
|
||||
} else if (field.fieldType === 'date') {
|
||||
exampleData[field.displayName] = '2023-01-01';
|
||||
} else if (field.fieldType === 'select' && field.options && field.options.length > 0) {
|
||||
exampleData[field.displayName] = field.options[0].value;
|
||||
} else {
|
||||
exampleData[field.displayName] = `示例${field.displayName}`;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const templateData = [exampleData];
|
||||
|
||||
// 确保temp目录存在
|
||||
const tempDir = path.join(__dirname, '../temp');
|
||||
if (!fs.existsSync(tempDir)) {
|
||||
fs.mkdirSync(tempDir, { recursive: true });
|
||||
}
|
||||
|
||||
const csvWriter = createObjectCsvWriter({
|
||||
path: path.join(__dirname, '../temp/import_template.csv'),
|
||||
header: [...baseHeaders, ...customHeaders]
|
||||
path: path.join(tempDir, 'import_template.csv'),
|
||||
header: headers
|
||||
});
|
||||
|
||||
// 写入CSV文件
|
||||
await csvWriter.writeRecords(templateData);
|
||||
|
||||
// 读取文件并转换为GBK编码
|
||||
const csvContent = fs.readFileSync(path.join(tempDir, 'import_template.csv'), 'utf8');
|
||||
const gbkContent = iconv.encode(csvContent, 'gbk');
|
||||
|
||||
// 设置响应头
|
||||
res.setHeader('Content-Type', 'text/csv; charset=gbk');
|
||||
res.setHeader('Content-Disposition', `attachment; filename*=UTF-8''${encodeURIComponent('设备导入模板.csv')}`);
|
||||
|
||||
// 发送文件
|
||||
const fileStream = fs.createReadStream(path.join(__dirname, '../temp/import_template.csv'));
|
||||
fileStream.pipe(res);
|
||||
// 发送CSV数据
|
||||
res.send(gbkContent);
|
||||
|
||||
// 删除临时文件
|
||||
fileStream.on('end', () => {
|
||||
fs.unlinkSync(path.join(__dirname, '../temp/import_template.csv'));
|
||||
});
|
||||
fs.unlinkSync(path.join(tempDir, 'import_template.csv'));
|
||||
|
||||
} catch (error) {
|
||||
console.error('生成导入模板失败:', error);
|
||||
@@ -392,12 +450,18 @@ router.post('/import', async (req, res) => {
|
||||
// 获取设备字段配置
|
||||
const deviceFields = await DeviceField.findAll();
|
||||
|
||||
// 创建字段映射:displayName -> fieldName
|
||||
// 创建字段映射:displayName -> fieldConfig
|
||||
const fieldMapping = {};
|
||||
deviceFields.forEach(field => {
|
||||
fieldMapping[field.displayName] = field;
|
||||
});
|
||||
|
||||
// 创建字段名到显示名称的映射(用于识别基础字段)
|
||||
const fieldNameToDisplayName = {};
|
||||
deviceFields.forEach(field => {
|
||||
fieldNameToDisplayName[field.fieldName] = field.displayName;
|
||||
});
|
||||
|
||||
// 处理导入数据
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
const row = results[i];
|
||||
@@ -436,125 +500,147 @@ router.post('/import', async (req, res) => {
|
||||
throw new Error(`缺少必填字段:${missingFields.join('、')}`);
|
||||
}
|
||||
|
||||
// 获取动态字段显示名称(支持用户在字段管理中修改后的名称)
|
||||
const getFieldValue = (fieldName) => {
|
||||
const displayName = fieldNameToDisplayName[fieldName];
|
||||
return displayName ? fieldValueMap[displayName] : undefined;
|
||||
};
|
||||
|
||||
// 验证设备类型值
|
||||
const validTypes = ['server', 'switch', 'router', 'storage', 'other'];
|
||||
if (!validTypes.includes(fieldValueMap['设备类型'])) {
|
||||
throw new Error(`设备类型无效,有效值为:${validTypes.join('、')},当前值:${fieldValueMap['设备类型']}`);
|
||||
}
|
||||
|
||||
// 验证设备ID是否已存在
|
||||
const existingDeviceById = await Device.findOne({
|
||||
where: { deviceId: fieldValueMap['设备ID'] }
|
||||
const validTypes = ['server', 'switch', 'router', 'storage', 'other'];
|
||||
const deviceType = getFieldValue('type');
|
||||
if (!validTypes.includes(deviceType)) {
|
||||
throw new Error(`设备类型无效,有效值为:${validTypes.join('、')},当前值:${deviceType}`);
|
||||
}
|
||||
|
||||
// 验证设备ID是否已存在
|
||||
const deviceId = getFieldValue('deviceId');
|
||||
if (!deviceId) {
|
||||
throw new Error('设备ID不能为空');
|
||||
}
|
||||
const existingDeviceById = await Device.findOne({
|
||||
where: { deviceId: deviceId }
|
||||
});
|
||||
if (existingDeviceById) {
|
||||
throw new Error(`设备ID已存在:${deviceId}`);
|
||||
}
|
||||
|
||||
// 验证序列号是否已存在
|
||||
const serialNumber = getFieldValue('serialNumber');
|
||||
if (!serialNumber) {
|
||||
throw new Error('序列号不能为空');
|
||||
}
|
||||
const existingDevice = await Device.findOne({
|
||||
where: { serialNumber: serialNumber }
|
||||
});
|
||||
if (existingDevice) {
|
||||
throw new Error(`序列号已存在:${serialNumber}`);
|
||||
}
|
||||
|
||||
// 验证机柜ID是否为空
|
||||
const rackId = getFieldValue('rackId');
|
||||
if (!rackId || rackId.trim() === '') {
|
||||
throw new Error('所在机柜ID不能为空');
|
||||
}
|
||||
|
||||
// 验证机柜是否存在,如果不存在则自动创建
|
||||
if (!rackIds.includes(rackId)) {
|
||||
// 自动创建机柜
|
||||
const newRack = await Rack.create({
|
||||
rackId: rackId,
|
||||
name: rackId, // 使用机柜ID作为默认名称
|
||||
height: 42, // 默认42U高度
|
||||
maxPower: 10000, // 默认最大功耗10000W
|
||||
currentPower: 0,
|
||||
status: 'active',
|
||||
roomId: defaultRoomId
|
||||
});
|
||||
if (existingDeviceById) {
|
||||
throw new Error(`设备ID已存在:${fieldValueMap['设备ID']}`);
|
||||
}
|
||||
|
||||
// 验证序列号是否已存在
|
||||
const existingDevice = await Device.findOne({
|
||||
where: { serialNumber: fieldValueMap['序列号'] }
|
||||
});
|
||||
if (existingDevice) {
|
||||
throw new Error(`序列号已存在:${fieldValueMap['序列号']}`);
|
||||
}
|
||||
|
||||
// 验证机柜ID是否为空
|
||||
if (!fieldValueMap['所在机柜ID'] || fieldValueMap['所在机柜ID'].trim() === '') {
|
||||
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
|
||||
});
|
||||
|
||||
// 更新机柜列表和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['保修到期']}`);
|
||||
}
|
||||
// 更新机柜列表和ID列表
|
||||
racks.push(newRack);
|
||||
rackIds.push(newRack.rackId);
|
||||
}
|
||||
|
||||
// 验证状态值
|
||||
const validStatus = ['running', 'maintenance', 'offline', 'fault'];
|
||||
const status = getFieldValue('status');
|
||||
if (!validStatus.includes(status)) {
|
||||
throw new Error(`状态值无效,有效值为:${validStatus.join('、')},当前值:${status}`);
|
||||
}
|
||||
|
||||
// 验证数字字段
|
||||
const position = getFieldValue('position');
|
||||
const height = getFieldValue('height');
|
||||
const powerConsumption = getFieldValue('powerConsumption');
|
||||
|
||||
if (position !== undefined && isNaN(Number(position))) {
|
||||
throw new Error(`${fieldNameToDisplayName['position']}必须是数字,当前值:${position}`);
|
||||
}
|
||||
if (height !== undefined && isNaN(Number(height))) {
|
||||
throw new Error(`${fieldNameToDisplayName['height']}必须是数字,当前值:${height}`);
|
||||
}
|
||||
if (powerConsumption !== undefined && isNaN(Number(powerConsumption))) {
|
||||
throw new Error(`${fieldNameToDisplayName['powerConsumption']}必须是数字,当前值:${powerConsumption}`);
|
||||
}
|
||||
|
||||
// 验证日期格式并解析日期
|
||||
const purchaseDateValue = getFieldValue('purchaseDate');
|
||||
const warrantyExpiryValue = getFieldValue('warrantyExpiry');
|
||||
const purchaseDate = purchaseDateValue ? new Date(purchaseDateValue) : null;
|
||||
const warrantyExpiry = warrantyExpiryValue ? new Date(warrantyExpiryValue) : null;
|
||||
|
||||
if (purchaseDateValue && isNaN(purchaseDate.getTime())) {
|
||||
throw new Error(`${fieldNameToDisplayName['purchaseDate']}格式无效:${purchaseDateValue},请使用YYYY-MM-DD格式`);
|
||||
}
|
||||
if (warrantyExpiryValue && isNaN(warrantyExpiry.getTime())) {
|
||||
throw new Error(`${fieldNameToDisplayName['warrantyExpiry']}格式无效:${warrantyExpiryValue},请使用YYYY-MM-DD格式`);
|
||||
}
|
||||
|
||||
// 验证日期逻辑
|
||||
if (purchaseDate && warrantyExpiry && warrantyExpiry <= purchaseDate) {
|
||||
throw new Error(`${fieldNameToDisplayName['warrantyExpiry']}必须晚于${fieldNameToDisplayName['purchaseDate']}:${purchaseDateValue} ~ ${warrantyExpiryValue}`);
|
||||
}
|
||||
|
||||
// 构建设备数据
|
||||
const deviceData = {
|
||||
deviceId: fieldValueMap['设备ID'],
|
||||
name: fieldValueMap['设备名称'],
|
||||
type: fieldValueMap['设备类型'],
|
||||
model: fieldValueMap['型号'],
|
||||
serialNumber: fieldValueMap['序列号'],
|
||||
rackId: fieldValueMap['所在机柜ID'],
|
||||
position: parseInt(fieldValueMap['位置(U)']),
|
||||
height: parseInt(fieldValueMap['高度(U)']),
|
||||
powerConsumption: parseFloat(fieldValueMap['功率(W)']),
|
||||
ipAddress: fieldValueMap['IP地址'] || '',
|
||||
status: fieldValueMap['状态'],
|
||||
deviceId: deviceId,
|
||||
name: getFieldValue('name'),
|
||||
type: deviceType,
|
||||
model: getFieldValue('model'),
|
||||
serialNumber: serialNumber,
|
||||
rackId: rackId,
|
||||
position: position ? parseInt(position) : 0,
|
||||
height: height ? parseInt(height) : 1,
|
||||
powerConsumption: powerConsumption ? parseFloat(powerConsumption) : 0,
|
||||
ipAddress: getFieldValue('ipAddress') || '',
|
||||
status: status,
|
||||
purchaseDate: purchaseDate,
|
||||
warrantyExpiry: warrantyExpiry,
|
||||
description: fieldValueMap['描述'] || ''
|
||||
description: getFieldValue('description') || ''
|
||||
};
|
||||
|
||||
// 处理自定义字段
|
||||
// 处理自定义字段(排除基础字段)
|
||||
const customFields = {};
|
||||
const baseFieldNames = ['deviceId', 'name', 'type', 'model', 'serialNumber', 'rackId', 'position', 'height', 'powerConsumption', 'ipAddress', 'status', 'purchaseDate', 'warrantyExpiry', 'description'];
|
||||
|
||||
Object.entries(row).forEach(([displayName, value]) => {
|
||||
// 跳过基础字段
|
||||
const originalFieldName = extractFieldName(displayName);
|
||||
const baseFields = ['设备ID', '设备名称', '设备类型', '型号', '序列号', '所在机柜ID', '位置(U)', '高度(U)', '功率(W)', '状态', '购买日期', '保修到期', 'IP地址', '描述'];
|
||||
if (!baseFields.includes(originalFieldName)) {
|
||||
// 查找对应的字段配置(同时尝试原始字段名和带格式的字段名)
|
||||
const fieldConfig = fieldMapping[displayName] || fieldMapping[originalFieldName];
|
||||
if (fieldConfig) {
|
||||
// 根据字段类型处理值
|
||||
let processedValue = value;
|
||||
if (fieldConfig.fieldType === 'number') {
|
||||
processedValue = parseFloat(value);
|
||||
} else if (fieldConfig.fieldType === 'boolean') {
|
||||
processedValue = value.toLowerCase() === 'true' || value === '1' || value.toLowerCase() === '是';
|
||||
} else if (fieldConfig.fieldType === 'date') {
|
||||
processedValue = new Date(value);
|
||||
}
|
||||
|
||||
customFields[fieldConfig.fieldName] = processedValue;
|
||||
// 从带格式的字段名中提取原始显示名称
|
||||
const originalDisplayName = extractFieldName(displayName);
|
||||
// 查找对应的字段配置
|
||||
const fieldConfig = fieldMapping[originalDisplayName];
|
||||
|
||||
if (fieldConfig && !baseFieldNames.includes(fieldConfig.fieldName)) {
|
||||
// 这是自定义字段,根据字段类型处理值
|
||||
let processedValue = value;
|
||||
if (fieldConfig.fieldType === 'number') {
|
||||
processedValue = value ? parseFloat(value) : null;
|
||||
} else if (fieldConfig.fieldType === 'boolean') {
|
||||
processedValue = value ? (value.toLowerCase() === 'true' || value === '1' || value.toLowerCase() === '是') : false;
|
||||
} else if (fieldConfig.fieldType === 'date') {
|
||||
processedValue = value ? new Date(value) : null;
|
||||
}
|
||||
|
||||
customFields[fieldConfig.fieldName] = processedValue;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -567,7 +653,7 @@ router.post('/import', async (req, res) => {
|
||||
const device = await Device.create(deviceData);
|
||||
|
||||
// 更新机柜当前功率
|
||||
const rack = await Rack.findByPk(row['所在机柜ID']);
|
||||
const rack = await Rack.findByPk(rackId);
|
||||
if (rack) {
|
||||
await rack.update({
|
||||
currentPower: rack.currentPower + device.powerConsumption
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
const { sequelize } = require('../db');
|
||||
|
||||
async function addIsSystemColumn() {
|
||||
try {
|
||||
console.log('开始添加 isSystem 列到 deviceFields 表...');
|
||||
|
||||
// 检查列是否已存在
|
||||
const tableInfo = await sequelize.query(
|
||||
"PRAGMA table_info(deviceFields)",
|
||||
{ type: sequelize.QueryTypes.SELECT }
|
||||
);
|
||||
|
||||
const hasIsSystemColumn = tableInfo.some(col => col.name === 'isSystem');
|
||||
|
||||
if (hasIsSystemColumn) {
|
||||
console.log('isSystem 列已存在,跳过添加');
|
||||
} else {
|
||||
// 添加 isSystem 列
|
||||
await sequelize.query(
|
||||
"ALTER TABLE deviceFields ADD COLUMN isSystem BOOLEAN DEFAULT 0",
|
||||
{ type: sequelize.QueryTypes.RAW }
|
||||
);
|
||||
console.log('isSystem 列添加成功');
|
||||
}
|
||||
|
||||
// 更新现有数据:将核心字段标记为系统字段
|
||||
const systemFields = [
|
||||
'deviceId', 'name', 'type', 'model', 'serialNumber',
|
||||
'rackId', 'position', 'height', 'powerConsumption',
|
||||
'status', 'purchaseDate', 'warrantyExpiry',
|
||||
'ipAddress', 'description'
|
||||
];
|
||||
|
||||
for (const fieldName of systemFields) {
|
||||
await sequelize.query(
|
||||
`UPDATE deviceFields SET isSystem = 1 WHERE fieldName = '${fieldName}'`,
|
||||
{ type: sequelize.QueryTypes.RAW }
|
||||
);
|
||||
}
|
||||
console.log('系统字段标记完成');
|
||||
|
||||
console.log('数据库迁移完成');
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('迁移失败:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
addIsSystemColumn();
|
||||
@@ -0,0 +1,55 @@
|
||||
const { sequelize } = require('../db');
|
||||
|
||||
async function updateSystemFields() {
|
||||
try {
|
||||
console.log('开始调整系统字段标记...');
|
||||
|
||||
// 核心系统字段(数据库必填字段,不可删除)
|
||||
const coreSystemFields = [
|
||||
'deviceId', 'name', 'type', 'model', 'serialNumber',
|
||||
'rackId', 'position', 'height', 'powerConsumption',
|
||||
'status', 'purchaseDate', 'warrantyExpiry'
|
||||
];
|
||||
|
||||
// 可选字段(非系统字段,可删除)
|
||||
const optionalFields = [
|
||||
'ipAddress', 'description', 'owner', 'department', 'assetId', 'brand'
|
||||
];
|
||||
|
||||
// 先将所有字段设为非系统字段
|
||||
await sequelize.query(
|
||||
`UPDATE deviceFields SET isSystem = 0`,
|
||||
{ type: sequelize.QueryTypes.RAW }
|
||||
);
|
||||
console.log('已重置所有字段为非系统字段');
|
||||
|
||||
// 标记核心系统字段
|
||||
for (const fieldName of coreSystemFields) {
|
||||
await sequelize.query(
|
||||
`UPDATE deviceFields SET isSystem = 1 WHERE fieldName = '${fieldName}'`,
|
||||
{ type: sequelize.QueryTypes.RAW }
|
||||
);
|
||||
console.log(`标记为核心系统字段: ${fieldName}`);
|
||||
}
|
||||
|
||||
// 确保可选字段为非系统字段
|
||||
for (const fieldName of optionalFields) {
|
||||
await sequelize.query(
|
||||
`UPDATE deviceFields SET isSystem = 0 WHERE fieldName = '${fieldName}'`,
|
||||
{ type: sequelize.QueryTypes.RAW }
|
||||
);
|
||||
console.log(`标记为可选字段: ${fieldName}`);
|
||||
}
|
||||
|
||||
console.log('\n系统字段调整完成!');
|
||||
console.log('核心系统字段(不可删除):', coreSystemFields.join(', '));
|
||||
console.log('可选字段(可删除):', optionalFields.join(', '));
|
||||
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('更新失败:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
updateSystemFields();
|
||||
@@ -0,0 +1,32 @@
|
||||
const { sequelize } = require('../db');
|
||||
|
||||
async function updateSystemFields() {
|
||||
try {
|
||||
console.log('开始更新系统字段标记...');
|
||||
|
||||
// 系统字段列表
|
||||
const systemFields = [
|
||||
'deviceId', 'name', 'type', 'model', 'serialNumber',
|
||||
'rackId', 'position', 'height', 'powerConsumption',
|
||||
'status', 'purchaseDate', 'warrantyExpiry',
|
||||
'ipAddress', 'description'
|
||||
];
|
||||
|
||||
// 更新系统字段标记
|
||||
for (const fieldName of systemFields) {
|
||||
const [result] = await sequelize.query(
|
||||
`UPDATE deviceFields SET isSystem = 1 WHERE fieldName = '${fieldName}'`,
|
||||
{ type: sequelize.QueryTypes.RAW }
|
||||
);
|
||||
console.log(`标记系统字段: ${fieldName}`);
|
||||
}
|
||||
|
||||
console.log('系统字段标记更新完成');
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('更新失败:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
updateSystemFields();
|
||||
Reference in New Issue
Block a user