feat: 热点事件UI调整成轮播图

This commit is contained in:
zdl
2025-10-27 17:22:03 +08:00
parent 03c113fe1b
commit 31a7500388
4 changed files with 257 additions and 51 deletions

View File

@@ -145,7 +145,7 @@ export const fetchHotEvents = createAsyncThunk(
try { try {
return await fetchWithCache({ return await fetchWithCache({
cacheKey: CACHE_KEYS.HOT_EVENTS, cacheKey: CACHE_KEYS.HOT_EVENTS,
fetchFn: () => eventService.getHotEvents({ days: 5, limit: 4 }), fetchFn: () => eventService.getHotEvents({ days: 5, limit: 20 }),
getState, getState,
stateKey: 'hotEvents', stateKey: 'hotEvents',
forceRefresh forceRefresh

View File

@@ -16,11 +16,76 @@
margin-bottom: 24px; margin-bottom: 24px;
} }
/* Carousel */
.carousel-wrapper {
position: relative;
}
.carousel-counter {
position: absolute;
top: 8px; /* 容器内部顶部 */
right: 48px; /* 避开右侧箭头 */
z-index: 100; /* 确保在卡片和箭头上方 */
background: rgba(24, 144, 255, 0.95);
color: white;
font-size: 13px;
font-weight: 600;
padding: 4px 10px;
border-radius: 12px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
pointer-events: none; /* 不阻挡鼠标事件 */
}
.hot-events-carousel {
padding: 0 40px; /* 增加左右padding为箭头留出空间 */
position: relative;
}
.hot-events-carousel .carousel-item {
padding: 0 8px;
}
/* 自定义箭头样式 */
.custom-carousel-arrow {
width: 40px !important;
height: 40px !important;
background: rgba(255, 255, 255, 0.9) !important;
border-radius: 50% !important;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15) !important;
transition: all 0.3s ease !important;
z-index: 10 !important;
}
.custom-carousel-arrow:hover {
background: rgba(255, 255, 255, 1) !important;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2) !important;
}
.custom-carousel-arrow:hover .anticon {
color: #096dd9 !important;
}
/* 箭头位置 */
.hot-events-carousel .slick-prev.custom-carousel-arrow {
left: 0 !important;
}
.hot-events-carousel .slick-next.custom-carousel-arrow {
right: 0 !important;
}
/* 禁用状态 */
.custom-carousel-arrow.slick-disabled {
opacity: 0.3 !important;
cursor: not-allowed !important;
}
/* Card */ /* Card */
.hot-event-card { .hot-event-card {
border-radius: 8px; border-radius: 8px;
overflow: hidden; overflow: hidden;
transition: transform 0.2s ease, box-shadow 0.2s ease; transition: transform 0.2s ease, box-shadow 0.2s ease;
margin: 0 auto;
} }
.hot-event-card:hover { .hot-event-card:hover {
@@ -28,11 +93,16 @@
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08); box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08);
} }
/* Cover image */ /* Card body padding */
.hot-event-card .ant-card-body {
padding: 12px;
}
/* Cover image - 高度减半 */
.event-cover { .event-cover {
position: relative; position: relative;
width: 100%; width: 100%;
height: 160px; height: 80px;
overflow: hidden; overflow: hidden;
} }
@@ -54,28 +124,53 @@
/* Card content */ /* Card content */
.event-header { .event-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 4px;
}
.event-header .ant-tag {
margin-right: 6px;
}
.event-title {
font-size: 16px; font-size: 16px;
font-weight: 600; font-weight: 600;
color: #000; color: #000;
flex: 1; margin-bottom: 8px;
line-height: 1.4;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
word-break: break-word;
} }
/* 标题文字 - inline显示可以换行 */
.event-title {
cursor: pointer;
}
/* 标签紧跟标题后面 */
.event-tag {
display: inline;
margin-left: 4px;
white-space: nowrap;
vertical-align: baseline;
}
.event-tag .ant-tag {
font-size: 11px;
padding: 0 6px;
height: 18px;
line-height: 18px;
transform: scale(0.9);
vertical-align: middle;
}
/* 详情描述 - 三行省略 */
.event-description { .event-description {
margin: 8px 0; margin: 8px 0;
font-size: 14px; font-size: 14px;
color: #595959; color: #595959;
line-height: 1.5; line-height: 1.5;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
max-height: 4.5em;
cursor: pointer;
} }
.event-footer { .event-footer {
@@ -83,6 +178,7 @@
justify-content: space-between; justify-content: space-between;
font-size: 12px; font-size: 12px;
color: #8c8c8c; color: #8c8c8c;
margin-top: 8px;
} }
.creator { .creator {
@@ -92,6 +188,19 @@
max-width: 60%; max-width: 60%;
} }
/* 时间样式 - 年月日高亮 */
.time { .time {
white-space: nowrap; white-space: nowrap;
display: flex;
align-items: center;
gap: 4px;
}
.time-date {
color: #1890ff;
font-weight: 600;
}
.time-hour {
color: #8c8c8c;
} }

View File

@@ -1,13 +1,34 @@
// src/views/Community/components/HotEvents.js // src/views/Community/components/HotEvents.js
import React from 'react'; import React, { useState } from 'react';
import { Card, Row, Col, Badge, Tag, Empty } from 'antd'; import { Card, Badge, Tag, Empty, Carousel, Tooltip } from 'antd';
import { ArrowUpOutlined, ArrowDownOutlined, FireOutlined } from '@ant-design/icons'; import { ArrowUpOutlined, ArrowDownOutlined, LeftOutlined, RightOutlined } from '@ant-design/icons';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import moment from 'moment'; import moment from 'moment';
import './HotEvents.css'; import './HotEvents.css';
import defaultEventImage from '../../../assets/img/default-event.jpg' import defaultEventImage from '../../../assets/img/default-event.jpg'
const HotEvents = ({ events }) => {
// 自定义箭头组件
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 }) => {
const navigate = useNavigate(); const navigate = useNavigate();
const [currentSlide, setCurrentSlide] = useState(0);
const renderPriceChange = (value) => { const renderPriceChange = (value) => {
if (value === null || value === undefined) { if (value === null || value === undefined) {
@@ -39,12 +60,60 @@ const HotEvents = ({ events }) => {
navigate(`/event-detail/${eventId}`); navigate(`/event-detail/${eventId}`);
}; };
// 计算总页数
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 ( return (
<div className="hot-events-section"> <div className="hot-events-section">
{events && events.length > 0 ? ( {events && events.length > 0 ? (
<Row gutter={[16, 16]}> <Carousel {...carouselSettings} className="hot-events-carousel">
{events.map((event, index) => ( {events.map((event, index) => (
<Col lg={6} md={12} sm={24} key={event.id}> <div key={event.id} className="carousel-item">
<Card <Card
hoverable hoverable
className="hot-event-card" className="hot-event-card"
@@ -69,33 +138,36 @@ const HotEvents = ({ events }) => {
</div> </div>
} }
> >
<Card.Meta {/* Custom layout without Card.Meta */}
title={ <div className="event-header">
<div className="event-header"> <Tooltip title={event.title}>
{renderPriceChange(event.related_avg_chg)} <span className="event-title">
<span className="event-title"> {event.title}
{event.title} </span>
</span> </Tooltip>
</div> <span className="event-tag">
} {renderPriceChange(event.related_avg_chg)}
description={ </span>
<> </div>
<p className="event-description">
{event.description && event.description.length > 80 <Tooltip title={event.description}>
? `${event.description.substring(0, 80)}...` <div className="event-description">
: event.description} {event.description}
</p> </div>
<div className="event-footer"> </Tooltip>
<span className="creator">{event.creator?.username || 'Anonymous'}</span>
<span className="time">{moment(event.created_at).format('MM-DD HH:mm')}</span> <div className="event-footer">
</div> <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> </Card>
</Col> </div>
))} ))}
</Row> </Carousel>
) : ( ) : (
<Card> <Card>
<Empty description="暂无热点信息" /> <Empty description="暂无热点信息" />

View File

@@ -1,12 +1,14 @@
// src/views/Community/components/HotEventsSection.js // src/views/Community/components/HotEventsSection.js
// 热点事件区域组件 // 热点事件区域组件
import React from 'react'; import React, { useState } from 'react';
import { import {
Card, Card,
CardHeader, CardHeader,
CardBody, CardBody,
Heading, Heading,
Badge,
Box,
useColorModeValue useColorModeValue
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import HotEvents from './HotEvents'; import HotEvents from './HotEvents';
@@ -17,6 +19,14 @@ import HotEvents from './HotEvents';
*/ */
const HotEventsSection = ({ events }) => { const HotEventsSection = ({ events }) => {
const cardBg = useColorModeValue('white', 'gray.800'); const cardBg = useColorModeValue('white', 'gray.800');
const [currentPage, setCurrentPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
// 处理页码变化
const handlePageChange = (page, total) => {
setCurrentPage(page);
setTotalPages(total);
};
// 如果没有热点事件,不渲染组件 // 如果没有热点事件,不渲染组件
if (!events || events.length === 0) { if (!events || events.length === 0) {
@@ -25,12 +35,27 @@ const HotEventsSection = ({ events }) => {
return ( return (
<Card mt={0} bg={cardBg}> <Card mt={0} bg={cardBg}>
<CardHeader pb={0}> <CardHeader pb={0} display="flex" justifyContent="space-between" alignItems="flex-start">
<Heading size="md">🔥 热点事件</Heading> <Box>
<p className="section-subtitle" style={{paddingTop: '8px'}}>展示最近5天内涨幅最高的事件助您把握市场热点</p> <Heading size="md">🔥 热点事件</Heading>
<p className="section-subtitle" style={{paddingTop: '8px'}}>展示最近5天内涨幅最高的事件助您把握市场热点</p>
</Box>
{/* 页码指示器 */}
{totalPages > 1 && (
<Badge
colorScheme="blue"
fontSize="sm"
px={3}
py={1}
borderRadius="full"
ml={4}
>
{currentPage} / {totalPages}
</Badge>
)}
</CardHeader> </CardHeader>
<CardBody py={0} px={4}> <CardBody py={0} px={4}>
<HotEvents events={events} /> <HotEvents events={events} onPageChange={handlePageChange} />
</CardBody> </CardBody>
</Card> </Card>
); );