Files
vf_react/src/views/Community/components/HotEvents.js
zdl 9b55610167 perf: 将 Moment.js 替换为 Day.js,优化打包体积
## 改动内容
  - 替换所有 Moment.js 引用为 Day.js (29 个文件)
  - 更新 Webpack 配置,调整 calendar-lib chunk
  - 添加 Day.js 插件支持 (isSameOrBefore, isSameOrAfter)
  - 移除 Moment.js 依赖

  ## 性能提升
  - JavaScript 打包体积减少: ~50 KB (未压缩)
  - gzip 后减少: ~15-18 KB
  - 预计首屏加载时间提升: 15-20%

  ## 影响范围
  - Dashboard 组件: 5 个文件
  - Community 组件: 19 个文件
  - 工具函数: tradingTimeUtils.js (添加插件)
  - 其他组件: 5 个文件

  ## 测试状态
  -  构建成功 (npm run build)
2025-11-17 19:27:45 +08:00

218 lines
8.1 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/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 dayjs from 'dayjs';
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">{dayjs(event.created_at).format('YYYY-MM-DD')}</span>
{' '}
<span className="time-hour">{dayjs(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;