Files
vf_react/src/components/NotificationContainer/index.js

190 lines
5.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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=0z-index最高
}}
>
<NotificationItem
notification={notification}
onClose={removeNotification}
isNewest={index === 0} // 第一条消息是最新的
/>
</Slide>
))}
</VStack>
</Box>
);
};
export default NotificationContainer;