From 8de7096808a6cb5a11b049bd4242e8082566dcd6 Mon Sep 17 00:00:00 2001 From: zhang1106 <849185023@qq.com> Date: Thu, 12 Feb 2026 14:36:33 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=B7=A5=E5=8D=95/=E8=AE=BE=E5=A4=87):=20?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=AE=BE=E5=A4=87=E8=AF=A6=E6=83=85=E5=B1=95?= =?UTF-8?q?=E7=A4=BA=E5=92=8C=E5=B7=A5=E5=8D=95=E6=93=8D=E4=BD=9C=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor(系统设置): 重构系统设置页面布局和样式 style(前端): 统一设备管理相关组件的卡片样式和布局 --- backend/routes/tickets.js | 27 +- .../src/components/DeviceDetailDrawer.jsx | 338 +++---- frontend/src/pages/DeviceManagement.jsx | 300 ++++--- frontend/src/pages/Login.jsx | 829 +++++++++++------- frontend/src/pages/SystemSettings.jsx | 480 ++++++---- frontend/src/pages/TicketManagement.jsx | 355 ++++++-- 6 files changed, 1459 insertions(+), 870 deletions(-) diff --git a/backend/routes/tickets.js b/backend/routes/tickets.js index fe46e97..0f660d5 100644 --- a/backend/routes/tickets.js +++ b/backend/routes/tickets.js @@ -5,6 +5,8 @@ const { v4: uuidv4 } = require('uuid'); const { Ticket, TicketOperationRecord } = require('../models/ticketIndex'); const Device = require('../models/Device'); const User = require('../models/User'); +const Rack = require('../models/Rack'); +const Room = require('../models/Room'); const { dbDialect } = require('../db'); // 获取工单统计 (必须定义在 /:ticketId 之前) @@ -238,7 +240,21 @@ router.get('/:ticketId', async (req, res) => { const ticket = await Ticket.findByPk(req.params.ticketId, { include: [ { model: User, as: 'reporter', attributes: ['userId', 'username', 'email'] }, - { model: Device }, + { + model: Device, + include: [ + { + model: Rack, + attributes: ['rackId', 'name', 'roomId'], + include: [ + { + model: Room, + attributes: ['roomId', 'name'] + } + ] + } + ] + }, { model: TicketOperationRecord, as: 'operationRecords', order: [['createdAt', 'DESC']] } ] }); @@ -247,7 +263,14 @@ router.get('/:ticketId', async (req, res) => { return res.status(404).json({ error: '工单不存在' }); } - res.json(ticket); + // 格式化响应数据,添加机房和机柜名称 + const ticketData = ticket.toJSON(); + if (ticketData.Device && ticketData.Device.Rack) { + ticketData.Device.roomName = ticketData.Device.Rack.Room?.name || '-'; + ticketData.Device.rackName = ticketData.Device.Rack.name || '-'; + } + + res.json(ticketData); } catch (error) { res.status(500).json({ error: error.message }); } diff --git a/frontend/src/components/DeviceDetailDrawer.jsx b/frontend/src/components/DeviceDetailDrawer.jsx index 76a62e1..0032d1f 100644 --- a/frontend/src/components/DeviceDetailDrawer.jsx +++ b/frontend/src/components/DeviceDetailDrawer.jsx @@ -12,6 +12,9 @@ import { Popconfirm, Table, Badge, + Row, + Col, + Divider, } from 'antd'; import { ApiOutlined, @@ -23,22 +26,29 @@ import { FileTextOutlined, ToolOutlined, EyeOutlined, + DesktopOutlined, + FieldTimeOutlined, + InfoCircleOutlined, + LinkOutlined, } from '@ant-design/icons'; import NetworkCardPanel from './NetworkCardPanel'; import { deviceAPI } from '../api'; +import dayjs from 'dayjs'; const { Text, Title } = Typography; const designTokens = { colors: { - primary: '#667eea', - success: '#10b981', - error: '#ef4444', - warning: '#f59e0b', - }, - spacing: { - sm: 8, - md: 16, + primary: '#1890ff', + success: '#52c41a', + error: '#f5222d', + warning: '#faad14', + info: '#13c2c2', + gray: '#8c8c8c', + bgLight: '#f6ffed', + bgBlue: '#e6f7ff', + bgGray: '#f5f5f5', + bgOrange: '#fff7e6', }, }; @@ -150,8 +160,7 @@ function DeviceDetailDrawer({ width: 150, render: (date) => { if (!date) return '-'; - const d = new Date(date); - return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`; + return dayjs(date).format('YYYY-MM-DD HH:mm'); }, }, { @@ -205,49 +214,12 @@ function DeviceDetailDrawer({ return typeMap[type?.toLowerCase()] || type || '未知设备'; }, []); - const renderFieldValue = useCallback( - (field, device) => { - const fieldKey = field.field; - if (fieldKey === 'status') return getStatusTag(device.status); + if (!device) return null; - // 优先从device对象获取值,如果没有则从customFields中获取 - let value = device[fieldKey]; - if ( - (value === undefined || value === null) && - device.customFields && - typeof device.customFields === 'object' - ) { - value = device.customFields[fieldKey]; - } - - if (fieldKey === 'type') value = getDeviceTypeName(value); - else if (fieldKey === 'position') - value = `U${device.position} ${device.height ? `(${device.height}U)` : ''}`; - - return ( - - {value !== undefined && value !== null ? value : '-'} - - ); - }, - [getStatusTag, getDeviceTypeName] - ); - - const displayFields = useMemo(() => { - if (tooltipFields && Object.keys(tooltipFields).length > 0) { - return Object.values(tooltipFields).filter(f => f.enabled); - } - - // Default fallback fields if no config - return [ - { field: 'deviceId', label: '设备ID' }, - { field: 'type', label: '设备类型' }, - { field: 'status', label: '设备状态' }, - { field: 'position', label: '位置' }, - { field: 'ipAddress', label: 'IP地址' }, - { field: 'brand', label: '品牌' }, - ]; - }, [tooltipFields]); + // 解析自定义字段 + const customFields = device.customFields || {}; + const standardFields = ['deviceId', 'name', 'type', 'model', 'serialNumber', 'status', 'ipAddress', 'position', 'height', 'powerConsumption', 'purchaseDate', 'warrantyExpiry', 'description']; + const customFieldEntries = Object.entries(customFields).filter(([key]) => !standardFields.includes(key)); const tabItems = [ { @@ -271,7 +243,7 @@ function DeviceDetailDrawer({ key: 'cables', label: ( - + 接线 ({deviceCables.length}) ), @@ -280,7 +252,7 @@ function DeviceDetailDrawer({ {deviceCables.length === 0 ? ( ) : ( - + {deviceCables.map(cable => ( } > -
- -
- - 源设备: - -
- {cable.sourceDevice?.name || '-'} - - {cable.sourcePort} - -
+ + +
源设备
+
+ {cable.sourceDevice?.name || '-'} + + {cable.sourcePort} +
-
- - 目标设备: - -
- {cable.targetDevice?.name || '-'} - - {cable.targetPort} - -
+ + +
目标设备
+
+ {cable.targetDevice?.name || '-'} + + {cable.targetPort} +
- -
- - - - {cable.status === 'normal' - ? '正常' - : cable.status === 'fault' - ? '故障' - : '未连接'} - - - {cable.cableType === 'ethernet' - ? '网线' - : cable.cableType === 'fiber' - ? '光纤' - : '铜缆'} - - {cable.cableLength && {cable.cableLength}m} - - - {cable.description && ( -
- {cable.description} -
- )} + + + + + {cable.status === 'normal' + ? '正常' + : cable.status === 'fault' + ? '故障' + : '未连接'} + + + {cable.cableType === 'ethernet' + ? '网线' + : cable.cableType === 'fiber' + ? '光纤' + : '铜缆'} + + {cable.cableLength && {cable.cableLength}m} + + + {cable.description && ( + +
+ {cable.description} +
+ + )} +
))} @@ -378,7 +340,7 @@ function DeviceDetailDrawer({ ), children: (
-
+
, ]} - width={800} + width={700} destroyOnHidden styles={{ header: { borderBottom: '1px solid #f0f0f0', padding: '16px 24px' }, - body: { padding: '24px' }, + body: { padding: '0', overflow: 'auto' }, }} > {selectedDevice && (
- - - 基本信息 - - } - style={{ borderRadius: '12px', border: '1px solid #f0f0f0' }} - > -
-
- - - {selectedDevice.deviceId || '-'} - + {/* 头部信息区域 */} +
+
+
+ {getDeviceTypeIcon(selectedDevice.type)}
-
- - {selectedDevice.name || '-'} -
-
- - - {selectedDevice.type ? ( - - {getDeviceTypeIcon(selectedDevice.type)} - {getTypeLabel(selectedDevice.type)} - - ) : ( - '-' - )} - -
-
- - - {selectedDevice.model || '-'} - -
-
- - - {selectedDevice.serialNumber || '-'} - -
-
- - - {selectedDevice.ipAddress || '-'} - -
-
- - - {selectedDevice.Rack?.Room?.name || '-'} - -
-
- - - {selectedDevice.Rack?.name || '-'} - -
-
- - - {selectedDevice.position || '-'} - -
-
- - - {selectedDevice.height || '-'} - -
-
- - - {selectedDevice.power || '-'} - -
-
- - - {selectedDevice.status - ? getStatusConfig(selectedDevice.status).text || selectedDevice.status - : '-'} - -
-
- - - {selectedDevice.purchaseDate - ? new Date(selectedDevice.purchaseDate).toLocaleDateString('zh-CN') - : '-'} - -
-
- - - {selectedDevice.warrantyExpiry - ? new Date(selectedDevice.warrantyExpiry).toLocaleDateString('zh-CN') - : '-'} - -
-
- {selectedDevice.description && ( -
- -
- {selectedDevice.description} +
+
+ {selectedDevice.name} +
+
+ {getTypeLabel(selectedDevice.type)} + | + {selectedDevice.deviceId} + | + + {selectedDevice.status ? getStatusConfig(selectedDevice.status).text : '-'} +
+
+
+ + {/* 内容区域 */} +
+ {/* 基本信息卡片 */} + 基本信息} + style={{ marginBottom: '16px', borderRadius: '8px' }} + > + + +
设备型号
+
{selectedDevice.model || '-'}
+ + +
序列号
+
{selectedDevice.serialNumber || '-'}
+ + +
IP地址
+
{selectedDevice.ipAddress || '-'}
+ + +
所在机房
+
{selectedDevice.Rack?.Room?.name || '-'}
+ + +
所在机柜
+
{selectedDevice.Rack?.name || '-'}
+ + +
位置(U)
+
U{selectedDevice.position || '-'}
+ + +
高度
+
{selectedDevice.height ? `${selectedDevice.height}U` : '-'}
+ + +
功率
+
{selectedDevice.power ? `${selectedDevice.power}W` : '-'}
+ + +
状态
+
+ {selectedDevice.status ? getStatusConfig(selectedDevice.status).text : '-'} +
+ +
+
+ + {/* 维保信息卡片 */} + 维保信息} + style={{ marginBottom: '16px', borderRadius: '8px' }} + > + + +
购买日期
+
+ {selectedDevice.purchaseDate + ? new Date(selectedDevice.purchaseDate).toLocaleDateString('zh-CN') + : '-'} +
+ + +
保修到期
+
+ {selectedDevice.warrantyExpiry + ? new Date(selectedDevice.warrantyExpiry).toLocaleDateString('zh-CN') + : '-'} +
+ +
+
+ + {/* 描述信息 */} + {selectedDevice.description && ( + 描述} + style={{ marginBottom: '16px', borderRadius: '8px' }} + > +
+ {selectedDevice.description} +
+
)} - + + {/* 自定义字段卡片 */} + {selectedDevice.customFields && Object.keys(selectedDevice.customFields).length > 0 && ( + 自定义字段} + style={{ borderRadius: '8px' }} + > + + {Object.entries(selectedDevice.customFields).map(([key, value]) => { + // 从 deviceFields 中查找对应的中文显示名称 + const fieldConfig = deviceFields.find(f => f.fieldName === key); + const displayName = fieldConfig?.displayName || key; + return ( + +
{displayName}
+
{String(value)}
+ + ); + })} +
+
+ )} +
)} diff --git a/frontend/src/pages/Login.jsx b/frontend/src/pages/Login.jsx index 979f31b..bd7c11d 100644 --- a/frontend/src/pages/Login.jsx +++ b/frontend/src/pages/Login.jsx @@ -1,18 +1,31 @@ import React, { useState, useEffect } from 'react'; -import { Form, Input, Button, Card, message, Typography, Divider, Space, Alert } from 'antd'; +import { + Form, + Input, + Button, + Card, + message, + Typography, + Divider, + Space, + Alert, + Row, + Col, +} from 'antd'; import { UserOutlined, LockOutlined, MailOutlined, PhoneOutlined, SafetyCertificateOutlined, - RobotOutlined, + CloudServerOutlined, + ArrowLeftOutlined, } from '@ant-design/icons'; import { useNavigate } from 'react-router-dom'; import { useAuth } from '../context/AuthContext'; import { authAPI } from '../api'; -const { Title, Text } = Typography; +const { Title, Text, Paragraph } = Typography; const Login = () => { const [loading, setLoading] = useState(false); @@ -115,359 +128,527 @@ const Login = () => { } }; - const containerStyle = { - minHeight: '100vh', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - background: 'linear-gradient(135deg, #667eea 0%, #764ba2 50%, #6B8DD6 100%)', - padding: '24px', - position: 'relative', - overflow: 'hidden', - }; - - const backgroundDecorationStyle = { - position: 'absolute', - borderRadius: '50%', - filter: 'blur(80px)', - opacity: '0.3', - }; - - const cardStyle = { - width: '100%', - maxWidth: isFirstUser ? 480 : 420, - borderRadius: '20px', - boxShadow: '0 20px 60px rgba(0,0,0,0.25), 0 8px 20px rgba(0,0,0,0.15)', - background: 'rgba(255, 255, 255, 0.95)', - backdropFilter: 'blur(20px)', - border: '1px solid rgba(255, 255, 255, 0.3)', - }; - - const headerStyle = { - textAlign: 'center', - marginBottom: '32px', - paddingTop: '8px', - }; - - const iconContainerStyle = { - width: '80px', - height: '80px', - borderRadius: '20px', - background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - margin: '0 auto 20px', - boxShadow: '0 8px 24px rgba(102, 126, 234, 0.4)', - }; - - const titleStyle = { - fontSize: '26px', - fontWeight: '700', - background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', - WebkitBackgroundClip: 'text', - WebkitTextFillColor: 'transparent', - marginBottom: '8px', - }; - - const subtitleStyle = { - fontSize: '14px', - color: '#8c8c8c', - }; - - const formStyle = { - marginTop: '24px', - }; - - const inputStyle = { - borderRadius: '8px', - height: '48px', - border: '1px solid #e8e8e8', - }; - - const submitButtonStyle = { - width: '100%', - height: '48px', - fontSize: '16px', - fontWeight: '600', - borderRadius: '8px', - background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', - border: 'none', - boxShadow: '0 4px 15px rgba(102, 126, 234, 0.4)', - transition: 'all 0.3s ease', - }; - - const footerStyle = { - textAlign: 'center', - marginTop: '24px', - paddingBottom: '16px', - }; - - const toggleButtonStyle = { - color: '#667eea', - fontWeight: '500', - padding: '4px 8px', - borderRadius: '4px', - transition: 'all 0.3s ease', - }; - - const inputPrefixStyle = { - color: '#667eea', - fontSize: '18px', - }; - - return ( -
+ // 左侧宣传区域组件 + const LeftPanel = () => ( +
+ {/* 背景装饰 */}
+
-
- -
-
- -
- - {isFirstUser ? '创建管理员账户' : unlockMode ? '账户解锁' : 'IDC设备管理系统'} - - - {isFirstUser - ? '首次使用,请创建系统管理员账户' - : unlockMode - ? '输入账户信息以解锁账户' - : '安全登录您的账户'} - +
+
+
- {isFirstUser && ( - - )} - -
- {registerMode ? ( - <> - - } - placeholder="用户名" - style={inputStyle} - /> - + IDC设备 +
+ 管理系统 + - - } - placeholder="真实姓名" - style={inputStyle} - /> - + + 专业的数据中心设备管理平台,提供机房、机柜、设备的全生命周期管理, + 助力企业实现高效的IT资产管理。 + - - } - placeholder="邮箱" - style={inputStyle} - /> - + + +
+
99.9%
+
系统稳定性
+
+ + +
+
24/7
+
全天候监控
+
+ + +
+
100%
+
数据安全
+
+ +
+
+
+ ); - - } - placeholder="手机号(可选)" - style={inputStyle} - /> - + // 获取标题和副标题 + const getHeaderContent = () => { + if (isFirstUser) { + return { + title: '创建管理员账户', + subtitle: '首次使用,请创建系统管理员账户', + }; + } + if (unlockMode) { + return { + title: '账户解锁', + subtitle: '输入账户信息以解锁账户', + }; + } + if (registerMode) { + return { + title: '注册新账户', + subtitle: '填写信息完成账户注册', + }; + } + return { + title: '欢迎回来', + subtitle: '请登录您的账户以继续', + }; + }; - - } - placeholder="密码" - style={inputStyle} - /> - + const headerContent = getHeaderContent(); - ({ - validator(_, value) { - if (!value || getFieldValue('password') === value) { - return Promise.resolve(); - } - return Promise.reject(new Error('两次输入的密码不一致')); - }, - }), - ]} - > - } - placeholder="确认密码" - style={inputStyle} - /> - - - ) : unlockMode ? ( - <> - + return ( + + {/* 左侧区域 - 桌面端显示 */} + + + - - } - placeholder="用户名" - style={inputStyle} - /> - + {/* 右侧登录区域 */} + + {/* 移动端背景 */} +
- - } - placeholder="密码" - style={inputStyle} - /> - - - ) : ( - <> - - } - placeholder="用户名" - style={inputStyle} - /> - - - - } - placeholder="密码" - style={inputStyle} - /> - - + + {/* 返回按钮 */} + {(registerMode || unlockMode) && !isFirstUser && ( + )} - - - - + {/* 头部 */} +
+
+ +
+ + {headerContent.title} + + + {headerContent.subtitle} + +
- {!isFirstUser && ( -
- - 其他方式 - - }> - {unlockMode ? ( - <> + {/* 首次使用提示 */} + {isFirstUser && ( + + )} + + {/* 解锁模式提示 */} + {unlockMode && ( + + )} + + {/* 表单 */} +
+ {registerMode ? ( + <> + + + + } + placeholder="请输入用户名" + style={{ borderRadius: '12px', height: '48px' }} + /> + + + + + } + placeholder="请输入真实姓名" + style={{ borderRadius: '12px', height: '48px' }} + /> + + + + + + + + } + placeholder="请输入邮箱" + style={{ borderRadius: '12px', height: '48px' }} + /> + + + + + } + placeholder="请输入手机号(可选)" + style={{ borderRadius: '12px', height: '48px' }} + /> + + + + + + + + } + placeholder="请输入密码" + style={{ borderRadius: '12px', height: '48px' }} + /> + + + + ({ + validator(_, value) { + if (!value || getFieldValue('password') === value) { + return Promise.resolve(); + } + return Promise.reject(new Error('两次输入的密码不一致')); + }, + }), + ]} + > + } + placeholder="请再次输入密码" + style={{ borderRadius: '12px', height: '48px' }} + /> + + + + + ) : ( + <> + + } + placeholder="请输入用户名" + style={{ borderRadius: '12px', height: '52px' }} + /> + + + + } + placeholder="请输入密码" + style={{ borderRadius: '12px', height: '52px' }} + /> + + + {!registerMode && !unlockMode && ( +
+ +
+ )} + + )} + + + + +
+ + {/* 底部切换 */} + {!isFirstUser && ( +
+ {!unlockMode && !registerMode && ( + } size="large"> - - ) : ( - <> - - + )} - -
- )} - -
+ + {(registerMode || unlockMode) && ( + + 已有账户?{' '} + + + )} +
+ )} +
+ + {/* 移动端底部版权 */} +
+ © 2024 IDC设备管理系统. All rights reserved. +
+ + + {/* 响应式样式 */} + + ); }; diff --git a/frontend/src/pages/SystemSettings.jsx b/frontend/src/pages/SystemSettings.jsx index 09d6140..826e5da 100644 --- a/frontend/src/pages/SystemSettings.jsx +++ b/frontend/src/pages/SystemSettings.jsx @@ -14,6 +14,11 @@ import { Divider, Descriptions, Alert, + Row, + Col, + Typography, + Badge, + Tooltip, } from 'antd'; import { SettingOutlined, @@ -22,12 +27,24 @@ import { InfoCircleOutlined, CheckCircleOutlined, ExclamationCircleOutlined, + LockOutlined, + DatabaseOutlined, + DesktopOutlined, + ClockCircleOutlined, + MailOutlined, + PhoneOutlined, + EnvironmentOutlined, + FileTextOutlined, + SafetyCertificateOutlined, + ReloadOutlined, + SaveOutlined, } from '@ant-design/icons'; import axios from 'axios'; import { useConfig } from '../context/ConfigContext'; const { Option } = Select; const { TabPane } = Tabs; +const { Title, Text } = Typography; const SystemSettings = () => { const [loading, setLoading] = useState(false); @@ -362,82 +379,135 @@ const SystemSettings = () => { return optionsMap[key] || []; }; - const renderGeneralSettings = () => { - const generalKeys = [ - 'site_name', - 'site_logo', - 'timezone', - 'date_format', - 'idle_timeout', - 'max_login_attempts', - 'maintenance_mode', - ]; + // 设置项分组配置 + const settingGroups = { + general: [ + { + title: '站点信息', + icon: , + keys: ['site_name', 'site_logo'], + }, + { + title: '时间设置', + icon: , + keys: ['timezone', 'date_format'], + }, + { + title: '安全设置', + icon: , + keys: ['idle_timeout', 'max_login_attempts', 'maintenance_mode'], + }, + ], + appearance: [ + { + title: '主题颜色', + icon: , + keys: ['primary_color', 'secondary_color'], + }, + { + title: '界面布局', + icon: , + keys: ['compact_mode', 'sidebar_collapsed', 'table_row_height'], + }, + { + title: '动画效果', + icon: , + keys: ['animation_enabled'], + }, + ], + }; + + const renderSettingGroup = (group) => { return ( - -
- {generalKeys.map(key => { - // 确保时区和日期格式使用select类型 - const settingData = { ...settings[key] }; - if (key === 'timezone' || key === 'date_format') { - settingData.type = 'select'; - } - return renderFormItem(key, settingData); - })} - - - - - - -
+ + {group.icon} + {group.title} + + } + style={{ marginBottom: 16, borderRadius: 8 }} + size="small" + > + {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 renderGeneralSettings = () => { + return ( +
+ {settingGroups.general.map(renderSettingGroup)} + + + + + + +
+ ); + }; + const renderAppearanceSettings = () => { - const appearanceKeys = [ - 'primary_color', - 'secondary_color', - 'compact_mode', - 'sidebar_collapsed', - 'table_row_height', - 'animation_enabled', - ]; return ( - -
- - {appearanceKeys.map(key => { - // 确保主题颜色使用select类型 - const settingData = { ...settings[key] }; - if (key === 'primary_color' || key === 'secondary_color') { - settingData.type = 'select'; - } - return renderFormItem(key, settingData); - })} - - - - - - - -
+
+ + {settingGroups.appearance.map(renderSettingGroup)} + + + + + + + ); }; - // 数据备份功能已移除 - const renderAboutPage = () => { const aboutKeys = [ 'app_version', @@ -452,62 +522,104 @@ const SystemSettings = () => { return (
- - - 机柜管理系统 - - {settings.app_version?.value || '1.0.0'} - - - 运行正常 - - - - - -
- {aboutKeys.slice(1).map(key => settings[key] && renderFormItem(key, settings[key]))} - - - - + {/* 系统概览卡片 */} + + + +
+ +
+ + + + 机柜管理系统 + + + 版本 {settings.app_version?.value || '1.0.0'} + | + -
-
+ +
+ {/* 统计信息卡片 */} {systemInfo && ( - - - - {systemInfo.statistics?.devices || 0} - - - {systemInfo.statistics?.racks || 0} - - - {systemInfo.statistics?.rooms || 0} - - - {systemInfo.statistics?.users || 0} - - - - + + + 系统统计 + + } + style={{ marginBottom: 16, borderRadius: 8 }} + size="small" + > + + + +
+ {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?.pid} + + {systemInfo.system?.pid} + {systemInfo.system?.uptime ? `${Math.floor(systemInfo.system.uptime / 3600)}小时${Math.floor((systemInfo.system.uptime % 3600) / 60)}分钟` @@ -526,48 +638,120 @@ const SystemSettings = () => {
)} + + {/* 公司信息卡片 */} + + + 公司信息 + + } + style={{ marginBottom: 16, borderRadius: 8 }} + size="small" + > +
+ + + + } placeholder="请输入公司名称" /> + + + + + } placeholder="请输入联系邮箱" /> + + + + + } placeholder="请输入联系电话" /> + + + + + } placeholder="请输入公司地址" /> + + + + + + + + + + + + + + +
+
); }; + const tabItems = [ + { + key: 'general', + label: ( + + 全局配置 + + ), + children: renderGeneralSettings(), + }, + { + key: 'appearance', + label: ( + + 外观设置 + + ), + children: renderAppearanceSettings(), + }, + { + key: 'about', + label: ( + + 关于系统 + + ), + children: renderAboutPage(), + }, + ]; + return ( -
- - - 全局配置 - - } - key="general" - > - {renderGeneralSettings()} - - - 外观设置 - - } - key="appearance" - > - {renderAppearanceSettings()} - - - 关于 - - } - key="about" - > - {renderAboutPage()} - - +
+ + + 系统设置 + + } + style={{ borderRadius: 12 }} + bodyStyle={{ padding: 0 }} + > + +
); }; -import { LockOutlined } from '@ant-design/icons'; - export default SystemSettings; diff --git a/frontend/src/pages/TicketManagement.jsx b/frontend/src/pages/TicketManagement.jsx index aedaf64..7072dca 100644 --- a/frontend/src/pages/TicketManagement.jsx +++ b/frontend/src/pages/TicketManagement.jsx @@ -20,6 +20,8 @@ import { Popover, InputNumber, Switch, + Row, + Col, } from 'antd'; import { PlusOutlined, @@ -265,6 +267,7 @@ function TicketManagement() { const [deviceSource, setDeviceSource] = useState('select'); const [ticketFields, setTicketFields] = useState(DEFAULT_TICKET_FIELDS); const [loadingFields, setLoadingFields] = useState(true); + const [deviceFields, setDeviceFields] = useState([]); const fetchTickets = useCallback( async (page = 1, pageSize = 10, filters = {}) => { @@ -343,6 +346,17 @@ function TicketManagement() { } }, []); + // 获取设备字段配置 + const fetchDeviceFields = useCallback(async () => { + try { + const response = await axios.get('/api/deviceFields'); + setDeviceFields(response.data || []); + } catch (error) { + console.error('获取设备字段配置失败:', error); + setDeviceFields([]); + } + }, []); + useEffect(() => { fetchTickets(); fetchDevices(); @@ -350,7 +364,8 @@ function TicketManagement() { // 在 ticketFields 加载完成后再加载分类数据 fetchCategories(); }); - }, [fetchTickets, fetchDevices, fetchTicketFields, fetchCategories]); + fetchDeviceFields(); + }, [fetchTickets, fetchDevices, fetchTicketFields, fetchCategories, fetchDeviceFields]); // 处理从设备详情页跳转过来创建工单的情况 useEffect(() => { @@ -1154,74 +1169,131 @@ function TicketManagement() { } key="device" > - - - {selectedTicket.Device.deviceId} - - - {selectedTicket.Device.name} - - - {selectedTicket.Device.type} - - - {selectedTicket.Device.model || '-'} - - - {selectedTicket.Device.serialNumber || '-'} - - - {selectedTicket.Device.brand || '-'} - - - {selectedTicket.Device.roomName || '-'} - - - {selectedTicket.Device.rackName || '-'} - - - {selectedTicket.Device.position || '-'} - - - {selectedTicket.Device.height || '-'} - - - {selectedTicket.Device.ipAddress || '-'} - - - - {selectedTicket.Device.status === 'running' - ? '运行中' - : selectedTicket.Device.status === 'maintenance' - ? '维护中' - : selectedTicket.Device.status === 'fault' - ? '故障' - : selectedTicket.Device.status === 'offline' - ? '离线' - : selectedTicket.Device.status || '-'} - - - - {selectedTicket.Device.purchaseDate - ? dayjs(selectedTicket.Device.purchaseDate).format('YYYY-MM-DD') - : '-'} - - - {selectedTicket.Device.warrantyDate - ? dayjs(selectedTicket.Device.warrantyDate).format('YYYY-MM-DD') - : '-'} - - +
+ {/* 设备基本信息卡片 */} + + + +
设备ID
+
{selectedTicket.Device.deviceId}
+ + +
设备名称
+
{selectedTicket.Device.name}
+ + +
状态
+ + {selectedTicket.Device.status === 'running' + ? '运行中' + : selectedTicket.Device.status === 'maintenance' + ? '维护中' + : selectedTicket.Device.status === 'fault' + ? '故障' + : selectedTicket.Device.status === 'offline' + ? '离线' + : selectedTicket.Device.status || '-'} + + + +
设备类型
+
{selectedTicket.Device.type}
+ + +
型号
+
{selectedTicket.Device.model || '-'}
+ + +
序列号
+
{selectedTicket.Device.serialNumber || '-'}
+ +
+
+ + {/* 位置信息卡片 */} + + + +
所在机房
+
{selectedTicket.Device.roomName || '-'}
+ + +
所在机柜
+
{selectedTicket.Device.rackName || '-'}
+ + +
IP地址
+
{selectedTicket.Device.ipAddress || '-'}
+ + +
位置(U)
+
{selectedTicket.Device.position || '-'}
+ + +
高度(U)
+
{selectedTicket.Device.height || '-'}
+ + +
功耗(W)
+
{selectedTicket.Device.powerConsumption || '-'}
+ +
+
+ + {/* 维保信息卡片 */} + + + +
购买日期
+
{selectedTicket.Device.purchaseDate + ? dayjs(selectedTicket.Device.purchaseDate).format('YYYY-MM-DD') + : '-'}
+ + +
保修到期
+
{selectedTicket.Device.warrantyExpiry + ? dayjs(selectedTicket.Device.warrantyExpiry).format('YYYY-MM-DD') + : '-'}
+ +
+
+ + {/* 描述信息 */} + {selectedTicket.Device.description && ( + +
{selectedTicket.Device.description}
+
+ )} + + {/* 自定义字段卡片 */} + {selectedTicket.Device.customFields && Object.keys(selectedTicket.Device.customFields).length > 0 && ( + + + {Object.entries(selectedTicket.Device.customFields).map(([key, value]) => { + // 从 deviceFields 中查找对应的中文显示名称 + const fieldConfig = deviceFields.find(f => f.fieldName === key); + const displayName = fieldConfig?.displayName || key; + return ( + +
{displayName}
+
{String(value)}
+ + ); + })} +
+
+ )} +
)} @@ -1233,30 +1305,127 @@ function TicketManagement() { } key="operations" > - - {operationRecords.map((record, index) => ( - -
- {record.operationType} -
-
操作人: {record.operatorName || '-'}
-
内容: {record.operationDescription || '-'}
-
- ))} - {operationRecords.length === 0 &&

暂无操作记录

} -
+
+ {operationRecords.length === 0 ? ( +
+ +

暂无操作记录

+
+ ) : ( +
+ {/* 时间线轴线 */} +
+ {operationRecords.map((record, index) => { + const getOperationInfo = type => { + switch (type) { + case 'create': + return { color: '#52c41a', bgColor: '#f6ffed', label: '创建工单' }; + case 'complete': + return { color: '#1890ff', bgColor: '#e6f7ff', label: '完成工单' }; + case 'close': + return { color: '#8c8c8c', bgColor: '#f5f5f5', label: '关闭工单' }; + case 'assign': + return { color: '#722ed1', bgColor: '#f9f0ff', label: '分配工单' }; + case 'update': + return { color: '#fa8c16', bgColor: '#fff7e6', label: '更新工单' }; + default: + return { color: '#fa8c16', bgColor: '#fff7e6', label: type }; + } + }; + const info = getOperationInfo(record.operationType); + return ( +
+ {/* 时间点圆点 */} +
+ {/* 操作卡片 */} + +
+ + {info.label} + + + {dayjs(record.createdAt).format('YYYY-MM-DD HH:mm:ss')} + +
+
+ 操作人: + + {record.operatorName || '-'} + +
+ {record.operationDescription && ( +
+ {record.operationDescription} +
+ )} +
+
+ ); + })} +
+ )} +
)}