From 5ceffc53d685a67589f8e976e5d17b2273b46544 Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Fri, 7 Nov 2025 18:39:49 +0800 Subject: [PATCH 01/10] =?UTF-8?q?feat:=20=E4=BA=8B=E4=BB=B6=E4=B8=AD?= =?UTF-8?q?=E5=BF=83=E8=AF=A6=E6=83=85=E9=9D=A2=E6=9D=BFUi=E8=B0=83?= =?UTF-8?q?=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DynamicNewsCard/EventDetailScrollPanel.js | 10 +- .../DynamicNewsCard/VerticalModeLayout.js | 1 + .../DynamicNewsDetail/CompactMetaBar.js | 100 ++++++++++++++++++ 3 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 src/views/Community/components/DynamicNewsDetail/CompactMetaBar.js diff --git a/src/views/Community/components/DynamicNewsCard/EventDetailScrollPanel.js b/src/views/Community/components/DynamicNewsCard/EventDetailScrollPanel.js index 7a12194f..c531220f 100644 --- a/src/views/Community/components/DynamicNewsCard/EventDetailScrollPanel.js +++ b/src/views/Community/components/DynamicNewsCard/EventDetailScrollPanel.js @@ -13,13 +13,21 @@ import DynamicNewsDetailPanel from '../DynamicNewsDetail'; * @param {string} scrollbarTrackBg - 滚动条轨道背景色 * @param {string} scrollbarThumbBg - 滚动条滑块背景色 * @param {string} scrollbarThumbHoverBg - 滚动条滑块悬浮背景色 + * @param {string} detailMode - 详情模式:'full' | 'no-header'(默认 'full') + * @param {boolean} showHeader - 是否显示头部(可选,优先级高于 detailMode) */ const EventDetailScrollPanel = ({ selectedEvent, scrollbarTrackBg, scrollbarThumbBg, scrollbarThumbHoverBg, + detailMode = 'full', + showHeader, }) => { + // 计算是否显示头部:showHeader 显式指定时优先,否则根据 detailMode 判断 + const shouldShowHeader = showHeader !== undefined + ? showHeader + : detailMode === 'full'; return ( {selectedEvent ? ( - + ) : (
diff --git a/src/views/Community/components/DynamicNewsCard/VerticalModeLayout.js b/src/views/Community/components/DynamicNewsCard/VerticalModeLayout.js index 05eca5c7..1db9c7cf 100644 --- a/src/views/Community/components/DynamicNewsCard/VerticalModeLayout.js +++ b/src/views/Community/components/DynamicNewsCard/VerticalModeLayout.js @@ -161,6 +161,7 @@ const VerticalModeLayout = ({ {/* 详情面板 */} diff --git a/src/views/Community/components/DynamicNewsDetail/CompactMetaBar.js b/src/views/Community/components/DynamicNewsDetail/CompactMetaBar.js new file mode 100644 index 00000000..7abe78ad --- /dev/null +++ b/src/views/Community/components/DynamicNewsDetail/CompactMetaBar.js @@ -0,0 +1,100 @@ +// src/views/Community/components/DynamicNewsDetail/CompactMetaBar.js +// 精简信息栏组件(无头部模式下右上角显示) + +import React from 'react'; +import { + HStack, + Badge, + Text, + Icon, + useColorModeValue, +} from '@chakra-ui/react'; +import { ViewIcon } from '@chakra-ui/icons'; +import EventFollowButton from '../EventCard/EventFollowButton'; + +/** + * 精简信息栏组件 + * 在无头部模式下,显示在 CardBody 右上角 + * 包含:重要性徽章、浏览次数、关注按钮 + * + * @param {Object} props + * @param {Object} props.event - 事件对象 + * @param {Object} props.importance - 重要性配置对象(包含 level, icon 等) + * @param {boolean} props.isFollowing - 是否已关注 + * @param {number} props.followerCount - 关注数 + * @param {Function} props.onToggleFollow - 切换关注回调 + */ +const CompactMetaBar = ({ event, importance, isFollowing, followerCount, onToggleFollow }) => { + const viewCountBg = useColorModeValue('white', 'gray.700'); + const viewCountTextColor = useColorModeValue('gray.600', 'gray.300'); + + // 获取重要性文本 + const getImportanceText = () => { + const levelMap = { + 'S': '极高', + 'A': '高', + 'B': '中', + 'C': '低' + }; + return levelMap[importance.level] || '中'; + }; + + return ( + + {/* 重要性徽章 - 与 EventHeaderInfo 样式一致,尺寸略小 */} + + + 重要性:{getImportanceText()} + + + {/* 浏览次数 - 添加容器背景以提高可读性 */} + + + + {(event.view_count || 0).toLocaleString()} + + + + {/* 关注按钮 */} + + + ); +}; + +export default CompactMetaBar; From 44b8c64907b5bd224f331e119be171dca20a26ec Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Fri, 7 Nov 2025 19:25:10 +0800 Subject: [PATCH 02/10] =?UTF-8?q?feat(community):=20=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E4=BA=8B=E4=BB=B6=E5=8D=A1=E7=89=87=E9=AB=98?= =?UTF-8?q?=E5=BA=A6=E8=87=AA=E9=80=82=E5=BA=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/DynamicNewsCard/VerticalModeLayout.js | 1 + .../components/EventCard/HorizontalDynamicNewsEventCard.js | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/views/Community/components/DynamicNewsCard/VerticalModeLayout.js b/src/views/Community/components/DynamicNewsCard/VerticalModeLayout.js index 1db9c7cf..c2437d02 100644 --- a/src/views/Community/components/DynamicNewsCard/VerticalModeLayout.js +++ b/src/views/Community/components/DynamicNewsCard/VerticalModeLayout.js @@ -112,6 +112,7 @@ const VerticalModeLayout = ({ timelineStyle={getTimelineBoxStyle()} borderColor={borderColor} indicatorSize={layoutMode === 'detail' ? 'default' : 'comfortable'} + layout="vertical" /> ))} diff --git a/src/views/Community/components/EventCard/HorizontalDynamicNewsEventCard.js b/src/views/Community/components/EventCard/HorizontalDynamicNewsEventCard.js index 8a0ca736..2e86349c 100644 --- a/src/views/Community/components/EventCard/HorizontalDynamicNewsEventCard.js +++ b/src/views/Community/components/EventCard/HorizontalDynamicNewsEventCard.js @@ -34,6 +34,7 @@ import StockChangeIndicators from '../../../../components/StockChangeIndicators' * @param {Object} props.timelineStyle - 时间轴样式配置 * @param {string} props.borderColor - 边框颜色 * @param {string} props.indicatorSize - 涨幅指标尺寸 ('default' | 'comfortable' | 'large') + * @param {string} props.layout - 布局模式 ('vertical' | 'four-row'),影响时间轴竖线高度 */ const HorizontalDynamicNewsEventCard = ({ event, @@ -47,6 +48,7 @@ const HorizontalDynamicNewsEventCard = ({ timelineStyle, borderColor, indicatorSize = 'comfortable', + layout = 'vertical', }) => { const importance = getImportanceConfig(event.importance); @@ -98,7 +100,7 @@ const HorizontalDynamicNewsEventCard = ({ createdAt={event.created_at} timelineStyle={timelineStyle} borderColor={borderColor} - minHeight="60px" + minHeight={layout === 'four-row' ? '60px' : 0} /> {/* 右侧事件卡片容器(带印章) */} From 2da71a3c037b4a6839c019c1bb9e5766e7662431 Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Fri, 7 Nov 2025 19:29:19 +0800 Subject: [PATCH 03/10] =?UTF-8?q?feat:=20=E7=9B=B8=E5=85=B3=E8=82=A1?= =?UTF-8?q?=E7=A5=A8=E6=B7=BB=E5=8A=A0=E5=90=88=E8=A7=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DynamicNewsDetail/StockListItem.js | 120 +++++++++++------- 1 file changed, 71 insertions(+), 49 deletions(-) diff --git a/src/views/Community/components/DynamicNewsDetail/StockListItem.js b/src/views/Community/components/DynamicNewsDetail/StockListItem.js index 107594c9..03fb858a 100644 --- a/src/views/Community/components/DynamicNewsDetail/StockListItem.js +++ b/src/views/Community/components/DynamicNewsDetail/StockListItem.js @@ -12,12 +12,14 @@ import { IconButton, Collapse, Tooltip, + Badge, useColorModeValue, } from '@chakra-ui/react'; import { StarIcon } from '@chakra-ui/icons'; import MiniTimelineChart from '../StockDetailPanel/components/MiniTimelineChart'; import MiniKLineChart from './MiniKLineChart'; import StockChartModal from '../../../../components/StockChart/StockChartModal'; +import CitedContent from '../../../../components/Citation/CitedContent'; import { getChangeColor } from '../../../../utils/colorUtils'; /** @@ -104,7 +106,7 @@ const StockListItem = ({ borderRadius="lg" p={3} position="relative" - overflow="hidden" + overflow="visible" _before={{ content: '""', position: 'absolute', @@ -113,6 +115,8 @@ const StockListItem = ({ right: 0, height: '3px', bgGradient: 'linear(to-r, blue.400, purple.500, pink.500)', + borderTopLeftRadius: 'lg', + borderTopRightRadius: 'lg', }} _hover={{ boxShadow: 'lg', @@ -241,55 +245,73 @@ const StockListItem = ({ /> - {/* 关联描述(单行显示,点击展开)- 占据更多空间 */} - {relationText && relationText !== '--' && ( - - { - e.stopPropagation(); - setIsDescExpanded(!isDescExpanded); - }} - cursor="pointer" - px={3} - py={2} - bg={useColorModeValue('gray.50', 'gray.700')} - borderRadius="md" - _hover={{ - bg: useColorModeValue('gray.100', 'gray.600'), - }} - transition="background 0.2s" - > - {/* 去掉"关联描述"标题 */} - - + {stock.relation_desc?.data ? ( + // 升级:带引用来源的版本 + + ) : ( + // 降级:纯文本版本(保留展开/收起功能) + + { + e.stopPropagation(); + setIsDescExpanded(!isDescExpanded); + }} + cursor="pointer" + px={3} + py={2} + bg={useColorModeValue('gray.50', 'gray.700')} + borderRadius="md" + _hover={{ + bg: useColorModeValue('gray.100', 'gray.600'), + }} + transition="background 0.2s" + position="relative" > - {relationText} - - - {isDescExpanded && ( - - ⚠️ AI生成,仅供参考 - - )} - - + {/* 去掉"关联描述"标题 */} + + + {relationText} + + + + {/* 提示信息 */} + {isDescExpanded && ( + + ⚠️ AI生成,仅供参考 + + )} + + + )} + )} From 63fb8a3aa8586753070a778a250ef1a0e9d6d9e4 Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Fri, 7 Nov 2025 19:31:42 +0800 Subject: [PATCH 04/10] =?UTF-8?q?feat:=20=E5=8A=9F=E8=83=BD:=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=E2=94=82=20=E2=94=82=20=E2=94=82=20?= =?UTF-8?q?=E2=94=82=20-=20=E6=96=B0=E5=A2=9E=20showModeToggle,=20currentM?= =?UTF-8?q?ode,=20onModeToggle=20=E7=AD=89=20props=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=E2=94=82=20?= =?UTF-8?q?=E2=94=82=20=E2=94=82=20=E2=94=82=20-=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E6=A8=A1=E5=BC=8F=E5=88=87=E6=8D=A2=E6=8C=89?= =?UTF-8?q?=E9=92=AE=EF=BC=88"=E7=B2=BE=E7=AE=80=E6=A8=A1=E5=BC=8F"=20/=20?= =?UTF-8?q?"=E6=9F=A5=E7=9C=8B=E8=AF=A6=E6=83=85"=EF=BC=89=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=E2=94=82=20=E2=94=82=20=E2=94=82=20=E2=94=82=20?= =?UTF-8?q?-=20=E6=A0=B9=E6=8D=AE=E6=A8=A1=E5=BC=8F=E5=8A=A8=E6=80=81?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E6=8C=89=E9=92=AE=E6=96=87=E6=A1=88=E5=92=8C?= =?UTF-8?q?=E5=9B=BE=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DynamicNewsDetail/CollapsibleHeader.js | 72 ++++++++++++++++--- 1 file changed, 62 insertions(+), 10 deletions(-) diff --git a/src/views/Community/components/DynamicNewsDetail/CollapsibleHeader.js b/src/views/Community/components/DynamicNewsDetail/CollapsibleHeader.js index 95d32fd4..5d9175a2 100644 --- a/src/views/Community/components/DynamicNewsDetail/CollapsibleHeader.js +++ b/src/views/Community/components/DynamicNewsDetail/CollapsibleHeader.js @@ -9,6 +9,7 @@ import { Heading, Badge, IconButton, + Button, useColorModeValue, } from '@chakra-ui/react'; import { ChevronDownIcon, ChevronUpIcon } from '@chakra-ui/icons'; @@ -21,22 +22,53 @@ import { ChevronDownIcon, ChevronUpIcon } from '@chakra-ui/icons'; * @param {Function} props.onToggle - 切换展开/收起的回调 * @param {number} props.count - 可选的数量徽章 * @param {React.ReactNode} props.subscriptionBadge - 可选的会员标签组件 + * @param {boolean} props.showModeToggle - 是否显示模式切换按钮(默认 false) + * @param {string} props.currentMode - 当前模式:'detailed' | 'simple' + * @param {Function} props.onModeToggle - 模式切换回调 + * @param {boolean} props.isLocked - 是否锁定(不可展开) */ -const CollapsibleHeader = ({ title, isOpen, onToggle, count = null, subscriptionBadge = null }) => { +const CollapsibleHeader = ({ + title, + isOpen, + onToggle, + count = null, + subscriptionBadge = null, + showModeToggle = false, + currentMode = 'detailed', + onModeToggle = null, + isLocked = false +}) => { const sectionBg = useColorModeValue('gray.50', 'gray.750'); const hoverBg = useColorModeValue('gray.100', 'gray.700'); const headingColor = useColorModeValue('gray.700', 'gray.200'); + // 获取按钮文案 + const getButtonText = () => { + if (currentMode === 'simple') { + return '查看详情'; // 简单模式时,按钮显示"查看详情" + } + return '精简模式'; // 详细模式时,按钮显示"精简模式" + }; + + // 获取按钮图标 + const getButtonIcon = () => { + if (currentMode === 'simple') { + return null; // 简单模式不显示图标 + } + // 详细模式:展开显示向上箭头,收起显示向下箭头 + return isOpen ? : ; + }; + return ( @@ -54,12 +86,32 @@ const CollapsibleHeader = ({ title, isOpen, onToggle, count = null, subscription )} - : } - size="sm" - variant="ghost" - aria-label={isOpen ? '收起' : '展开'} - /> + + {/* 只有 showModeToggle=true 时才显示模式切换按钮 */} + {showModeToggle && onModeToggle && ( + + )} + + {/* showModeToggle=false 时显示原有的 IconButton */} + {!showModeToggle && ( + : } + size="sm" + variant="ghost" + aria-label={isOpen ? '收起' : '展开'} + /> + )} ); }; From 11789b5ec7342b228a6e8a32dfe5b475e7d0e9c5 Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Fri, 7 Nov 2025 19:32:10 +0800 Subject: [PATCH 05/10] =?UTF-8?q?Commit=202:=20CollapsibleSection=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E7=B2=BE=E7=AE=80/=E8=AF=A6=E7=BB=86?= =?UTF-8?q?=E5=8F=8C=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DynamicNewsDetail/CollapsibleSection.js | 80 ++++++++++++++++--- 1 file changed, 68 insertions(+), 12 deletions(-) diff --git a/src/views/Community/components/DynamicNewsDetail/CollapsibleSection.js b/src/views/Community/components/DynamicNewsDetail/CollapsibleSection.js index ce01f031..3b34dfdc 100644 --- a/src/views/Community/components/DynamicNewsDetail/CollapsibleSection.js +++ b/src/views/Community/components/DynamicNewsDetail/CollapsibleSection.js @@ -1,7 +1,7 @@ // src/views/Community/components/DynamicNewsDetail/CollapsibleSection.js // 通用可折叠区块组件 -import React from 'react'; +import React, { useState } from 'react'; import { Box, Collapse, @@ -19,7 +19,10 @@ import CollapsibleHeader from './CollapsibleHeader'; * @param {React.ReactNode} props.subscriptionBadge - 可选的会员标签组件 * @param {boolean} props.isLocked - 是否锁定(不可展开) * @param {Function} props.onLockedClick - 锁定时点击的回调 - * @param {React.ReactNode} props.children - 子内容 + * @param {React.ReactNode} props.children - 详细内容 + * @param {React.ReactNode} props.simpleContent - 精简模式的内容(可选) + * @param {boolean} props.showModeToggle - 是否显示模式切换按钮(默认 false) + * @param {string} props.defaultMode - 默认模式:'detailed' | 'simple'(默认 'detailed') */ const CollapsibleSection = ({ title, @@ -29,10 +32,16 @@ const CollapsibleSection = ({ subscriptionBadge = null, isLocked = false, onLockedClick = null, - children + children, + simpleContent = null, + showModeToggle = false, + defaultMode = 'detailed' }) => { const sectionBg = useColorModeValue('gray.50', 'gray.750'); + // 模式状态:'detailed' | 'simple' + const [displayMode, setDisplayMode] = useState(defaultMode); + // 处理点击:如果锁定则触发锁定回调,否则触发正常切换 const handleToggle = () => { if (isLocked && onLockedClick) { @@ -42,15 +51,43 @@ const CollapsibleSection = ({ } }; - return ( - - + // 处理模式切换 + const handleModeToggle = (e) => { + e.stopPropagation(); // 阻止冒泡到标题栏的 onToggle + + if (isLocked && onLockedClick) { + // 如果被锁定,触发付费弹窗 + onLockedClick(); + return; + } + + if (displayMode === 'detailed') { + // 从详细模式切换到精简模式 + setDisplayMode('simple'); + } else { + // 从精简模式切换回详细模式 + setDisplayMode('detailed'); + // 切换回详细模式时,如果未展开则自动展开 + if (!isOpen && onToggle) { + onToggle(); + } + } + }; + + // 渲染精简模式 + const renderSimpleMode = () => { + if (!simpleContent) return null; + + return ( + + {simpleContent} + + ); + }; + + // 渲染详细模式 + const renderDetailedMode = () => { + return ( + ); + }; + + return ( + + + + {/* 根据当前模式渲染对应内容 */} + {displayMode === 'simple' ? renderSimpleMode() : renderDetailedMode()} ); }; From b30cbd6c626d7ffa1919cb3aaace001304c8363d Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Fri, 7 Nov 2025 19:32:36 +0800 Subject: [PATCH 06/10] =?UTF-8?q?RelatedStocksSection=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E4=B8=BA=E7=BA=AF=E8=AF=A6=E7=BB=86=E6=A8=A1=E5=BC=8F=E7=BB=84?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DynamicNewsDetail/RelatedStocksSection.js | 80 ++++--------------- 1 file changed, 14 insertions(+), 66 deletions(-) diff --git a/src/views/Community/components/DynamicNewsDetail/RelatedStocksSection.js b/src/views/Community/components/DynamicNewsDetail/RelatedStocksSection.js index 8802db74..2be81942 100644 --- a/src/views/Community/components/DynamicNewsDetail/RelatedStocksSection.js +++ b/src/views/Community/components/DynamicNewsDetail/RelatedStocksSection.js @@ -1,21 +1,13 @@ // src/views/Community/components/DynamicNewsDetail/RelatedStocksSection.js // 相关股票列表区组件(纯内容,不含标题) -import React, { useState } from 'react'; -import { - VStack, - Flex, - Button, - ButtonGroup, - Wrap, - WrapItem, -} from '@chakra-ui/react'; -import { ViewIcon, ViewOffIcon } from '@chakra-ui/icons'; +import React from 'react'; +import { VStack } from '@chakra-ui/react'; import StockListItem from './StockListItem'; -import CompactStockItem from './CompactStockItem'; /** * 相关股票列表区组件(纯内容部分) + * 只负责渲染详细的股票列表,精简模式由外层 CollapsibleSection 的 simpleContent 提供 * @param {Object} props * @param {Array} props.stocks - 股票数组 * @param {Object} props.quotes - 股票行情字典 { [stockCode]: { change: number } } @@ -30,67 +22,23 @@ const RelatedStocksSection = ({ watchlistSet = new Set(), onWatchlistToggle }) => { - // 显示模式:'detail' 详情模式, 'compact' 精简模式 - const [viewMode, setViewMode] = useState('detail'); - // 如果没有股票数据,不渲染 if (!stocks || stocks.length === 0) { return null; } return ( - - {/* 模式切换按钮 */} - - - - - - - - {/* 详情模式 */} - {viewMode === 'detail' && ( - - {stocks.map((stock, index) => ( - - ))} - - )} - - {/* 精简模式 */} - {viewMode === 'compact' && ( - - {stocks.map((stock, index) => ( - - - - ))} - - )} + + {stocks.map((stock, index) => ( + + ))} ); }; From 4979293320f1951faf15d8f05883a8fcee0355d5 Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Fri, 7 Nov 2025 19:46:29 +0800 Subject: [PATCH 07/10] =?UTF-8?q?feat:=20RelatedConceptsSection=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=8F=97=E6=8E=A7=E6=A8=A1=E5=BC=8F=E5=92=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 isOpen, onToggle props 支持外部控制展开状态(受控模式) - 添加 hasNoConcepts 判断,优化空数据处理逻辑 - 改进精简模式和详细模式的空状态显示 - 增强点击处理逻辑,支持受控/非受控两种模式 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../DynamicNewsDetailPanel.js | 5 +- .../RelatedConceptsSection/index.js | 76 ++++++++++++------- 2 files changed, 54 insertions(+), 27 deletions(-) diff --git a/src/views/Community/components/DynamicNewsDetail/DynamicNewsDetailPanel.js b/src/views/Community/components/DynamicNewsDetail/DynamicNewsDetailPanel.js index 668d9ab9..120c989e 100644 --- a/src/views/Community/components/DynamicNewsDetail/DynamicNewsDetailPanel.js +++ b/src/views/Community/components/DynamicNewsDetail/DynamicNewsDetailPanel.js @@ -10,6 +10,8 @@ import { Text, Spinner, Center, + Wrap, + WrapItem, useColorModeValue, useToast, } from '@chakra-ui/react'; @@ -32,8 +34,9 @@ import SubscriptionUpgradeModal from '../../../../components/SubscriptionUpgrade * 动态新闻详情面板主组件 * @param {Object} props * @param {Object} props.event - 事件对象(包含详情数据) + * @param {boolean} props.showHeader - 是否显示头部信息(默认 true) */ -const DynamicNewsDetailPanel = ({ event }) => { +const DynamicNewsDetailPanel = ({ event, showHeader = true }) => { const dispatch = useDispatch(); const { user } = useAuth(); const cardBg = useColorModeValue('white', 'gray.800'); diff --git a/src/views/Community/components/DynamicNewsDetail/RelatedConceptsSection/index.js b/src/views/Community/components/DynamicNewsDetail/RelatedConceptsSection/index.js index 009e8573..5df76c95 100644 --- a/src/views/Community/components/DynamicNewsDetail/RelatedConceptsSection/index.js +++ b/src/views/Community/components/DynamicNewsDetail/RelatedConceptsSection/index.js @@ -38,9 +38,13 @@ const RelatedConceptsSection = ({ eventTime, subscriptionBadge = null, isLocked = false, - onLockedClick = null + onLockedClick = null, + isOpen = undefined, // 新增:受控模式(外部控制展开状态) + onToggle = undefined // 新增:受控模式(外部控制展开回调) }) => { - const [isExpanded, setIsExpanded] = useState(false); + // 使用外部 isOpen,如果没有则使用内部 useState + const [internalExpanded, setInternalExpanded] = useState(false); + const isExpanded = onToggle !== undefined ? isOpen : internalExpanded; const [concepts, setConcepts] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -179,10 +183,8 @@ const RelatedConceptsSection = ({ ); } - // 如果没有概念,不渲染 - if (!concepts || concepts.length === 0) { - return null; - } + // 判断是否有数据 + const hasNoConcepts = !concepts || concepts.length === 0; /** * 根据相关度获取颜色(浅色背景 + 深色文字) @@ -232,9 +234,12 @@ const RelatedConceptsSection = ({ // 如果被锁定且有回调函数,触发付费弹窗 if (isLocked && onLockedClick) { onLockedClick(); + } else if (onToggle !== undefined) { + // 受控模式:调用外部回调 + onToggle(); } else { - // 否则正常展开/收起 - setIsExpanded(!isExpanded); + // 非受控模式:使用内部状态 + setInternalExpanded(!internalExpanded); } }} > @@ -249,30 +254,49 @@ const RelatedConceptsSection = ({ {/* 简单模式:横向卡片列表(总是显示) */} - - {concepts.map((concept, index) => ( - - ))} - - - - {/* 详细模式:卡片网格(可折叠) */} - - {/* 详细概念卡片网格 */} - + {hasNoConcepts ? ( + + {error ? ( + {error} + ) : ( + 暂无相关概念数据 + )} + + ) : ( + {concepts.map((concept, index) => ( - ))} - + + )} + + {/* 详细模式:卡片网格(可折叠) */} + + {hasNoConcepts ? ( + + {error ? ( + {error} + ) : ( + 暂无详细数据 + )} + + ) : ( + /* 详细概念卡片网格 */ + + {concepts.map((concept, index) => ( + + ))} + + )} ); From 52c3e25218b9923f508c04957fe144f45737ff4e Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Fri, 7 Nov 2025 19:46:56 +0800 Subject: [PATCH 08/10] =?UTF-8?q?feat:=20HistoricalEvents=20UI=20=E5=B8=83?= =?UTF-8?q?=E5=B1=80=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 从网格布局(SimpleGrid 3列)改为单列纵向布局(VStack) - 卡片样式优化:添加顶部渐变条装饰(蓝-紫-粉渐变) - 卡片内部从垂直布局改为横向布局(HStack) - 优化间距和边距,提升视觉层次感 - 调整卡片padding和borderRadius 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../components/HistoricalEvents.js | 181 +++++++++++------- 1 file changed, 108 insertions(+), 73 deletions(-) diff --git a/src/views/EventDetail/components/HistoricalEvents.js b/src/views/EventDetail/components/HistoricalEvents.js index b594670f..5ba45fa9 100644 --- a/src/views/EventDetail/components/HistoricalEvents.js +++ b/src/views/EventDetail/components/HistoricalEvents.js @@ -31,6 +31,7 @@ import { } from 'react-icons/fa'; import { stockService } from '../../../services/eventService'; import { logger } from '../../../utils/logger'; +import CitedContent from '../../../components/Citation/CitedContent'; const HistoricalEvents = ({ events = [], @@ -224,8 +225,8 @@ const HistoricalEvents = ({ )} - {/* 历史事件卡片网格 */} - + {/* 历史事件卡片列表 - 混合布局 */} + {events.map((event) => { const importanceColor = getImportanceColor(event.importance); @@ -235,92 +236,126 @@ const HistoricalEvents = ({ bg={cardBg} borderWidth="1px" borderColor={borderColor} - borderRadius="md" - p={4} + borderRadius="lg" + position="relative" + overflow="visible" cursor="pointer" onClick={() => handleCardClick(event)} + _before={{ + content: '""', + position: 'absolute', + top: 0, + left: 0, + right: 0, + height: '3px', + bgGradient: 'linear(to-r, blue.400, purple.500, pink.500)', + borderTopLeftRadius: 'lg', + borderTopRightRadius: 'lg', + }} _hover={{ boxShadow: 'lg', borderColor: 'blue.400', - transform: 'translateY(-2px)', }} transition="all 0.2s" > - - {/* 事件名称 */} - { - e.stopPropagation(); - handleCardClick(event); - }} - _hover={{ textDecoration: 'underline' }} - > - {event.title || '未命名事件'} - - - {/* 日期 + Badges */} - - - {formatDate(getEventDate(event))} - - - ({getRelativeTime(getEventDate(event))}) - - {event.relevance && ( - - 相关度: {event.relevance} - - )} - {event.importance && ( - - 重要性: {event.importance} - - )} - {event.avg_change_pct !== undefined && event.avg_change_pct !== null && ( - 0 ? 'red' : event.avg_change_pct < 0 ? 'green' : 'gray'} - size="sm" + + {/* 顶部区域:左侧(标题+时间) + 右侧(按钮) */} + + {/* 左侧:标题 + 时间信息(允许折行) */} + + {/* 标题 */} + { + e.stopPropagation(); + handleCardClick(event); + }} + _hover={{ textDecoration: 'underline' }} > - 涨幅: {event.avg_change_pct > 0 ? '+' : ''}{event.avg_change_pct.toFixed(2)}% - - )} + {event.title || '未命名事件'} + + + {/* 时间 + Badges(允许折行) */} + + + {formatDate(getEventDate(event))} + + + ({getRelativeTime(getEventDate(event))}) + + {event.importance && ( + + 重要性: {event.importance} + + )} + {event.avg_change_pct !== undefined && event.avg_change_pct !== null && ( + 0 ? 'red' : event.avg_change_pct < 0 ? 'green' : 'gray'} + size="sm" + > + 涨幅: {event.avg_change_pct > 0 ? '+' : ''}{event.avg_change_pct.toFixed(2)}% + + )} + + + + {/* 右侧:相关股票按钮 */} + - {/* 事件描述 */} - - {getEventContent(event) ? `${getEventContent(event)}(AI合成)` : '暂无内容'} - - - {/* 相关股票按钮 */} - + {/* 底部:描述(独占整行)- 升级和降级处理 */} + + {(() => { + const content = getEventContent(event); + // 检查是否有 data 结构(升级版本) + if (content && typeof content === 'object' && content.data) { + return ( + + ); + } + // 降级版本:纯文本 + return ( + + {content ? `${content}(AI合成)` : '暂无内容'} + + ); + })()} + ); })} - + {/* 相关股票 Modal - 条件渲染 */} {stocksModalOpen && ( From 7b49062986d61d15d741615c7948140bf1f15c47 Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Fri, 7 Nov 2025 19:47:14 +0800 Subject: [PATCH 09/10] =?UTF-8?q?docs:=20=E6=9B=B4=E6=96=B0=20Community=20?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 补充精简/详细模式切换功能文档 - 添加无头部模式(showHeader)使用说明 - 更新 CollapsibleSection 和 DynamicNewsDetailPanel 的 API 参考 - 添加相关组件的使用示例 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/Community.md | 64 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/docs/Community.md b/docs/Community.md index 641c4800..05f8a39a 100644 --- a/docs/Community.md +++ b/docs/Community.md @@ -1101,6 +1101,70 @@ feat: 删除不需要的组件 --- +### 2025-01-XX: CollapsibleSection 支持精简/详细模式切换 + +**变更类型**: 功能增强 + +**优化内容**: 为事件详情面板的 CollapsibleSection 组件添加精简/详细双模式切换功能,提升用户浏览体验。 + +**新增功能**: + +1. **CollapsibleHeader.js** - 支持模式切换按钮 + - 新增 `showModeToggle` prop:是否显示模式切换按钮 + - 新增 `currentMode` prop:当前模式('detailed' | 'simple') + - 新增 `onModeToggle` prop:模式切换回调 + - 按钮样式: + - 详细模式展开:`详细模式 ▲` + - 详细模式收起:`详细模式 ▼` + - 精简模式:`精简模式` + - 向后兼容:不提供 `showModeToggle` 时,保持原有的 IconButton 样式 + +2. **CollapsibleSection.js** - 支持精简/详细模式 + - 新增 `simpleContent` prop:精简模式的内容 + - 新增 `showModeToggle` prop:是否显示模式切换按钮 + - 新增 `defaultMode` prop:默认模式(默认 'detailed') + - 模式切换逻辑: + - 详细模式 → 精简模式:显示 simpleContent + - 精简模式 → 详细模式:自动展开 children + - 权限控制:锁定状态下点击模式切换,触发付费弹窗 + +3. **SimpleStocksList.js** - 新增精简股票列表组件 + - 横向展示股票名称和涨跌幅 + - 自动根据涨跌幅显示颜色(红涨绿跌) + - 支持加载中状态 + - 响应式设计,自动折行 + +4. **DynamicNewsDetailPanel.js** - 相关股票模块启用双模式 + - 添加 `showModeToggle={true}` + - 添加 `simpleContent`:使用 SimpleStocksList 显示精简股票列表 + - `children`:保持原有的 RelatedStocksSection(完整信息) + - 默认详细模式展开(PRO 会员) + +**用户体验提升**: +- ✅ 支持精简/详细两种浏览模式,满足不同场景需求 +- ✅ 精简模式:快速浏览股票名称和涨跌幅 +- ✅ 详细模式:查看完整股票信息(价格、按钮等) +- ✅ 一键切换,无需重新加载数据 +- ✅ PRO 会员默认展开详细模式,优化会员体验 + +**技术细节**: +- 使用 React useState 管理模式状态 +- 模式切换不触发数据重新加载 +- 向后兼容:不使用 `showModeToggle` 的 CollapsibleSection 保持原有行为 +- 权限控制:锁定状态下切换模式触发付费引导 + +**影响范围**: +- `src/views/Community/components/DynamicNewsDetail/CollapsibleHeader.js` +- `src/views/Community/components/DynamicNewsDetail/CollapsibleSection.js` +- `src/views/Community/components/DynamicNewsDetail/SimpleStocksList.js`(新增) +- `src/views/Community/components/DynamicNewsDetail/DynamicNewsDetailPanel.js` + +**扩展性**: +- 其他模块(历史事件对比、传导链分析)可以复用此功能 +- 只需提供对应的 `simpleContent` 即可启用双模式 + +--- + ## 🔗 相关文档 - [项目总览 - CLAUDE.md](../CLAUDE.md) From 00aabfacead378c7929883225ee4b61c8567dbd8 Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Fri, 7 Nov 2025 19:48:08 +0800 Subject: [PATCH 10/10] =?UTF-8?q?feat:=20DynamicNewsDetailPanel=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E6=97=A0=E5=A4=B4=E9=83=A8=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=E5=92=8C=E7=B2=BE=E7=AE=80=E6=A8=A1=E5=BC=8F=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增功能: - 添加 showHeader prop 控制头部显示/隐藏(默认 true) - 无头部模式下显示 CompactMetaBar 精简信息栏(右上角浮动) - 相关股票支持精简模式(使用 CompactStockItem + Wrap 布局) - 添加 showModeToggle 和 simpleContent props 到相关股票模块 Bug 修复和优化: - 修复 isStocksOpen 初始值依赖未就绪变量的问题(改为 false) - 优化股票加载逻辑:PRO 和 MAX 会员都默认展开和自动加载 - 更新日志文案:从"PRO会员"改为"PRO/MAX会员" 导入调整: - 添加 Wrap, WrapItem(用于精简模式布局) - 添加 CompactMetaBar(无头部模式信息栏) - 添加 CompactStockItem(精简模式股票卡片) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../DynamicNewsDetailPanel.js | 64 +++++++++++++++---- 1 file changed, 52 insertions(+), 12 deletions(-) diff --git a/src/views/Community/components/DynamicNewsDetail/DynamicNewsDetailPanel.js b/src/views/Community/components/DynamicNewsDetail/DynamicNewsDetailPanel.js index 120c989e..e76054df 100644 --- a/src/views/Community/components/DynamicNewsDetail/DynamicNewsDetailPanel.js +++ b/src/views/Community/components/DynamicNewsDetail/DynamicNewsDetailPanel.js @@ -21,9 +21,11 @@ import { useEventStocks } from '../StockDetailPanel/hooks/useEventStocks'; import { toggleEventFollow, selectEventFollowStatus } from '../../../../store/slices/communityDataSlice'; import { useAuth } from '../../../../contexts/AuthContext'; import EventHeaderInfo from './EventHeaderInfo'; +import CompactMetaBar from './CompactMetaBar'; import EventDescriptionSection from './EventDescriptionSection'; import RelatedConceptsSection from './RelatedConceptsSection'; import RelatedStocksSection from './RelatedStocksSection'; +import CompactStockItem from './CompactStockItem'; import CollapsibleSection from './CollapsibleSection'; import HistoricalEvents from '../../../EventDetail/components/HistoricalEvents'; import TransmissionChainAnalysis from '../../../EventDetail/components/TransmissionChainAnalysis'; @@ -114,8 +116,8 @@ const DynamicNewsDetailPanel = ({ event, showHeader = true }) => { const canAccessTransmission = hasAccess('max'); // 子区块折叠状态管理 + 加载追踪 - // PRO 会员的相关股票默认展开 - const [isStocksOpen, setIsStocksOpen] = useState(canAccessStocks && userTier === 'pro'); + // 初始值为 false,由 useEffect 根据权限动态设置 + const [isStocksOpen, setIsStocksOpen] = useState(false); const [hasLoadedStocks, setHasLoadedStocks] = useState(false); const [isConceptsOpen, setIsConceptsOpen] = useState(false); @@ -202,14 +204,18 @@ const DynamicNewsDetailPanel = ({ event, showHeader = true }) => { // 🎯 加载事件详情(增加浏览量) loadEventDetail(); - // PRO 会员的相关股票默认展开,其他情况收起 - const shouldOpenStocks = canAccessStocks && userTier === 'pro'; + // PRO 和 MAX 会员的相关股票默认展开,其他情况收起 + const shouldOpenStocks = canAccessStocks; setIsStocksOpen(shouldOpenStocks); setHasLoadedStocks(false); - // PRO 会员默认展开时,自动加载股票数据 - if (shouldOpenStocks) { - console.log('%c📊 [PRO会员] 自动加载相关股票数据', 'color: #10B981; font-weight: bold;', { eventId: event?.id }); + // PRO 和 MAX 会员自动加载股票数据(无论是否展开) + const shouldLoadStocks = canAccessStocks; // PRO 或 MAX 都有权限 + if (shouldLoadStocks) { + console.log('%c📊 [PRO/MAX会员] 自动加载相关股票数据', 'color: #10B981; font-weight: bold;', { + eventId: event?.id, + userTier + }); loadStocksData(); setHasLoadedStocks(true); } @@ -281,21 +287,34 @@ const DynamicNewsDetailPanel = ({ event, showHeader = true }) => { return ( - - - {/* 头部信息区 - 优先使用完整详情数据(包含最新浏览量) */} - + {/* 无头部模式:显示右上角精简信息栏 */} + {!showHeader && ( + + )} + + + {/* 头部信息区 - 优先使用完整详情数据(包含最新浏览量) - 可配置显示/隐藏 */} + {showHeader && ( + + )} {/* 事件描述 */} - {/* 相关股票(可折叠) - 懒加载 - 需要 PRO 权限 */} + {/* 相关股票(可折叠) - 懒加载 - 需要 PRO 权限 - 支持精简/详细模式 */} { })()} isLocked={!canAccessStocks} onLockedClick={() => handleLockedClick('相关股票', 'pro')} + showModeToggle={canAccessStocks} + defaultMode="detailed" + simpleContent={ + loading.stocks || loading.quotes ? ( +
+ + 加载股票数据中... +
+ ) : ( + + {stocks?.map((stock, index) => ( + + + + ))} + + ) + } > {loading.stocks || loading.quotes ? (