mirror of
https://github.com/Jerryplusy/AI-powered-switches.git
synced 2025-07-04 05:09:19 +00:00
通知hook
This commit is contained in:
parent
c186b8f3bb
commit
a528009674
@ -1,9 +1,11 @@
|
|||||||
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
||||||
import AppShell from '@/components/system/layout/AppShell';
|
import AppShell from '@/components/system/layout/AppShell';
|
||||||
import buildRoutes from '@/constants/routes/routes';
|
import buildRoutes from '@/constants/routes/routes';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const isProd = process.env.NODE_ENV === 'production';
|
const isProd = process.env.NODE_ENV === 'production';
|
||||||
|
useEffect(() => {}, []);
|
||||||
return (
|
return (
|
||||||
<BrowserRouter basename={isProd ? '/AI-powered-switches' : '/'}>
|
<BrowserRouter basename={isProd ? '/AI-powered-switches' : '/'}>
|
||||||
<Routes>
|
<Routes>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import { Button, HStack } from '@chakra-ui/react';
|
import { Box, Button, HStack } from '@chakra-ui/react';
|
||||||
import web from '@/resources/icon/pages/weclome/web.svg';
|
import web from '@/resources/icon/pages/weclome/web.svg';
|
||||||
import githubIcon from '@/resources/icon/pages/weclome/github.svg';
|
import githubIcon from '@/resources/icon/pages/weclome/github.svg';
|
||||||
import FadeInWrapper from '@/components/system/layout/FadeInWrapper';
|
import FadeInWrapper from '@/components/system/layout/FadeInWrapper';
|
||||||
@ -31,10 +31,10 @@ const GithubTransitionCard = () => {
|
|||||||
}, 400);
|
}, 400);
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}, [isDashboard]);
|
}, [isDashboard]);
|
||||||
|
const MotionBox = motion(Box);
|
||||||
return (
|
return (
|
||||||
<AnimatePresence mode={'wait'}>
|
<AnimatePresence mode={'wait'}>
|
||||||
<motion.div
|
<MotionBox
|
||||||
key={isDashboard ? 'dashboard' : 'welcome'}
|
key={isDashboard ? 'dashboard' : 'welcome'}
|
||||||
initial={{ opacity: 0, height: 'auto', width: isDashboard ? 200 : 'auto' }}
|
initial={{ opacity: 0, height: 'auto', width: isDashboard ? 200 : 'auto' }}
|
||||||
animate={{
|
animate={{
|
||||||
@ -95,7 +95,7 @@ const GithubTransitionCard = () => {
|
|||||||
)}
|
)}
|
||||||
</MotionCard>
|
</MotionCard>
|
||||||
</FadeInWrapper>
|
</FadeInWrapper>
|
||||||
</motion.div>
|
</MotionBox>
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -2,10 +2,13 @@ import React from 'react';
|
|||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import App from '@/App';
|
import App from '@/App';
|
||||||
import { Provider } from '@/components/ui/provider';
|
import { Provider } from '@/components/ui/provider';
|
||||||
|
import { NotificationProvider } from '@/libs/system/Notification';
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||||
root.render(
|
root.render(
|
||||||
<Provider>
|
<Provider>
|
||||||
<App />
|
<NotificationProvider>
|
||||||
|
<App />
|
||||||
|
</NotificationProvider>
|
||||||
</Provider>
|
</Provider>
|
||||||
);
|
);
|
||||||
|
113
src/frontend/src/libs/system/Notification.jsx
Normal file
113
src/frontend/src/libs/system/Notification.jsx
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import React, { createContext, useContext, useState, useCallback } from 'react';
|
||||||
|
import { Box, Alert, Button, Icon, Text } from '@chakra-ui/react';
|
||||||
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
|
import { AiOutlineInfoCircle, AiFillWarning } 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);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NotificationContext.Provider value={notify}>
|
||||||
|
{children}
|
||||||
|
<Box
|
||||||
|
position={'fixed'}
|
||||||
|
bottom={4}
|
||||||
|
right={4}
|
||||||
|
zIndex={9999}
|
||||||
|
display={'flex'}
|
||||||
|
flexDirection={'column'}
|
||||||
|
alignItems={'flex-end'}
|
||||||
|
>
|
||||||
|
<AnimatePresence>
|
||||||
|
{notifications.map((item) => (
|
||||||
|
<MotionBox
|
||||||
|
key={item.id}
|
||||||
|
layout
|
||||||
|
initial={{ opacity: 0, x: 200 }}
|
||||||
|
animate={{ opacity: 1, x: 0 }}
|
||||||
|
exit={{ opacity: 0, x: 200 }}
|
||||||
|
transition={{ type: 'spring', stiffness: 500, damping: 30 }}
|
||||||
|
mb={3}
|
||||||
|
width={'320px'}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
bg={item.type === 'error' ? '' : ''}
|
||||||
|
color={'white'}
|
||||||
|
borderRadius={'xl'}
|
||||||
|
px={5}
|
||||||
|
py={4}
|
||||||
|
boxShadow={'2xl'}
|
||||||
|
border={'1px solid rgba(255, 255, 255, 0.4)'}
|
||||||
|
backdropFilter={'blur(8px)'}
|
||||||
|
>
|
||||||
|
<Box display={'flex'} alignItems={'center'}>
|
||||||
|
<Icon
|
||||||
|
as={item.type === 'error' ? AiFillWarning : AiOutlineInfoCircle}
|
||||||
|
boxSize={6}
|
||||||
|
mr={3}
|
||||||
|
/>
|
||||||
|
<Text fontSize={'lg'} fontWeight={'bold'}>
|
||||||
|
{item.title}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Text mt={2}>{item.description}</Text>
|
||||||
|
{item.button && (
|
||||||
|
<Button
|
||||||
|
colorScheme={'whiteAlpha'}
|
||||||
|
size={'sm'}
|
||||||
|
mt={3}
|
||||||
|
onClick={item.button.onClick}
|
||||||
|
>
|
||||||
|
{item.button.label}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</MotionBox>
|
||||||
|
))}
|
||||||
|
</AnimatePresence>
|
||||||
|
</Box>
|
||||||
|
</NotificationContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
@ -1,17 +1,13 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState, useCallback } from 'react';
|
||||||
import { Box, Text, VStack, HStack, SimpleGrid, Badge } from '@chakra-ui/react';
|
import { Box, Text, VStack, HStack, SimpleGrid, Badge, Button, Spinner } from '@chakra-ui/react';
|
||||||
import DocumentTitle from '@/components/system/pages/DocumentTitle';
|
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 FeatureCard from '@/components/pages/dashboard/FeatureCard';
|
import FeatureCard from '@/components/pages/dashboard/FeatureCard';
|
||||||
import StatCard from '@/components/pages/dashboard/StatCard';
|
import StatCard from '@/components/pages/dashboard/StatCard';
|
||||||
|
import { useNotification } from '@/libs/system/Notification';
|
||||||
|
|
||||||
/**
|
|
||||||
* 控制台页面
|
|
||||||
* @returns {JSX.Element}
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
const [stats, setStats] = useState({
|
const [stats, setStats] = useState({
|
||||||
totalDevices: 0,
|
totalDevices: 0,
|
||||||
@ -19,7 +15,33 @@ const Dashboard = () => {
|
|||||||
lastScan: '',
|
lastScan: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {}, []);
|
const [networkStatus, setNetworkStatus] = useState('idle'); // idle | loading | ok | fail
|
||||||
|
const notify = useNotification();
|
||||||
|
|
||||||
|
const checkBackend = useCallback(async () => {
|
||||||
|
setNetworkStatus('loading');
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/test');
|
||||||
|
if (res.ok) {
|
||||||
|
setNetworkStatus('ok');
|
||||||
|
notify.info({ title: '成功连接至后端服务!' });
|
||||||
|
} else {
|
||||||
|
setNetworkStatus('fail');
|
||||||
|
notify.error({ title: '后端服务响应异常!' });
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setNetworkStatus('fail');
|
||||||
|
notify.error({ title: '无法连接到后端服务!' });
|
||||||
|
}
|
||||||
|
}, [notify]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
checkBackend();
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [checkBackend]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DocumentTitle title={'控制台'}>
|
<DocumentTitle title={'控制台'}>
|
||||||
@ -38,10 +60,10 @@ const Dashboard = () => {
|
|||||||
_hover={{ transform: 'translateY(-4px)' }}
|
_hover={{ transform: 'translateY(-4px)' }}
|
||||||
>
|
>
|
||||||
<Text fontSize={'3xl'} fontWeight={'bold'} color={'teal.300'}>
|
<Text fontSize={'3xl'} fontWeight={'bold'} color={'teal.300'}>
|
||||||
{`欢迎使用智能交换机管理系统`}
|
{'欢迎使用智能交换机管理系统'}
|
||||||
</Text>
|
</Text>
|
||||||
<Text mt={2} fontSize={'lg'} color={'gray.300'}>
|
<Text mt={2} fontSize={'lg'} color={'gray.300'}>
|
||||||
{`实时监控您的网络设备状态,快速配置并掌控全局网络环境`}
|
{'实时监控您的网络设备状态,快速配置并掌控全局网络环境'}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@ -67,19 +89,40 @@ const Dashboard = () => {
|
|||||||
_hover={{ transform: 'translateY(-4px)' }}
|
_hover={{ transform: 'translateY(-4px)' }}
|
||||||
>
|
>
|
||||||
<Text fontSize={'xl'} fontWeight={'bold'} mb={4} color={'white'}>
|
<Text fontSize={'xl'} fontWeight={'bold'} mb={4} color={'white'}>
|
||||||
{`网络健康状态`}
|
{'网络健康状态'}
|
||||||
</Text>
|
</Text>
|
||||||
<HStack spacing={4}>
|
<HStack spacing={4}>
|
||||||
<Badge colorScheme={'green'} variant={'solid'} p={2} borderRadius={'lg'}>
|
{networkStatus === 'idle' && (
|
||||||
{`网络连接正常`}
|
<Badge colorScheme={'gray'} variant={'solid'} p={2} borderRadius={'lg'}>
|
||||||
</Badge>
|
{'等待检测'}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
{networkStatus === 'loading' && (
|
||||||
|
<Badge colorScheme={'gray'} variant={'solid'} p={2} borderRadius={'lg'}>
|
||||||
|
<Spinner size="sm" mr={2} /> {'检测网络中...'}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
{networkStatus === 'ok' && (
|
||||||
|
<Badge colorScheme={'green'} variant={'solid'} p={2} borderRadius={'lg'}>
|
||||||
|
{'网络连接正常'}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
{networkStatus === 'fail' && (
|
||||||
|
<Badge colorScheme={'red'} variant={'solid'} p={2} borderRadius={'lg'}>
|
||||||
|
{'无法连接后端'}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
<Badge colorScheme={'blue'} variant={'solid'} p={2} borderRadius={'lg'}>
|
<Badge colorScheme={'blue'} variant={'solid'} p={2} borderRadius={'lg'}>
|
||||||
{`交换机正常运行`}
|
{'交换机正常运行'}
|
||||||
</Badge>
|
</Badge>
|
||||||
<Badge colorScheme={'yellow'} variant={'solid'} p={2} borderRadius={'lg'}>
|
<Badge colorScheme={'yellow'} variant={'solid'} p={2} borderRadius={'lg'}>
|
||||||
{`流量监控未启动`}
|
{'流量监控未启动'}
|
||||||
</Badge>
|
</Badge>
|
||||||
</HStack>
|
</HStack>
|
||||||
|
|
||||||
|
<Button mt={4} onClick={checkBackend} colorScheme={'teal'}>
|
||||||
|
{'重新检测'}
|
||||||
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</FadeInWrapper>
|
</FadeInWrapper>
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user