feat: 创建原子组件(Atoms) - EventTimeline: 时间轴显示(60行) │ │
│ │ - EventImportanceBadge: 重要性等级标签(100行) │ │ │ │ - EventStats: 统计信息组件(60行) │ │ │ │ - EventFollowButton: 关注按钮(40行) │ │ │ │ - EventPriceDisplay: 价格变动显示(130行) │ │ │ │ - EventDescription: 描述文本组件(60行)
This commit is contained in:
56
src/views/Community/components/EventCard/EventDescription.js
Normal file
56
src/views/Community/components/EventCard/EventDescription.js
Normal 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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
121
src/views/Community/components/EventCard/EventPriceDisplay.js
Normal file
121
src/views/Community/components/EventCard/EventPriceDisplay.js
Normal 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;
|
||||
58
src/views/Community/components/EventCard/EventStats.js
Normal file
58
src/views/Community/components/EventCard/EventStats.js
Normal 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;
|
||||
62
src/views/Community/components/EventCard/EventTimeline.js
Normal file
62
src/views/Community/components/EventCard/EventTimeline.js
Normal 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;
|
||||
Reference in New Issue
Block a user