feat(设备字段): 添加系统字段保护机制

为设备字段添加isSystem属性标记系统字段,防止误删核心字段
后端添加字段删除前的系统字段检查
前端在管理界面标记并禁用系统字段的删除操作
更新导入模板生成逻辑以适配动态字段配置
优化设备导入功能,支持字段名称变更
This commit is contained in:
zhang1106
2026-02-04 17:10:58 +08:00
parent b4d8749fff
commit 8ac528356e
9 changed files with 501 additions and 190 deletions
+29 -6
View File
@@ -1,6 +1,6 @@
import React, { useState, useEffect, useMemo } from 'react';
import { Table, Button, Modal, Form, Input, Select, message, Card, Space, InputNumber, Switch, Tag, Statistic } from 'antd';
import { PlusOutlined, EditOutlined, DeleteOutlined, AppstoreOutlined, FontSizeOutlined, NumberOutlined, CheckCircleOutlined, CalendarOutlined, FileTextOutlined } from '@ant-design/icons';
import { Table, Button, Modal, Form, Input, Select, message, Card, Space, InputNumber, Switch, Tag, Statistic, Tooltip } from 'antd';
import { PlusOutlined, EditOutlined, DeleteOutlined, AppstoreOutlined, FontSizeOutlined, NumberOutlined, CheckCircleOutlined, CalendarOutlined, FileTextOutlined, LockOutlined } from '@ant-design/icons';
import axios from 'axios';
const { Option = Select.Option } = Select;
@@ -317,7 +317,16 @@ function DeviceFieldManagement() {
dataIndex: 'fieldName',
key: 'fieldName',
width: 150,
render: (text) => <span style={tableCellStyle}>{text}</span>
render: (text, record) => (
<Space>
<span style={tableCellStyle}>{text}</span>
{record.isSystem && (
<Tooltip title="系统字段,不可删除">
<LockOutlined style={{ color: '#f59e0b', fontSize: '14px' }} />
</Tooltip>
)}
</Space>
)
},
{
title: '显示名称',
@@ -385,9 +394,23 @@ function DeviceFieldManagement() {
<Button type="text" icon={<EditOutlined />} onClick={() => showModal(record)} style={editButtonStyle}>
编辑
</Button>
<Button type="text" danger icon={<DeleteOutlined />} onClick={() => handleDelete(record.fieldId)} style={deleteButtonStyle}>
删除
</Button>
{record.isSystem ? (
<Tooltip title="系统字段不可删除">
<Button
type="text"
danger
icon={<DeleteOutlined />}
disabled
style={{ ...deleteButtonStyle, opacity: 0.3, cursor: 'not-allowed' }}
>
删除
</Button>
</Tooltip>
) : (
<Button type="text" danger icon={<DeleteOutlined />} onClick={() => handleDelete(record.fieldId)} style={deleteButtonStyle}>
删除
</Button>
)}
</Space>
),
},
+35 -8
View File
@@ -1879,13 +1879,40 @@ function DeviceManagement() {
<div style={{ marginBottom: '20px', padding: '16px', background: 'linear-gradient(180deg, #fafafa 0%, #ffffff 100%)', borderRadius: '12px', border: '1px solid #f0f0f0' }}>
<p style={{ fontWeight: '600', marginBottom: '8px', color: '#333' }}>CSV文件格式要求</p>
<ul style={{ paddingLeft: '20px', marginBottom: '10px', color: '#666', fontSize: '13px' }}>
<li>必填字段设备ID设备名称设备类型型号序列号所在机柜ID位置(U)高度(U)功率(W)状态购买日期保修到期</li>
<li>可选字段IP地址描述</li>
<li>设备类型server(服务器)switch(交换机)router(路由器)storage(存储设备)</li>
<li>状态值running(运行中)maintenance(维护中)offline(离线)fault(故障)</li>
<li>日期格式YYYY-MM-DD (例如2023-01-01)</li>
</ul>
<div style={{ maxHeight: '200px', overflowY: 'auto' }}>
{(() => {
// 获取必填字段
const requiredFields = deviceFields.filter(f => f.visible && f.required);
// 获取可选字段
const optionalFields = deviceFields.filter(f => f.visible && !f.required);
return (
<>
{requiredFields.length > 0 && (
<div style={{ marginBottom: '8px' }}>
<span style={{ color: '#d93025', fontWeight: '500' }}>必填字段</span>
<span style={{ color: '#666', fontSize: '13px' }}>
{requiredFields.map(f => f.displayName).join('、')}
</span>
</div>
)}
{optionalFields.length > 0 && (
<div style={{ marginBottom: '8px' }}>
<span style={{ color: '#666', fontWeight: '500' }}>可选字段</span>
<span style={{ color: '#666', fontSize: '13px' }}>
{optionalFields.map(f => f.displayName).join('、')}
</span>
</div>
)}
</>
);
})()}
<ul style={{ paddingLeft: '20px', marginBottom: '10px', color: '#666', fontSize: '13px', marginTop: '12px' }}>
<li>设备类型server(服务器)switch(交换机)router(路由器)storage(存储设备)other(其他)</li>
<li>状态值running(运行中)maintenance(维护中)offline(离线)fault(故障)</li>
<li>日期格式YYYY-MM-DD (例如2023-01-01)</li>
</ul>
</div>
</div>
<div style={{ marginBottom: '20px' }}>
@@ -1894,7 +1921,7 @@ function DeviceManagement() {
下载导入模板
</Button>
</a>
<span style={{ color: '#999', fontSize: '12px', marginLeft: '10px' }}>包含示例数据的CSV模板文件</span>
<span style={{ color: '#999', fontSize: '12px', marginLeft: '10px' }}>包含示例数据的CSV模板文件根据当前字段配置生成</span>
</div>
<Upload