feat: 创建原子组件(Atoms) - EventTimeline: 时间轴显示(60行) │ │

│ │ - EventImportanceBadge: 重要性等级标签(100行)                                                                                  │ │
│ │ - EventStats: 统计信息组件(60行)                                                                                               │ │
│ │ - EventFollowButton: 关注按钮(40行)                                                                                            │ │
│ │ - EventPriceDisplay: 价格变动显示(130行)                                                                                       │ │
│ │ - EventDescription: 描述文本组件(60行)
This commit is contained in:
zdl
2025-10-30 12:14:27 +08:00
parent 57a7d3b9e7
commit a39d57f9de
6 changed files with 436 additions and 0 deletions

View File

@@ -0,0 +1,56 @@
// src/views/Community/components/EventCard/EventDescription.js
import React, { useState } from 'react';
import { Box, Text, Button } from '@chakra-ui/react';
/**
* 事件描述组件(支持展开/收起)
* @param {Object} props
* @param {string} props.description - 描述文本
* @param {string} props.textColor - 文字颜色
* @param {number} props.minLength - 触发展开/收起的最小长度(默认 120
* @param {number} props.noOfLines - 未展开时显示的行数(默认 3
*/
const EventDescription = ({
description,
textColor,
minLength = 120,
noOfLines = 3
}) => {
const [isExpanded, setIsExpanded] = useState(false);
// 如果没有描述,不渲染
if (!description) {
return null;
}
const handleToggle = (e) => {
e.stopPropagation();
setIsExpanded(!isExpanded);
};
return (
<Box>
<Text
color={textColor}
fontSize="sm"
lineHeight="tall"
noOfLines={isExpanded ? undefined : noOfLines}
>
{description}
</Text>
{description.length > minLength && (
<Button
variant="link"
size="xs"
colorScheme="blue"
onClick={handleToggle}
mt={1}
>
{isExpanded ? '收起' : '...展开'}
</Button>
)}
</Box>
);
};
export default EventDescription;

View File

@@ -0,0 +1,43 @@
// src/views/Community/components/EventCard/EventFollowButton.js
import React from 'react';
import { Button } from '@chakra-ui/react';
import { StarIcon } from '@chakra-ui/icons';
/**
* 事件关注按钮组件
* @param {Object} props
* @param {boolean} props.isFollowing - 是否已关注
* @param {number} props.followerCount - 关注数
* @param {Function} props.onToggle - 切换关注状态的回调函数
* @param {string} props.size - 按钮尺寸('xs' | 'sm' | 'md',默认 'sm'
* @param {boolean} props.showCount - 是否显示关注数(默认 true
*/
const EventFollowButton = ({
isFollowing,
followerCount = 0,
onToggle,
size = 'sm',
showCount = true
}) => {
const iconSize = size === 'xs' ? '10px' : '12px';
const handleClick = (e) => {
e.stopPropagation();
onToggle?.();
};
return (
<Button
size={size}
colorScheme="yellow"
variant={isFollowing ? 'solid' : 'outline'}
leftIcon={<StarIcon boxSize={iconSize} />}
onClick={handleClick}
>
{isFollowing ? '已关注' : '关注'}
{showCount && followerCount > 0 && `(${followerCount})`}
</Button>
);
};
export default EventFollowButton;

View File

@@ -0,0 +1,96 @@
// src/views/Community/components/EventCard/EventImportanceBadge.js
import React from 'react';
import { Badge, Tooltip, VStack, HStack, Text, Divider, Circle } from '@chakra-ui/react';
import { InfoIcon } from '@chakra-ui/icons';
import { getImportanceConfig, getAllImportanceLevels } from '../../../../constants/importanceLevels';
/**
* 事件重要性等级标签组件
* @param {Object} props
* @param {string} props.importance - 重要性等级S/A/B/C/D
* @param {boolean} props.showTooltip - 是否显示详细提示框(默认 false
* @param {boolean} props.showIcon - 是否显示信息图标(默认 false
* @param {string} props.size - 标签大小xs/sm/md/lg默认 xs
*/
const EventImportanceBadge = ({
importance,
showTooltip = false,
showIcon = false,
size = 'xs'
}) => {
const importanceConfig = getImportanceConfig(importance);
// 简单模式:只显示标签
if (!showTooltip) {
return (
<Badge
colorScheme={importanceConfig.color.split('.')[0]}
fontSize={size}
px={size === 'xs' ? 2 : size === 'sm' ? 2.5 : 3}
py={size === 'xs' ? 1 : size === 'sm' ? 1 : 1.5}
borderRadius="md"
fontWeight="bold"
display="inline-flex"
alignItems="center"
verticalAlign="middle"
>
{importance || 'C'}
</Badge>
);
}
// 详细模式:带提示框的标签
return (
<Tooltip
label={
<VStack align="start" spacing={1} maxW="320px">
<Text fontWeight="bold" fontSize="sm" mb={1}>
重要性等级说明
</Text>
<Divider borderColor="gray.300" />
{getAllImportanceLevels().map((level) => (
<HStack key={level.level} spacing={2} align="center" w="full" py={0.5}>
<Circle
size="8px"
bg={level.dotBg}
flexShrink={0}
/>
<Text fontSize="xs" color="gray.700" lineHeight="1.5">
<Text as="span" fontWeight="bold">{level.level}</Text>
{level.description}
</Text>
</HStack>
))}
</VStack>
}
placement="top"
hasArrow
bg="white"
color="gray.800"
fontSize="md"
p={3}
borderRadius="lg"
borderWidth="1px"
borderColor="gray.200"
boxShadow="lg"
>
<Badge
colorScheme={importanceConfig.color.split('.')[0]}
px={1.5}
py={0.5}
borderRadius="md"
fontSize={size}
cursor="help"
display="flex"
alignItems="center"
gap={1}
flexShrink={0}
>
{showIcon && <InfoIcon boxSize={2.5} />}
{importance || 'C'}
</Badge>
</Tooltip>
);
};
export default EventImportanceBadge;

View File

@@ -0,0 +1,121 @@
// src/views/Community/components/EventCard/EventPriceDisplay.js
import React from 'react';
import { HStack, Badge, Text, Tooltip } from '@chakra-ui/react';
import { PriceArrow } from '../../../../utils/priceFormatters';
/**
* 事件价格变动显示组件
* @param {Object} props
* @param {number|null} props.avgChange - 平均涨跌幅
* @param {number|null} props.maxChange - 最大涨跌幅
* @param {number|null} props.weekChange - 周涨跌幅
* @param {boolean} props.compact - 是否为紧凑模式(只显示平均值,默认 false
* @param {boolean} props.inline - 是否内联显示(默认 false
*/
const EventPriceDisplay = ({
avgChange,
maxChange,
weekChange,
compact = false,
inline = false
}) => {
// 获取颜色方案
const getColorScheme = (value) => {
if (value == null) return 'gray';
return value > 0 ? 'red' : value < 0 ? 'green' : 'gray';
};
// 格式化百分比
const formatPercent = (value) => {
if (value == null) return '--';
return `${value > 0 ? '+' : ''}${value.toFixed(2)}%`;
};
// 紧凑模式:只显示平均值,内联在标题后
if (compact && avgChange != null) {
return (
<Tooltip label="平均" placement="top">
<Badge
colorScheme={getColorScheme(avgChange)}
fontSize="xs"
px={2}
py={1}
borderRadius="md"
fontWeight="bold"
display={inline ? "inline-flex" : "flex"}
alignItems="center"
gap={1}
verticalAlign="middle"
>
<PriceArrow value={avgChange} />
{formatPercent(avgChange)}
</Badge>
</Tooltip>
);
}
// 详细模式:显示所有价格变动
return (
<HStack spacing={2} flexWrap="wrap">
{/* 平均涨幅 - 始终显示,无数据时显示 -- */}
<Badge
colorScheme={getColorScheme(avgChange)}
fontSize="xs"
px={2}
py={0.5}
borderRadius="md"
cursor="pointer"
_hover={{ transform: 'scale(1.05)', boxShadow: 'md' }}
transition="all 0.2s"
>
<HStack spacing={1}>
<Text fontSize="xs" opacity={0.8}>平均</Text>
<Text fontWeight="bold">
{formatPercent(avgChange)}
</Text>
</HStack>
</Badge>
{/* 最大涨幅 - 始终显示,无数据时显示 -- */}
<Badge
colorScheme={getColorScheme(maxChange)}
fontSize="xs"
px={2}
py={0.5}
borderRadius="md"
cursor="pointer"
_hover={{ transform: 'scale(1.05)', boxShadow: 'md' }}
transition="all 0.2s"
>
<HStack spacing={1}>
<Text fontSize="xs" opacity={0.8}>最大</Text>
<Text fontWeight="bold">
{formatPercent(maxChange)}
</Text>
</HStack>
</Badge>
{/* 周涨幅 - 始终显示,无数据时显示 -- */}
<Badge
colorScheme={getColorScheme(weekChange)}
fontSize="xs"
px={2}
py={0.5}
borderRadius="md"
cursor="pointer"
_hover={{ transform: 'scale(1.05)', boxShadow: 'md' }}
transition="all 0.2s"
>
<HStack spacing={1}>
<Text fontSize="xs" opacity={0.8}></Text>
{weekChange != null && <PriceArrow value={weekChange} />}
<Text fontWeight="bold">
{formatPercent(weekChange)}
</Text>
</HStack>
</Badge>
</HStack>
);
};
export default EventPriceDisplay;

View File

@@ -0,0 +1,58 @@
// src/views/Community/components/EventCard/EventStats.js
import React from 'react';
import { HStack, Text, Tooltip } from '@chakra-ui/react';
import { ViewIcon, ChatIcon, StarIcon } from '@chakra-ui/icons';
/**
* 事件统计信息组件(浏览量、帖子数、关注数)
* @param {Object} props
* @param {number} props.viewCount - 浏览量
* @param {number} props.postCount - 帖子数
* @param {number} props.followerCount - 关注数
* @param {string} props.size - 尺寸('sm' | 'md',默认 'sm'
* @param {number} props.spacing - 间距(默认 3
* @param {Object} props.display - 响应式显示控制(默认 { base: 'none', md: 'flex' }
* @param {string} props.mutedColor - 文字颜色(可选)
*/
const EventStats = ({
viewCount = 0,
postCount = 0,
followerCount = 0,
size = 'sm',
spacing = 3,
display = { base: 'none', md: 'flex' },
mutedColor
}) => {
const fontSize = size === 'sm' ? 'xs' : 'sm';
const iconSize = size === 'sm' ? '12px' : '16px';
return (
<HStack spacing={spacing} display={display} color={mutedColor}>
{/* 浏览量 */}
<Tooltip label="浏览量" placement="top">
<HStack spacing={1}>
<ViewIcon boxSize={iconSize} />
<Text fontSize={fontSize}>{viewCount}</Text>
</HStack>
</Tooltip>
{/* 帖子数 */}
<Tooltip label="帖子数" placement="top">
<HStack spacing={1}>
<ChatIcon boxSize={iconSize} />
<Text fontSize={fontSize}>{postCount}</Text>
</HStack>
</Tooltip>
{/* 关注数 */}
<Tooltip label="关注数" placement="top">
<HStack spacing={1}>
<StarIcon boxSize={iconSize} />
<Text fontSize={fontSize}>{followerCount}</Text>
</HStack>
</Tooltip>
</HStack>
);
};
export default EventStats;

View File

@@ -0,0 +1,62 @@
// src/views/Community/components/EventCard/EventTimeline.js
import React from 'react';
import { Box, VStack, Text, useColorModeValue } from '@chakra-ui/react';
import moment from 'moment';
/**
* 事件时间轴组件
* @param {Object} props
* @param {string} props.createdAt - 事件创建时间
* @param {Object} props.timelineStyle - 时间轴样式配置
* @param {string} props.borderColor - 竖线边框颜色
* @param {string} props.minHeight - 竖线最小高度(例如:'40px' 或 '80px'
*/
const EventTimeline = ({ createdAt, timelineStyle, borderColor, minHeight = '40px' }) => {
return (
<VStack spacing={0} align="center" minW="90px">
{/* 时间长方形卡片 */}
<Box
{...(timelineStyle.bgGradient ? { bgGradient: timelineStyle.bgGradient } : { bg: timelineStyle.bg })}
borderWidth={timelineStyle.borderWidth}
borderColor={timelineStyle.borderColor}
borderRadius="md"
px={2}
py={2}
minW="85px"
textAlign="center"
boxShadow={timelineStyle.boxShadow}
transition="all 0.3s ease"
>
{/* 日期 YYYY-MM-DD */}
<Text
fontSize="xs"
fontWeight="bold"
color={timelineStyle.textColor}
lineHeight="1.3"
>
{moment(createdAt).format('YYYY-MM-DD')}
</Text>
{/* 时间 HH:mm */}
<Text
fontSize="xs"
fontWeight="bold"
color={timelineStyle.textColor}
lineHeight="1.3"
mt={0.5}
>
{moment(createdAt).format('HH:mm')}
</Text>
</Box>
{/* 时间轴竖线 */}
<Box
w="2px"
flex="1"
bg={borderColor}
minH={minHeight}
mt={1}
/>
</VStack>
);
};
export default EventTimeline;