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}
>
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
);
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);