feat: 添加 EventScrollList.js 组件

This commit is contained in:
zdl
2025-11-03 11:42:04 +08:00
parent 6cde2175db
commit bdea4209b2
2 changed files with 184 additions and 149 deletions

View File

@@ -1,7 +1,7 @@
// src/views/Community/components/DynamicNewsCard.js // src/views/Community/components/DynamicNewsCard.js
// 横向滚动事件卡片组件(实时要闻·动态追踪) // 横向滚动事件卡片组件(实时要闻·动态追踪)
import React, { forwardRef, useRef, useState, useEffect } from 'react'; import React, { forwardRef, useState, useEffect } from 'react';
import { import {
Card, Card,
CardHeader, CardHeader,
@@ -13,13 +13,12 @@ import {
Heading, Heading,
Text, Text,
Badge, Badge,
IconButton,
Center, Center,
Spinner, Spinner,
useColorModeValue useColorModeValue
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { ChevronLeftIcon, ChevronRightIcon, TimeIcon } from '@chakra-ui/icons'; import { TimeIcon } from '@chakra-ui/icons';
import DynamicNewsEventCard from './EventCard/DynamicNewsEventCard'; import EventScrollList from './DynamicNewsCard/EventScrollList';
import DynamicNewsDetailPanel from './DynamicNewsDetail'; import DynamicNewsDetailPanel from './DynamicNewsDetail';
/** /**
@@ -41,9 +40,6 @@ const DynamicNewsCard = forwardRef(({
}, ref) => { }, ref) => {
const cardBg = useColorModeValue('white', 'gray.800'); const cardBg = useColorModeValue('white', 'gray.800');
const borderColor = useColorModeValue('gray.200', 'gray.700'); const borderColor = useColorModeValue('gray.200', 'gray.700');
const scrollContainerRef = useRef(null);
const [showLeftArrow, setShowLeftArrow] = useState(false);
const [showRightArrow, setShowRightArrow] = useState(true);
const [selectedEvent, setSelectedEvent] = useState(null); const [selectedEvent, setSelectedEvent] = useState(null);
// 默认选中第一个事件 // 默认选中第一个事件
@@ -53,48 +49,6 @@ const DynamicNewsCard = forwardRef(({
} }
}, [events, selectedEvent]); }, [events, selectedEvent]);
// 滚动到左侧
const scrollLeft = () => {
if (scrollContainerRef.current) {
scrollContainerRef.current.scrollBy({
left: -400,
behavior: 'smooth'
});
}
};
// 滚动到右侧
const scrollRight = () => {
if (scrollContainerRef.current) {
scrollContainerRef.current.scrollBy({
left: 400,
behavior: 'smooth'
});
}
};
// 监听滚动位置,更新箭头显示状态
const handleScroll = (e) => {
const container = e.target;
const scrollLeft = container.scrollLeft;
const scrollWidth = container.scrollWidth;
const clientWidth = container.clientWidth;
setShowLeftArrow(scrollLeft > 0);
setShowRightArrow(scrollLeft < scrollWidth - clientWidth - 10);
};
// 时间轴样式配置
const getTimelineBoxStyle = () => {
return {
bg: useColorModeValue('gray.50', 'gray.700'),
borderColor: useColorModeValue('gray.400', 'gray.500'),
borderWidth: '2px',
textColor: useColorModeValue('blue.600', 'blue.400'),
boxShadow: 'sm',
};
};
return ( return (
<Card ref={ref} {...rest} bg={cardBg} borderColor={borderColor} mb={4}> <Card ref={ref} {...rest} bg={cardBg} borderColor={borderColor} mb={4}>
{/* 标题部分 */} {/* 标题部分 */}
@@ -142,106 +96,12 @@ const DynamicNewsCard = forwardRef(({
{/* 横向滚动事件列表 */} {/* 横向滚动事件列表 */}
{!loading && events && events.length > 0 && ( {!loading && events && events.length > 0 && (
<Box position="relative"> <EventScrollList
{/* 左侧滚动按钮 */} events={events}
{showLeftArrow && ( selectedEvent={selectedEvent}
<IconButton onEventSelect={setSelectedEvent}
icon={<ChevronLeftIcon boxSize={6} />}
position="absolute"
left="-4"
top="50%"
transform="translateY(-50%)"
zIndex={2}
onClick={scrollLeft}
colorScheme="blue"
variant="solid"
size="md"
borderRadius="full"
shadow="md"
aria-label="向左滚动"
/>
)}
{/* 右侧滚动按钮 */}
{showRightArrow && (
<IconButton
icon={<ChevronRightIcon boxSize={6} />}
position="absolute"
right="-4"
top="50%"
transform="translateY(-50%)"
zIndex={2}
onClick={scrollRight}
colorScheme="blue"
variant="solid"
size="md"
borderRadius="full"
shadow="md"
aria-label="向右滚动"
/>
)}
{/* 横向滚动容器 */}
<Flex
ref={scrollContainerRef}
overflowX="auto"
overflowY="hidden"
gap={4}
py={4}
px={2}
onScroll={handleScroll}
css={{
'&::-webkit-scrollbar': {
height: '8px',
},
'&::-webkit-scrollbar-track': {
background: useColorModeValue('#f1f1f1', '#2D3748'),
borderRadius: '10px',
},
'&::-webkit-scrollbar-thumb': {
background: useColorModeValue('#888', '#4A5568'),
borderRadius: '10px',
},
'&::-webkit-scrollbar-thumb:hover': {
background: useColorModeValue('#555', '#718096'),
},
// 平滑滚动
scrollBehavior: 'smooth',
// 触摸设备优化
WebkitOverflowScrolling: 'touch',
}}
>
{events.map((event, index) => (
<Box
key={event.id}
minW="calc((100% - 64px) / 5)"
maxW="calc((100% - 64px) / 5)"
flexShrink={0}
>
<DynamicNewsEventCard
event={event}
index={index}
isFollowing={false}
followerCount={event.follower_count || 0}
isSelected={selectedEvent?.id === event.id}
onEventClick={(clickedEvent) => {
setSelectedEvent(clickedEvent);
// 只更新详情面板,不触发父组件回调
}}
onTitleClick={(e) => {
e.preventDefault();
e.stopPropagation();
setSelectedEvent(event);
// 只更新详情面板,不触发父组件回调
}}
onToggleFollow={() => {}}
timelineStyle={getTimelineBoxStyle()}
borderColor={borderColor} borderColor={borderColor}
/> />
</Box>
))}
</Flex>
</Box>
)} )}
{/* 详情面板 */} {/* 详情面板 */}

View File

@@ -0,0 +1,175 @@
// src/views/Community/components/DynamicNewsCard/EventScrollList.js
// 横向滚动事件列表组件
import React, { useRef, useState } from 'react';
import {
Box,
Flex,
IconButton,
useColorModeValue
} from '@chakra-ui/react';
import { ChevronLeftIcon, ChevronRightIcon } from '@chakra-ui/icons';
import DynamicNewsEventCard from '../EventCard/DynamicNewsEventCard';
/**
* 横向滚动事件列表组件
* @param {Array} events - 事件列表
* @param {Object} selectedEvent - 当前选中的事件
* @param {Function} onEventSelect - 事件选择回调
* @param {string} borderColor - 边框颜色
*/
const EventScrollList = ({
events,
selectedEvent,
onEventSelect,
borderColor
}) => {
const scrollContainerRef = useRef(null);
const [showLeftArrow, setShowLeftArrow] = useState(false);
const [showRightArrow, setShowRightArrow] = useState(true);
// 滚动到左侧
const scrollLeft = () => {
if (scrollContainerRef.current) {
scrollContainerRef.current.scrollBy({
left: -400,
behavior: 'smooth'
});
}
};
// 滚动到右侧
const scrollRight = () => {
if (scrollContainerRef.current) {
scrollContainerRef.current.scrollBy({
left: 400,
behavior: 'smooth'
});
}
};
// 监听滚动位置,更新箭头显示状态
const handleScroll = (e) => {
const container = e.target;
const scrollLeft = container.scrollLeft;
const scrollWidth = container.scrollWidth;
const clientWidth = container.clientWidth;
setShowLeftArrow(scrollLeft > 0);
setShowRightArrow(scrollLeft < scrollWidth - clientWidth - 10);
};
// 时间轴样式配置
const getTimelineBoxStyle = () => {
return {
bg: useColorModeValue('gray.50', 'gray.700'),
borderColor: useColorModeValue('gray.400', 'gray.500'),
borderWidth: '2px',
textColor: useColorModeValue('blue.600', 'blue.400'),
boxShadow: 'sm',
};
};
return (
<Box position="relative">
{/* 左侧滚动按钮 */}
{showLeftArrow && (
<IconButton
icon={<ChevronLeftIcon boxSize={6} />}
position="absolute"
left="-4"
top="50%"
transform="translateY(-50%)"
zIndex={2}
onClick={scrollLeft}
colorScheme="blue"
variant="solid"
size="md"
borderRadius="full"
shadow="md"
aria-label="向左滚动"
/>
)}
{/* 右侧滚动按钮 */}
{showRightArrow && (
<IconButton
icon={<ChevronRightIcon boxSize={6} />}
position="absolute"
right="-4"
top="50%"
transform="translateY(-50%)"
zIndex={2}
onClick={scrollRight}
colorScheme="blue"
variant="solid"
size="md"
borderRadius="full"
shadow="md"
aria-label="向右滚动"
/>
)}
{/* 横向滚动容器 */}
<Flex
ref={scrollContainerRef}
overflowX="auto"
overflowY="hidden"
gap={4}
py={4}
px={2}
onScroll={handleScroll}
css={{
'&::-webkit-scrollbar': {
height: '8px',
},
'&::-webkit-scrollbar-track': {
background: useColorModeValue('#f1f1f1', '#2D3748'),
borderRadius: '10px',
},
'&::-webkit-scrollbar-thumb': {
background: useColorModeValue('#888', '#4A5568'),
borderRadius: '10px',
},
'&::-webkit-scrollbar-thumb:hover': {
background: useColorModeValue('#555', '#718096'),
},
// 平滑滚动
scrollBehavior: 'smooth',
// 触摸设备优化
WebkitOverflowScrolling: 'touch',
}}
>
{events.map((event, index) => (
<Box
key={event.id}
minW="calc((100% - 64px) / 5)"
maxW="calc((100% - 64px) / 5)"
flexShrink={0}
>
<DynamicNewsEventCard
event={event}
index={index}
isFollowing={false}
followerCount={event.follower_count || 0}
isSelected={selectedEvent?.id === event.id}
onEventClick={(clickedEvent) => {
onEventSelect(clickedEvent);
}}
onTitleClick={(e) => {
e.preventDefault();
e.stopPropagation();
onEventSelect(event);
}}
onToggleFollow={() => {}}
timelineStyle={getTimelineBoxStyle()}
borderColor={borderColor}
/>
</Box>
))}
</Flex>
</Box>
);
};
export default EventScrollList;