From b2c3e6c348e01190f415da20df23e3b03fcfd1a5 Mon Sep 17 00:00:00 2001 From: zhang1106 <849185023@qq.com> Date: Thu, 12 Feb 2026 15:10:24 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E7=B3=BB=E7=BB=9F=E8=AE=BE=E7=BD=AE):=20?= =?UTF-8?q?=E9=87=8D=E6=9E=84=E7=B3=BB=E7=BB=9F=E8=AE=BE=E7=BD=AE=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2UI=E5=B9=B6=E4=BC=98=E5=8C=96=E5=93=8D=E5=BA=94?= =?UTF-8?q?=E5=BC=8F=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/SystemSettings.jsx | 778 +++++++++++++++++--------- 1 file changed, 517 insertions(+), 261 deletions(-) diff --git a/frontend/src/pages/SystemSettings.jsx b/frontend/src/pages/SystemSettings.jsx index 826e5da..7cc708c 100644 --- a/frontend/src/pages/SystemSettings.jsx +++ b/frontend/src/pages/SystemSettings.jsx @@ -1,6 +1,5 @@ import React, { useState, useEffect } from 'react'; import { - Tabs, Form, Input, Switch, @@ -19,6 +18,7 @@ import { Typography, Badge, Tooltip, + Avatar, } from 'antd'; import { SettingOutlined, @@ -38,26 +38,41 @@ import { SafetyCertificateOutlined, ReloadOutlined, SaveOutlined, + BellOutlined, + CloudServerOutlined, + UserOutlined, + SecurityScanOutlined, + ApiOutlined, + ThunderboltOutlined, + MenuFoldOutlined, + MenuUnfoldOutlined, } from '@ant-design/icons'; import axios from 'axios'; import { useConfig } from '../context/ConfigContext'; const { Option } = Select; -const { TabPane } = Tabs; -const { Title, Text } = Typography; +const { Title, Text, Paragraph } = Typography; const SystemSettings = () => { const [loading, setLoading] = useState(false); const [saving, setSaving] = useState(false); const [settings, setSettings] = useState({}); - const [activeTab, setActiveTab] = useState('general'); + const [activeMenu, setActiveMenu] = useState('general'); const [systemInfo, setSystemInfo] = useState(null); const [form] = Form.useForm(); + const [drawerVisible, setDrawerVisible] = useState(false); + const [isMobile, setIsMobile] = useState(window.innerWidth < 768); const { reloadConfig } = useConfig(); useEffect(() => { fetchSettings(); fetchSystemInfo(); + + const handleResize = () => { + setIsMobile(window.innerWidth < 768); + }; + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); }, []); const fetchSettings = async () => { @@ -99,18 +114,13 @@ const SystemSettings = () => { await axios.put('/api/system-settings', { settings: updates }); - // 如果修改了前端端口,同步配置并自动重启(仅开发环境) if ('frontend_port' in updates) { try { - // 1. 同步端口到配置文件 await axios.post('/api/system-settings/frontend/port/sync'); - - // 2. 检查是否为生产环境 const statusRes = await axios.get('/api/system-settings/frontend/status'); const isProduction = statusRes.data.isProduction; if (isProduction) { - // 生产环境:只显示提示,不自动重启 Modal.info({ title: '前端端口已修改(生产环境)', content: ( @@ -123,10 +133,7 @@ const SystemSettings = () => { message="请手动更新服务器配置" description={
-

生产环境由 Nginx 或其他服务器托管,请手动更新服务器配置文件:

-

1. 更新 Nginx 配置中的监听端口

-

2. 重启 Nginx 服务

-

3. 更新 vite.config.js 中的端口配置(用于下次构建)

+

生产环境由 Nginx 或其他服务器托管,请手动更新服务器配置文件

} type="info" @@ -138,7 +145,6 @@ const SystemSettings = () => { okText: '知道了', }); } else { - // 开发环境:显示确认对话框并自动重启 Modal.confirm({ title: '前端端口已修改', icon: , @@ -149,13 +155,6 @@ const SystemSettings = () => { {updates.frontend_port}

是否立即重启前端服务以应用新端口?

- ), okText: '立即重启', @@ -163,20 +162,12 @@ const SystemSettings = () => { onOk: async () => { const newPort = updates.frontend_port; const newUrl = `http://localhost:${newPort}`; - - // 先显示跳转提示,再调用重启API - // 因为重启API会导致当前服务中断 Modal.success({ title: '正在重启前端服务', content: (
-

- 前端服务正在重启,新端口:{newPort} -

+

前端服务正在重启,新端口:{newPort}

页面将在3秒后自动跳转到新地址...

-

- 如果跳转失败,请手动访问:{newUrl} -

), okText: '立即跳转', @@ -186,23 +177,13 @@ const SystemSettings = () => { window.location.href = newUrl; }, }); - - // 延迟调用重启API,让用户看到提示 setTimeout(async () => { try { - // 调用重启API(这个请求可能会因为服务重启而失败) - await axios.post( - '/api/system-settings/frontend/restart', - {}, - { timeout: 5000 } - ); + await axios.post('/api/system-settings/frontend/restart', {}, { timeout: 5000 }); } catch (error) { - // 忽略错误,因为服务重启会导致连接中断 console.log('重启请求已发送,服务正在重启...'); } }, 1000); - - // 延迟3秒后跳转 setTimeout(() => { window.location.href = newUrl; }, 3000); @@ -218,7 +199,6 @@ const SystemSettings = () => { } fetchSettings(); - // 重新加载全局配置,使更改立即生效 await reloadConfig(); } catch (error) { message.error('保存设置失败'); @@ -237,7 +217,6 @@ const SystemSettings = () => { await axios.post(`/api/system-settings/reset/${key}`); message.success('重置成功'); fetchSettings(); - // 重新加载全局配置,使更改立即生效 await reloadConfig(); } catch (error) { message.error('重置失败'); @@ -379,22 +358,24 @@ const SystemSettings = () => { return optionsMap[key] || []; }; - // 设置项分组配置 const settingGroups = { general: [ { title: '站点信息', icon: , + description: '配置系统基本信息和站点标识', keys: ['site_name', 'site_logo'], }, { title: '时间设置', icon: , + description: '设置系统时区和日期显示格式', keys: ['timezone', 'date_format'], }, { title: '安全设置', icon: , + description: '配置登录安全策略和维护模式', keys: ['idle_timeout', 'max_login_attempts', 'maintenance_mode'], }, ], @@ -402,73 +383,287 @@ const SystemSettings = () => { { title: '主题颜色', icon: , + description: '自定义系统主题配色方案', keys: ['primary_color', 'secondary_color'], }, { title: '界面布局', icon: , + description: '调整界面显示密度和布局方式', keys: ['compact_mode', 'sidebar_collapsed', 'table_row_height'], }, { title: '动画效果', - icon: , + icon: , + description: '控制界面动画和过渡效果', keys: ['animation_enabled'], }, ], }; + const menuItems = [ + { + key: 'general', + icon: , + label: '全局配置', + description: '站点信息、时间、安全设置', + }, + { + key: 'appearance', + icon: , + label: '外观设置', + description: '主题颜色、界面布局', + }, + { + key: 'about', + icon: , + label: '关于系统', + description: '系统信息、版本详情', + }, + ]; + const renderSettingGroup = (group) => { + // 根据分组决定布局 + const isSiteInfo = group.title === '站点信息'; + const isTimeSetting = group.title === '时间设置'; + const isSecurity = group.title === '安全设置'; + return ( - {group.icon} - {group.title} - - } - style={{ marginBottom: 16, borderRadius: 8 }} - size="small" + style={{ + marginBottom: 24, + borderRadius: 12, + border: '1px solid #e8e8e8', + boxShadow: '0 2px 8px rgba(0,0,0,0.04)', + }} + bodyStyle={{ padding: '24px' }} > - {group.keys.map(key => { - const settingData = { ...settings[key] }; - // 确保特定字段使用正确的类型 - if (key === 'timezone' || key === 'date_format') { - settingData.type = 'select'; - } - if (key === 'primary_color' || key === 'secondary_color') { - settingData.type = 'select'; - } - return renderFormItem(key, settingData); - })} + {/* 居中的标题区 */} +
+
+ {group.icon} +
+
+ {group.title} +
+
+ {group.description} +
+
+ + + + {/* 表单内容区 */} + {isSiteInfo && ( + + + 网站名称} + name="site_name" + style={{ marginBottom: 0 }} + > + + + + + 网站Logo URL} + name="site_logo" + style={{ marginBottom: 0 }} + > + + 预览 + + } + /> + + + + )} + + {isTimeSetting && ( + + + 时区设置} + name="timezone" + style={{ marginBottom: 0 }} + > + + + + + 日期格式} + name="date_format" + style={{ marginBottom: 0 }} + > + + + + + )} + + {isSecurity && ( + + + 用户空闲超时时间} + name="idle_timeout" + style={{ marginBottom: 0 }} + > + 分钟} + /> + + + + 最大登录尝试次数} + name="max_login_attempts" + style={{ marginBottom: 0 }} + > + 次} + /> + + + + 维护模式} + name="maintenance_mode" + valuePropName="checked" + style={{ marginBottom: 0 }} + > + + + + + + + )} + + {!isSiteInfo && !isTimeSetting && !isSecurity && ( + + {group.keys.map(key => { + const settingData = { ...settings[key] }; + if (key === 'timezone' || key === 'date_format') { + settingData.type = 'select'; + } + if (key === 'primary_color' || key === 'secondary_color') { + settingData.type = 'select'; + } + return ( + + {renderFormItem(key, settingData)} + + ); + })} + + )}
); }; + // 底部固定操作栏 + const renderFixedFooter = (onSubmit) => ( +
+ + +
+ ); + const renderGeneralSettings = () => { return (
- {settingGroups.general.map(renderSettingGroup)} - - - - - - +
+ {settingGroups.general.map(group => renderSettingGroup(group))} +
+ {renderFixedFooter()}
); }; @@ -476,280 +671,341 @@ const SystemSettings = () => { const renderAppearanceSettings = () => { return (
- - {settingGroups.appearance.map(renderSettingGroup)} - - - - - - +
+ + {settingGroups.appearance.map(group => renderSettingGroup(group))} +
+ {renderFixedFooter()} ); }; const renderAboutPage = () => { - const aboutKeys = [ - 'app_version', - 'company_name', - 'contact_email', - 'contact_phone', - 'company_address', - 'system_description', - 'privacy_policy', - 'terms_of_service', - ]; - return (
- {/* 系统概览卡片 */} - - + +
- - + <Col xs={24} sm={18} md={19}> + <Title level={3} style={{ color: '#fff', margin: 0, marginBottom: 6, fontSize: '22px' }}> 机柜管理系统 - - 版本 {settings.app_version?.value || '1.0.0'} - | - + + 版本 {settings.app_version?.value || '1.0.0'} + | + 运行正常} /> + + 专业的数据中心设备管理平台,提供机房、机柜、设备的全生命周期管理 +
- {/* 统计信息卡片 */} {systemInfo && ( - - 系统统计 - - } - style={{ marginBottom: 16, borderRadius: 8 }} - size="small" + style={{ marginBottom: 16, borderRadius: 12, border: '1px solid #e8e8e8' }} + bodyStyle={{ padding: '20px' }} > - - - -
+
+ + } size={32} /> + 系统统计 + +
+ + +
+
{systemInfo.statistics?.devices || 0}
-
设备总数
- + 设备总数 +
- - -
+ +
+
{systemInfo.statistics?.racks || 0}
-
机柜总数
- + 机柜总数 +
- - -
+ +
+
{systemInfo.statistics?.rooms || 0}
-
机房总数
- + 机房总数 +
- - -
+ +
+
{systemInfo.statistics?.users || 0}
-
用户总数
- + 用户总数 +
- + - + - {systemInfo.system?.nodeVersion} + {systemInfo.system?.nodeVersion} - {systemInfo.system?.platform} ({systemInfo.system?.arch}) + {systemInfo.system?.platform} ({systemInfo.system?.arch}) - {systemInfo.system?.pid} + {systemInfo.system?.pid} - {systemInfo.system?.uptime + {systemInfo.system?.uptime ? `${Math.floor(systemInfo.system.uptime / 3600)}小时${Math.floor((systemInfo.system.uptime % 3600) / 60)}分钟` - : '-'} + : '-'} - {systemInfo.system?.memoryUsage + {systemInfo.system?.memoryUsage ? `${(systemInfo.system.memoryUsage.heapUsed / 1024 / 1024).toFixed(2)} MB` - : '-'} + : '-'} - {systemInfo.timestamp + {systemInfo.timestamp ? new Date(systemInfo.timestamp).toLocaleString('zh-CN') - : '-'} + : '-'} )} - {/* 公司信息卡片 */} - - 公司信息 - - } - style={{ marginBottom: 16, borderRadius: 8 }} - size="small" + style={{ marginBottom: 16, borderRadius: 12, border: '1px solid #e8e8e8' }} + bodyStyle={{ padding: '20px' }} > -
- - +
+ + } size={32} /> + 公司信息 + +
+ + + - } placeholder="请输入公司名称" /> + } placeholder="请输入公司名称" /> - + - } placeholder="请输入联系邮箱" /> + } placeholder="请输入联系邮箱" /> - + - } placeholder="请输入联系电话" /> + } placeholder="请输入联系电话" /> - + - } placeholder="请输入公司地址" /> + } placeholder="请输入公司地址" /> - + - - - - - - + + + + +
); }; - const tabItems = [ - { - key: 'general', - label: ( - - 全局配置 - - ), - children: renderGeneralSettings(), - }, - { - key: 'appearance', - label: ( - - 外观设置 - - ), - children: renderAppearanceSettings(), - }, - { - key: 'about', - label: ( - - 关于系统 - - ), - children: renderAboutPage(), - }, - ]; + const renderContent = () => { + switch (activeMenu) { + case 'general': + return renderGeneralSettings(); + case 'appearance': + return renderAppearanceSettings(); + case 'about': + return renderAboutPage(); + default: + return renderGeneralSettings(); + } + }; + + // 顶部导航栏菜单 + const renderTopNav = () => ( +
+ {menuItems.map(item => ( +
setActiveMenu(item.key)} + style={{ + display: 'flex', + alignItems: 'center', + padding: '8px 16px', + borderRadius: 20, + cursor: 'pointer', + transition: 'all 0.3s ease', + backgroundColor: activeMenu === item.key ? 'rgba(255,255,255,0.25)' : 'transparent', + color: '#fff', + whiteSpace: 'nowrap', + fontSize: 14, + fontWeight: activeMenu === item.key ? 600 : 400, + }} + onMouseEnter={(e) => { + if (activeMenu !== item.key) { + e.currentTarget.style.backgroundColor = 'rgba(255,255,255,0.1)'; + } + }} + onMouseLeave={(e) => { + if (activeMenu !== item.key) { + e.currentTarget.style.backgroundColor = 'transparent'; + } + }} + > + {item.icon} + {item.label} +
+ ))} +
+ ); + + // 移动端下拉菜单 + const renderMobileNav = () => ( + + ); return ( -
- - - 系统设置 +
+ {/* 顶部固定导航栏 */} +
+
+ + + 系统设置 - } - style={{ borderRadius: 12 }} - bodyStyle={{ padding: 0 }} - > - - + + {/* 桌面端导航 */} + {!isMobile && renderTopNav()} + + {/* 移动端导航 */} + {isMobile && renderMobileNav()} +
+
+ + {/* 主内容区 - 平铺填充 */} +
+
+ {renderContent()} +
+
); };