mirror of
https://github.com/Jerryplusy/AI-powered-switches.git
synced 2025-07-04 13:19:20 +00:00
优化api模块
This commit is contained in:
parent
921b17e3f5
commit
d6787a11dd
@ -18,12 +18,9 @@ import {
|
|||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import { IoClose } from 'react-icons/io5';
|
import { IoClose } from 'react-icons/io5';
|
||||||
import { FiCheck } from 'react-icons/fi';
|
import { FiCheck } from 'react-icons/fi';
|
||||||
|
import ConfigTool, { defaultConfig } from '@/libs/config/ConfigTool';
|
||||||
|
|
||||||
const MotionBox = motion(Box);
|
const MotionBox = motion(Box);
|
||||||
const defaultConfig = {
|
|
||||||
backendUrl: '',
|
|
||||||
authKey: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 连接地址配置组件
|
* 连接地址配置组件
|
||||||
@ -39,10 +36,7 @@ const ConnectionConfigModal = ({ isOpen, onClose, onSave }) => {
|
|||||||
const backendRef = useRef(null);
|
const backendRef = useRef(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const savedData = localStorage.getItem('connection-config');
|
setConfig(ConfigTool.load());
|
||||||
if (savedData) {
|
|
||||||
setConfig(JSON.parse(savedData));
|
|
||||||
}
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleChange = (e) => {
|
const handleChange = (e) => {
|
||||||
@ -51,7 +45,7 @@ const ConnectionConfigModal = ({ isOpen, onClose, onSave }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
localStorage.setItem('connection-config', JSON.stringify(config));
|
ConfigTool.save(config);
|
||||||
onSave(config);
|
onSave(config);
|
||||||
setSaved(true);
|
setSaved(true);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -61,8 +55,9 @@ const ConnectionConfigModal = ({ isOpen, onClose, onSave }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleClear = () => {
|
const handleClear = () => {
|
||||||
localStorage.removeItem('connection-config');
|
ConfigTool.clear();
|
||||||
setConfig(defaultConfig);
|
setConfig(defaultConfig);
|
||||||
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -155,8 +150,8 @@ const ConnectionConfigModal = ({ isOpen, onClose, onSave }) => {
|
|||||||
color={'white'}
|
color={'white'}
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
isDisabled={saved}
|
isDisabled={saved}
|
||||||
position={'relative'}
|
|
||||||
width={'80px'}
|
width={'80px'}
|
||||||
|
position={'relative'}
|
||||||
_hover={{ bg: 'rgba(0, 0, 255, 0.3)' }}
|
_hover={{ bg: 'rgba(0, 0, 255, 0.3)' }}
|
||||||
>
|
>
|
||||||
<AnimatePresence initial={false} mode={'wait'}>
|
<AnimatePresence initial={false} mode={'wait'}>
|
||||||
|
@ -4,6 +4,7 @@ import FadeInWrapper from '@/components/system/layout/FadeInWrapper';
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import ConnectionConfigModal from '@/components/pages/welcome/ConnectionConfigModal';
|
import ConnectionConfigModal from '@/components/pages/welcome/ConnectionConfigModal';
|
||||||
import ConfigureCard from '@/components/pages/welcome/ConfigureCard';
|
import ConfigureCard from '@/components/pages/welcome/ConfigureCard';
|
||||||
|
import ConfigTool from '@/libs/config/ConfigTool';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 欢迎页面内容
|
* 欢迎页面内容
|
||||||
@ -16,7 +17,7 @@ const WelcomeContent = () => {
|
|||||||
//const [showAlert, setShowAlert] = useState(false);
|
//const [showAlert, setShowAlert] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const saved = localStorage.getItem('connection-config');
|
const saved = ConfigTool.load();
|
||||||
if (saved) {
|
if (saved) {
|
||||||
setIsConfigured(true);
|
setIsConfigured(true);
|
||||||
}
|
}
|
||||||
|
85
src/frontend/src/libs/config/ConfigTool.js
Normal file
85
src/frontend/src/libs/config/ConfigTool.js
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
const CONFIG_KEY = 'app_config';
|
||||||
|
|
||||||
|
export const defaultConfig = {
|
||||||
|
backendUrl: '',
|
||||||
|
authKey: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 浏览器环境配置操作工具
|
||||||
|
*/
|
||||||
|
const ConfigTool = {
|
||||||
|
/**
|
||||||
|
* 从本地存储读取配置
|
||||||
|
* @returns {{backendUrl: string, authKey: string}}
|
||||||
|
*/
|
||||||
|
load: () => {
|
||||||
|
try {
|
||||||
|
const stored = localStorage.getItem(CONFIG_KEY);
|
||||||
|
if (stored) {
|
||||||
|
return { ...defaultConfig, ...JSON.parse(stored) };
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('读取配置失败:', e);
|
||||||
|
localStorage.removeItem(CONFIG_KEY);
|
||||||
|
}
|
||||||
|
return { ...defaultConfig };
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存配置到本地存储
|
||||||
|
* @param {Object} 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);
|
||||||
|
if (e.name === 'QuotaExceededError') {
|
||||||
|
alert('存储空间不足,请清理后重试');
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除配置
|
||||||
|
* @returns {boolean} 是否清除成功
|
||||||
|
*/
|
||||||
|
clear: () => {
|
||||||
|
try {
|
||||||
|
localStorage.removeItem(CONFIG_KEY);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('清除配置失败:', e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取存储标识(浏览器环境返回固定值)
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
getConfigPath: () => `localStorage:${CONFIG_KEY}`,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查存储是否可用
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
isStorageAvailable: () => {
|
||||||
|
try {
|
||||||
|
const testKey = 'test';
|
||||||
|
localStorage.setItem(testKey, testKey);
|
||||||
|
localStorage.removeItem(testKey);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ConfigTool;
|
@ -1,5 +1,5 @@
|
|||||||
import React, { createContext, useContext, useState, useCallback } from 'react';
|
import React, { createContext, useContext, useState, useCallback } from 'react';
|
||||||
import { Box, Alert, Button, Icon, Text } from '@chakra-ui/react';
|
import { Box, Button, Icon, Text } from '@chakra-ui/react';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import { AiOutlineInfoCircle, AiFillWarning } from 'react-icons/ai';
|
import { AiOutlineInfoCircle, AiFillWarning } from 'react-icons/ai';
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ import DocumentTitle from '@/components/system/pages/DocumentTitle';
|
|||||||
import PageContainer from '@/components/system/PageContainer';
|
import PageContainer from '@/components/system/PageContainer';
|
||||||
import DashboardBackground from '@/components/system/pages/DashboardBackground';
|
import DashboardBackground from '@/components/system/pages/DashboardBackground';
|
||||||
import FadeInWrapper from '@/components/system/layout/FadeInWrapper';
|
import FadeInWrapper from '@/components/system/layout/FadeInWrapper';
|
||||||
import { api } from '@/services/api';
|
import { api } from '@/services/api/api';
|
||||||
import { useNotification } from '@/libs/system/Notification';
|
import { useNotification } from '@/libs/system/Notification';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -32,18 +32,10 @@ const ScanPage = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchLocalInfo = async () => {
|
const fetchLocalInfo = async () => {
|
||||||
try {
|
setLocalIp('172.17.99.208');
|
||||||
const res = await api.test();
|
if (!subnet) {
|
||||||
if (res.message) {
|
const ipParts = '172.17.99.208'.split('.');
|
||||||
setLocalIp(res.local_ip || '172.17.99.208');
|
setSubnet(`${ipParts[0]}.${ipParts[1]}.${ipParts[2]}.0/24`);
|
||||||
if (!subnet) {
|
|
||||||
const ipParts = (res.local_ip || '172.17.99.208').split('.');
|
|
||||||
setSubnet(`${ipParts[0]}.${ipParts[1]}.${ipParts[2]}.0/24`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
setLocalIp('未知');
|
|
||||||
notify.error({ title: '获取后端信息失败' });
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
fetchLocalInfo();
|
fetchLocalInfo();
|
||||||
|
@ -1,51 +0,0 @@
|
|||||||
/**
|
|
||||||
* 获取config
|
|
||||||
* @returns {any|null}
|
|
||||||
*/
|
|
||||||
export const getConfig = () => {
|
|
||||||
const cfg = localStorage.getItem('connection-config');
|
|
||||||
return cfg ? JSON.parse(cfg) : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取后端url
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
export const getBaseUrl = () => {
|
|
||||||
const cfg = getConfig();
|
|
||||||
return cfg?.backendUrl || '';
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* fetchUrl
|
|
||||||
* @param path 路径
|
|
||||||
* @param options 选项
|
|
||||||
* @returns {Promise<any>}
|
|
||||||
*/
|
|
||||||
const fetchWithBase = async (path, options = {}) => {
|
|
||||||
const base = getBaseUrl();
|
|
||||||
const res = await fetch(`${base}${path}`, options);
|
|
||||||
return res.json();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* api模块
|
|
||||||
* @type {{test: (function(): Promise<*>), scan: (function(*): Promise<*>), listDevices: (function(): Promise<*>), parseCommand: (function(*): Promise<*>), applyConfig: (function(*, *): Promise<*>)}}
|
|
||||||
*/
|
|
||||||
export const api = {
|
|
||||||
test: () => fetchWithBase('/api/test'),
|
|
||||||
scan: (subnet) => fetchWithBase(`/api/scan_network?subnet=${encodeURIComponent(subnet)}`),
|
|
||||||
listDevices: () => fetchWithBase('/api/list_devices'),
|
|
||||||
parseCommand: (text) =>
|
|
||||||
fetchWithBase('/api/parse_command', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ command: text }),
|
|
||||||
}),
|
|
||||||
applyConfig: (switch_ip, config) =>
|
|
||||||
fetchWithBase('/api/apply_config', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ switch_ip, config }),
|
|
||||||
}),
|
|
||||||
};
|
|
100
src/frontend/src/services/api/api.js
Normal file
100
src/frontend/src/services/api/api.js
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
import ConfigTool from '@/libs/config/ConfigTool';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建带基础URL的axios实例
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 响应拦截器
|
||||||
|
apiClient.interceptors.response.use(
|
||||||
|
(response) => response.data,
|
||||||
|
(error) => {
|
||||||
|
console.error('API请求错误:', error);
|
||||||
|
return Promise.reject(error.response?.data || error.message);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API模块
|
||||||
|
*/
|
||||||
|
export const api = {
|
||||||
|
/**
|
||||||
|
* 测试API连接
|
||||||
|
* @returns {Promise<axios.AxiosResponse<any>>}
|
||||||
|
*/
|
||||||
|
test: () => apiClient.get('/api/test'),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扫描网络
|
||||||
|
* @param subnet 子网地址
|
||||||
|
* @returns {Promise<axios.AxiosResponse<any>>}
|
||||||
|
*/
|
||||||
|
scan: (subnet) => apiClient.get(`/api/scan_network`, { params: { subnet } }),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 列出所有设备
|
||||||
|
* @returns {Promise<axios.AxiosResponse<any>>}
|
||||||
|
*/
|
||||||
|
listDevices: () => apiClient.get('/api/list_devices'),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析命令
|
||||||
|
* @param text 文本
|
||||||
|
* @returns {Promise<axios.AxiosResponse<any>>}
|
||||||
|
*/
|
||||||
|
parseCommand: (text) => apiClient.post('/api/parse_command', { command: text }),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用配置
|
||||||
|
* @param switch_ip 交换机ip
|
||||||
|
* @param config 配置
|
||||||
|
* @returns {Promise<axios.AxiosResponse<any>>}
|
||||||
|
*/
|
||||||
|
applyConfig: (switch_ip, config) => apiClient.post('/api/apply_config', { switch_ip, config }),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新基础URL
|
||||||
|
* @param url
|
||||||
|
*/
|
||||||
|
updateBaseUrl: (url) => {
|
||||||
|
const config = ConfigTool.load();
|
||||||
|
config.backendUrl = url;
|
||||||
|
ConfigTool.save(config);
|
||||||
|
apiClient.defaults.baseURL = url;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前配置
|
||||||
|
* @returns {{backendUrl: string, authKey: string}}
|
||||||
|
*/
|
||||||
|
export const getConfig = () => ConfigTool.load();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取基础URL
|
||||||
|
* @returns {string|string}
|
||||||
|
*/
|
||||||
|
export const getBaseUrl = () => ConfigTool.load().backendUrl || '';
|
||||||
|
|
||||||
|
export default apiClient;
|
Loading…
x
Reference in New Issue
Block a user