218 lines
8.1 KiB
JavaScript
218 lines
8.1 KiB
JavaScript
// src/views/Community/components/HotEvents.js
|
||
import React, { useState } from 'react';
|
||
import { Card, Badge, Tag, Empty, Carousel, Tooltip } from 'antd';
|
||
import { ArrowUpOutlined, ArrowDownOutlined, LeftOutlined, RightOutlined } from '@ant-design/icons';
|
||
import {
|
||
Modal,
|
||
ModalOverlay,
|
||
ModalContent,
|
||
ModalHeader,
|
||
ModalBody,
|
||
ModalCloseButton,
|
||
useDisclosure
|
||
} from '@chakra-ui/react';
|
||
import moment from 'moment';
|
||
import './HotEvents.css';
|
||
import defaultEventImage from '../../../assets/img/default-event.jpg';
|
||
import DynamicNewsDetailPanel from './DynamicNewsDetail';
|
||
|
||
// 自定义箭头组件
|
||
const CustomArrow = ({ className, style, onClick, direction }) => {
|
||
const Icon = direction === 'left' ? LeftOutlined : RightOutlined;
|
||
return (
|
||
<div
|
||
className={`${className} custom-carousel-arrow`}
|
||
style={{
|
||
...style,
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'center'
|
||
}}
|
||
onClick={onClick}
|
||
>
|
||
<Icon style={{ fontSize: '20px', color: '#1890ff' }} />
|
||
</div>
|
||
);
|
||
};
|
||
|
||
const HotEvents = ({ events, onPageChange, onEventClick }) => {
|
||
const [currentSlide, setCurrentSlide] = useState(0);
|
||
const { isOpen: isModalOpen, onOpen: onModalOpen, onClose: onModalClose } = useDisclosure();
|
||
const [modalEvent, setModalEvent] = useState(null);
|
||
|
||
const renderPriceChange = (value) => {
|
||
if (value === null || value === undefined) {
|
||
return <Tag color="default">--</Tag>;
|
||
}
|
||
|
||
const isPositive = value > 0;
|
||
const icon = isPositive ? <ArrowUpOutlined /> : <ArrowDownOutlined />;
|
||
const color = isPositive ? '#ff4d4f' : '#52c41a';
|
||
|
||
return (
|
||
<Tag color={color}>
|
||
{icon} {Math.abs(value).toFixed(2)}%
|
||
</Tag>
|
||
);
|
||
};
|
||
|
||
const getImportanceColor = (importance) => {
|
||
const colors = {
|
||
S: 'red',
|
||
A: 'orange',
|
||
B: 'blue',
|
||
C: 'green'
|
||
};
|
||
return colors[importance] || 'default';
|
||
};
|
||
|
||
const handleCardClick = (event) => {
|
||
// 🎯 追踪热点事件点击
|
||
if (onEventClick) {
|
||
onEventClick({
|
||
eventId: event.id,
|
||
eventTitle: event.title,
|
||
importance: event.importance,
|
||
source: 'hot_events_section',
|
||
timestamp: new Date().toISOString(),
|
||
});
|
||
}
|
||
|
||
setModalEvent(event);
|
||
onModalOpen();
|
||
};
|
||
|
||
// 计算总页数
|
||
const totalPages = Math.ceil((events?.length || 0) / 4);
|
||
|
||
// Carousel 配置
|
||
const carouselSettings = {
|
||
dots: false, // 隐藏圆点导航
|
||
infinite: true, // 始终启用无限循环,确保箭头显示
|
||
speed: 500,
|
||
slidesToShow: 4,
|
||
slidesToScroll: 1,
|
||
arrows: true, // 保留左右箭头
|
||
prevArrow: <CustomArrow direction="left" />,
|
||
nextArrow: <CustomArrow direction="right" />,
|
||
autoplay: false,
|
||
beforeChange: (_current, next) => {
|
||
// 计算实际页码(考虑无限循环)
|
||
const actualPage = next % totalPages;
|
||
setCurrentSlide(actualPage);
|
||
// 通知父组件页码变化
|
||
if (onPageChange) {
|
||
onPageChange(actualPage + 1, totalPages);
|
||
}
|
||
},
|
||
responsive: [
|
||
{
|
||
breakpoint: 1200,
|
||
settings: {
|
||
slidesToShow: 3,
|
||
slidesToScroll: 1,
|
||
}
|
||
},
|
||
{
|
||
breakpoint: 992,
|
||
settings: {
|
||
slidesToShow: 2,
|
||
slidesToScroll: 1,
|
||
}
|
||
},
|
||
{
|
||
breakpoint: 576,
|
||
settings: {
|
||
slidesToShow: 1,
|
||
slidesToScroll: 1,
|
||
}
|
||
}
|
||
]
|
||
};
|
||
|
||
return (
|
||
<div className="hot-events-section">
|
||
{events && events.length > 0 ? (
|
||
<Carousel {...carouselSettings} className="hot-events-carousel">
|
||
{events.map((event, index) => (
|
||
<div key={event.id} className="carousel-item">
|
||
<Card
|
||
hoverable
|
||
className="hot-event-card"
|
||
onClick={() => handleCardClick(event)}
|
||
cover={
|
||
<div className="event-cover">
|
||
<img
|
||
alt={event.title}
|
||
src={`/images/events/${['first', 'second', 'third', 'fourth'][index] || 'first'}.jpg`}
|
||
onError={e => {
|
||
e.target.onerror = null;
|
||
e.target.src = defaultEventImage;
|
||
}}
|
||
/>
|
||
{event.importance && (
|
||
<Badge
|
||
className="importance-badge"
|
||
color={getImportanceColor(event.importance)}
|
||
text={`${event.importance}级`}
|
||
/>
|
||
)}
|
||
</div>
|
||
}
|
||
>
|
||
{/* Custom layout without Card.Meta */}
|
||
<div className="event-header">
|
||
<Tooltip title={event.title}>
|
||
<span className="event-title">
|
||
{event.title}
|
||
</span>
|
||
</Tooltip>
|
||
<span className="event-tag">
|
||
{renderPriceChange(event.related_avg_chg)}
|
||
</span>
|
||
</div>
|
||
|
||
<Tooltip title={event.description}>
|
||
<div className="event-description">
|
||
{event.description}
|
||
</div>
|
||
</Tooltip>
|
||
|
||
<div className="event-footer">
|
||
<span className="creator">{event.creator?.username || 'Anonymous'}</span>
|
||
<span className="time">
|
||
<span className="time-date">{moment(event.created_at).format('YYYY-MM-DD')}</span>
|
||
{' '}
|
||
<span className="time-hour">{moment(event.created_at).format('HH:mm')}</span>
|
||
</span>
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
))}
|
||
</Carousel>
|
||
) : (
|
||
<Card>
|
||
<Empty description="暂无热点信息" />
|
||
</Card>
|
||
)}
|
||
|
||
{/* 事件详情弹窗 - 使用 Chakra UI Modal(与平铺模式一致) */}
|
||
{isModalOpen ? (
|
||
<Modal isOpen={isModalOpen} onClose={onModalClose} size="6xl" scrollBehavior="inside">
|
||
<ModalOverlay />
|
||
<ModalContent>
|
||
<ModalHeader>
|
||
{modalEvent?.title || '事件详情'}
|
||
</ModalHeader>
|
||
<ModalCloseButton />
|
||
<ModalBody pb={6}>
|
||
{modalEvent && <DynamicNewsDetailPanel event={modalEvent} />}
|
||
</ModalBody>
|
||
</ModalContent>
|
||
</Modal>
|
||
): null}
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default HotEvents; |