refactor(前端): 集中管理设计令牌和工具函数
This commit is contained in:
@@ -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}
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16 }}>
|
||||
{/* Source Side */}
|
||||
<div style={{ padding: '12px', background: '#f9f9f9', borderRadius: '8px' }}>
|
||||
<div style={{ marginBottom: 12, fontWeight: 500, color: '#666' }}>源设备 (起点)</div>
|
||||
<Form.Item
|
||||
name="sourceDeviceId"
|
||||
label="设备"
|
||||
rules={[{ required: true, message: '请选择源设备' }]}
|
||||
>
|
||||
<Select
|
||||
placeholder="输入关键词搜索源设备"
|
||||
showSearch
|
||||
filterOption={false}
|
||||
onSearch={handleDeviceSearch}
|
||||
onDropdownVisibleChange={open => {
|
||||
if (open && devices.length === 0) {
|
||||
fetchDevices();
|
||||
}
|
||||
}}
|
||||
onChange={handleSourceDeviceChange}
|
||||
loading={fetchingDevices}
|
||||
disabled={!!sourceDevice}
|
||||
>
|
||||
{devices.map(d => (
|
||||
<Option key={d.deviceId} value={d.deviceId}>
|
||||
{d.name}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="sourcePort"
|
||||
label="端口"
|
||||
rules={[{ required: true, message: '请选择源端口' }]}
|
||||
>
|
||||
<Select placeholder="选择源端口" showSearch>
|
||||
{sourcePorts.map(p => (
|
||||
<Option key={p.portId} value={p.portName}>
|
||||
{p.portName} ({p.portType})
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</div>
|
||||
<Form.Item label="源设备" name="sourceDeviceId" rules={[{ required: true, message: '请选择源设备' }]}>
|
||||
<Select
|
||||
showSearch
|
||||
filterOption={false}
|
||||
placeholder="搜索设备..."
|
||||
onSearch={handleDeviceSearch}
|
||||
onChange={handleSourceDeviceChange}
|
||||
>
|
||||
{devices.map(device => (
|
||||
<Option key={device.deviceId} value={device.deviceId}>
|
||||
{device.name} ({device.deviceId})
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
{/* Target Side */}
|
||||
<div style={{ padding: '12px', background: '#f9f9f9', borderRadius: '8px' }}>
|
||||
<div style={{ marginBottom: 12, fontWeight: 500, color: '#666' }}>目标设备 (终点)</div>
|
||||
<Form.Item
|
||||
name="targetDeviceId"
|
||||
label="设备"
|
||||
rules={[{ required: true, message: '请选择目标设备' }]}
|
||||
>
|
||||
<Select
|
||||
placeholder="输入关键词搜索目标设备"
|
||||
showSearch
|
||||
filterOption={false}
|
||||
onSearch={handleDeviceSearch}
|
||||
onDropdownVisibleChange={open => {
|
||||
if (open && devices.length === 0) {
|
||||
fetchDevices();
|
||||
}
|
||||
}}
|
||||
onChange={handleTargetDeviceChange}
|
||||
loading={fetchingDevices}
|
||||
>
|
||||
{devices
|
||||
.filter(d => d.deviceId !== form.getFieldValue('sourceDeviceId'))
|
||||
.map(d => (
|
||||
<Option key={d.deviceId} value={d.deviceId}>
|
||||
{d.name}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="targetPort"
|
||||
label="端口"
|
||||
rules={[{ required: true, message: '请选择目标端口' }]}
|
||||
>
|
||||
<Select
|
||||
placeholder="选择目标端口"
|
||||
showSearch
|
||||
disabled={!form.getFieldValue('targetDeviceId')}
|
||||
>
|
||||
{targetPorts.map(p => (
|
||||
<Option key={p.portId} value={p.portName}>
|
||||
{p.portName} ({p.portType})
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</div>
|
||||
<Form.Item label="源端口" name="sourcePort" rules={[{ required: true, message: '请选择源端口' }]}>
|
||||
<Select
|
||||
placeholder="请先选择设备"
|
||||
disabled={!sourcePorts.length}
|
||||
>
|
||||
{sourcePorts.map(port => (
|
||||
<Option key={port.portId} value={port.portName}>
|
||||
{port.portName}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 16 }}>
|
||||
<Form.Item
|
||||
name="cableType"
|
||||
label="线缆类型"
|
||||
initialValue="ethernet"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Select>
|
||||
<Option value="ethernet">网线</Option>
|
||||
<Option value="fiber">光纤</Option>
|
||||
<Option value="copper">铜缆</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="status"
|
||||
label="状态"
|
||||
initialValue="normal"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Select>
|
||||
<Option value="normal">正常</Option>
|
||||
<Option value="fault">故障</Option>
|
||||
<Option value="disconnected">未连接</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item name="cableLength" label="长度 (米)">
|
||||
<Input type="number" min={0} />
|
||||
</Form.Item>
|
||||
</div>
|
||||
<Form.Item name="description" label="描述">
|
||||
<Input.TextArea rows={2} />
|
||||
</Form.Item>
|
||||
</div>
|
||||
<Form.Item label="目标设备" name="targetDeviceId" rules={[{ required: true, message: '请选择目标设备' }]}>
|
||||
<Select
|
||||
showSearch
|
||||
filterOption={false}
|
||||
placeholder="搜索设备..."
|
||||
onSearch={handleDeviceSearch}
|
||||
onChange={handleTargetDeviceChange}
|
||||
>
|
||||
{devices.map(device => (
|
||||
<Option key={device.deviceId} value={device.deviceId}>
|
||||
{device.name} ({device.deviceId})
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="目标端口" name="targetPort" rules={[{ required: true, message: '请选择目标端口' }]}>
|
||||
<Select
|
||||
placeholder="请先选择设备"
|
||||
disabled={!targetPorts.length}
|
||||
>
|
||||
{targetPorts.map(port => (
|
||||
<Option key={port.portId} value={port.portName}>
|
||||
{port.portName}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="备注" name="notes">
|
||||
<Input.TextArea placeholder="接线备注信息" rows={2} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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([]);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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([]);
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* 服务器背板可视化组件
|
||||
* 按照真实服务器背板布局展示网卡和端口
|
||||
|
||||
@@ -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%',
|
||||
},
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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%)',
|
||||
|
||||
@@ -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%)',
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user