diff --git a/src/frontend/src/App.jsx b/src/frontend/src/App.jsx
index 4eb355c..1244fb4 100644
--- a/src/frontend/src/App.jsx
+++ b/src/frontend/src/App.jsx
@@ -2,6 +2,7 @@ import { BrowserRouter, Routes, Route } from 'react-router-dom';
import AppShell from '@/components/system/layout/AppShell';
import buildRoutes from '@/constants/routes/routes';
import { useEffect } from 'react';
+import { Toaster } from '@/components/ui/toaster';
const App = () => {
const isProd = process.env.NODE_ENV === 'production';
@@ -13,6 +14,7 @@ const App = () => {
{buildRoutes()}
+
);
};
diff --git a/src/frontend/src/components/system/layout/github/GithubTransitionCard.jsx b/src/frontend/src/components/system/layout/github/GithubTransitionCard.jsx
index 6af0039..c2f5e31 100644
--- a/src/frontend/src/components/system/layout/github/GithubTransitionCard.jsx
+++ b/src/frontend/src/components/system/layout/github/GithubTransitionCard.jsx
@@ -1,11 +1,12 @@
import { useEffect, useState } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { AnimatePresence, motion } from 'framer-motion';
-import { Box, Button, HStack } from '@chakra-ui/react';
+import { Box, Button, HStack, Switch, Text } from '@chakra-ui/react';
import web from '@/resources/icon/pages/weclome/web.svg';
import githubIcon from '@/resources/icon/pages/weclome/github.svg';
import FadeInWrapper from '@/components/system/layout/FadeInWrapper';
import MotionCard from '@/components/ui/MotionCard';
+import ConfigTool from '@/libs/config/ConfigTool';
const navItems = [
{ label: '面板', path: '/dashboard' },
@@ -25,6 +26,7 @@ const GithubTransitionCard = () => {
const navigate = useNavigate();
const isDashboard = pathname.startsWith('/dashboard');
const [showNavButtons, setShowNavButtons] = useState(false);
+ const [testMode, setTestMode] = useState(false);
useEffect(() => {
setShowNavButtons(false);
@@ -33,6 +35,19 @@ const GithubTransitionCard = () => {
}, 400);
return () => clearTimeout(timer);
}, [isDashboard]);
+
+ useEffect(() => {
+ const config = ConfigTool.load();
+ setTestMode(config.testMode);
+ }, []);
+
+ const handleTestModeToggle = (checked) => {
+ console.log(`切换测试模式..`);
+ setTestMode(checked);
+ const config = ConfigTool.load();
+ ConfigTool.save({ ...config, testMode: checked });
+ };
+
const MotionBox = motion(Box);
return (
@@ -71,29 +86,48 @@ const GithubTransitionCard = () => {
w={'100%'}
px={isDashboard ? 4 : 3}
py={isDashboard ? 3 : 2}
- noHover={isDashboard}
+ disableHover={isDashboard}
>
{isDashboard && showNavButtons && (
-
- {navItems.map((item) => (
-
- ))}
-
+ <>
+
+ {navItems.map((item) => (
+
+ ))}
+
+
+ {'测试模式'}
+
+ handleTestModeToggle(details.checked)}
+ colorPalette={'teal'}
+ zIndex={100}
+ >
+
+
+
+
+
+
+
+ >
)}
@@ -101,5 +135,5 @@ const GithubTransitionCard = () => {
);
};
-
+// TODO 解决组件重复渲染问题
export default GithubTransitionCard;
diff --git a/src/frontend/src/constants/routes/routes.jsx b/src/frontend/src/constants/routes/routes.jsx
index e4ea434..f001118 100644
--- a/src/frontend/src/constants/routes/routes.jsx
+++ b/src/frontend/src/constants/routes/routes.jsx
@@ -2,6 +2,8 @@ import { Route } from 'react-router-dom';
import Welcome from '@/pages/Welcome';
import Dashboard from '@/pages/Dashboard';
import ScanPage from '@/pages/ScanPage';
+import DevicesPage from '@/pages/DevicesPage';
+import ConfigPage from '@/pages/ConfigPage';
/**
* 路由
@@ -11,6 +13,8 @@ const routeList = [
{ path: '/', element: },
{ path: '/dashboard', element: },
{ path: '/dashboard/scan', element: },
+ { path: '/dashboard/devices', element: },
+ { path: '/dashboard/config', element: },
];
const buildRoutes = () =>
diff --git a/src/frontend/src/index.js b/src/frontend/src/index.js
index d22913e..d021c8d 100644
--- a/src/frontend/src/index.js
+++ b/src/frontend/src/index.js
@@ -2,13 +2,10 @@ import React from 'react';
import ReactDOM from 'react-dom/client';
import App from '@/App';
import { Provider } from '@/components/ui/provider';
-import { NotificationProvider } from '@/libs/system/Notification';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
-
-
-
+
);
diff --git a/src/frontend/src/libs/common.js b/src/frontend/src/libs/common.js
new file mode 100644
index 0000000..a523457
--- /dev/null
+++ b/src/frontend/src/libs/common.js
@@ -0,0 +1,21 @@
+const Common = {
+ /**
+ * 睡眠指定毫秒
+ * @param {number} ms - 毫秒数,必须是非负数
+ * @returns {Promise}
+ */
+ async sleep(ms) {
+ if (!Number.isFinite(ms)) {
+ return Promise.resolve();
+ }
+ if (ms === 0) {
+ return Promise.resolve();
+ }
+ return new Promise((resolve) => {
+ const timer = setTimeout(resolve, ms);
+ return () => clearTimeout(timer);
+ });
+ },
+};
+
+export default Common;
diff --git a/src/frontend/src/libs/config/ConfigTool.js b/src/frontend/src/libs/config/ConfigTool.js
index adda66c..49a26fb 100644
--- a/src/frontend/src/libs/config/ConfigTool.js
+++ b/src/frontend/src/libs/config/ConfigTool.js
@@ -1,18 +1,26 @@
const CONFIG_KEY = 'app_config';
+/**
+ * 默认配置
+ * @type {{backendUrl: string, authKey: string, testMode: boolean, stats: {totalDevices: number, onlineDevices: number, lastScan: string}, devices: *[]}}
+ */
export const defaultConfig = {
backendUrl: '',
authKey: '',
+ testMode: false,
+ stats: {
+ totalDevices: 0,
+ onlineDevices: 0,
+ lastScan: '',
+ },
+ devices: [],
};
/**
- * 浏览器环境配置操作工具
+ * 配置管理工具
+ * @type {{load: ((function(): (any))|*), save: ((function(*): (boolean|undefined))|*), clear: ((function(): (boolean|undefined))|*), getConfigPath: (function(): string), isStorageAvailable: ((function(): (boolean|undefined))|*), getStats: (function(): *)}}
*/
const ConfigTool = {
- /**
- * 从本地存储读取配置
- * @returns {{backendUrl: string, authKey: string}}
- */
load: () => {
try {
const stored = localStorage.getItem(CONFIG_KEY);
@@ -27,15 +35,13 @@ const ConfigTool = {
},
/**
- * 保存配置到本地存储
- * @param {Object} config - 要保存的配置对象
- * @returns {boolean} 是否保存成功
+ * 保存配置
+ * @param config
+ * @returns {boolean}
*/
save: (config) => {
try {
localStorage.setItem(CONFIG_KEY, JSON.stringify(config));
- //console.log(`正在保存配置:${JSON.stringify(config)}`);
- //console.log(`测试读取配置:${JSON.stringify(ConfigTool.load())}`);
return true;
} catch (e) {
console.error('保存配置失败:', e);
@@ -48,7 +54,7 @@ const ConfigTool = {
/**
* 清除配置
- * @returns {boolean} 是否清除成功
+ * @returns {boolean}
*/
clear: () => {
try {
@@ -60,16 +66,8 @@ const ConfigTool = {
}
},
- /**
- * 获取存储标识(浏览器环境返回固定值)
- * @returns {string}
- */
getConfigPath: () => `localStorage:${CONFIG_KEY}`,
- /**
- * 检查存储是否可用
- * @returns {boolean}
- */
isStorageAvailable: () => {
try {
const testKey = 'test';
@@ -80,6 +78,11 @@ const ConfigTool = {
return false;
}
},
+
+ getStats: () => {
+ const config = ConfigTool.load();
+ return config.stats || { totalDevices: 0, onlineDevices: 0, lastScan: '' };
+ },
};
export default ConfigTool;
diff --git a/src/frontend/src/libs/system/Notification.jsx b/src/frontend/src/libs/system/Notification.jsx
index ae772e0..5bcd461 100644
--- a/src/frontend/src/libs/system/Notification.jsx
+++ b/src/frontend/src/libs/system/Notification.jsx
@@ -1,114 +1,99 @@
-import React, { createContext, useContext, useState, useCallback } from 'react';
-import { Box, Button, Icon, Text } from '@chakra-ui/react';
-import { AnimatePresence, motion } from 'framer-motion';
-import { AiOutlineInfoCircle, AiFillWarning } from 'react-icons/ai';
+import { toaster } from '@/components/ui/toaster';
+import {
+ AiOutlineInfoCircle,
+ AiFillWarning,
+ AiFillCheckCircle,
+ AiFillExclamationCircle,
+} from 'react-icons/ai';
-const NotificationContext = createContext(null);
-
-/**
- * 通知hook
- * @returns {null}
- */
-export const useNotification = () => useContext(NotificationContext);
-
-/**
- * 通知根组件
- * @param children
- * @returns {JSX.Element}
- * @constructor
- */
-export const NotificationProvider = ({ children }) => {
- const [notifications, setNotifications] = useState([]);
-
- const addNotification = useCallback((notification) => {
- const id = Date.now() + Math.random();
- setNotifications((prev) => [...prev, { ...notification, id }]);
-
- setTimeout(() => {
- setNotifications((prev) => prev.filter((n) => n.id !== id));
- }, 3000);
- }, []);
-
- const notify = {
- info: ({ title, description, button }) =>
- addNotification({
- type: 'info',
- title,
- description,
- button,
- }),
- error: ({ title, description, button }) =>
- addNotification({
- type: 'error',
- title,
- description,
- button,
- }),
- };
-
- const MotionBox = motion(Box);
-
- // TODO 弹窗颜色问题及重新渲染问题
- return (
-
- {children}
-
-
- {notifications.map((item) => (
-
-
-
-
-
- {item.title}
-
-
- {item.description}
- {item.button && (
-
- )}
-
-
- ))}
-
-
-
- );
+const surfaceStyle = {
+ borderRadius: '1rem',
+ border: '1px solid rgba(255,255,255,0.3)',
+ backdropFilter: 'blur(10px)',
+ background: 'rgba(255, 255, 255, 0.1)',
+ color: 'white',
};
+
+/**
+ * 通用通知组件
+ * @type {{info({title: *, description: *, button: *}): void, success({title: *, description: *, button: *}): void, warn({title: *, description: *, button: *}): void, error({title: *, description: *, button: *}): void}}
+ */
+const Notification = {
+ /**
+ * 信息
+ * @param title 标题
+ * @param description 描述
+ * @param button 按钮
+ */
+ info({ title, description, button }) {
+ toaster.create({
+ title,
+ description,
+ type: 'info',
+ duration: 3000,
+ icon: ,
+ actionLabel: button?.label,
+ action: button?.onClick,
+ style: surfaceStyle,
+ });
+ },
+
+ /**
+ * 成功信息
+ * @param title 标题
+ * @param description 描述
+ * @param button 按钮
+ */
+ success({ title, description, button }) {
+ toaster.create({
+ title,
+ description,
+ type: 'success',
+ duration: 3000,
+ icon: ,
+ actionLabel: button?.label,
+ action: button?.onClick,
+ style: surfaceStyle,
+ });
+ },
+
+ /**
+ * 警告
+ * @param title 标题
+ * @param description 描述
+ * @param button 按钮
+ */
+ warn({ title, description, button }) {
+ toaster.create({
+ title,
+ description,
+ type: 'warning',
+ duration: 3000,
+ icon: ,
+ actionLabel: button?.label,
+ action: button?.onClick,
+ style: surfaceStyle,
+ });
+ },
+
+ /**
+ * 错误提示
+ * @param title 标题
+ * @param description 描述
+ * @param button 按钮
+ */
+ error({ title, description, button }) {
+ toaster.create({
+ title,
+ description,
+ type: 'error',
+ duration: 3000,
+ icon: ,
+ actionLabel: button?.label,
+ action: button?.onClick,
+ style: surfaceStyle,
+ });
+ },
+};
+
+export default Notification;
diff --git a/src/frontend/src/pages/ConfigPage.jsx b/src/frontend/src/pages/ConfigPage.jsx
new file mode 100644
index 0000000..d0704bc
--- /dev/null
+++ b/src/frontend/src/pages/ConfigPage.jsx
@@ -0,0 +1,114 @@
+import React, { useEffect, useState } from 'react';
+import { Box, VStack, Heading, Textarea, Button, Select, Text } from '@chakra-ui/react';
+import DocumentTitle from '@/components/system/pages/DocumentTitle';
+import PageContainer from '@/components/system/PageContainer';
+import DashboardBackground from '@/components/system/pages/DashboardBackground';
+import FadeInWrapper from '@/components/system/layout/FadeInWrapper';
+import ConfigTool from '@/libs/config/ConfigTool';
+import { api } from '@/services/api/api';
+import Notification from '@/libs/system/Notification';
+
+const ConfigPage = () => {
+ const [devices, setDevices] = useState([]);
+ const [selectedDevice, setSelectedDevice] = useState('');
+ const [inputText, setInputText] = useState('');
+ const [parsedConfig, setParsedConfig] = useState('');
+ const [loading, setLoading] = useState(false);
+
+ useEffect(() => {
+ const config = ConfigTool.load();
+ setDevices(config.devices || []);
+ }, []);
+
+ const handleParse = async () => {
+ if (!selectedDevice || !inputText) {
+ Notification.error({ title: '请选择设备并输入配置指令' });
+ return;
+ }
+ setLoading(true);
+ try {
+ const res = await api.parseCommand(inputText);
+ setParsedConfig(res.config);
+ } catch (e) {
+ Notification.error({ title: '配置解析失败' });
+ }
+ setLoading(false);
+ };
+
+ const handleApply = async () => {
+ if (!parsedConfig) return;
+ setLoading(true);
+ try {
+ await api.applyConfig(selectedDevice, parsedConfig);
+ Notification.success({ title: '配置已成功应用!' });
+ } catch (e) {
+ Notification.error({ title: '配置应用失败' });
+ }
+ setLoading(false);
+ };
+
+ return (
+
+
+
+
+
+
+ {'交换机配置中心'}
+
+
+
+
+
+
+
+
+ );
+};
+
+export default ConfigPage;
diff --git a/src/frontend/src/pages/Dashboard.jsx b/src/frontend/src/pages/Dashboard.jsx
index d878cbb..3728090 100644
--- a/src/frontend/src/pages/Dashboard.jsx
+++ b/src/frontend/src/pages/Dashboard.jsx
@@ -6,7 +6,9 @@ import DashboardBackground from '@/components/system/pages/DashboardBackground';
import FadeInWrapper from '@/components/system/layout/FadeInWrapper';
import FeatureCard from '@/components/pages/dashboard/FeatureCard';
import StatCard from '@/components/pages/dashboard/StatCard';
-import { useNotification } from '@/libs/system/Notification';
+import api from '@/services/api/api';
+import ConfigTool from '@/libs/config/ConfigTool';
+import Notification from '@/libs/system/Notification';
/**
* 控制台
@@ -21,24 +23,24 @@ const Dashboard = () => {
});
const [networkStatus, setNetworkStatus] = useState('loading'); // loading | ok | fail
- const notify = useNotification();
const checkBackend = useCallback(async () => {
setNetworkStatus('loading');
try {
- const res = await fetch('/api/test');
- if (res.ok) {
+ const res = await api.test();
+ if (res) {
setNetworkStatus('ok');
- notify.info({ title: '成功连接至后端服务!' });
+ console.log(JSON.stringify(res));
+ Notification.info({ title: '成功连接至后端服务!' });
} else {
setNetworkStatus('fail');
- notify.error({ title: '后端服务响应异常!' });
+ Notification.error({ title: '后端服务响应异常!' });
}
} catch (err) {
setNetworkStatus('fail');
- notify.error({ title: '无法连接到后端服务!' });
+ Notification.error({ title: '无法连接到后端服务!' });
}
- }, [notify]);
+ }, [Notification]);
useEffect(() => {
const timer = setTimeout(() => {
@@ -47,6 +49,14 @@ const Dashboard = () => {
return () => clearTimeout(timer);
}, [checkBackend]);
+ useEffect(() => {
+ const loadStats = () => {
+ const stats = ConfigTool.getStats();
+ setStats(stats);
+ };
+ loadStats();
+ }, []);
+
return (
@@ -97,29 +107,17 @@ const Dashboard = () => {
{networkStatus === 'loading' && (
-
+
{'检测网络中...'}
)}
{networkStatus === 'ok' && (
-
+
{'网络连接正常'}
)}
{networkStatus === 'fail' && (
-
+
{'无法连接后端'}
)}
diff --git a/src/frontend/src/pages/DevicesPage.jsx b/src/frontend/src/pages/DevicesPage.jsx
new file mode 100644
index 0000000..0826dbf
--- /dev/null
+++ b/src/frontend/src/pages/DevicesPage.jsx
@@ -0,0 +1,135 @@
+import React, { useEffect, useState } from 'react';
+import {
+ Box,
+ VStack,
+ Heading,
+ Text,
+ SimpleGrid,
+ Input,
+ Button,
+ HStack,
+ Image,
+ Collapsible,
+} from '@chakra-ui/react';
+import DocumentTitle from '@/components/system/pages/DocumentTitle';
+import PageContainer from '@/components/system/PageContainer';
+import DashboardBackground from '@/components/system/pages/DashboardBackground';
+import FadeInWrapper from '@/components/system/layout/FadeInWrapper';
+import ConfigTool from '@/libs/config/ConfigTool';
+import Common from '@/libs/common';
+
+/**
+ * 交换机管理
+ * @returns {JSX.Element}
+ * @constructor
+ */
+const DevicesPage = () => {
+ const [devices, setDevices] = useState([]);
+ const [editingIndex, setEditingIndex] = useState(null);
+ const [editingName, setEditingName] = useState('');
+
+ useEffect(() => {
+ const config = ConfigTool.load();
+ setDevices(config.devices || []);
+ }, []);
+
+ const handleEdit = (idx) => {
+ setEditingIndex(idx);
+ setEditingName(devices[idx].name);
+ };
+
+ const handleSave = (idx) => {
+ const updated = [...devices];
+ updated[idx].name = editingName;
+ setDevices(updated);
+ ConfigTool.save({ ...ConfigTool.load(), devices: updated });
+ Common.sleep(500);
+ setEditingIndex(null);
+ };
+
+ return (
+
+
+
+
+
+
+ {'交换机设备管理'}
+
+
+
+ {devices.map((device, idx) => (
+
+
+
+
+
+
+ {editingIndex === idx ? null : (
+
+
+ {device.name}
+
+
+
+ )}
+
+
+
+
+
+ setEditingName(e.target.value)}
+ size={'sm'}
+ bg={'whiteAlpha.300'}
+ />
+
+
+
+
+
+
+ {'IP: '}
+ {device.ip}
+
+
+ {'MAC: '}
+ {device.mac}
+
+
+ {'端口: '}
+ {device.ports.join(', ')}
+
+
+ ))}
+
+
+ {devices.length === 0 && (
+ {'暂无扫描记录,请先进行网络扫描'}
+ )}
+
+
+
+
+ );
+};
+
+export default DevicesPage;
diff --git a/src/frontend/src/pages/ScanPage.jsx b/src/frontend/src/pages/ScanPage.jsx
index 6e9fdb7..0ed1a8e 100644
--- a/src/frontend/src/pages/ScanPage.jsx
+++ b/src/frontend/src/pages/ScanPage.jsx
@@ -16,7 +16,9 @@ import PageContainer from '@/components/system/PageContainer';
import DashboardBackground from '@/components/system/pages/DashboardBackground';
import FadeInWrapper from '@/components/system/layout/FadeInWrapper';
import { api } from '@/services/api/api';
-import { useNotification } from '@/libs/system/Notification';
+import ConfigTool from '@/libs/config/ConfigTool';
+import Common from '@/libs/common';
+import Notification from '@/libs/system/Notification';
/**
* 网络扫描页面
@@ -28,7 +30,9 @@ const ScanPage = () => {
const [devices, setDevices] = useState([]);
const [loading, setLoading] = useState(false);
const [localIp, setLocalIp] = useState('');
- const notify = useNotification();
+
+ const config = ConfigTool.load();
+ const testMode = config.testMode;
useEffect(() => {
const fetchLocalInfo = async () => {
@@ -39,15 +43,51 @@ const ScanPage = () => {
}
};
fetchLocalInfo();
- }, [subnet, notify]);
+ }, [subnet, Notification]);
+
+ const getTestDevices = () => {
+ return [
+ { ip: '192.168.1.1', mac: '00:1A:2B:3C:4D:5E', ports: [22, 23, 161] },
+ { ip: '192.168.1.2', mac: '00:1D:7D:AA:1B:01', ports: [22, 23, 161] },
+ { ip: '192.168.1.3', mac: '00:0C:29:5A:3B:11', ports: [22, 23, 161, 443] },
+ { ip: '192.168.1.4', mac: '00:23:CD:FF:10:02', ports: [22, 23] },
+ { ip: '192.168.1.5', mac: '00:04:4B:AA:BB:CC', ports: [22, 23, 80, 443] },
+ { ip: '192.168.1.6', mac: '00:11:22:33:44:55', ports: [22, 23, 161] },
+ { ip: '192.168.1.7', mac: '00:1F:45:67:89:AB', ports: [23] },
+ { ip: '192.168.1.8', mac: '00:50:56:11:22:33', ports: [22, 443, 8443] },
+ ];
+ };
const handleScan = async () => {
setLoading(true);
try {
- const res = await api.scan(subnet);
- setDevices(res.devices || []);
+ let scanDevices;
+ if (testMode) {
+ await Common.sleep(2000);
+ scanDevices = getTestDevices();
+ } else {
+ const res = await api.scan(subnet);
+ scanDevices = res.devices || [];
+ }
+
+ scanDevices = scanDevices.map((d, idx) => ({
+ ...d,
+ name: `交换机 ${idx + 1}`,
+ }));
+ setDevices(scanDevices);
+ const updatedStats = {
+ totalDevices: scanDevices.length,
+ onlineDevices: scanDevices.length,
+ lastScan: new Date().toISOString(),
+ };
+
+ ConfigTool.save({
+ ...config,
+ stats: updatedStats,
+ devices: scanDevices,
+ });
} catch (err) {
- notify.error({ title: '扫描网络失败' });
+ Notification.error({ title: '扫描网络失败' });
}
setLoading(false);
};
@@ -55,10 +95,17 @@ const ScanPage = () => {
const handleLastScan = async () => {
setLoading(true);
try {
- const res = await api.listDevices();
- setDevices(res.devices || []);
+ let scanDevices;
+ if (testMode) {
+ await Common.sleep(2000);
+ scanDevices = getTestDevices();
+ } else {
+ const res = await api.listDevices();
+ scanDevices = res.devices || [];
+ }
+ setDevices(scanDevices);
} catch (err) {
- notify.error({ title: '获取上次扫描记录失败' });
+ Notification.error({ title: '获取上次扫描记录失败' });
}
setLoading(false);
};
@@ -122,24 +169,50 @@ const ScanPage = () => {
)}
{!loading && devices.length > 0 && (
-
-
-
- {'IP 地址'}
- {'MAC 地址'}
- {'开放端口'}
-
-
-
- {devices.map((d) => (
-
- {d.ip}
- {d.mac}
- {d.ports.join(', ')}
+
+
+
+
+
+ {'IP 地址'}
+
+
+ {'MAC 地址'}
+
+
+ {'开放端口'}
+
- ))}
-
-
+
+
+ {devices.map((d) => (
+
+ {d.ip}
+ {d.mac}
+ {d.ports.join(', ')}
+
+ ))}
+
+
+
)}
{!loading && devices.length === 0 && (
diff --git a/src/frontend/src/services/api/api.js b/src/frontend/src/services/api/api.js
index 47a9b84..812d350 100644
--- a/src/frontend/src/services/api/api.js
+++ b/src/frontend/src/services/api/api.js
@@ -2,38 +2,16 @@ import axios from 'axios';
import ConfigTool from '@/libs/config/ConfigTool';
/**
- * 创建带基础URL的axios实例
+ * 动态拼接完整URL
*/
-const apiClient = axios.create({
- baseURL: ConfigTool.load().backendUrl || '',
- timeout: 10000,
- headers: {
- 'Content-Type': 'application/json',
- },
-});
-
-// 请求拦截器
-apiClient.interceptors.request.use(
- (config) => {
- const cfg = ConfigTool.load();
- if (cfg?.authKey) {
- config.headers['Authorization'] = `Bearer ${cfg.authKey}`;
- }
- return config;
- },
- (error) => {
- return Promise.reject(error);
+const buildUrl = (path) => {
+ const config = ConfigTool.load();
+ let baseUrl = config.backendUrl || '';
+ if (baseUrl.endsWith('/')) {
+ baseUrl = baseUrl.slice(0, -1);
}
-);
-
-// 响应拦截器
-apiClient.interceptors.response.use(
- (response) => response.data,
- (error) => {
- console.error('API请求错误:', error);
- return Promise.reject(error.response?.data || error.message);
- }
-);
+ return `${baseUrl}${path}`;
+};
/**
* API模块
@@ -43,27 +21,27 @@ export const api = {
* 测试API连接
* @returns {Promise>}
*/
- test: () => apiClient.get('/api/test'),
+ test: () => axios.get(buildUrl('/api/test')),
/**
* 扫描网络
* @param subnet 子网地址
* @returns {Promise>}
*/
- scan: (subnet) => apiClient.get(`/api/scan_network`, { params: { subnet } }),
+ scan: (subnet) => axios.get(buildUrl('/api/scan_network'), { params: { subnet } }),
/**
* 列出所有设备
* @returns {Promise>}
*/
- listDevices: () => apiClient.get('/api/list_devices'),
+ listDevices: () => axios.get(buildUrl('/api/list_devices')),
/**
* 解析命令
* @param text 文本
* @returns {Promise>}
*/
- parseCommand: (text) => apiClient.post('/api/parse_command', { command: text }),
+ parseCommand: (text) => axios.post(buildUrl('/api/parse_command'), { command: text }),
/**
* 应用配置
@@ -71,7 +49,8 @@ export const api = {
* @param config 配置
* @returns {Promise>}
*/
- applyConfig: (switch_ip, config) => apiClient.post('/api/apply_config', { switch_ip, config }),
+ applyConfig: (switch_ip, config) =>
+ axios.post(buildUrl('/api/apply_config'), { switch_ip, config }),
/**
* 更新基础URL
@@ -81,7 +60,6 @@ export const api = {
const config = ConfigTool.load();
config.backendUrl = url;
ConfigTool.save(config);
- apiClient.defaults.baseURL = url;
},
};
@@ -97,4 +75,4 @@ export const getConfig = () => ConfigTool.load();
*/
export const getBaseUrl = () => ConfigTool.load().backendUrl || '';
-export default apiClient;
+export default api;