diff --git a/src/views/Community/components/DynamicNewsDetail/RelatedConceptsSection/ConceptStockItem.js b/src/views/Community/components/DynamicNewsDetail/RelatedConceptsSection/ConceptStockItem.js new file mode 100644 index 00000000..9268b53b --- /dev/null +++ b/src/views/Community/components/DynamicNewsDetail/RelatedConceptsSection/ConceptStockItem.js @@ -0,0 +1,65 @@ +// src/views/Community/components/DynamicNewsDetail/RelatedConceptsSection/ConceptStockItem.js +// 概念股票列表项组件 + +import React from 'react'; +import { + Box, + HStack, + Text, + Badge, + useColorModeValue, +} from '@chakra-ui/react'; + +/** + * 概念股票列表项组件 + * @param {Object} props + * @param {Object} props.stock - 股票对象 + * - stock_name: 股票名称 + * - stock_code: 股票代码 + * - change_pct: 涨跌幅 + * - reason: 关联原因 + */ +const ConceptStockItem = ({ stock }) => { + const sectionBg = useColorModeValue('gray.50', 'gray.750'); + const conceptNameColor = useColorModeValue('gray.800', 'gray.100'); + const stockCountColor = useColorModeValue('gray.500', 'gray.400'); + + const stockChangePct = parseFloat(stock.change_pct); + const stockChangeColor = stockChangePct > 0 ? 'red' : stockChangePct < 0 ? 'green' : 'gray'; + const stockChangeSymbol = stockChangePct > 0 ? '+' : ''; + + return ( + + + + + {stock.stock_name} + + + {stock.stock_code} + + + {stock.change_pct && ( + + {stockChangeSymbol}{stockChangePct.toFixed(2)}% + + )} + + {stock.reason && ( + + {stock.reason} + + )} + + ); +}; + +export default ConceptStockItem; diff --git a/src/views/Community/components/DynamicNewsDetail/RelatedConceptsSection/DetailedConceptCard.js b/src/views/Community/components/DynamicNewsDetail/RelatedConceptsSection/DetailedConceptCard.js new file mode 100644 index 00000000..21a97aff --- /dev/null +++ b/src/views/Community/components/DynamicNewsDetail/RelatedConceptsSection/DetailedConceptCard.js @@ -0,0 +1,150 @@ +// src/views/Community/components/DynamicNewsDetail/RelatedConceptsSection/DetailedConceptCard.js +// 详细概念卡片组件 + +import React from 'react'; +import { + Box, + HStack, + VStack, + Text, + Badge, + Card, + CardBody, + Divider, + SimpleGrid, + useColorModeValue, +} from '@chakra-ui/react'; +import ConceptStockItem from './ConceptStockItem'; + +/** + * 详细概念卡片组件 + * @param {Object} props + * @param {Object} props.concept - 概念对象 + * - name: 概念名称 + * - stock_count: 相关股票数量 + * - relevance: 相关度(0-100) + * - avg_change_pct: 平均涨跌幅 + * - description: 概念描述 + * - happened_times: 历史触发时间数组 + * - stocks: 相关股票数组 + * @param {Function} props.onClick - 点击回调 + */ +const DetailedConceptCard = ({ concept, onClick }) => { + const cardBg = useColorModeValue('white', 'gray.700'); + const borderColor = useColorModeValue('gray.200', 'gray.600'); + const headingColor = useColorModeValue('gray.700', 'gray.200'); + const stockCountColor = useColorModeValue('gray.500', 'gray.400'); + + // 计算涨跌幅颜色 + const changePct = parseFloat(concept.avg_change_pct); + const changeColor = changePct > 0 ? 'red' : changePct < 0 ? 'green' : 'gray'; + const changeSymbol = changePct > 0 ? '+' : ''; + + return ( + onClick(concept)} + > + + + {/* 头部信息 */} + + {/* 左侧:概念名称 + Badge */} + + + {concept.name} + + + + 相关度: {concept.relevance}% + + + {concept.stock_count} 只股票 + + + + + {/* 右侧:涨跌幅 */} + {concept.avg_change_pct && ( + + + 平均涨跌幅 + + + {changeSymbol}{changePct.toFixed(2)}% + + + )} + + + + + {/* 概念描述 */} + {concept.description && ( + + {concept.description} + + )} + + {/* 历史触发时间 */} + {concept.happened_times && concept.happened_times.length > 0 && ( + + + 历史触发时间: + + + {concept.happened_times.map((time, idx) => ( + + {time} + + ))} + + + )} + + {/* 核心相关股票 */} + {concept.stocks && concept.stocks.length > 0 && ( + + + + 核心相关股票 + + + 共 {concept.stock_count} 只 + + + + + {concept.stocks.slice(0, 4).map((stock, idx) => ( + + ))} + + + )} + + + + ); +}; + +export default DetailedConceptCard; diff --git a/src/views/Community/components/DynamicNewsDetail/RelatedConceptsSection/SimpleConceptCard.js b/src/views/Community/components/DynamicNewsDetail/RelatedConceptsSection/SimpleConceptCard.js new file mode 100644 index 00000000..6d09ecea --- /dev/null +++ b/src/views/Community/components/DynamicNewsDetail/RelatedConceptsSection/SimpleConceptCard.js @@ -0,0 +1,73 @@ +// src/views/Community/components/DynamicNewsDetail/RelatedConceptsSection/SimpleConceptCard.js +// 简单概念卡片组件(横向卡片) + +import React from 'react'; +import { + Flex, + Box, + Text, + useColorModeValue, +} from '@chakra-ui/react'; + +/** + * 简单概念卡片组件 + * @param {Object} props + * @param {Object} props.concept - 概念对象 + * - name: 概念名称 + * - stock_count: 相关股票数量 + * - relevance: 相关度(0-100) + * @param {Function} props.onClick - 点击回调 + * @param {Function} props.getRelevanceColor - 获取相关度颜色的函数 + */ +const SimpleConceptCard = ({ concept, onClick, getRelevanceColor }) => { + const cardBg = useColorModeValue('white', 'gray.700'); + const conceptNameColor = useColorModeValue('gray.800', 'gray.100'); + const borderColor = useColorModeValue('gray.300', 'gray.600'); + + const relevanceColors = getRelevanceColor(concept.relevance); + + return ( + onClick(concept)} + > + {/* 左侧:概念名 + 数量 */} + + {concept.name}{' '} + + ({concept.stock_count}) + + + + {/* 右侧:相关度标签 */} + + + 相关度: {concept.relevance}% + + + + ); +}; + +export default SimpleConceptCard; diff --git a/src/views/Community/components/DynamicNewsDetail/RelatedConceptsSection/TradingDateInfo.js b/src/views/Community/components/DynamicNewsDetail/RelatedConceptsSection/TradingDateInfo.js new file mode 100644 index 00000000..068ae3d2 --- /dev/null +++ b/src/views/Community/components/DynamicNewsDetail/RelatedConceptsSection/TradingDateInfo.js @@ -0,0 +1,46 @@ +// src/views/Community/components/DynamicNewsDetail/RelatedConceptsSection/TradingDateInfo.js +// 交易日期信息提示组件 + +import React from 'react'; +import { + Box, + HStack, + Text, + useColorModeValue, +} from '@chakra-ui/react'; +import { FaCalendarAlt } from 'react-icons/fa'; +import moment from 'moment'; + +/** + * 交易日期信息提示组件 + * @param {Object} props + * @param {string} props.effectiveTradingDate - 有效交易日期(涨跌幅数据日期) + * @param {string|Object} props.eventTime - 事件发生时间 + */ +const TradingDateInfo = ({ effectiveTradingDate, eventTime }) => { + const sectionBg = useColorModeValue('gray.50', 'gray.750'); + const headingColor = useColorModeValue('gray.700', 'gray.200'); + const stockCountColor = useColorModeValue('gray.500', 'gray.400'); + + if (!effectiveTradingDate) { + return null; + } + + return ( + + + + + 涨跌幅数据日期:{effectiveTradingDate} + {eventTime && effectiveTradingDate !== moment(eventTime).format('YYYY-MM-DD') && ( + + (事件发生于 {typeof eventTime === 'object' ? moment(eventTime).format('YYYY-MM-DD HH:mm') : moment(eventTime).format('YYYY-MM-DD HH:mm')},显示下一交易日数据) + + )} + + + + ); +}; + +export default TradingDateInfo; diff --git a/src/views/Community/components/DynamicNewsDetail/RelatedConceptsSection/index.js b/src/views/Community/components/DynamicNewsDetail/RelatedConceptsSection/index.js new file mode 100644 index 00000000..39e8377b --- /dev/null +++ b/src/views/Community/components/DynamicNewsDetail/RelatedConceptsSection/index.js @@ -0,0 +1,122 @@ +// src/views/Community/components/DynamicNewsDetail/RelatedConceptsSection/index.js +// 相关概念区组件(主组件) + +import React, { useState } from 'react'; +import { + Box, + SimpleGrid, + Flex, + Button, + Collapse, + Heading, + useColorModeValue, +} from '@chakra-ui/react'; +import { ChevronDownIcon, ChevronUpIcon } from '@chakra-ui/icons'; +import { useNavigate } from 'react-router-dom'; +import SimpleConceptCard from './SimpleConceptCard'; +import DetailedConceptCard from './DetailedConceptCard'; +import TradingDateInfo from './TradingDateInfo'; + +/** + * 相关概念区组件 + * @param {Object} props + * @param {Array} props.keywords - 相关概念数组 + * - name: 概念名称 + * - stock_count: 相关股票数量 + * - relevance: 相关度(0-100) + * @param {string} props.effectiveTradingDate - 有效交易日期(涨跌幅数据日期) + * @param {string|Object} props.eventTime - 事件发生时间 + */ +const RelatedConceptsSection = ({ keywords, effectiveTradingDate, eventTime }) => { + const [isExpanded, setIsExpanded] = useState(false); + const navigate = useNavigate(); + + // 颜色配置 + const sectionBg = useColorModeValue('gray.50', 'gray.750'); + const headingColor = useColorModeValue('gray.700', 'gray.200'); + + // 如果没有关键词,不渲染 + if (!keywords || keywords.length === 0) { + return null; + } + + /** + * 根据相关度获取颜色(浅色背景 + 深色文字) + * @param {number} relevance - 相关度(0-100) + * @returns {Object} 包含背景色和文字色 + */ + const getRelevanceColor = (relevance) => { + if (relevance >= 90) { + return { bg: 'purple.50', color: 'purple.800' }; // 极高相关 + } else if (relevance >= 80) { + return { bg: 'pink.50', color: 'pink.800' }; // 高相关 + } else if (relevance >= 70) { + return { bg: 'orange.50', color: 'orange.800' }; // 中等相关 + } else { + return { bg: 'gray.100', color: 'gray.700' }; // 低相关 + } + }; + + /** + * 处理概念点击 + * @param {Object} concept - 概念对象 + */ + const handleConceptClick = (concept) => { + // 跳转到概念详情页 + navigate(`/concept/${concept.name}`); + }; + + return ( + + {/* 标题栏 */} + + + 相关概念 + + + + + {/* 简单模式:横向卡片列表(总是显示) */} + + {keywords.map((concept, index) => ( + + ))} + + + {/* 详细模式:卡片网格(可折叠) */} + + {/* 交易日期信息 */} + + + {/* 详细概念卡片网格 */} + + {keywords.map((concept, index) => ( + + ))} + + + + ); +}; + +export default RelatedConceptsSection;