import React, { useState, useEffect } from 'react';
import {
Form,
Input,
Switch,
Select,
Button,
Card,
Space,
message,
Modal,
Tag,
Divider,
Descriptions,
Alert,
Row,
Col,
Typography,
Badge,
Tooltip,
Avatar,
ColorPicker,
} from 'antd';
import {
SettingOutlined,
GlobalOutlined,
BgColorsOutlined,
InfoCircleOutlined,
CheckCircleOutlined,
ExclamationCircleOutlined,
LockOutlined,
DatabaseOutlined,
DesktopOutlined,
ClockCircleOutlined,
MailOutlined,
PhoneOutlined,
EnvironmentOutlined,
FileTextOutlined,
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 { Title, Text, Paragraph } = Typography;
const SystemSettings = () => {
const [loading, setLoading] = useState(false);
const [saving, setSaving] = useState(false);
const [settings, setSettings] = useState({});
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 () => {
setLoading(true);
try {
const response = await axios.get('/api/system-settings');
setSettings(response.data);
const formValues = {};
Object.entries(response.data).forEach(([key, data]) => {
formValues[key] = data.value;
});
form.setFieldsValue(formValues);
} catch (error) {
message.error('获取设置失败');
} finally {
setLoading(false);
}
};
const fetchSystemInfo = async () => {
try {
const response = await axios.get('/api/system-settings/system/info');
setSystemInfo(response.data);
} catch (error) {
console.error('获取系统信息失败');
}
};
const handleSaveSettings = async values => {
setSaving(true);
try {
const updates = {};
Object.entries(values).forEach(([key, value]) => {
if (settings[key] && settings[key].isEditable) {
updates[key] = value;
}
});
await axios.put('/api/system-settings', { settings: updates });
if ('frontend_port' in updates) {
try {
await axios.post('/api/system-settings/frontend/port/sync');
const statusRes = await axios.get('/api/system-settings/frontend/status');
const isProduction = statusRes.data.isProduction;
if (isProduction) {
Modal.info({
title: '前端端口已修改(生产环境)',
content: (
前端端口已从 {settings.frontend_port?.value} 更改为{' '}
{updates.frontend_port}
生产环境由 Nginx 或其他服务器托管,请手动更新服务器配置文件
}
type="info"
showIcon
style={{ marginTop: 16 }}
/>
),
okText: '知道了',
});
} else {
Modal.confirm({
title: '前端端口已修改',
icon: ,
content: (
前端端口已从 {settings.frontend_port?.value} 更改为{' '}
{updates.frontend_port}
是否立即重启前端服务以应用新端口?
),
okText: '立即重启',
cancelText: '稍后手动重启',
onOk: async () => {
const newPort = updates.frontend_port;
const newUrl = `http://localhost:${newPort}`;
Modal.success({
title: '正在重启前端服务',
content: (
前端服务正在重启,新端口:{newPort}
页面将在3秒后自动跳转到新地址...
),
okText: '立即跳转',
closable: false,
maskClosable: false,
onOk: () => {
window.location.href = newUrl;
},
});
setTimeout(async () => {
try {
await axios.post('/api/system-settings/frontend/restart', {}, { timeout: 5000 });
} catch (error) {
console.log('重启请求已发送,服务正在重启...');
}
}, 1000);
setTimeout(() => {
window.location.href = newUrl;
}, 3000);
},
});
}
} catch (syncError) {
console.warn('同步前端端口配置失败:', syncError);
message.warning('端口配置已保存,但同步到配置文件失败');
}
} else {
message.success('设置保存成功');
}
fetchSettings();
await reloadConfig();
} catch (error) {
message.error('保存设置失败');
} finally {
setSaving(false);
}
};
const handleResetSetting = key => {
Modal.confirm({
title: '确认重置',
icon: ,
content: `确定要将 "${settings[key]?.description || key}" 重置为默认值吗?`,
onOk: async () => {
try {
await axios.post(`/api/system-settings/reset/${key}`);
message.success('重置成功');
fetchSettings();
await reloadConfig();
} catch (error) {
message.error('重置失败');
}
},
});
};
const renderFormItem = (key, data) => {
if (!data.isEditable) {
return (
} />
);
}
switch (data.type) {
case 'boolean':
return (
);
case 'number':
const isPortField = key === 'frontend_port';
return (
Number(value),
},
]
: []),
]}
>
);
case 'select':
const options = getSelectOptions(key);
return (
);
default:
return (
);
}
};
const getSelectOptions = key => {
const optionsMap = {
timezone: [
{ value: 'Asia/Shanghai', label: '亚洲/上海 (UTC+8)' },
{ value: 'Asia/Beijing', label: '亚洲/北京 (UTC+8)' },
{ value: 'America/New_York', label: '美洲/纽约 (UTC-5)' },
{ value: 'Europe/London', label: '欧洲/伦敦 (UTC+0)' },
{ value: 'UTC', label: 'UTC (UTC+0)' },
],
date_format: [
{ value: 'YYYY-MM-DD', label: '2024-01-01' },
{ value: 'YYYY/MM/DD', label: '2024/01/01' },
{ value: 'DD/MM/YYYY', label: '01/01/2024' },
{ value: 'MM/DD/YYYY', label: '01/01/2024' },
],
primary_color: [
{ value: '#667eea', label: '蓝色 (#667eea)' },
{ value: '#764ba2', label: '紫色 (#764ba2)' },
{ value: '#f093fb', label: '粉色 (#f093fb)' },
{ value: '#4facfe', label: '浅蓝色 (#4facfe)' },
{ value: '#43e97b', label: '绿色 (#43e97b)' },
{ value: '#fa709a', label: '红色 (#fa709a)' },
{ value: '#fee140', label: '黄色 (#fee140)' },
{ value: '#00b4db', label: '青色 (#00b4db)' },
{ value: '#0083b0', label: '深蓝色 (#0083b0)' },
{ value: '#fcb045', label: '橙色 (#fcb045)' },
],
secondary_color: [
{ value: '#764ba2', label: '紫色 (#764ba2)' },
{ value: '#667eea', label: '蓝色 (#667eea)' },
{ value: '#4facfe', label: '浅蓝色 (#4facfe)' },
{ value: '#43e97b', label: '绿色 (#43e97b)' },
{ value: '#fa709a', label: '红色 (#fa709a)' },
{ value: '#fee140', label: '黄色 (#fee140)' },
{ value: '#00b4db', label: '青色 (#00b4db)' },
{ value: '#0083b0', label: '深蓝色 (#0083b0)' },
{ value: '#fcb045', label: '橙色 (#fcb045)' },
{ value: '#f093fb', label: '粉色 (#f093fb)' },
],
table_row_height: [
{ value: 'small', label: '紧凑 (Small)' },
{ value: 'default', label: '默认 (Default)' },
{ value: 'middle', label: '中等 (Middle)' },
{ value: 'large', label: '宽松 (Large)' },
],
dark_mode: [
{ value: 'false', label: '关闭' },
{ value: 'true', label: '开启' },
],
compact_mode: [
{ value: 'false', label: '关闭' },
{ value: 'true', label: '开启' },
],
animation_enabled: [
{ value: 'false', label: '关闭' },
{ value: 'true', label: '开启' },
],
sidebar_collapsed: [
{ value: 'false', label: '展开' },
{ value: 'true', label: '折叠' },
],
};
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'],
},
],
appearance: [
{
title: '主题颜色',
icon: ,
description: '自定义系统主题配色方案',
keys: ['primary_color', 'secondary_color'],
},
{
title: '界面布局',
icon: ,
description: '调整界面显示密度和布局方式',
keys: ['compact_mode', 'sidebar_collapsed', 'table_row_height'],
},
{
title: '动画效果',
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}
{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) => (
}
size="large"
style={{
borderRadius: 8,
minWidth: 140,
height: 44,
fontSize: 15,
fontWeight: 500,
}}
>
保存设置
);
const renderGeneralSettings = () => {
return (
);
};
const presetColors = [
'#667eea',
'#764ba2',
'#f093fb',
'#4facfe',
'#43e97b',
'#fa709a',
'#fee140',
'#00b4db',
'#0083b0',
'#fcb045',
'#1890ff',
'#52c41a',
'#eb2f96',
'#722ed1',
'#13c2c2',
'#fa8c16',
];
const handleColorChange = (color, key) => {
const hexColor = typeof color === 'string' ? color : color.toHexString();
form.setFieldValue(key, hexColor);
const root = document.documentElement;
if (key === 'primary_color') {
root.style.setProperty('--primary-color', hexColor);
root.style.setProperty('--primary-light', `${hexColor}20`);
} else if (key === 'secondary_color') {
root.style.setProperty('--secondary-color', hexColor);
root.style.setProperty('--secondary-light', `${hexColor}20`);
}
};
const renderColorFormItem = (key, data) => {
const currentValue = form.getFieldValue(key) || settings[key]?.value || '#667eea';
return (
{data.description || key}
}
name={key}
style={{ marginBottom: 0 }}
>
);
};
const renderAppearanceSettings = () => {
const primaryColor = form.getFieldValue('primary_color') || settings.primary_color?.value || '#667eea';
const secondaryColor = form.getFieldValue('secondary_color') || settings.secondary_color?.value || '#764ba2';
return (
);
};
const renderAboutPage = () => {
return (
机柜管理系统
版本 {settings.app_version?.value || '1.0.0'}
|
运行正常} />
专业的数据中心设备管理平台,提供机房、机柜、设备的全生命周期管理
{systemInfo && (
{systemInfo.statistics?.devices || 0}
设备总数
{systemInfo.statistics?.racks || 0}
机柜总数
{systemInfo.statistics?.rooms || 0}
机房总数
{systemInfo.statistics?.users || 0}
用户总数
{systemInfo.system?.nodeVersion}
{systemInfo.system?.platform} ({systemInfo.system?.arch})
{systemInfo.system?.pid}
{systemInfo.system?.uptime
? `${Math.floor(systemInfo.system.uptime / 3600)}小时${Math.floor((systemInfo.system.uptime % 3600) / 60)}分钟`
: '-'}
{systemInfo.system?.memoryUsage
? `${(systemInfo.system.memoryUsage.heapUsed / 1024 / 1024).toFixed(2)} MB`
: '-'}
{systemInfo.timestamp
? new Date(systemInfo.timestamp).toLocaleString('zh-CN')
: '-'}
)}
);
};
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 (
{/* 顶部固定导航栏 */}
系统设置
{/* 桌面端导航 */}
{!isMobile && renderTopNav()}
{/* 移动端导航 */}
{isMobile && renderMobileNav()}
{/* 主内容区 - 平铺填充 */}
);
};
export default SystemSettings;