mirror of
https://github.com/Jerryplusy/AI-powered-switches.git
synced 2025-07-03 20:59: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 AppShell from '@/components/system/layout/AppShell';
|
||||
import buildRoutes from '@/constants/routes/routes';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
const App = () => {
|
||||
const isProd = process.env.NODE_ENV === 'production';
|
||||
useEffect(() => {}, []);
|
||||
return (
|
||||
<BrowserRouter basename={isProd ? '/AI-powered-switches' : '/'}>
|
||||
<Routes>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
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 githubIcon from '@/resources/icon/pages/weclome/github.svg';
|
||||
import FadeInWrapper from '@/components/system/layout/FadeInWrapper';
|
||||
@ -31,10 +31,10 @@ const GithubTransitionCard = () => {
|
||||
}, 400);
|
||||
return () => clearTimeout(timer);
|
||||
}, [isDashboard]);
|
||||
|
||||
const MotionBox = motion(Box);
|
||||
return (
|
||||
<AnimatePresence mode={'wait'}>
|
||||
<motion.div
|
||||
<MotionBox
|
||||
key={isDashboard ? 'dashboard' : 'welcome'}
|
||||
initial={{ opacity: 0, height: 'auto', width: isDashboard ? 200 : 'auto' }}
|
||||
animate={{
|
||||
@ -95,7 +95,7 @@ const GithubTransitionCard = () => {
|
||||
)}
|
||||
</MotionCard>
|
||||
</FadeInWrapper>
|
||||
</motion.div>
|
||||
</MotionBox>
|
||||
</AnimatePresence>
|
||||
);
|
||||
};
|
||||
|
@ -2,10 +2,13 @@ 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(
|
||||
<Provider>
|
||||
<App />
|
||||
<NotificationProvider>
|
||||
<App />
|
||||
</NotificationProvider>
|
||||
</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 { Box, Text, VStack, HStack, SimpleGrid, Badge } from '@chakra-ui/react';
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import { Box, Text, VStack, HStack, SimpleGrid, Badge, Button, Spinner } 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 FeatureCard from '@/components/pages/dashboard/FeatureCard';
|
||||
import StatCard from '@/components/pages/dashboard/StatCard';
|
||||
import { useNotification } from '@/libs/system/Notification';
|
||||
|
||||
/**
|
||||
* 控制台页面
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
const Dashboard = () => {
|
||||
const [stats, setStats] = useState({
|
||||
totalDevices: 0,
|
||||
@ -19,7 +15,33 @@ const Dashboard = () => {
|
||||
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 (
|
||||
<DocumentTitle title={'控制台'}>
|
||||
@ -38,10 +60,10 @@ const Dashboard = () => {
|
||||
_hover={{ transform: 'translateY(-4px)' }}
|
||||
>
|
||||
<Text fontSize={'3xl'} fontWeight={'bold'} color={'teal.300'}>
|
||||
{`欢迎使用智能交换机管理系统`}
|
||||
{'欢迎使用智能交换机管理系统'}
|
||||
</Text>
|
||||
<Text mt={2} fontSize={'lg'} color={'gray.300'}>
|
||||
{`实时监控您的网络设备状态,快速配置并掌控全局网络环境`}
|
||||
{'实时监控您的网络设备状态,快速配置并掌控全局网络环境'}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
@ -67,19 +89,40 @@ const Dashboard = () => {
|
||||
_hover={{ transform: 'translateY(-4px)' }}
|
||||
>
|
||||
<Text fontSize={'xl'} fontWeight={'bold'} mb={4} color={'white'}>
|
||||
{`网络健康状态`}
|
||||
{'网络健康状态'}
|
||||
</Text>
|
||||
<HStack spacing={4}>
|
||||
<Badge colorScheme={'green'} variant={'solid'} p={2} borderRadius={'lg'}>
|
||||
{`网络连接正常`}
|
||||
</Badge>
|
||||
{networkStatus === 'idle' && (
|
||||
<Badge colorScheme={'gray'} variant={'solid'} p={2} borderRadius={'lg'}>
|
||||
{'等待检测'}
|
||||
</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>
|
||||
<Badge colorScheme={'yellow'} variant={'solid'} p={2} borderRadius={'lg'}>
|
||||
{`流量监控未启动`}
|
||||
{'流量监控未启动'}
|
||||
</Badge>
|
||||
</HStack>
|
||||
|
||||
<Button mt={4} onClick={checkBackend} colorScheme={'teal'}>
|
||||
{'重新检测'}
|
||||
</Button>
|
||||
</Box>
|
||||
</FadeInWrapper>
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user