feat(系统设置): 重构系统设置页面UI并优化响应式布局

This commit is contained in:
zhang1106
2026-02-12 15:10:24 +08:00
parent 8de7096808
commit b2c3e6c348
+478 -222
View File
@@ -1,6 +1,5 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { import {
Tabs,
Form, Form,
Input, Input,
Switch, Switch,
@@ -19,6 +18,7 @@ import {
Typography, Typography,
Badge, Badge,
Tooltip, Tooltip,
Avatar,
} from 'antd'; } from 'antd';
import { import {
SettingOutlined, SettingOutlined,
@@ -38,26 +38,41 @@ import {
SafetyCertificateOutlined, SafetyCertificateOutlined,
ReloadOutlined, ReloadOutlined,
SaveOutlined, SaveOutlined,
BellOutlined,
CloudServerOutlined,
UserOutlined,
SecurityScanOutlined,
ApiOutlined,
ThunderboltOutlined,
MenuFoldOutlined,
MenuUnfoldOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import axios from 'axios'; import axios from 'axios';
import { useConfig } from '../context/ConfigContext'; import { useConfig } from '../context/ConfigContext';
const { Option } = Select; const { Option } = Select;
const { TabPane } = Tabs; const { Title, Text, Paragraph } = Typography;
const { Title, Text } = Typography;
const SystemSettings = () => { const SystemSettings = () => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const [settings, setSettings] = useState({}); const [settings, setSettings] = useState({});
const [activeTab, setActiveTab] = useState('general'); const [activeMenu, setActiveMenu] = useState('general');
const [systemInfo, setSystemInfo] = useState(null); const [systemInfo, setSystemInfo] = useState(null);
const [form] = Form.useForm(); const [form] = Form.useForm();
const [drawerVisible, setDrawerVisible] = useState(false);
const [isMobile, setIsMobile] = useState(window.innerWidth < 768);
const { reloadConfig } = useConfig(); const { reloadConfig } = useConfig();
useEffect(() => { useEffect(() => {
fetchSettings(); fetchSettings();
fetchSystemInfo(); fetchSystemInfo();
const handleResize = () => {
setIsMobile(window.innerWidth < 768);
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []); }, []);
const fetchSettings = async () => { const fetchSettings = async () => {
@@ -99,18 +114,13 @@ const SystemSettings = () => {
await axios.put('/api/system-settings', { settings: updates }); await axios.put('/api/system-settings', { settings: updates });
// 如果修改了前端端口,同步配置并自动重启(仅开发环境)
if ('frontend_port' in updates) { if ('frontend_port' in updates) {
try { try {
// 1. 同步端口到配置文件
await axios.post('/api/system-settings/frontend/port/sync'); await axios.post('/api/system-settings/frontend/port/sync');
// 2. 检查是否为生产环境
const statusRes = await axios.get('/api/system-settings/frontend/status'); const statusRes = await axios.get('/api/system-settings/frontend/status');
const isProduction = statusRes.data.isProduction; const isProduction = statusRes.data.isProduction;
if (isProduction) { if (isProduction) {
// 生产环境:只显示提示,不自动重启
Modal.info({ Modal.info({
title: '前端端口已修改(生产环境)', title: '前端端口已修改(生产环境)',
content: ( content: (
@@ -123,10 +133,7 @@ const SystemSettings = () => {
message="请手动更新服务器配置" message="请手动更新服务器配置"
description={ description={
<div> <div>
<p>生产环境由 Nginx 或其他服务器托管请手动更新服务器配置文件</p> <p>生产环境由 Nginx 或其他服务器托管请手动更新服务器配置文件</p>
<p>1. 更新 Nginx 配置中的监听端口</p>
<p>2. 重启 Nginx 服务</p>
<p>3. 更新 vite.config.js 中的端口配置用于下次构建</p>
</div> </div>
} }
type="info" type="info"
@@ -138,7 +145,6 @@ const SystemSettings = () => {
okText: '知道了', okText: '知道了',
}); });
} else { } else {
// 开发环境:显示确认对话框并自动重启
Modal.confirm({ Modal.confirm({
title: '前端端口已修改', title: '前端端口已修改',
icon: <ExclamationCircleOutlined />, icon: <ExclamationCircleOutlined />,
@@ -149,13 +155,6 @@ const SystemSettings = () => {
<strong>{updates.frontend_port}</strong> <strong>{updates.frontend_port}</strong>
</p> </p>
<p>是否立即重启前端服务以应用新端口</p> <p>是否立即重启前端服务以应用新端口</p>
<Alert
message="注意"
description="重启后页面将自动跳转到新地址,如果新端口无法访问,请手动使用原端口访问。"
type="warning"
showIcon
style={{ marginTop: 16 }}
/>
</div> </div>
), ),
okText: '立即重启', okText: '立即重启',
@@ -163,20 +162,12 @@ const SystemSettings = () => {
onOk: async () => { onOk: async () => {
const newPort = updates.frontend_port; const newPort = updates.frontend_port;
const newUrl = `http://localhost:${newPort}`; const newUrl = `http://localhost:${newPort}`;
// 先显示跳转提示,再调用重启API
// 因为重启API会导致当前服务中断
Modal.success({ Modal.success({
title: '正在重启前端服务', title: '正在重启前端服务',
content: ( content: (
<div> <div>
<p> <p>前端服务正在重启新端口<strong>{newPort}</strong></p>
前端服务正在重启新端口<strong>{newPort}</strong>
</p>
<p>页面将在3秒后自动跳转到新地址...</p> <p>页面将在3秒后自动跳转到新地址...</p>
<p>
如果跳转失败请手动访问<a href={newUrl}>{newUrl}</a>
</p>
</div> </div>
), ),
okText: '立即跳转', okText: '立即跳转',
@@ -186,23 +177,13 @@ const SystemSettings = () => {
window.location.href = newUrl; window.location.href = newUrl;
}, },
}); });
// 延迟调用重启API,让用户看到提示
setTimeout(async () => { setTimeout(async () => {
try { try {
// 调用重启API(这个请求可能会因为服务重启而失败) await axios.post('/api/system-settings/frontend/restart', {}, { timeout: 5000 });
await axios.post(
'/api/system-settings/frontend/restart',
{},
{ timeout: 5000 }
);
} catch (error) { } catch (error) {
// 忽略错误,因为服务重启会导致连接中断
console.log('重启请求已发送,服务正在重启...'); console.log('重启请求已发送,服务正在重启...');
} }
}, 1000); }, 1000);
// 延迟3秒后跳转
setTimeout(() => { setTimeout(() => {
window.location.href = newUrl; window.location.href = newUrl;
}, 3000); }, 3000);
@@ -218,7 +199,6 @@ const SystemSettings = () => {
} }
fetchSettings(); fetchSettings();
// 重新加载全局配置,使更改立即生效
await reloadConfig(); await reloadConfig();
} catch (error) { } catch (error) {
message.error('保存设置失败'); message.error('保存设置失败');
@@ -237,7 +217,6 @@ const SystemSettings = () => {
await axios.post(`/api/system-settings/reset/${key}`); await axios.post(`/api/system-settings/reset/${key}`);
message.success('重置成功'); message.success('重置成功');
fetchSettings(); fetchSettings();
// 重新加载全局配置,使更改立即生效
await reloadConfig(); await reloadConfig();
} catch (error) { } catch (error) {
message.error('重置失败'); message.error('重置失败');
@@ -379,22 +358,24 @@ const SystemSettings = () => {
return optionsMap[key] || []; return optionsMap[key] || [];
}; };
// 设置项分组配置
const settingGroups = { const settingGroups = {
general: [ general: [
{ {
title: '站点信息', title: '站点信息',
icon: <GlobalOutlined />, icon: <GlobalOutlined />,
description: '配置系统基本信息和站点标识',
keys: ['site_name', 'site_logo'], keys: ['site_name', 'site_logo'],
}, },
{ {
title: '时间设置', title: '时间设置',
icon: <ClockCircleOutlined />, icon: <ClockCircleOutlined />,
description: '设置系统时区和日期显示格式',
keys: ['timezone', 'date_format'], keys: ['timezone', 'date_format'],
}, },
{ {
title: '安全设置', title: '安全设置',
icon: <SafetyCertificateOutlined />, icon: <SafetyCertificateOutlined />,
description: '配置登录安全策略和维护模式',
keys: ['idle_timeout', 'max_login_attempts', 'maintenance_mode'], keys: ['idle_timeout', 'max_login_attempts', 'maintenance_mode'],
}, },
], ],
@@ -402,61 +383,261 @@ const SystemSettings = () => {
{ {
title: '主题颜色', title: '主题颜色',
icon: <BgColorsOutlined />, icon: <BgColorsOutlined />,
description: '自定义系统主题配色方案',
keys: ['primary_color', 'secondary_color'], keys: ['primary_color', 'secondary_color'],
}, },
{ {
title: '界面布局', title: '界面布局',
icon: <DesktopOutlined />, icon: <DesktopOutlined />,
description: '调整界面显示密度和布局方式',
keys: ['compact_mode', 'sidebar_collapsed', 'table_row_height'], keys: ['compact_mode', 'sidebar_collapsed', 'table_row_height'],
}, },
{ {
title: '动画效果', title: '动画效果',
icon: <CheckCircleOutlined />, icon: <ThunderboltOutlined />,
description: '控制界面动画和过渡效果',
keys: ['animation_enabled'], keys: ['animation_enabled'],
}, },
], ],
}; };
const menuItems = [
{
key: 'general',
icon: <GlobalOutlined />,
label: '全局配置',
description: '站点信息、时间、安全设置',
},
{
key: 'appearance',
icon: <BgColorsOutlined />,
label: '外观设置',
description: '主题颜色、界面布局',
},
{
key: 'about',
icon: <InfoCircleOutlined />,
label: '关于系统',
description: '系统信息、版本详情',
},
];
const renderSettingGroup = (group) => { const renderSettingGroup = (group) => {
// 根据分组决定布局
const isSiteInfo = group.title === '站点信息';
const isTimeSetting = group.title === '时间设置';
const isSecurity = group.title === '安全设置';
return ( return (
<Card <Card
key={group.title} key={group.title}
title={ style={{
<Space> marginBottom: 24,
{group.icon} borderRadius: 12,
<span style={{ fontWeight: 600 }}>{group.title}</span> border: '1px solid #e8e8e8',
</Space> boxShadow: '0 2px 8px rgba(0,0,0,0.04)',
} }}
style={{ marginBottom: 16, borderRadius: 8 }} bodyStyle={{ padding: '24px' }}
size="small"
> >
{/* 居中的标题区 */}
<div style={{ textAlign: 'center', marginBottom: 24 }}>
<div style={{
width: 48,
height: 48,
borderRadius: '50%',
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
marginBottom: 12,
}}>
<span style={{ color: '#fff', fontSize: 24 }}>{group.icon}</span>
</div>
<div style={{ fontSize: 18, fontWeight: 600, color: '#262626', marginBottom: 6 }}>
{group.title}
</div>
<div style={{ fontSize: 14, color: '#666' }}>
{group.description}
</div>
</div>
<Divider style={{ margin: '0 0 24px 0' }} />
{/* 表单内容区 */}
{isSiteInfo && (
<Row gutter={[32, 16]}>
<Col xs={24} md={12}>
<Form.Item
label={<span style={{ fontSize: 14, color: '#262626', fontWeight: 500 }}>网站名称</span>}
name="site_name"
style={{ marginBottom: 0 }}
>
<Input
placeholder="请输入网站名称"
style={{ height: 40, borderRadius: 8 }}
/>
</Form.Item>
</Col>
<Col xs={24} md={12}>
<Form.Item
label={<span style={{ fontSize: 14, color: '#262626', fontWeight: 500 }}>网站Logo URL</span>}
name="site_logo"
style={{ marginBottom: 0 }}
>
<Input
placeholder="请输入Logo URL"
style={{ height: 40, borderRadius: 8 }}
suffix={
<Button type="link" size="small" style={{ padding: 0 }}>
预览
</Button>
}
/>
</Form.Item>
</Col>
</Row>
)}
{isTimeSetting && (
<Row gutter={[32, 16]}>
<Col xs={24} md={12}>
<Form.Item
label={<span style={{ fontSize: 14, color: '#262626', fontWeight: 500 }}>时区设置</span>}
name="timezone"
style={{ marginBottom: 0 }}
>
<Select
placeholder="请选择时区"
style={{ width: '100%', height: 40 }}
dropdownStyle={{ borderRadius: 8 }}
>
{getSelectOptions('timezone').map(opt => (
<Option key={opt.value} value={opt.value}>{opt.label}</Option>
))}
</Select>
</Form.Item>
</Col>
<Col xs={24} md={12}>
<Form.Item
label={<span style={{ fontSize: 14, color: '#262626', fontWeight: 500 }}>日期格式</span>}
name="date_format"
style={{ marginBottom: 0 }}
>
<Select
placeholder="请选择日期格式"
style={{ width: '100%', height: 40 }}
dropdownStyle={{ borderRadius: 8 }}
>
{getSelectOptions('date_format').map(opt => (
<Option key={opt.value} value={opt.value}>{opt.label}</Option>
))}
</Select>
</Form.Item>
</Col>
</Row>
)}
{isSecurity && (
<Row gutter={[32, 16]} align="middle">
<Col xs={24} md={8}>
<Form.Item
label={<span style={{ fontSize: 14, color: '#262626', fontWeight: 500 }}>用户空闲超时时间</span>}
name="idle_timeout"
style={{ marginBottom: 0 }}
>
<Input
type="number"
placeholder="请输入超时时间"
style={{ height: 40, borderRadius: 8 }}
suffix={<span style={{ color: '#999' }}>分钟</span>}
/>
</Form.Item>
</Col>
<Col xs={24} md={8}>
<Form.Item
label={<span style={{ fontSize: 14, color: '#262626', fontWeight: 500 }}>最大登录尝试次数</span>}
name="max_login_attempts"
style={{ marginBottom: 0 }}
>
<Input
type="number"
placeholder="请输入次数"
style={{ height: 40, borderRadius: 8 }}
suffix={<span style={{ color: '#999' }}></span>}
/>
</Form.Item>
</Col>
<Col xs={24} md={8}>
<Form.Item
label={<span style={{ fontSize: 14, color: '#262626', fontWeight: 500 }}>维护模式</span>}
name="maintenance_mode"
valuePropName="checked"
style={{ marginBottom: 0 }}
>
<Tooltip title="开启后所有用户将无法登录">
<Switch
checkedChildren="开启"
unCheckedChildren="关闭"
style={{ marginTop: 8 }}
/>
</Tooltip>
</Form.Item>
</Col>
</Row>
)}
{!isSiteInfo && !isTimeSetting && !isSecurity && (
<Row gutter={[32, 16]}>
{group.keys.map(key => { {group.keys.map(key => {
const settingData = { ...settings[key] }; const settingData = { ...settings[key] };
// 确保特定字段使用正确的类型
if (key === 'timezone' || key === 'date_format') { if (key === 'timezone' || key === 'date_format') {
settingData.type = 'select'; settingData.type = 'select';
} }
if (key === 'primary_color' || key === 'secondary_color') { if (key === 'primary_color' || key === 'secondary_color') {
settingData.type = 'select'; settingData.type = 'select';
} }
return renderFormItem(key, settingData); return (
<Col xs={24} md={12} lg={8} key={key}>
{renderFormItem(key, settingData)}
</Col>
);
})} })}
</Row>
)}
</Card> </Card>
); );
}; };
const renderGeneralSettings = () => { // 底部固定操作栏
return ( const renderFixedFooter = (onSubmit) => (
<Form form={form} layout="vertical" onFinish={handleSaveSettings}> <div style={{
{settingGroups.general.map(renderSettingGroup)} position: 'sticky',
<Card style={{ borderRadius: 8 }} size="small"> bottom: 0,
<Space> left: 0,
right: 0,
background: '#fff',
borderTop: '1px solid #e8e8e8',
padding: '16px 24px',
display: 'flex',
justifyContent: 'center',
gap: 16,
boxShadow: '0 -2px 8px rgba(0,0,0,0.06)',
zIndex: 10,
marginTop: 24,
}}>
<Button <Button
type="primary" type="primary"
htmlType="submit" htmlType="submit"
loading={saving} loading={saving}
icon={<SaveOutlined />} icon={<SaveOutlined />}
size="large" size="large"
style={{
borderRadius: 8,
minWidth: 140,
height: 44,
fontSize: 15,
fontWeight: 500,
}}
> >
保存设置 保存设置
</Button> </Button>
@@ -464,11 +645,25 @@ const SystemSettings = () => {
onClick={() => fetchSettings()} onClick={() => fetchSettings()}
icon={<ReloadOutlined />} icon={<ReloadOutlined />}
size="large" size="large"
style={{
borderRadius: 8,
minWidth: 120,
height: 44,
fontSize: 15,
}}
> >
重置表单 重置表单
</Button> </Button>
</Space> </div>
</Card> );
const renderGeneralSettings = () => {
return (
<Form form={form} layout="vertical" onFinish={handleSaveSettings}>
<div style={{ paddingBottom: 80 }}>
{settingGroups.general.map(group => renderSettingGroup(group))}
</div>
{renderFixedFooter()}
</Form> </Form>
); );
}; };
@@ -476,280 +671,341 @@ const SystemSettings = () => {
const renderAppearanceSettings = () => { const renderAppearanceSettings = () => {
return ( return (
<Form form={form} layout="vertical" onFinish={handleSaveSettings}> <Form form={form} layout="vertical" onFinish={handleSaveSettings}>
<div style={{ paddingBottom: 80 }}>
<Alert <Alert
message="主题颜色设置" message="主题颜色设置"
description="修改主题颜色后需要刷新页面才能生效。建议选择对比度适中的颜色组合。" description="修改主题颜色后需要刷新页面才能生效。建议选择对比度适中的颜色组合。"
type="info" type="info"
showIcon showIcon
style={{ marginBottom: 16, borderRadius: 8 }} style={{ marginBottom: 24, borderRadius: 12 }}
/> />
{settingGroups.appearance.map(renderSettingGroup)} {settingGroups.appearance.map(group => renderSettingGroup(group))}
<Card style={{ borderRadius: 8 }} size="small"> </div>
<Space> {renderFixedFooter()}
<Button
type="primary"
htmlType="submit"
loading={saving}
icon={<SaveOutlined />}
size="large"
>
保存设置
</Button>
<Button
onClick={() => fetchSettings()}
icon={<ReloadOutlined />}
size="large"
>
重置表单
</Button>
</Space>
</Card>
</Form> </Form>
); );
}; };
const renderAboutPage = () => { const renderAboutPage = () => {
const aboutKeys = [
'app_version',
'company_name',
'contact_email',
'contact_phone',
'company_address',
'system_description',
'privacy_policy',
'terms_of_service',
];
return ( return (
<div> <div>
{/* 系统概览卡片 */}
<Card <Card
style={{ style={{
marginBottom: 16, marginBottom: 16,
borderRadius: 8, borderRadius: 12,
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
color: '#fff', border: 'none',
}} }}
bodyStyle={{ padding: 24 }} bodyStyle={{ padding: '24px' }}
> >
<Row gutter={[24, 24]} align="middle"> <Row gutter={[20, 20]} align="middle">
<Col> <Col xs={24} sm={6} md={5}>
<div <div
style={{ style={{
width: 80, width: 80,
height: 80, height: 80,
borderRadius: 16, borderRadius: 16,
backgroundColor: 'rgba(255,255,255,0.2)', backgroundColor: 'rgba(255,255,255,0.15)',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
margin: '0 auto',
}} }}
> >
<DatabaseOutlined style={{ fontSize: 40, color: '#fff' }} /> <DatabaseOutlined style={{ fontSize: 40, color: '#fff' }} />
</div> </div>
</Col> </Col>
<Col flex="auto"> <Col xs={24} sm={18} md={19}>
<Title level={3} style={{ color: '#fff', margin: 0, marginBottom: 8 }}> <Title level={3} style={{ color: '#fff', margin: 0, marginBottom: 6, fontSize: '22px' }}>
机柜管理系统 机柜管理系统
</Title> </Title>
<Space size={16} style={{ color: 'rgba(255,255,255,0.9)' }}> <Space size={12} wrap style={{ color: 'rgba(255,255,255,0.9)' }}>
<span>版本 {settings.app_version?.value || '1.0.0'}</span> <span style={{ fontSize: 14 }}>版本 {settings.app_version?.value || '1.0.0'}</span>
<span>|</span> <span style={{ opacity: 0.5 }}>|</span>
<Badge status="success" text="运行正常" style={{ color: '#fff' }} /> <Badge status="success" text={<span style={{ color: '#fff' }}>运行正常</span>} />
</Space> </Space>
<Paragraph style={{ color: 'rgba(255,255,255,0.85)', marginTop: 8, marginBottom: 0, fontSize: 13 }}>
专业的数据中心设备管理平台提供机房机柜设备的全生命周期管理
</Paragraph>
</Col> </Col>
</Row> </Row>
</Card> </Card>
{/* 统计信息卡片 */}
{systemInfo && ( {systemInfo && (
<Card <Card
title={ style={{ marginBottom: 16, borderRadius: 12, border: '1px solid #e8e8e8' }}
<Space> bodyStyle={{ padding: '20px' }}
<InfoCircleOutlined />
<span style={{ fontWeight: 600 }}>系统统计</span>
</Space>
}
style={{ marginBottom: 16, borderRadius: 8 }}
size="small"
> >
<Row gutter={[16, 16]}> <div style={{ marginBottom: 16 }}>
<Col span={6}> <Space size={10}>
<Card size="small" style={{ textAlign: 'center', backgroundColor: '#f6ffed', border: 'none' }}> <Avatar style={{ backgroundColor: '#52c41a', borderRadius: 8 }} icon={<CloudServerOutlined />} size={32} />
<div style={{ fontSize: 24, fontWeight: 600, color: '#52c41a' }}> <Text strong style={{ fontSize: 15 }}>系统统计</Text>
</Space>
</div>
<Row gutter={[12, 12]}>
<Col xs={12} sm={6}>
<div
style={{
textAlign: 'center',
borderRadius: 10,
background: 'linear-gradient(135deg, #f6ffed 0%, #d9f7be 100%)',
padding: '16px 12px',
}}
>
<div style={{ fontSize: 24, fontWeight: 700, color: '#52c41a', marginBottom: 2 }}>
{systemInfo.statistics?.devices || 0} {systemInfo.statistics?.devices || 0}
</div> </div>
<div style={{ color: '#666', fontSize: 12 }}>设备总数</div> <Text type="secondary" style={{ fontSize: 12 }}>设备总数</Text>
</Card> </div>
</Col> </Col>
<Col span={6}> <Col xs={12} sm={6}>
<Card size="small" style={{ textAlign: 'center', backgroundColor: '#e6f7ff', border: 'none' }}> <div
<div style={{ fontSize: 24, fontWeight: 600, color: '#1890ff' }}> style={{
textAlign: 'center',
borderRadius: 10,
background: 'linear-gradient(135deg, #e6f7ff 0%, #bae7ff 100%)',
padding: '16px 12px',
}}
>
<div style={{ fontSize: 24, fontWeight: 700, color: '#1890ff', marginBottom: 2 }}>
{systemInfo.statistics?.racks || 0} {systemInfo.statistics?.racks || 0}
</div> </div>
<div style={{ color: '#666', fontSize: 12 }}>机柜总数</div> <Text type="secondary" style={{ fontSize: 12 }}>机柜总数</Text>
</Card> </div>
</Col> </Col>
<Col span={6}> <Col xs={12} sm={6}>
<Card size="small" style={{ textAlign: 'center', backgroundColor: '#f9f0ff', border: 'none' }}> <div
<div style={{ fontSize: 24, fontWeight: 600, color: '#722ed1' }}> style={{
textAlign: 'center',
borderRadius: 10,
background: 'linear-gradient(135deg, #f9f0ff 0%, #efdbff 100%)',
padding: '16px 12px',
}}
>
<div style={{ fontSize: 24, fontWeight: 700, color: '#722ed1', marginBottom: 2 }}>
{systemInfo.statistics?.rooms || 0} {systemInfo.statistics?.rooms || 0}
</div> </div>
<div style={{ color: '#666', fontSize: 12 }}>机房总数</div> <Text type="secondary" style={{ fontSize: 12 }}>机房总数</Text>
</Card> </div>
</Col> </Col>
<Col span={6}> <Col xs={12} sm={6}>
<Card size="small" style={{ textAlign: 'center', backgroundColor: '#fff7e6', border: 'none' }}> <div
<div style={{ fontSize: 24, fontWeight: 600, color: '#fa8c16' }}> style={{
textAlign: 'center',
borderRadius: 10,
background: 'linear-gradient(135deg, #fff7e6 0%, #ffd591 100%)',
padding: '16px 12px',
}}
>
<div style={{ fontSize: 24, fontWeight: 700, color: '#fa8c16', marginBottom: 2 }}>
{systemInfo.statistics?.users || 0} {systemInfo.statistics?.users || 0}
</div> </div>
<div style={{ color: '#666', fontSize: 12 }}>用户总数</div> <Text type="secondary" style={{ fontSize: 12 }}>用户总数</Text>
</Card> </div>
</Col> </Col>
</Row> </Row>
<Divider style={{ margin: '16px 0' }} /> <Divider style={{ margin: '20px 0' }} />
<Descriptions column={{ xs: 1, sm: 2 }} size="small"> <Descriptions column={{ xs: 1, sm: 2, md: 3 }} size="small" labelStyle={{ fontWeight: 500, fontSize: 13 }}>
<Descriptions.Item label="Node.js 版本"> <Descriptions.Item label="Node.js 版本">
<Tag color="blue">{systemInfo.system?.nodeVersion}</Tag> <Tag color="blue" size="small">{systemInfo.system?.nodeVersion}</Tag>
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label="运行平台"> <Descriptions.Item label="运行平台">
{systemInfo.system?.platform} ({systemInfo.system?.arch}) <Text style={{ fontSize: 13 }}>{systemInfo.system?.platform} ({systemInfo.system?.arch})</Text>
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label="进程 ID"> <Descriptions.Item label="进程 ID">
<Tag>{systemInfo.system?.pid}</Tag> <Tag size="small">{systemInfo.system?.pid}</Tag>
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label="运行时间"> <Descriptions.Item label="运行时间">
{systemInfo.system?.uptime <Text style={{ fontSize: 13 }}>{systemInfo.system?.uptime
? `${Math.floor(systemInfo.system.uptime / 3600)}小时${Math.floor((systemInfo.system.uptime % 3600) / 60)}分钟` ? `${Math.floor(systemInfo.system.uptime / 3600)}小时${Math.floor((systemInfo.system.uptime % 3600) / 60)}分钟`
: '-'} : '-'}</Text>
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label="内存使用"> <Descriptions.Item label="内存使用">
{systemInfo.system?.memoryUsage <Text style={{ fontSize: 13 }}>{systemInfo.system?.memoryUsage
? `${(systemInfo.system.memoryUsage.heapUsed / 1024 / 1024).toFixed(2)} MB` ? `${(systemInfo.system.memoryUsage.heapUsed / 1024 / 1024).toFixed(2)} MB`
: '-'} : '-'}</Text>
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label="系统时间"> <Descriptions.Item label="系统时间">
{systemInfo.timestamp <Text style={{ fontSize: 13 }}>{systemInfo.timestamp
? new Date(systemInfo.timestamp).toLocaleString('zh-CN') ? new Date(systemInfo.timestamp).toLocaleString('zh-CN')
: '-'} : '-'}</Text>
</Descriptions.Item> </Descriptions.Item>
</Descriptions> </Descriptions>
</Card> </Card>
)} )}
{/* 公司信息卡片 */}
<Card <Card
title={ style={{ marginBottom: 16, borderRadius: 12, border: '1px solid #e8e8e8' }}
<Space> bodyStyle={{ padding: '20px' }}
<FileTextOutlined />
<span style={{ fontWeight: 600 }}>公司信息</span>
</Space>
}
style={{ marginBottom: 16, borderRadius: 8 }}
size="small"
> >
<Form layout="vertical"> <div style={{ marginBottom: 16 }}>
<Row gutter={[24, 0]}> <Space size={10}>
<Col span={12}> <Avatar style={{ backgroundColor: '#667eea', borderRadius: 8 }} icon={<FileTextOutlined />} size={32} />
<Text strong style={{ fontSize: 15 }}>公司信息</Text>
</Space>
</div>
<Form form={form} layout="vertical" onFinish={handleSaveSettings}>
<Row gutter={[24, 8]}>
<Col xs={24} sm={12}>
<Form.Item label="公司名称" name="company_name"> <Form.Item label="公司名称" name="company_name">
<Input prefix={<GlobalOutlined />} placeholder="请输入公司名称" /> <Input prefix={<GlobalOutlined style={{ color: '#bfbfbf' }} />} placeholder="请输入公司名称" />
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col xs={24} sm={12}>
<Form.Item label="联系邮箱" name="contact_email"> <Form.Item label="联系邮箱" name="contact_email">
<Input prefix={<MailOutlined />} placeholder="请输入联系邮箱" /> <Input prefix={<MailOutlined style={{ color: '#bfbfbf' }} />} placeholder="请输入联系邮箱" />
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col xs={24} sm={12}>
<Form.Item label="联系电话" name="contact_phone"> <Form.Item label="联系电话" name="contact_phone">
<Input prefix={<PhoneOutlined />} placeholder="请输入联系电话" /> <Input prefix={<PhoneOutlined style={{ color: '#bfbfbf' }} />} placeholder="请输入联系电话" />
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col xs={24} sm={12}>
<Form.Item label="公司地址" name="company_address"> <Form.Item label="公司地址" name="company_address">
<Input prefix={<EnvironmentOutlined />} placeholder="请输入公司地址" /> <Input prefix={<EnvironmentOutlined style={{ color: '#bfbfbf' }} />} placeholder="请输入公司地址" />
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={24}> <Col span={24}>
<Form.Item label="系统描述" name="system_description"> <Form.Item label="系统描述" name="system_description">
<Input.TextArea rows={3} placeholder="请输入系统描述" /> <Input.TextArea rows={3} placeholder="请输入系统描述" style={{ borderRadius: 8 }} />
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>
<Form.Item> <Divider style={{ margin: '16px 0' }} />
<Space> <Space size="middle">
<Button <Button
type="primary" type="primary"
htmlType="submit" htmlType="submit"
loading={saving} loading={saving}
onClick={() => form.submit()}
icon={<SaveOutlined />} icon={<SaveOutlined />}
style={{ borderRadius: 8 }}
> >
保存信息 保存信息
</Button> </Button>
<Button onClick={() => fetchSettings()} icon={<ReloadOutlined />}> <Button onClick={() => fetchSettings()} icon={<ReloadOutlined />} style={{ borderRadius: 8 }}>
重置 重置
</Button> </Button>
</Space> </Space>
</Form.Item>
</Form> </Form>
</Card> </Card>
</div> </div>
); );
}; };
const tabItems = [ const renderContent = () => {
{ switch (activeMenu) {
key: 'general', case 'general':
label: ( return renderGeneralSettings();
<span> case 'appearance':
<GlobalOutlined /> 全局配置 return renderAppearanceSettings();
</span> case 'about':
), return renderAboutPage();
children: renderGeneralSettings(), default:
}, return renderGeneralSettings();
{ }
key: 'appearance', };
label: (
<span> // 顶部导航栏菜单
<BgColorsOutlined /> 外观设置 const renderTopNav = () => (
</span> <div style={{
), display: 'flex',
children: renderAppearanceSettings(), alignItems: 'center',
}, gap: '4px',
{ flexWrap: 'nowrap',
key: 'about', overflowX: 'auto',
label: ( scrollbarWidth: 'none',
<span> msOverflowStyle: 'none',
<InfoCircleOutlined /> 关于系统 }}>
</span> {menuItems.map(item => (
), <div
children: renderAboutPage(), key={item.key}
}, onClick={() => 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';
}
}}
>
<span style={{ marginRight: 6, fontSize: 16 }}>{item.icon}</span>
<span>{item.label}</span>
</div>
))}
</div>
);
// 移动端下拉菜单
const renderMobileNav = () => (
<Select
value={activeMenu}
onChange={(value) => setActiveMenu(value)}
style={{ width: 140 }}
bordered={false}
dropdownStyle={{ borderRadius: 8 }}
suffixIcon={<MenuUnfoldOutlined style={{ color: '#fff' }} />}
>
{menuItems.map(item => (
<Option key={item.key} value={item.key}>
<Space>
{item.icon}
{item.label}
</Space>
</Option>
))}
</Select>
);
return ( return (
<div style={{ padding: 24, maxWidth: 1200, margin: '0 auto' }}> <div style={{ minHeight: '100vh', background: '#f5f7fa', display: 'flex', flexDirection: 'column' }}>
<Card {/* 顶部固定导航栏 */}
title={ <div style={{
<Space> background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
<SettingOutlined style={{ fontSize: 20, color: '#667eea' }} /> padding: '16px 24px',
<Title level={4} style={{ margin: 0 }}>系统设置</Title> position: 'sticky',
top: 0,
zIndex: 100,
boxShadow: '0 2px 8px rgba(0,0,0,0.15)',
}}>
<div style={{ maxWidth: '100%', margin: '0 auto', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<Space align="center">
<SettingOutlined style={{ fontSize: 20, color: '#fff' }} />
<Title level={4} style={{ margin: 0, color: '#fff', fontSize: 18 }}>系统设置</Title>
</Space> </Space>
}
style={{ borderRadius: 12 }} {/* 桌面端导航 */}
bodyStyle={{ padding: 0 }} {!isMobile && renderTopNav()}
>
<Tabs {/* 移动端导航 */}
activeKey={activeTab} {isMobile && renderMobileNav()}
onChange={setActiveTab} </div>
items={tabItems} </div>
style={{ padding: '0 24px 24px' }}
tabBarStyle={{ marginBottom: 24 }} {/* 主内容区 - 平铺填充 */}
/> <div style={{ flex: 1, padding: '16px 24px' }}>
</Card> <div style={{ maxWidth: '100%' }}>
{renderContent()}
</div>
</div>
</div> </div>
); );
}; };