通知hook

This commit is contained in:
Jerry 2025-06-19 18:45:10 +08:00
parent c186b8f3bb
commit a528009674
6 changed files with 182 additions and 21 deletions

View File

@ -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>

View File

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

View File

@ -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>
<NotificationProvider>
<App />
</NotificationProvider>
</Provider>
);

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

View File

@ -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'}>
{`网络连接正常`}
{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>