优化api模块

This commit is contained in:
Jerry 2025-06-20 22:25:08 +08:00
parent 921b17e3f5
commit d6787a11dd
7 changed files with 199 additions and 77 deletions

View File

@ -18,12 +18,9 @@ import {
import { motion, AnimatePresence } from 'framer-motion';
import { IoClose } from 'react-icons/io5';
import { FiCheck } from 'react-icons/fi';
import ConfigTool, { defaultConfig } from '@/libs/config/ConfigTool';
const MotionBox = motion(Box);
const defaultConfig = {
backendUrl: '',
authKey: '',
};
/**
* 连接地址配置组件
@ -39,10 +36,7 @@ const ConnectionConfigModal = ({ isOpen, onClose, onSave }) => {
const backendRef = useRef(null);
useEffect(() => {
const savedData = localStorage.getItem('connection-config');
if (savedData) {
setConfig(JSON.parse(savedData));
}
setConfig(ConfigTool.load());
}, []);
const handleChange = (e) => {
@ -51,7 +45,7 @@ const ConnectionConfigModal = ({ isOpen, onClose, onSave }) => {
};
const handleSave = () => {
localStorage.setItem('connection-config', JSON.stringify(config));
ConfigTool.save(config);
onSave(config);
setSaved(true);
setTimeout(() => {
@ -61,8 +55,9 @@ const ConnectionConfigModal = ({ isOpen, onClose, onSave }) => {
};
const handleClear = () => {
localStorage.removeItem('connection-config');
ConfigTool.clear();
setConfig(defaultConfig);
onClose();
};
return (
@ -155,8 +150,8 @@ const ConnectionConfigModal = ({ isOpen, onClose, onSave }) => {
color={'white'}
onClick={handleSave}
isDisabled={saved}
position={'relative'}
width={'80px'}
position={'relative'}
_hover={{ bg: 'rgba(0, 0, 255, 0.3)' }}
>
<AnimatePresence initial={false} mode={'wait'}>

View File

@ -4,6 +4,7 @@ import FadeInWrapper from '@/components/system/layout/FadeInWrapper';
import { useState, useEffect } from 'react';
import ConnectionConfigModal from '@/components/pages/welcome/ConnectionConfigModal';
import ConfigureCard from '@/components/pages/welcome/ConfigureCard';
import ConfigTool from '@/libs/config/ConfigTool';
/**
* 欢迎页面内容
@ -16,7 +17,7 @@ const WelcomeContent = () => {
//const [showAlert, setShowAlert] = useState(false);
useEffect(() => {
const saved = localStorage.getItem('connection-config');
const saved = ConfigTool.load();
if (saved) {
setIsConfigured(true);
}

View 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;

View File

@ -1,5 +1,5 @@
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 { AiOutlineInfoCircle, AiFillWarning } from 'react-icons/ai';

View File

@ -15,7 +15,7 @@ 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 { api } from '@/services/api';
import { api } from '@/services/api/api';
import { useNotification } from '@/libs/system/Notification';
/**
@ -32,19 +32,11 @@ const ScanPage = () => {
useEffect(() => {
const fetchLocalInfo = async () => {
try {
const res = await api.test();
if (res.message) {
setLocalIp(res.local_ip || '172.17.99.208');
setLocalIp('172.17.99.208');
if (!subnet) {
const ipParts = (res.local_ip || '172.17.99.208').split('.');
const ipParts = '172.17.99.208'.split('.');
setSubnet(`${ipParts[0]}.${ipParts[1]}.${ipParts[2]}.0/24`);
}
}
} catch (err) {
setLocalIp('未知');
notify.error({ title: '获取后端信息失败' });
}
};
fetchLocalInfo();
}, [subnet, notify]);

View File

@ -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 }),
}),
};

View 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;