From 7f007f50104bdf2fab5bbd779333133cbebf7137 Mon Sep 17 00:00:00 2001 From: zhang1106 <849185023@qq.com> Date: Tue, 10 Mar 2026 15:49:02 +0800 Subject: [PATCH] =?UTF-8?q?refactor(=E5=89=8D=E7=AB=AF):=20=E9=9B=86?= =?UTF-8?q?=E4=B8=AD=E7=AE=A1=E7=90=86=E8=AE=BE=E8=AE=A1=E4=BB=A4=E7=89=8C?= =?UTF-8?q?=E5=92=8C=E5=B7=A5=E5=85=B7=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/CableCreateModal.jsx | 202 ++++++------------ .../src/components/DeviceDetailDrawer.jsx | 16 +- .../src/components/NetworkCardCreateModal.jsx | 12 +- frontend/src/components/NetworkCardPanel.jsx | 12 +- frontend/src/components/PortCreateModal.jsx | 13 +- .../src/components/PortManagementPanel.jsx | 12 +- .../src/components/ServerBackplanePanel.jsx | 12 +- frontend/src/config/theme.js | 39 +++- frontend/src/pages/CableManagement.jsx | 76 +------ frontend/src/pages/ConsumableManagement.jsx | 63 +----- frontend/src/pages/ConsumableStatistics.jsx | 61 +----- frontend/src/pages/DeviceFieldManagement.jsx | 70 +----- frontend/src/pages/PortManagement.jsx | 76 +------ frontend/src/pages/RackManagement.jsx | 45 +--- frontend/src/pages/RoomManagement.jsx | 41 +--- frontend/src/pages/TicketManagement.jsx | 24 +-- frontend/src/utils/common.js | 47 ++++ 17 files changed, 158 insertions(+), 663 deletions(-) create mode 100644 frontend/src/utils/common.js diff --git a/frontend/src/components/CableCreateModal.jsx b/frontend/src/components/CableCreateModal.jsx index 725ec8c..1a19852 100644 --- a/frontend/src/components/CableCreateModal.jsx +++ b/frontend/src/components/CableCreateModal.jsx @@ -2,21 +2,10 @@ import React, { useState, useEffect, useCallback, useRef } from 'react'; import { Modal, Form, Select, Input, Button, message, Space, Spin } from 'antd'; import { SwapOutlined } from '@ant-design/icons'; import axios from 'axios'; +import { debounce } from '../utils/common'; const { Option } = Select; -const debounce = (func, wait) => { - let timeout; - return function executedFunction(...args) { - const later = () => { - clearTimeout(timeout); - func(...args); - }; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - }; -}; - const CableCreateModal = ({ visible, onClose, onSuccess, sourceDevice }) => { const [form] = Form.useForm(); const [loading, setLoading] = useState(false); @@ -50,7 +39,6 @@ const CableCreateModal = ({ visible, onClose, onSuccess, sourceDevice }) => { [fetchDevices] ); - // Initialize when visible changes from false to true useEffect(() => { if (visible && !prevVisibleRef.current) { form.resetFields(); @@ -101,13 +89,11 @@ const CableCreateModal = ({ visible, onClose, onSuccess, sourceDevice }) => { const values = await form.validateFields(); setLoading(true); - // Logic to ensure Switch is always Source if possible (for Cable Management view consistency) const sourceDev = devices.find(d => d.deviceId === values.sourceDeviceId); const targetDev = devices.find(d => d.deviceId === values.targetDeviceId); let payload = { ...values }; - // If Source is NOT Switch AND Target IS Switch, swap them if (sourceDev && targetDev && sourceDev.type !== 'switch' && targetDev.type === 'switch') { payload = { ...values, @@ -148,135 +134,67 @@ const CableCreateModal = ({ visible, onClose, onSuccess, sourceDevice }) => { maskClosable={false} >
-
- {/* Source Side */} -
-
源设备 (起点)
- - - - - - -
+ + + - {/* Target Side */} -
-
目标设备 (终点)
- - - - - - -
-
+ + + -
-
- - - - - - - - - -
- - - -
+ + + + + + + + + + +
); diff --git a/frontend/src/components/DeviceDetailDrawer.jsx b/frontend/src/components/DeviceDetailDrawer.jsx index 0032d1f..c4b58a0 100644 --- a/frontend/src/components/DeviceDetailDrawer.jsx +++ b/frontend/src/components/DeviceDetailDrawer.jsx @@ -33,25 +33,11 @@ import { } from '@ant-design/icons'; import NetworkCardPanel from './NetworkCardPanel'; import { deviceAPI } from '../api'; +import { designTokens } from '../config/theme'; import dayjs from 'dayjs'; const { Text, Title } = Typography; -const designTokens = { - colors: { - primary: '#1890ff', - success: '#52c41a', - error: '#f5222d', - warning: '#faad14', - info: '#13c2c2', - gray: '#8c8c8c', - bgLight: '#f6ffed', - bgBlue: '#e6f7ff', - bgGray: '#f5f5f5', - bgOrange: '#fff7e6', - }, -}; - function DeviceDetailDrawer({ device, visible, diff --git a/frontend/src/components/NetworkCardCreateModal.jsx b/frontend/src/components/NetworkCardCreateModal.jsx index da09e69..0e080b2 100644 --- a/frontend/src/components/NetworkCardCreateModal.jsx +++ b/frontend/src/components/NetworkCardCreateModal.jsx @@ -2,21 +2,11 @@ import React, { useState, useCallback } from 'react'; import { Modal, Form, Input, InputNumber, Select, message, Space, Tooltip } from 'antd'; import { PlusOutlined, InfoCircleOutlined, CloudServerOutlined } from '@ant-design/icons'; import axios from 'axios'; +import { designTokens } from '../config/theme'; const { Option } = Select; const { TextArea } = Input; -const designTokens = { - colors: { - primary: { - main: '#667eea', - }, - }, - borderRadius: { - medium: '10px', - }, -}; - function NetworkCardCreateModal({ device, visible, onClose, onSuccess }) { const [form] = Form.useForm(); const [loading, setLoading] = useState(false); diff --git a/frontend/src/components/NetworkCardPanel.jsx b/frontend/src/components/NetworkCardPanel.jsx index b76f187..c7cd29c 100644 --- a/frontend/src/components/NetworkCardPanel.jsx +++ b/frontend/src/components/NetworkCardPanel.jsx @@ -23,20 +23,10 @@ import { import axios from 'axios'; import PortCreateModal from './PortCreateModal'; import NetworkCardCreateModal from './NetworkCardCreateModal'; +import { designTokens } from '../config/theme'; const { Panel } = Collapse; -const designTokens = { - colors: { - primary: { - main: '#667eea', - }, - success: '#10b981', - error: '#ef4444', - warning: '#f59e0b', - }, -}; - function NetworkCardPanel({ deviceId, deviceName, onRefresh, refreshTrigger }) { const [cards, setCards] = useState([]); const [networkCards, setNetworkCards] = useState([]); diff --git a/frontend/src/components/PortCreateModal.jsx b/frontend/src/components/PortCreateModal.jsx index 20570ba..3d60f58 100644 --- a/frontend/src/components/PortCreateModal.jsx +++ b/frontend/src/components/PortCreateModal.jsx @@ -14,22 +14,11 @@ import { } from 'antd'; import { PlusOutlined, InfoCircleOutlined } from '@ant-design/icons'; import axios from 'axios'; +import { designTokens } from '../config/theme'; const { Option } = Select; const { TextArea } = Input; -const designTokens = { - colors: { - primary: { - main: '#667eea', - gradient: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', - }, - }, - borderRadius: { - medium: '10px', - }, -}; - function parsePortRange(portName) { if (!portName || typeof portName !== 'string') { return null; diff --git a/frontend/src/components/PortManagementPanel.jsx b/frontend/src/components/PortManagementPanel.jsx index 14bac10..96786cf 100644 --- a/frontend/src/components/PortManagementPanel.jsx +++ b/frontend/src/components/PortManagementPanel.jsx @@ -9,17 +9,7 @@ import { } from '@ant-design/icons'; import axios from 'axios'; import PortCreateModal from './PortCreateModal'; - -const designTokens = { - colors: { - primary: { - main: '#667eea', - }, - success: '#10b981', - error: '#ef4444', - warning: '#f59e0b', - }, -}; +import { designTokens } from '../config/theme'; function PortManagementPanel({ deviceId, deviceName, onRefresh }) { const [ports, setPorts] = useState([]); diff --git a/frontend/src/components/ServerBackplanePanel.jsx b/frontend/src/components/ServerBackplanePanel.jsx index 3c4b1ef..b02caae 100644 --- a/frontend/src/components/ServerBackplanePanel.jsx +++ b/frontend/src/components/ServerBackplanePanel.jsx @@ -13,20 +13,10 @@ import { } from '@ant-design/icons'; import PortPanel from './PortPanel'; import axios from 'axios'; +import { designTokens } from '../config/theme'; const { Text } = Typography; -const designTokens = { - colors: { - primary: { main: '#667eea', gradient: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' }, - success: '#10b981', - error: '#ef4444', - warning: '#f59e0b', - metal: { light: '#9ca3af', DEFAULT: '#6b7280', dark: '#4b5563' }, - slot: { empty: '#d1d5db', occupied: '#3b82f6' }, - }, -}; - /** * 服务器背板可视化组件 * 按照真实服务器背板布局展示网卡和端口 diff --git a/frontend/src/config/theme.js b/frontend/src/config/theme.js index 6fe9151..446e8d2 100644 --- a/frontend/src/config/theme.js +++ b/frontend/src/config/theme.js @@ -6,28 +6,36 @@ export const designTokens = { colors: { primary: { - main: '#667eea', - gradient: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', - light: '#8b9ff0', - dark: '#4f5db8', + main: '#6366f1', + gradient: 'linear-gradient(135deg, #6366f1 0%, #764ba2 100%)', + light: '#818cf8', + dark: '#4f46e5', + bg: '#eef2ff', }, success: { main: '#10b981', gradient: 'linear-gradient(135deg, #10b981 0%, #059669 100%)', light: '#34d399', dark: '#047857', + bg: '#ecfdf5', }, warning: { main: '#f59e0b', gradient: 'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)', light: '#fbbf24', dark: '#b45309', + bg: '#fffbeb', }, error: { main: '#ef4444', gradient: 'linear-gradient(135deg, #ef4444 0%, #dc2626 100%)', light: '#f87171', dark: '#b91c1c', + bg: '#fef2f2', + }, + info: { + main: '#3b82f6', + bg: '#eff6ff', }, text: { primary: '#1e293b', @@ -46,6 +54,18 @@ export const designTokens = { medium: '#cbd5e1', dark: '#94a3b8', }, + neutral: { + 50: '#f8fafc', + 100: '#f1f5f9', + 200: '#e2e8f0', + 300: '#cbd5e1', + 400: '#94a3b8', + 500: '#64748b', + 600: '#475569', + 700: '#334155', + 800: '#1e293b', + 900: '#0f172a', + }, device: { server: '#3b82f6', switch: '#22c55e', @@ -62,18 +82,29 @@ export const designTokens = { offline: '#6b7280', maintenance: '#3b82f6', }, + sidebar: { + bg: '#1e293b', + text: '#94a3b8', + border: '#334155', + }, }, shadows: { small: '0 1px 2px 0 rgba(0, 0, 0, 0.05)', + sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)', medium: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)', + md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)', large: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)', + lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)', xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1)', glow: '0 0 20px rgba(102, 126, 234, 0.3)', }, borderRadius: { small: '6px', + sm: '6px', medium: '10px', + md: '10px', large: '16px', + lg: '16px', xl: '24px', round: '50%', }, diff --git a/frontend/src/pages/CableManagement.jsx b/frontend/src/pages/CableManagement.jsx index 75c6389..ff3923f 100644 --- a/frontend/src/pages/CableManagement.jsx +++ b/frontend/src/pages/CableManagement.jsx @@ -51,86 +51,14 @@ import axios from 'axios'; import * as XLSX from 'xlsx'; import Papa from 'papaparse'; import { motion, AnimatePresence } from 'framer-motion'; +import { designTokens } from '../config/theme'; +import { debounce } from '../utils/common'; const { Option } = Select; const { Panel } = Collapse; const { Text, Title } = Typography; const { TextArea } = Input; -const debounce = (func, wait) => { - let timeout; - return function executedFunction(...args) { - const later = () => { - clearTimeout(timeout); - func(...args); - }; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - }; -}; - -// 设计令牌 - 现代化配色方案 -const designTokens = { - colors: { - primary: { - main: '#6366f1', - gradient: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', - light: '#818cf8', - dark: '#4f46e5', - bg: '#eef2ff', - }, - success: { - main: '#10b981', - gradient: 'linear-gradient(135deg, #10b981 0%, #059669 100%)', - light: '#34d399', - dark: '#047857', - bg: '#ecfdf5', - }, - warning: { - main: '#f59e0b', - gradient: 'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)', - light: '#fbbf24', - dark: '#b45309', - bg: '#fffbeb', - }, - error: { - main: '#ef4444', - gradient: 'linear-gradient(135deg, #ef4444 0%, #dc2626 100%)', - light: '#f87171', - dark: '#b91c1c', - bg: '#fef2f2', - }, - info: { - main: '#3b82f6', - bg: '#eff6ff', - }, - neutral: { - 50: '#f8fafc', - 100: '#f1f5f9', - 200: '#e2e8f0', - 300: '#cbd5e1', - 400: '#94a3b8', - 500: '#64748b', - 600: '#475569', - 700: '#334155', - 800: '#1e293b', - 900: '#0f172a', - }, - }, - borderRadius: { - sm: '6px', - md: '10px', - lg: '16px', - xl: '20px', - }, - shadows: { - sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)', - md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)', - lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)', - xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1)', - }, -}; - // 动画配置 const animations = { container: { diff --git a/frontend/src/pages/ConsumableManagement.jsx b/frontend/src/pages/ConsumableManagement.jsx index 9e026ce..2e862e8 100644 --- a/frontend/src/pages/ConsumableManagement.jsx +++ b/frontend/src/pages/ConsumableManagement.jsx @@ -48,6 +48,7 @@ import { } from '@ant-design/icons'; import axios from 'axios'; import { motion, AnimatePresence } from 'framer-motion'; +import { designTokens } from '../config/theme'; import { inputStyles, selectStyles, @@ -62,68 +63,6 @@ const { Option } = Select; const { Text, Title } = Typography; const { TextArea } = Input; -// 设计令牌 - 与接线/端口管理页面保持一致 -const designTokens = { - colors: { - primary: { - main: '#6366f1', - gradient: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', - light: '#818cf8', - dark: '#4f46e5', - bg: '#eef2ff', - }, - success: { - main: '#10b981', - gradient: 'linear-gradient(135deg, #10b981 0%, #059669 100%)', - light: '#34d399', - dark: '#047857', - bg: '#ecfdf5', - }, - warning: { - main: '#f59e0b', - gradient: 'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)', - light: '#fbbf24', - dark: '#b45309', - bg: '#fffbeb', - }, - error: { - main: '#ef4444', - gradient: 'linear-gradient(135deg, #ef4444 0%, #dc2626 100%)', - light: '#f87171', - dark: '#b91c1c', - bg: '#fef2f2', - }, - info: { - main: '#3b82f6', - bg: '#eff6ff', - }, - neutral: { - 50: '#f8fafc', - 100: '#f1f5f9', - 200: '#e2e8f0', - 300: '#cbd5e1', - 400: '#94a3b8', - 500: '#64748b', - 600: '#475569', - 700: '#334155', - 800: '#1e293b', - 900: '#0f172a', - }, - }, - borderRadius: { - sm: '6px', - md: '10px', - lg: '16px', - xl: '20px', - }, - shadows: { - sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)', - md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)', - lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)', - xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1)', - }, -}; - // 动画配置 const animations = { container: { diff --git a/frontend/src/pages/ConsumableStatistics.jsx b/frontend/src/pages/ConsumableStatistics.jsx index 6b23081..aad8dce 100644 --- a/frontend/src/pages/ConsumableStatistics.jsx +++ b/frontend/src/pages/ConsumableStatistics.jsx @@ -43,71 +43,12 @@ import { datePickerStyles, inputPlaceholders, } from '../styles/deviceManagementStyles'; +import { designTokens } from '../config/theme'; const { Title, Text } = Typography; const { RangePicker } = DatePicker; const { Option } = Select; -// 设计令牌 - 与其他页面统一 -const designTokens = { - colors: { - primary: { - main: '#6366f1', - light: '#818cf8', - dark: '#4f46e5', - gradient: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', - }, - secondary: { - main: '#ec4899', - light: '#f472b6', - gradient: 'linear-gradient(135deg, #ec4899 0%, #f97316 100%)', - }, - success: { - main: '#10b981', - light: '#34d399', - gradient: 'linear-gradient(135deg, #10b981 0%, #059669 100%)', - }, - warning: { - main: '#f59e0b', - light: '#fbbf24', - gradient: 'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)', - }, - error: { - main: '#ef4444', - light: '#f87171', - gradient: 'linear-gradient(135deg, #ef4444 0%, #dc2626 100%)', - }, - info: { - main: '#3b82f6', - light: '#60a5fa', - }, - background: { - main: '#f8fafc', - card: '#ffffff', - elevated: '#ffffff', - }, - text: { - primary: '#1e293b', - secondary: '#64748b', - muted: '#94a3b8', - }, - border: '#e2e8f0', - }, - shadows: { - small: '0 1px 2px 0 rgba(0, 0, 0, 0.05)', - medium: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)', - large: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)', - glow: '0 0 20px rgba(99, 102, 241, 0.15)', - }, - borderRadius: { - small: '8px', - medium: '12px', - large: '16px', - xl: '20px', - full: '9999px', - }, -}; - // 动画配置 const containerVariants = { hidden: { opacity: 0 }, diff --git a/frontend/src/pages/DeviceFieldManagement.jsx b/frontend/src/pages/DeviceFieldManagement.jsx index b187fd7..ccf4a71 100644 --- a/frontend/src/pages/DeviceFieldManagement.jsx +++ b/frontend/src/pages/DeviceFieldManagement.jsx @@ -28,78 +28,10 @@ import { LockOutlined, } from '@ant-design/icons'; import axios from 'axios'; +import { designTokens } from '../config/theme'; const { Option = Select.Option } = Select; -const designTokens = { - colors: { - primary: { - main: '#667eea', - gradient: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', - light: '#8b9ff0', - dark: '#4f5db8', - }, - success: { - main: '#10b981', - gradient: 'linear-gradient(135deg, #10b981 0%, #059669 100%)', - }, - warning: { - main: '#f59e0b', - gradient: 'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)', - }, - error: { - main: '#ef4444', - gradient: 'linear-gradient(135deg, #ef4444 0%, #dc2626 100%)', - }, - text: { - primary: '#1e293b', - secondary: '#64748b', - tertiary: '#94a3b8', - inverse: '#ffffff', - }, - background: { - primary: '#ffffff', - secondary: '#f8fafc', - tertiary: '#f1f5f9', - }, - border: { - light: '#e2e8f0', - medium: '#cbd5e1', - dark: '#94a3b8', - }, - fieldType: { - string: '#3b82f6', - number: '#10b981', - boolean: '#f59e0b', - select: '#8b5cf6', - date: '#06b6d4', - textarea: '#64748b', - }, - }, - shadows: { - small: '0 1px 2px 0 rgba(0, 0, 0, 0.05)', - medium: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)', - large: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)', - glow: '0 0 20px rgba(102, 126, 234, 0.15)', - }, - borderRadius: { - small: '6px', - medium: '10px', - large: '16px', - }, - transitions: { - fast: '150ms cubic-bezier(0.4, 0, 0.2, 1)', - normal: '300ms cubic-bezier(0.4, 0, 0.2, 1)', - }, - spacing: { - xs: '4px', - sm: '8px', - md: '16px', - lg: '24px', - xl: '32px', - }, -}; - const pageContainerStyle = { minHeight: '100vh', background: designTokens.colors.background.secondary, diff --git a/frontend/src/pages/PortManagement.jsx b/frontend/src/pages/PortManagement.jsx index 7ad4571..aae5c9b 100644 --- a/frontend/src/pages/PortManagement.jsx +++ b/frontend/src/pages/PortManagement.jsx @@ -53,86 +53,14 @@ import { motion, AnimatePresence } from 'framer-motion'; import VirtualDeviceList from '../components/VirtualDeviceList'; import NetworkCardPanel from '../components/NetworkCardPanel'; import NetworkCardCreateModal from '../components/NetworkCardCreateModal'; +import { designTokens } from '../config/theme'; +import { debounce } from '../utils/common'; const { Option } = Select; const { Panel } = Collapse; const { Text, Title } = Typography; const { TextArea } = Input; -const debounce = (func, wait) => { - let timeout; - return function executedFunction(...args) { - const later = () => { - clearTimeout(timeout); - func(...args); - }; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - }; -}; - -// 设计令牌 - 与接线管理页面保持一致 -const designTokens = { - colors: { - primary: { - main: '#6366f1', - gradient: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', - light: '#818cf8', - dark: '#4f46e5', - bg: '#eef2ff', - }, - success: { - main: '#10b981', - gradient: 'linear-gradient(135deg, #10b981 0%, #059669 100%)', - light: '#34d399', - dark: '#047857', - bg: '#ecfdf5', - }, - warning: { - main: '#f59e0b', - gradient: 'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)', - light: '#fbbf24', - dark: '#b45309', - bg: '#fffbeb', - }, - error: { - main: '#ef4444', - gradient: 'linear-gradient(135deg, #ef4444 0%, #dc2626 100%)', - light: '#f87171', - dark: '#b91c1c', - bg: '#fef2f2', - }, - info: { - main: '#3b82f6', - bg: '#eff6ff', - }, - neutral: { - 50: '#f8fafc', - 100: '#f1f5f9', - 200: '#e2e8f0', - 300: '#cbd5e1', - 400: '#94a3b8', - 500: '#64748b', - 600: '#475569', - 700: '#334155', - 800: '#1e293b', - 900: '#0f172a', - }, - }, - borderRadius: { - sm: '6px', - md: '10px', - lg: '16px', - xl: '20px', - }, - shadows: { - sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)', - md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)', - lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)', - xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1)', - }, -}; - // 动画配置 const animations = { container: { diff --git a/frontend/src/pages/RackManagement.jsx b/frontend/src/pages/RackManagement.jsx index 43de7d8..0aa59b4 100644 --- a/frontend/src/pages/RackManagement.jsx +++ b/frontend/src/pages/RackManagement.jsx @@ -50,54 +50,11 @@ import { DeleteFilled, } from '@ant-design/icons'; import axios from 'axios'; +import { designTokens } from '../config/theme'; const { Option } = Select; const { Title, Text } = Typography; -const designTokens = { - colors: { - primary: { - main: '#1890ff', - gradient: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', - bgGradient: 'linear-gradient(135deg, #667eea15 0%, #764ba208 100%)', - }, - success: { - main: '#52c41a', - gradient: 'linear-gradient(135deg, #52c41a 0%, #389e0d 100%)', - }, - warning: { - main: '#faad14', - gradient: 'linear-gradient(135deg, #faad14 0%, #d48806 100%)', - }, - error: { - main: '#ff4d4f', - gradient: 'linear-gradient(135deg, #ff4d4f 0%, #cf1322 100%)', - }, - purple: { - main: '#722ed1', - gradient: 'linear-gradient(135deg, #722ed1 0%, #531dab 100%)', - }, - text: { - primary: '#262626', - secondary: '#8c8c8c', - tertiary: '#bfbfbf', - }, - }, - shadows: { - small: '0 2px 8px rgba(0, 0, 0, 0.06)', - medium: '0 4px 16px rgba(0, 0, 0, 0.08)', - large: '0 8px 24px rgba(0, 0, 0, 0.12)', - }, - borderRadius: { - small: '8px', - medium: '12px', - large: '16px', - }, - transitions: { - normal: '0.3s cubic-bezier(0.4, 0, 0.2, 1)', - }, -}; - const containerStyle = { minHeight: '100vh', background: 'linear-gradient(180deg, #f5f7fa 0%, #e8ecf1 100%)', diff --git a/frontend/src/pages/RoomManagement.jsx b/frontend/src/pages/RoomManagement.jsx index 51db977..5e2e55d 100644 --- a/frontend/src/pages/RoomManagement.jsx +++ b/frontend/src/pages/RoomManagement.jsx @@ -45,50 +45,11 @@ import { DeleteFilled, } from '@ant-design/icons'; import axios from 'axios'; +import { designTokens } from '../config/theme'; const { Option } = Select; const { Title, Text } = Typography; -const designTokens = { - colors: { - primary: { - main: '#1890ff', - gradient: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', - bgGradient: 'linear-gradient(135deg, #667eea15 0%, #764ba208 100%)', - }, - success: { - main: '#52c41a', - gradient: 'linear-gradient(135deg, #52c41a 0%, #389e0d 100%)', - }, - warning: { - main: '#faad14', - gradient: 'linear-gradient(135deg, #faad14 0%, #d48806 100%)', - }, - error: { - main: '#ff4d4f', - gradient: 'linear-gradient(135deg, #ff4d4f 0%, #cf1322 100%)', - }, - text: { - primary: '#262626', - secondary: '#8c8c8c', - tertiary: '#bfbfbf', - }, - }, - shadows: { - small: '0 2px 8px rgba(0, 0, 0, 0.06)', - medium: '0 4px 16px rgba(0, 0, 0, 0.08)', - large: '0 8px 24px rgba(0, 0, 0, 0.12)', - }, - borderRadius: { - small: '8px', - medium: '12px', - large: '16px', - }, - transitions: { - normal: '0.3s cubic-bezier(0.4, 0, 0.2, 1)', - }, -}; - const containerStyle = { minHeight: '100vh', background: 'linear-gradient(180deg, #f5f7fa 0%, #e8ecf1 100%)', diff --git a/frontend/src/pages/TicketManagement.jsx b/frontend/src/pages/TicketManagement.jsx index 897ec21..0564d5d 100644 --- a/frontend/src/pages/TicketManagement.jsx +++ b/frontend/src/pages/TicketManagement.jsx @@ -45,29 +45,7 @@ import { import axios from 'axios'; import dayjs from 'dayjs'; import { useSearchParams } from 'react-router-dom'; - -const debounce = (func, wait) => { - let timeout; - return function executedFunction(...args) { - const later = () => { - clearTimeout(timeout); - func(...args); - }; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - }; -}; - -// 安全地从 localStorage 获取用户信息 -const getUserFromStorage = () => { - try { - const userStr = localStorage.getItem('user'); - return userStr ? JSON.parse(userStr) : {}; - } catch (e) { - console.error('解析用户信息失败:', e); - return {}; - } -}; +import { debounce, getUserFromStorage } from '../utils/common'; const { Option } = Select; const { RangePicker } = DatePicker; diff --git a/frontend/src/utils/common.js b/frontend/src/utils/common.js new file mode 100644 index 0000000..b60aa41 --- /dev/null +++ b/frontend/src/utils/common.js @@ -0,0 +1,47 @@ +export const debounce = (func, wait) => { + let timeout; + return function executedFunction(...args) { + const later = () => { + clearTimeout(timeout); + func(...args); + }; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; +}; + +export const throttle = (func, limit) => { + let inThrottle; + return function executedFunction(...args) { + if (!inThrottle) { + func(...args); + inThrottle = true; + setTimeout(() => (inThrottle = false), limit); + } + }; +}; + +export const getUserFromStorage = () => { + try { + const userStr = localStorage.getItem('user'); + return userStr ? JSON.parse(userStr) : {}; + } catch (e) { + return {}; + } +}; + +export const formatFileSize = (bytes) => { + if (bytes === 0) return '0 B'; + const k = 1024; + const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; +}; + +export const generateId = (prefix = '') => { + return `${prefix}${Date.now()}${Math.random().toString(36).substr(2, 9)}`; +}; + +export const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); + +export const clamp = (value, min, max) => Math.min(Math.max(value, min), max);