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
{/* 简单模式:横向卡片列表(总是显示) */}
-
- {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)}%
+
+ )}
+
+
+
+ {/* 右侧:相关股票按钮 */}
+ }
+ onClick={(e) => {
+ e.stopPropagation();
+ handleViewStocks(event);
+ }}
+ colorScheme="blue"
+ variant="outline"
+ flexShrink={0}
+ >
+ 相关股票
+
- {/* 事件描述 */}
-
- {getEventContent(event) ? `${getEventContent(event)}(AI合成)` : '暂无内容'}
-
-
- {/* 相关股票按钮 */}
- }
- onClick={(e) => {
- e.stopPropagation();
- handleViewStocks(event);
- }}
- colorScheme="blue"
- variant="outline"
- width="full"
- >
- 相关股票
-
+ {/* 底部:描述(独占整行)- 升级和降级处理 */}
+
+ {(() => {
+ 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 ? (