From e640377609a2c26635fa3cd9b551b8146b1b79ec Mon Sep 17 00:00:00 2001 From: zhang1106 <849185023@qq.com> Date: Thu, 26 Mar 2026 17:05:20 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E7=AB=AF=E5=8F=A3=E7=AE=A1=E7=90=86):=20?= =?UTF-8?q?=E9=87=8D=E6=9E=84=E7=AB=AF=E5=8F=A3=E5=88=9B=E5=BB=BA=E6=A8=A1?= =?UTF-8?q?=E6=80=81=E6=A1=86=E5=B9=B6=E4=BC=98=E5=8C=96UI=E4=BA=A4?= =?UTF-8?q?=E4=BA=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重构端口创建模态框的UI设计,引入分步表单布局,增强用户体验 优化表单字段的视觉呈现,添加图标和状态提示 新增服务器网卡管理功能,方便查看未添加端口的服务器 统一消息提示样式,使用带图标的自定义样式 --- frontend/src/components/PortCreateModal.jsx | 718 ++++++++++++----- frontend/src/pages/PortManagement.jsx | 832 +++++++++++++++----- 2 files changed, 1149 insertions(+), 401 deletions(-) diff --git a/frontend/src/components/PortCreateModal.jsx b/frontend/src/components/PortCreateModal.jsx index 3ee90e6..fbdb7d2 100644 --- a/frontend/src/components/PortCreateModal.jsx +++ b/frontend/src/components/PortCreateModal.jsx @@ -1,4 +1,4 @@ -import React, { useState, useCallback, useEffect, useMemo } from 'react'; +import React, { useState, useCallback, useEffect, useRef } from 'react'; import { Modal, Form, @@ -11,8 +11,17 @@ import { Tooltip, Alert, Tag, + Divider, } from 'antd'; -import { PlusOutlined, InfoCircleOutlined } from '@ant-design/icons'; +import { + PlusOutlined, + InfoCircleOutlined, + ThunderboltOutlined, + LinkOutlined, + TagOutlined, + FileTextOutlined, + CheckCircleOutlined, +} from '@ant-design/icons'; import axios from 'axios'; import { designTokens } from '../config/theme'; import CloseButton from './CloseButton'; @@ -86,13 +95,14 @@ function PortCreateModal({ device, visible, onClose, onSuccess, defaultNicId, ne const [previewPorts, setPreviewPorts] = useState([]); const [showPreview, setShowPreview] = useState(false); const [nicList, setNicList] = useState([]); - const prevVisibleRef = React.useRef(false); + const [activeStep, setActiveStep] = useState(1); + const prevVisibleRef = useRef(false); useEffect(() => { - // 只有当 visible 从 false 变为 true 时才执行初始化 if (visible && !prevVisibleRef.current) { setPreviewPorts([]); setShowPreview(false); + setActiveStep(1); form.resetFields(); if (defaultNicId) { @@ -149,7 +159,7 @@ function PortCreateModal({ device, visible, onClose, onSuccess, defaultNicId, ne status: values.status, description: values.description, }); - message.success('端口创建成功'); + message.success({ content: '端口创建成功', icon: }); } else { const portsData = portNames.map((portName, index) => ({ portId: `PORT-${Date.now()}-${index}`, @@ -164,7 +174,7 @@ function PortCreateModal({ device, visible, onClose, onSuccess, defaultNicId, ne })); await axios.post('/api/device-ports/batch', { ports: portsData }); - message.success(`成功创建 ${portNames.length} 个端口`); + message.success({ content: `成功创建 ${portNames.length} 个端口`, icon: }); } form.resetFields(); @@ -190,215 +200,541 @@ function PortCreateModal({ device, visible, onClose, onSuccess, defaultNicId, ne onClose(); }, [form, onClose]); + const handleValuesChange = (changedValues) => { + if (changedValues.portName) { + handlePortNameChange({ target: { value: changedValues.portName } }); + } + }; + const portCount = previewPorts.length || (form.getFieldValue('portName') && !showPreview ? 1 : 0); + const selectedNic = nicList.find(nic => nic.nicId === form.getFieldValue('nicId')); + + const styles = { + modal: { + borderRadius: '16px', + overflow: 'hidden', + }, + header: { + background: `linear-gradient(135deg, ${designTokens.colors.primary.main} 0%, ${designTokens.colors.primary.dark} 100%)`, + padding: '20px 24px', + margin: '-1px -1px 0 -1px', + borderRadius: '16px 16px 0 0', + }, + headerTitle: { + color: '#fff', + fontSize: '18px', + fontWeight: 600, + display: 'flex', + alignItems: 'center', + gap: '10px', + }, + body: { + padding: '24px', + background: '#fff', + }, + stepContainer: { + display: 'flex', + justifyContent: 'center', + marginBottom: '24px', + }, + step: { + display: 'flex', + alignItems: 'center', + gap: '8px', + padding: '8px 20px', + borderRadius: '20px', + fontSize: '14px', + fontWeight: 500, + cursor: 'pointer', + transition: 'all 0.3s ease', + }, + stepActive: { + background: designTokens.colors.primary.bg, + color: designTokens.colors.primary.main, + }, + stepInactive: { + background: designTokens.colors.neutral[100], + color: designTokens.colors.neutral[500], + }, + stepNumber: { + width: '24px', + height: '24px', + borderRadius: '50%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + fontSize: '12px', + fontWeight: 600, + }, + section: { + background: designTokens.colors.neutral[50], + borderRadius: '12px', + padding: '20px', + marginBottom: '16px', + border: `1px solid ${designTokens.colors.neutral[200]}`, + }, + sectionTitle: { + fontSize: '14px', + fontWeight: 600, + color: designTokens.colors.neutral[800], + marginBottom: '16px', + display: 'flex', + alignItems: 'center', + gap: '8px', + }, + fieldLabel: { + fontSize: '13px', + fontWeight: 500, + color: designTokens.colors.neutral[700], + marginBottom: '6px', + }, + input: { + borderRadius: '8px', + borderColor: designTokens.colors.neutral[300], + transition: 'all 0.2s ease', + }, + select: { + borderRadius: '8px', + borderColor: designTokens.colors.neutral[300], + }, + previewCard: { + background: `linear-gradient(135deg, ${designTokens.colors.info.bg} 0%, ${designTokens.colors.primary.bg} 100%)`, + borderRadius: '12px', + padding: '16px', + marginTop: '16px', + border: `1px solid ${designTokens.colors.primary.light}20`, + }, + previewTitle: { + fontSize: '13px', + fontWeight: 600, + color: designTokens.colors.primary.dark, + marginBottom: '12px', + display: 'flex', + alignItems: 'center', + gap: '6px', + }, + previewTags: { + display: 'flex', + flexWrap: 'wrap', + gap: '6px', + }, + previewTag: { + background: '#fff', + border: `1px solid ${designTokens.colors.primary.light}`, + color: designTokens.colors.primary.main, + borderRadius: '6px', + fontSize: '12px', + fontWeight: 500, + }, + footer: { + padding: '16px 24px', + background: designTokens.colors.neutral[50], + borderTop: `1px solid ${designTokens.colors.neutral[200]}`, + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + }, + footerLeft: { + fontSize: '12px', + color: designTokens.colors.neutral[500], + }, + footerRight: { + display: 'flex', + gap: '12px', + }, + button: { + height: '40px', + borderRadius: '8px', + fontWeight: 500, + transition: 'all 0.2s ease', + }, + buttonPrimary: { + background: `linear-gradient(135deg, ${designTokens.colors.primary.main} 0%, ${designTokens.colors.primary.dark} 100%)`, + border: 'none', + boxShadow: `0 4px 12px ${designTokens.colors.primary.main}40`, + }, + nicSelectedCard: { + background: designTokens.colors.success.bg, + border: `1px solid ${designTokens.colors.success.light}`, + borderRadius: '8px', + padding: '12px 16px', + marginTop: '8px', + display: 'flex', + alignItems: 'center', + gap: '10px', + }, + nicSelectedIcon: { + color: designTokens.colors.success.main, + fontSize: '18px', + }, + nicSelectedInfo: { + flex: 1, + }, + nicSelectedName: { + fontSize: '14px', + fontWeight: 500, + color: designTokens.colors.success.dark, + }, + nicSelectedSlot: { + fontSize: '12px', + color: designTokens.colors.success.main, + }, + }; return ( - - 新增端口 - {device?.name || '设备'} - {portCount > 1 && {portCount} 个端口} - - } open={visible} closeIcon={} - onOk={handleSubmit} onCancel={handleCancel} - confirmLoading={loading} - okText={portCount > 1 ? `创建 ${portCount} 个端口` : '创建'} - cancelText="取消" - width={560} + footer={null} + width={600} zIndex={1050} - styles={{ body: { padding: '20px 24px' } }} + style={styles.modal} + styles={{ body: { padding: 0 } }} > -
- - - - - - 所属网卡 - - - - - } - > - - - - - 端口名称 - - - - - } - rules={[ - { required: true, message: '请输入端口名称' }, - { - pattern: /^[\w\/:\-]+$/, - message: '端口名称格式不正确', - }, - { - validator: (_, value) => { - if (!value) return Promise.resolve(); - const ports = generatePortNames(value); - if (ports.length > 1000) { - return Promise.reject(new Error('单次最多创建1000个端口')); - } - return Promise.resolve(); - }, - }, - ]} - > - { - // 确保 Form 值更新 - form.setFieldValue('portName', e.target.value); - handlePortNameChange(e); +
+
+ + 新增端口 + - + > + {device?.name || '设备'} + +
+
- {showPreview && ( - - +
+
+
+
+ 1 +
+ 基本信息 +
+
+
+
+ 2 +
+ 高级配置 +
+
+ + +
+
+ + 端口标识 +
+ + { + if (!value) return Promise.resolve(); + const ports = generatePortNames(value); + if (ports.length > 1000) { + return Promise.reject(new Error('单次最多创建1000个端口')); + } + return Promise.resolve(); + }, + }, + ]} + > + } + style={{ borderRadius: '8px' }} + suffix={ + + + + } + /> + + + {showPreview && ( +
+
+ + 预览:将创建 {previewPorts.length} 个端口 +
+
{previewPorts.map((port, index) => ( - + {port} ))} - {previewPorts.length < - parsePortRange(form.getFieldValue('portName'))?.portCount && ( - ...等 + {parsePortRange(form.getFieldValue('portName'))?.portCount > previewPorts.length && ( + + ...等 {parsePortRange(form.getFieldValue('portName'))?.portCount} 个 + )} - +
+
+ )} +
+ +
+
+ + 网卡关联 +
+ + + + + + {selectedNic && ( +
+
+ +
+
+
{selectedNic.name}
+ {selectedNic.slotNumber && ( +
插槽 {selectedNic.slotNumber}
+ )} +
+ 已选择 +
+ )} + +
+ + 不选择则端口不归属于任何网卡 +
+
+ +
+
+ + 端口属性 +
+ +
+ 端口类型} + rules={[{ required: true, message: '请选择端口类型' }]} + > + + + + 端口速率} + rules={[{ required: true, message: '请选择端口速率' }]} + > + + +
+ +
+ VLAN ID} + > + + + + 状态} + rules={[{ required: true, message: '请选择状态' }]} + > + + +
+
+ +
+
+ + 描述信息 +
+ + +