190 lines
5.9 KiB
JavaScript
190 lines
5.9 KiB
JavaScript
// src/components/NotificationContainer/index.js
|
||
/**
|
||
* 通知容器组件 - 右下角层叠显示实时通知
|
||
*/
|
||
|
||
import React from 'react';
|
||
import {
|
||
Box,
|
||
VStack,
|
||
HStack,
|
||
Text,
|
||
IconButton,
|
||
Icon,
|
||
useColorModeValue,
|
||
Slide,
|
||
ScaleFade,
|
||
} from '@chakra-ui/react';
|
||
import { MdClose, MdCheckCircle, MdError, MdWarning, MdInfo } from 'react-icons/md';
|
||
import { useNotification } from '../../contexts/NotificationContext';
|
||
|
||
// 通知类型对应的图标和颜色
|
||
const NOTIFICATION_STYLES = {
|
||
success: {
|
||
icon: MdCheckCircle,
|
||
colorScheme: 'green',
|
||
bg: 'green.50',
|
||
borderColor: 'green.400',
|
||
iconColor: 'green.500',
|
||
},
|
||
error: {
|
||
icon: MdError,
|
||
colorScheme: 'red',
|
||
bg: 'red.50',
|
||
borderColor: 'red.400',
|
||
iconColor: 'red.500',
|
||
},
|
||
warning: {
|
||
icon: MdWarning,
|
||
colorScheme: 'orange',
|
||
bg: 'orange.50',
|
||
borderColor: 'orange.400',
|
||
iconColor: 'orange.500',
|
||
},
|
||
info: {
|
||
icon: MdInfo,
|
||
colorScheme: 'blue',
|
||
bg: 'blue.50',
|
||
borderColor: 'blue.400',
|
||
iconColor: 'blue.500',
|
||
},
|
||
};
|
||
|
||
/**
|
||
* 单个通知项组件
|
||
*/
|
||
const NotificationItem = ({ notification, onClose, isNewest = false }) => {
|
||
const { id, severity = 'info', title, message } = notification;
|
||
const style = NOTIFICATION_STYLES[severity] || NOTIFICATION_STYLES.info;
|
||
|
||
const bgColor = useColorModeValue(style.bg, `${style.colorScheme}.900`);
|
||
const borderColor = useColorModeValue(style.borderColor, `${style.colorScheme}.500`);
|
||
const textColor = useColorModeValue('gray.800', 'white');
|
||
const subTextColor = useColorModeValue('gray.600', 'gray.300');
|
||
|
||
return (
|
||
<ScaleFade initialScale={0.9} in={true}>
|
||
<Box
|
||
bg={bgColor}
|
||
borderLeft="4px solid"
|
||
borderColor={borderColor}
|
||
borderRadius="md"
|
||
boxShadow={isNewest ? '2xl' : 'lg'} // 最新消息更强的阴影
|
||
p={4}
|
||
minW="350px"
|
||
maxW="450px"
|
||
position="relative"
|
||
_hover={{
|
||
boxShadow: 'xl',
|
||
transform: 'translateX(-4px)',
|
||
}}
|
||
transition="all 0.2s"
|
||
// 最新消息添加微妙的高亮边框
|
||
{...(isNewest && {
|
||
borderRight: '1px solid',
|
||
borderRightColor: borderColor,
|
||
borderTop: '1px solid',
|
||
borderTopColor: useColorModeValue(`${style.colorScheme}.100`, `${style.colorScheme}.700`),
|
||
})}
|
||
>
|
||
<HStack spacing={3} align="start">
|
||
{/* 图标 */}
|
||
<Icon
|
||
as={style.icon}
|
||
w={6}
|
||
h={6}
|
||
color={style.iconColor}
|
||
mt={0.5}
|
||
flexShrink={0}
|
||
/>
|
||
|
||
{/* 内容 */}
|
||
<VStack align="start" spacing={1} flex={1} mr={6}>
|
||
<Text
|
||
fontSize="md"
|
||
fontWeight="bold"
|
||
color={textColor}
|
||
lineHeight="short"
|
||
>
|
||
{title}
|
||
</Text>
|
||
{message && (
|
||
<Text
|
||
fontSize="sm"
|
||
color={subTextColor}
|
||
lineHeight="short"
|
||
>
|
||
{message}
|
||
</Text>
|
||
)}
|
||
</VStack>
|
||
|
||
{/* 关闭按钮 */}
|
||
<IconButton
|
||
icon={<MdClose />}
|
||
size="sm"
|
||
variant="ghost"
|
||
colorScheme={style.colorScheme}
|
||
aria-label="关闭通知"
|
||
onClick={() => onClose(id)}
|
||
position="absolute"
|
||
top={2}
|
||
right={2}
|
||
_hover={{
|
||
bg: useColorModeValue(`${style.colorScheme}.100`, `${style.colorScheme}.800`),
|
||
}}
|
||
/>
|
||
</HStack>
|
||
</Box>
|
||
</ScaleFade>
|
||
);
|
||
};
|
||
|
||
/**
|
||
* 通知容器组件 - 主组件
|
||
*/
|
||
const NotificationContainer = () => {
|
||
const { notifications, removeNotification } = useNotification();
|
||
|
||
// 如果没有通知,不渲染
|
||
if (notifications.length === 0) {
|
||
return null;
|
||
}
|
||
|
||
return (
|
||
<Box
|
||
position="fixed"
|
||
bottom={6}
|
||
right={6}
|
||
zIndex={9999}
|
||
pointerEvents="none"
|
||
>
|
||
<VStack
|
||
spacing={3} // 消息之间间距 12px
|
||
align="flex-end"
|
||
pointerEvents="auto"
|
||
>
|
||
{notifications.map((notification, index) => (
|
||
<Slide
|
||
key={notification.id}
|
||
direction="right"
|
||
in={true}
|
||
style={{
|
||
position: 'relative',
|
||
zIndex: 9999 - index, // 最新消息(index=0)z-index最高
|
||
}}
|
||
>
|
||
<NotificationItem
|
||
notification={notification}
|
||
onClose={removeNotification}
|
||
isNewest={index === 0} // 第一条消息是最新的
|
||
/>
|
||
</Slide>
|
||
))}
|
||
</VStack>
|
||
</Box>
|
||
);
|
||
};
|
||
|
||
export default NotificationContainer;
|