feat: 创建组合组件(Molecules)
- EventHeader: 标题头部组件(100行) │ │ │ │ - CompactEventCard: 紧凑模式卡片(160行) │ │ │ │ - DetailedEventCard: 详细模式卡片(170行) │ │ │ │ - index.js: EventCard 统一入口(60行)
This commit is contained in:
151
src/views/Community/components/EventCard/CompactEventCard.js
Normal file
151
src/views/Community/components/EventCard/CompactEventCard.js
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
// src/views/Community/components/EventCard/CompactEventCard.js
|
||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
HStack,
|
||||||
|
Card,
|
||||||
|
CardBody,
|
||||||
|
VStack,
|
||||||
|
Flex,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Text,
|
||||||
|
useColorModeValue,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { getImportanceConfig } from '../../../../constants/importanceLevels';
|
||||||
|
|
||||||
|
// 导入子组件
|
||||||
|
import EventTimeline from './EventTimeline';
|
||||||
|
import EventHeader from './EventHeader';
|
||||||
|
import EventStats from './EventStats';
|
||||||
|
import EventFollowButton from './EventFollowButton';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 紧凑模式事件卡片组件
|
||||||
|
* @param {Object} props
|
||||||
|
* @param {Object} props.event - 事件对象
|
||||||
|
* @param {number} props.index - 事件索引
|
||||||
|
* @param {boolean} props.isFollowing - 是否已关注
|
||||||
|
* @param {number} props.followerCount - 关注数
|
||||||
|
* @param {Function} props.onEventClick - 卡片点击事件
|
||||||
|
* @param {Function} props.onTitleClick - 标题点击事件
|
||||||
|
* @param {Function} props.onViewDetail - 查看详情事件
|
||||||
|
* @param {Function} props.onToggleFollow - 切换关注事件
|
||||||
|
* @param {Object} props.timelineStyle - 时间轴样式配置
|
||||||
|
* @param {string} props.borderColor - 边框颜色
|
||||||
|
*/
|
||||||
|
const CompactEventCard = ({
|
||||||
|
event,
|
||||||
|
index,
|
||||||
|
isFollowing,
|
||||||
|
followerCount,
|
||||||
|
onEventClick,
|
||||||
|
onTitleClick,
|
||||||
|
onViewDetail,
|
||||||
|
onToggleFollow,
|
||||||
|
timelineStyle,
|
||||||
|
borderColor,
|
||||||
|
}) => {
|
||||||
|
const importance = getImportanceConfig(event.importance);
|
||||||
|
const cardBg = useColorModeValue('white', 'gray.800');
|
||||||
|
const linkColor = useColorModeValue('blue.600', 'blue.400');
|
||||||
|
const mutedColor = useColorModeValue('gray.500', 'gray.400');
|
||||||
|
|
||||||
|
const handleViewDetailClick = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onViewDetail?.(event.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HStack align="stretch" spacing={3} w="full">
|
||||||
|
{/* 左侧时间轴 */}
|
||||||
|
<EventTimeline
|
||||||
|
createdAt={event.created_at}
|
||||||
|
timelineStyle={timelineStyle}
|
||||||
|
borderColor={borderColor}
|
||||||
|
minHeight="40px"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 右侧内容卡片 */}
|
||||||
|
<Card
|
||||||
|
flex="1"
|
||||||
|
bg={index % 2 === 0 ? cardBg : useColorModeValue('gray.50', 'gray.750')}
|
||||||
|
borderWidth="1px"
|
||||||
|
borderColor={borderColor}
|
||||||
|
borderRadius="md"
|
||||||
|
boxShadow="sm"
|
||||||
|
_hover={{
|
||||||
|
boxShadow: 'lg',
|
||||||
|
transform: 'translateY(-2px)',
|
||||||
|
borderColor: importance.color,
|
||||||
|
}}
|
||||||
|
transition="all 0.3s ease"
|
||||||
|
cursor="pointer"
|
||||||
|
onClick={() => onEventClick?.(event)}
|
||||||
|
mb={2}
|
||||||
|
>
|
||||||
|
<CardBody p={3}>
|
||||||
|
<VStack align="stretch" spacing={2}>
|
||||||
|
{/* 第一行:标题(2行)+ 标签(内联)+ 按钮(右侧) */}
|
||||||
|
<Flex align="flex-start" gap={2}>
|
||||||
|
{/* 标题区域:标题+标签(内联) */}
|
||||||
|
<EventHeader
|
||||||
|
title={event.title}
|
||||||
|
importance={event.importance}
|
||||||
|
onTitleClick={(e) => onTitleClick?.(e, event)}
|
||||||
|
linkColor={linkColor}
|
||||||
|
compact={true}
|
||||||
|
avgChange={event.related_avg_chg}
|
||||||
|
size="md"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 操作按钮 - 固定右侧 */}
|
||||||
|
<HStack spacing={2} flexShrink={0}>
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="ghost"
|
||||||
|
colorScheme="blue"
|
||||||
|
onClick={handleViewDetailClick}
|
||||||
|
>
|
||||||
|
详情
|
||||||
|
</Button>
|
||||||
|
<EventFollowButton
|
||||||
|
isFollowing={isFollowing}
|
||||||
|
followerCount={followerCount}
|
||||||
|
onToggle={() => onToggleFollow?.(event.id)}
|
||||||
|
size="xs"
|
||||||
|
showCount={true}
|
||||||
|
/>
|
||||||
|
</HStack>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
{/* 第二行:统计数据(左) + 作者时间(右) */}
|
||||||
|
<Flex justify="space-between" align="center" fontSize="xs" color={mutedColor}>
|
||||||
|
{/* 左侧:统计数据 */}
|
||||||
|
<EventStats
|
||||||
|
viewCount={event.view_count}
|
||||||
|
postCount={event.post_count}
|
||||||
|
followerCount={followerCount}
|
||||||
|
size="sm"
|
||||||
|
spacing={3}
|
||||||
|
display={{ base: 'none', md: 'flex' }}
|
||||||
|
mutedColor={mutedColor}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 右侧:作者 + 时间 */}
|
||||||
|
<HStack spacing={2}>
|
||||||
|
<Text>@{event.creator?.username || 'Anonymous'}</Text>
|
||||||
|
<Text>•</Text>
|
||||||
|
<Text fontWeight="bold" color={linkColor}>
|
||||||
|
{moment(event.created_at).format('YYYY-MM-DD HH:mm')}
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
</Flex>
|
||||||
|
</VStack>
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
</HStack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CompactEventCard;
|
||||||
151
src/views/Community/components/EventCard/DetailedEventCard.js
Normal file
151
src/views/Community/components/EventCard/DetailedEventCard.js
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
// src/views/Community/components/EventCard/DetailedEventCard.js
|
||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
HStack,
|
||||||
|
Card,
|
||||||
|
CardBody,
|
||||||
|
VStack,
|
||||||
|
Flex,
|
||||||
|
Text,
|
||||||
|
useColorModeValue,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { getImportanceConfig } from '../../../../constants/importanceLevels';
|
||||||
|
|
||||||
|
// 导入子组件
|
||||||
|
import EventTimeline from './EventTimeline';
|
||||||
|
import EventHeader from './EventHeader';
|
||||||
|
import EventStats from './EventStats';
|
||||||
|
import EventFollowButton from './EventFollowButton';
|
||||||
|
import EventPriceDisplay from './EventPriceDisplay';
|
||||||
|
import EventDescription from './EventDescription';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 详细模式事件卡片组件
|
||||||
|
* @param {Object} props
|
||||||
|
* @param {Object} props.event - 事件对象
|
||||||
|
* @param {boolean} props.isFollowing - 是否已关注
|
||||||
|
* @param {number} props.followerCount - 关注数
|
||||||
|
* @param {Function} props.onEventClick - 卡片点击事件
|
||||||
|
* @param {Function} props.onTitleClick - 标题点击事件
|
||||||
|
* @param {Function} props.onToggleFollow - 切换关注事件
|
||||||
|
* @param {Object} props.timelineStyle - 时间轴样式配置
|
||||||
|
* @param {string} props.borderColor - 边框颜色
|
||||||
|
*/
|
||||||
|
const DetailedEventCard = ({
|
||||||
|
event,
|
||||||
|
isFollowing,
|
||||||
|
followerCount,
|
||||||
|
onEventClick,
|
||||||
|
onTitleClick,
|
||||||
|
onToggleFollow,
|
||||||
|
timelineStyle,
|
||||||
|
borderColor,
|
||||||
|
}) => {
|
||||||
|
const importance = getImportanceConfig(event.importance);
|
||||||
|
const cardBg = useColorModeValue('white', 'gray.800');
|
||||||
|
const linkColor = useColorModeValue('blue.600', 'blue.400');
|
||||||
|
const mutedColor = useColorModeValue('gray.500', 'gray.400');
|
||||||
|
const textColor = useColorModeValue('gray.700', 'gray.200');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HStack align="stretch" spacing={3} w="full">
|
||||||
|
{/* 左侧时间轴 */}
|
||||||
|
<EventTimeline
|
||||||
|
createdAt={event.created_at}
|
||||||
|
timelineStyle={timelineStyle}
|
||||||
|
borderColor={borderColor}
|
||||||
|
minHeight="80px"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 事件卡片 */}
|
||||||
|
<Card
|
||||||
|
flex="1"
|
||||||
|
bg={cardBg}
|
||||||
|
borderWidth="1px"
|
||||||
|
borderColor={borderColor}
|
||||||
|
borderRadius="md"
|
||||||
|
boxShadow="sm"
|
||||||
|
_hover={{
|
||||||
|
boxShadow: 'xl',
|
||||||
|
transform: 'translateY(-3px)',
|
||||||
|
borderColor: importance.color,
|
||||||
|
}}
|
||||||
|
transition="all 0.3s ease"
|
||||||
|
cursor="pointer"
|
||||||
|
onClick={() => onEventClick?.(event)}
|
||||||
|
mb={3}
|
||||||
|
>
|
||||||
|
<CardBody p={4}>
|
||||||
|
<VStack align="stretch" spacing={2.5}>
|
||||||
|
{/* 第一行:标题+优先级 | 统计+关注 */}
|
||||||
|
<Flex align="center" justify="space-between" gap={3}>
|
||||||
|
{/* 左侧:标题 + 优先级标签 */}
|
||||||
|
<EventHeader
|
||||||
|
title={event.title}
|
||||||
|
importance={event.importance}
|
||||||
|
onTitleClick={(e) => onTitleClick?.(e, event)}
|
||||||
|
linkColor={linkColor}
|
||||||
|
compact={false}
|
||||||
|
size="md"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 右侧:统计数据 + 关注按钮 */}
|
||||||
|
<HStack spacing={4} flexShrink={0}>
|
||||||
|
{/* 统计数据 */}
|
||||||
|
<EventStats
|
||||||
|
viewCount={event.view_count}
|
||||||
|
postCount={event.post_count}
|
||||||
|
followerCount={followerCount}
|
||||||
|
size="md"
|
||||||
|
spacing={4}
|
||||||
|
display="flex"
|
||||||
|
mutedColor={mutedColor}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 关注按钮 */}
|
||||||
|
<EventFollowButton
|
||||||
|
isFollowing={isFollowing}
|
||||||
|
followerCount={followerCount}
|
||||||
|
onToggle={() => onToggleFollow?.(event.id)}
|
||||||
|
size="sm"
|
||||||
|
showCount={false}
|
||||||
|
/>
|
||||||
|
</HStack>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
{/* 第二行:价格标签 | 时间+作者 */}
|
||||||
|
<Flex justify="space-between" align="center" wrap="wrap" gap={3}>
|
||||||
|
{/* 左侧:价格标签 */}
|
||||||
|
<EventPriceDisplay
|
||||||
|
avgChange={event.related_avg_chg}
|
||||||
|
maxChange={event.related_max_chg}
|
||||||
|
weekChange={event.related_week_chg}
|
||||||
|
compact={false}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 右侧:时间 + 作者 */}
|
||||||
|
<HStack spacing={2} fontSize="sm" flexShrink={0}>
|
||||||
|
<Text fontWeight="bold" color={linkColor}>
|
||||||
|
{moment(event.created_at).format('YYYY-MM-DD HH:mm')}
|
||||||
|
</Text>
|
||||||
|
<Text color={mutedColor}>•</Text>
|
||||||
|
<Text color={mutedColor}>@{event.creator?.username || 'Anonymous'}</Text>
|
||||||
|
</HStack>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
{/* 第三行:描述文字 + 展开/收起 */}
|
||||||
|
<EventDescription
|
||||||
|
description={event.description}
|
||||||
|
textColor={textColor}
|
||||||
|
minLength={120}
|
||||||
|
noOfLines={3}
|
||||||
|
/>
|
||||||
|
</VStack>
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
</HStack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DetailedEventCard;
|
||||||
100
src/views/Community/components/EventCard/EventHeader.js
Normal file
100
src/views/Community/components/EventCard/EventHeader.js
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
// src/views/Community/components/EventCard/EventHeader.js
|
||||||
|
import React from 'react';
|
||||||
|
import { Box, Text, Heading, Tooltip, HStack } from '@chakra-ui/react';
|
||||||
|
import EventImportanceBadge from './EventImportanceBadge';
|
||||||
|
import EventPriceDisplay from './EventPriceDisplay';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件标题头部组件
|
||||||
|
* @param {Object} props
|
||||||
|
* @param {string} props.title - 事件标题
|
||||||
|
* @param {string} props.importance - 重要性等级
|
||||||
|
* @param {Function} props.onTitleClick - 标题点击事件
|
||||||
|
* @param {string} props.linkColor - 链接颜色
|
||||||
|
* @param {boolean} props.compact - 是否紧凑模式(默认 false)
|
||||||
|
* @param {number|null} props.avgChange - 平均涨跌幅(紧凑模式下使用)
|
||||||
|
* @param {string} props.size - 标题大小('sm' | 'md' | 'lg',默认 'md')
|
||||||
|
*/
|
||||||
|
const EventHeader = ({
|
||||||
|
title,
|
||||||
|
importance,
|
||||||
|
onTitleClick,
|
||||||
|
linkColor,
|
||||||
|
compact = false,
|
||||||
|
avgChange = null,
|
||||||
|
size = 'md'
|
||||||
|
}) => {
|
||||||
|
const handleClick = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
onTitleClick?.(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 紧凑模式:标题 + 标签内联
|
||||||
|
if (compact) {
|
||||||
|
return (
|
||||||
|
<Box flex="1" minW="150px">
|
||||||
|
<Text
|
||||||
|
fontSize={size}
|
||||||
|
fontWeight="bold"
|
||||||
|
color={linkColor}
|
||||||
|
lineHeight="1.4"
|
||||||
|
noOfLines={2}
|
||||||
|
display="inline"
|
||||||
|
_hover={{ textDecoration: 'underline', color: 'blue.500' }}
|
||||||
|
onClick={handleClick}
|
||||||
|
cursor="pointer"
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
{' '}
|
||||||
|
{/* 重要性标签 - 内联 */}
|
||||||
|
<EventImportanceBadge
|
||||||
|
importance={importance}
|
||||||
|
showTooltip={false}
|
||||||
|
size="xs"
|
||||||
|
/>
|
||||||
|
{' '}
|
||||||
|
{/* 价格标签 - 内联 */}
|
||||||
|
{avgChange != null && (
|
||||||
|
<EventPriceDisplay
|
||||||
|
avgChange={avgChange}
|
||||||
|
compact={true}
|
||||||
|
inline={true}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 详细模式:标题 + 提示框的重要性标签
|
||||||
|
return (
|
||||||
|
<HStack spacing={2} flex="1" align="center">
|
||||||
|
<Tooltip
|
||||||
|
label="点击查看事件详情"
|
||||||
|
placement="top"
|
||||||
|
hasArrow
|
||||||
|
openDelay={500}
|
||||||
|
>
|
||||||
|
<Heading
|
||||||
|
size={size}
|
||||||
|
color={linkColor}
|
||||||
|
_hover={{ textDecoration: 'underline', color: 'blue.500' }}
|
||||||
|
onClick={handleClick}
|
||||||
|
cursor="pointer"
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</Heading>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<EventImportanceBadge
|
||||||
|
importance={importance}
|
||||||
|
showTooltip={true}
|
||||||
|
showIcon={true}
|
||||||
|
size="xs"
|
||||||
|
/>
|
||||||
|
</HStack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EventHeader;
|
||||||
67
src/views/Community/components/EventCard/index.js
Normal file
67
src/views/Community/components/EventCard/index.js
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
// src/views/Community/components/EventCard/index.js
|
||||||
|
import React from 'react';
|
||||||
|
import CompactEventCard from './CompactEventCard';
|
||||||
|
import DetailedEventCard from './DetailedEventCard';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件卡片统一入口组件
|
||||||
|
* 根据 isCompactMode 自动选择紧凑模式或详细模式
|
||||||
|
*
|
||||||
|
* @param {Object} props
|
||||||
|
* @param {Object} props.event - 事件对象
|
||||||
|
* @param {number} props.index - 事件索引(紧凑模式下用于交替背景色)
|
||||||
|
* @param {boolean} props.isCompactMode - 是否为紧凑模式
|
||||||
|
* @param {boolean} props.isFollowing - 是否已关注
|
||||||
|
* @param {number} props.followerCount - 关注数
|
||||||
|
* @param {Function} props.onEventClick - 卡片点击事件
|
||||||
|
* @param {Function} props.onTitleClick - 标题点击事件
|
||||||
|
* @param {Function} props.onViewDetail - 查看详情事件(仅紧凑模式)
|
||||||
|
* @param {Function} props.onToggleFollow - 切换关注事件
|
||||||
|
* @param {Object} props.timelineStyle - 时间轴样式配置
|
||||||
|
* @param {string} props.borderColor - 边框颜色
|
||||||
|
*/
|
||||||
|
const EventCard = ({
|
||||||
|
event,
|
||||||
|
index,
|
||||||
|
isCompactMode,
|
||||||
|
isFollowing,
|
||||||
|
followerCount,
|
||||||
|
onEventClick,
|
||||||
|
onTitleClick,
|
||||||
|
onViewDetail,
|
||||||
|
onToggleFollow,
|
||||||
|
timelineStyle,
|
||||||
|
borderColor,
|
||||||
|
}) => {
|
||||||
|
if (isCompactMode) {
|
||||||
|
return (
|
||||||
|
<CompactEventCard
|
||||||
|
event={event}
|
||||||
|
index={index}
|
||||||
|
isFollowing={isFollowing}
|
||||||
|
followerCount={followerCount}
|
||||||
|
onEventClick={onEventClick}
|
||||||
|
onTitleClick={onTitleClick}
|
||||||
|
onViewDetail={onViewDetail}
|
||||||
|
onToggleFollow={onToggleFollow}
|
||||||
|
timelineStyle={timelineStyle}
|
||||||
|
borderColor={borderColor}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DetailedEventCard
|
||||||
|
event={event}
|
||||||
|
isFollowing={isFollowing}
|
||||||
|
followerCount={followerCount}
|
||||||
|
onEventClick={onEventClick}
|
||||||
|
onTitleClick={onTitleClick}
|
||||||
|
onToggleFollow={onToggleFollow}
|
||||||
|
timelineStyle={timelineStyle}
|
||||||
|
borderColor={borderColor}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EventCard;
|
||||||
Reference in New Issue
Block a user